# SilentSwap > Integrate private and public cross-chain swaps with the SilentSwap TypeScript SDK. ## For AI agents Use the machine-readable artifacts instead of scraping rendered HTML: * [llms.txt](https://docs.silentswap.com/llms.txt) — page index with descriptions. * [llms-full.txt](https://docs.silentswap.com/llms-full.txt) — all documentation in one file. * [SilentSwap integration skill](https://docs.silentswap.com/skill/SKILL.md) — operational integration procedure. * [Complete skill example](https://docs.silentswap.com/skill/references/complete-example.ts) — compiling EVM flow. * [Raw GitHub skill](https://raw.githubusercontent.com/SilentSwap-V3/silentswap-sdk/main/skills/silentswap-integration/SKILL.md). ### Install the skill ```sh mkdir -p .claude/skills/silentswap-integration/references curl -fsSL https://docs.silentswap.com/skill/SKILL.md \ -o .claude/skills/silentswap-integration/SKILL.md curl -fsSL https://docs.silentswap.com/skill/references/complete-example.ts \ -o .claude/skills/silentswap-integration/references/complete-example.ts ``` For Codex, place the same folder under .agents/skills/silentswap-integration. ### Three invariants 1. Render every output, amount, fee, and expiry before calling placeOrder(quote). V3 has no authorization-signing or confirmation ceremony. 2. Production baseUrl defaults to [https://api.silentswap.com](https://api.silentswap.com). Override it only for tests or self-hosted deployments. 3. Treat COMPLETED, FAILED, and ABORTED as the statuses that close the SDK watcher. DROPPED is resumable when a late deposit lands, so keep watching it. Always release the unsubscribe function when its owner unmounts. ### Type-check the real package ```ts twoslash import { createSilentSwapClient, type PrivateQuote } from '@silentswap/sdk'; const client = createSilentSwapClient(); declare const quote: PrivateQuote; client.placeOrder(quote); ``` ## Getting started SilentSwap V3 exposes one small, typed flow: quote, show the user what will happen, place, and track. ### Install ```sh npm install @silentswap/sdk viem ``` ### Create a client Production uses [https://api.silentswap.com](https://api.silentswap.com) automatically. Supply an EVM wallet client and any narrow non-EVM capability adapters the application already selected. ```ts twoslash import { createSilentSwapClient } from '@silentswap/sdk'; const client = createSilentSwapClient(); ``` ### Follow the trust boundary The wallet deposit calldata does not reveal the recipient. Render every output, asset, fee, and expiry in your own trusted UI, then call placeOrder(quote). There is no V2 authorization-signing replacement. Continue with the [quickstart](/sdk/quickstart), or read [how it works](/how-it-works) before handling funds. ## How it works SilentSwap separates what the user reviews from what the wallet signs. ### 1. Quote quote sends the source asset, source chain, and one or more outputs to the SilentSwap API. The response contains a ticket, fees, expiry, destination previews, and the deposit payload. ### 2. Review and place Show every output, asset, fee, and expiry. Then place the reviewed quote directly: ```ts twoslash import type { PrivateQuote, SilentSwapClient } from '@silentswap/sdk'; declare const client: SilentSwapClient; declare const quote: PrivateQuote; const order = await client.placeOrder(quote); order.reference; ``` V3 has no nonce, SIWE, facilitator, or authorization-signing ceremony. ### 3. Deposit For EVM sources, the SDK checks allowance, simulates the deposit, sends it, and waits for a receipt. Bitcoin, Litecoin, Solana, TON, and TRON payloads branch to their native wallet flow; see [deposits](/sdk/deposits). ### 4. Fulfill and claim The backend observes the deposit, privately fulfills every recipient, obtains the notary signature, and claims the gateway deposit. Your integration follows the ticket with trackOrderViaWebSocket rather than reconstructing this orchestration. ### Security invariants * Never place a quote before trusted UI displays its outputs, assets, fees, and expiry. * Never replace quoted calldata or destination metadata. * Never merge addresses across wallet brands. * Never hardcode registry addresses, token decimals, quote amounts, or fees. ## React hooks — coming soon @silentswap/react 2.x will provide hooks over the SDK's canonical client. It is not published with the 2.0.0 SDK release. Use the framework-independent client inside your current wallet and data layer: ```ts twoslash import { createSilentSwapClient, type SilentSwapClient, } from '@silentswap/sdk'; import { bsc } from 'viem/chains'; export const silentSwap: SilentSwapClient = createSilentSwapClient({ baseUrl: 'https://api.silentswap.com', chain: bsc, }); ``` Do not install a V2 React, Vue, widget, or UI-kit package into a V3 integration. Their authentication and order models target the discontinued V2 backend. ## Migrate from SilentSwap V2 @silentswap/sdk\@2.0.0 keeps the package name and familiar lifecycle names, but removes the V2 authentication and authorization ceremony. Existing V2 orders are not convertible: let them finish under your pinned 0.x integration before cutting over. ### Migrate with an agent Install the [SilentSwap integration skill](https://docs.silentswap.com/skill/SKILL.md) and let an agent perform the mechanical audit and rewrite. The skill preserves application-specific wallet decisions as explicit TODOs instead of guessing a wallet brand. ```sh mkdir -p .agents/skills/silentswap-integration/references curl -fsSL https://docs.silentswap.com/skill/SKILL.md \ -o .agents/skills/silentswap-integration/SKILL.md curl -fsSL https://docs.silentswap.com/skill/references/complete-example.ts \ -o .agents/skills/silentswap-integration/references/complete-example.ts ``` Use this prompt: ```text Use $silentswap-integration to migrate this application from @silentswap/sdk 0.x to 2.x. Audit every old import and call site; remove nonce, SIWE, facilitator, and authorization persistence; migrate quote, placeOrder, trackOrderViaWebSocket, executeRefund, errors, and client configuration; flag pending V2 orders that must finish on pinned 0.x; run the consumer typecheck; and leave TODOs only where this app must choose its wallet adapters. ``` For Claude Code, install the same folder under .claude/skills/silentswap-integration. ### API mapping | V2 | V3 | | -------------------------------------------- | ---------------------------------------------------------------------- | | createSilentSwapClient | unchanged | | nonce/SIWE authentication | removed | | facilitator group | removed | | quote(\{ outputs, pro }) | quote(\{ privacy, inputAddress, outputs, integratorFee }) | | sign authorizations | removed; review the quote, then deposit | | order() | placeOrder(quote) | | trackOrderViaWebSocket | same name; transport is adaptive | | executeRefund | same name; accepts a private order reference | | Simple Bridge | same product name; use privacy: false | | pro / integratorId | integratorFee / integratorAddress | | React/Vue/widget packages | discontinued; React 2.x is planned | Do not mechanically translate the old pro integrator ID into an integrator fee. Only add integratorFee and integratorAddress after the application owner has approved the USDC-denominated fee and payout address. #### Rewrite each output V2 and V3 both call the array outputs, but each row is simpler in V3: | V2 output field | V3 output field | | -------------------------------------- | ------------------------------------------------------------------------- | | recipient | address | | USDC decimal-string value | bigint amount in USDC base units | | non-USDC value | recompute the V3 USDC payout budget; do not copy destination-native units | | CAIP-19 asset | derive destination chainId and dest only | | method | removed | | facilitatorPublicKeys | removed | | extra.swap | optional dest, when it names an arbitrary destination asset | Derive top-level sourceAsset or sourceToken independently from the old application's source selection/deposit flow. Review ambiguous CAIP-19 or extra.swap values with the application owner; an agent should leave a focused TODO instead of guessing chain or token metadata. ### Revised order flow ```ts twoslash import { createSilentSwapClient } from '@silentswap/sdk'; import { parseUnits } from 'viem'; import type { WalletClient } from 'viem'; declare const account: `0x${string}`; declare const recipient: `0x${string}`; declare const walletClient: WalletClient; const client = createSilentSwapClient({ walletClient }); const quote = await client.quote({ privacy: true, inputAddress: account, sourceChainId: 1, sourceAsset: 'USDC', outputs: [ { address: recipient, chainId: 8453, amount: parseUnits('10', 6), }, ], }); // Render quote.outputs/recipients, amounts, assets, fees, and expiresAt here. const order = await client.placeOrder(quote); const stop = client.trackOrderViaWebSocket(order.reference, console.log, console.error); void stop; ``` There is no replacement authorization ceremony. The user reviews the RFQ in your trusted UI, then placeOrder handles an ERC-20 approval when necessary and submits the deposit. Unlike V2, tracking no longer returns a promise that resolves to the final status: it emits states through callbacks and immediately returns the teardown function. Refunds now return \{ refundTxHash } from client.executeRefund(privateReference). ### Behavioral changes * V3 throws typed errors instead of returning \[error, value] tuples. * Production baseUrl is optional and defaults to [https://api.silentswap.com](https://api.silentswap.com). * Public request terminology remains outputs\[]; the SDK maps it to the private backend's unchanged recipients\[] wire field. * privacy: true always requires inputAddress. * privacy: false uses Simple Bridge with the same quote/place/track lifecycle. * Integrator fees use USDC base units and require integratorAddress when positive. * Browser integrations must register their exact HTTPS production origin with SilentSwap support. Node and server integrations do not need CORS registration. Remove persisted V2 nonces, SIWE tokens, facilitator wallets, authorization signatures, and WebSocket recovery data. Do not reinterpret them as V3 credentials. ## Complete Simple Bridge example ```ts twoslash import { SimpleBridgeRevertedError, createSilentSwapClient } from '@silentswap/sdk'; import type { WalletClient } from 'viem'; declare const walletClient: WalletClient; const client = createSilentSwapClient({ walletClient }); export async function startSimpleBridge(): Promise<() => void> { const quote = await client.quote({ privacy: false, inputAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', sourceChainId: 8453, sourceAsset: 'USDC', amount: 25_000_000n, outputs: [ { address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', chainId: 1, dest: { kind: 'evm', chainId: 1, assetId: '0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', assetSymbol: 'USDC', assetDecimals: 6, }, }, ], }); try { const order = await client.placeOrder(quote); return client.trackOrderViaWebSocket( order.reference, (state) => console.log(state.status), console.error, ); } catch (error) { if (error instanceof SimpleBridgeRevertedError) { throw new Error(`Simple Bridge reverted: ${error.txHash}`, { cause: error }); } throw error; } } ``` ## Simple Bridge Simple Bridge is the non-private mode of the same client. Set privacy: false and use the same quote → placeOrder → trackOrderViaWebSocket lifecycle. The SDK normalizes bridge states into FULFILLING, COMPLETED, FAILED, or UNKNOWN. ```ts twoslash import { createSilentSwapClient } from '@silentswap/sdk'; import type { WalletClient } from 'viem'; declare const walletClient: WalletClient; const client = createSilentSwapClient({ walletClient, }); ``` Quotes and tracking use the configured SilentSwap API automatically. Integrations never need to configure or depend on an underlying routing provider. ## Simple Bridge usage ```ts twoslash import { createSilentSwapClient } from '@silentswap/sdk'; import type { WalletClient } from 'viem'; declare const walletClient: WalletClient; const client = createSilentSwapClient({ walletClient }); const quote = await client.quote({ privacy: false, inputAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', sourceChainId: 1, sourceAsset: 'ETH', amount: 10_000_000_000_000_000n, outputs: [ { address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', chainId: 8453, dest: { kind: 'evm', chainId: 8453, assetId: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', assetSymbol: 'USDC', assetDecimals: 6, }, }, ], }); const order = await client.placeOrder(quote); const stop = client.trackOrderViaWebSocket(order.reference, console.log, console.error); void stop; ``` Simple Bridge is exact-input and accepts exactly one output. Integrator fees are not currently available for public routes. ## Assets and chains Install the registries directly when an application needs picker or routing metadata without the full SDK. ```ts twoslash import { getAssetOnChain, listAssets, } from '@silentswap/assets'; import { getChainConfig, getSourceChain, listSourceChains, } from '@silentswap/chains'; import { ASSETS } from '@silentswap/sdk'; const baseUsdc = getAssetOnChain('USDC', 8453); const base = getChainConfig(8453); const bnbSource = getSourceChain(56); const sources = listSourceChains(); console.log(ASSETS, listAssets(), baseUsdc, base, bnbSource, sources); ``` Use the registry's token address and decimals as one pair. Do not hardcode them or assume USDC uses six decimals on every chain. Use NATIVE\_TOKEN\_SENTINEL to detect native gas assets. viem is a peer dependency of both public registry packages, preventing duplicate Chain type identities. ## Client API ### Configuration ```ts twoslash import type { SilentSwapConfig } from '@silentswap/sdk'; type ConfigKeys = keyof SilentSwapConfig; // ^? ``` | Field | Required | Purpose | | --------------------------- | -------------------- | ---------------------------------------------------------------------------------- | | baseUrl | No | Defaults to [https://api.silentswap.com](https://api.silentswap.com). | | chain | No | Defaults to the configured production gateway chain. | | walletClient | Wallet calls | Deposits, allowances, and refunds. | | walletAdapters | Non-EVM wallet calls | Narrow Solana, Bitcoin, TON, and TRON capabilities. | | publicClient | No | Read client for gateway allowance/receipt work. | | rpcUrlForChain | No | Resolve read RPCs for source chain IDs. | ### Methods | Method | Return | | ----------------------------------------------------------------- | --------------------------------------------------------------------------- | | quote(request) | Promise\ | | hasAllowance(quote) | Promise\ | | ensureAllowance(quote) | Approval hash and sufficiency | | previewDepositCost(quote) | Native-gas cost breakdown | | hasSufficientGasForDeposit(quote) | Balance/cost comparison | | placeOrder(quote) | Serializable reference, transaction hashes, or external pay-in instructions | | getOrder(privateReference) | Promise\ | | trackOrderViaWebSocket(reference, onState, onError?) | Unsubscribe function | | executeRefund(privateReference) | Refund transaction hash | The class SilentSwap, factory createSilentSwapClient, and type alias SilentSwapClient refer to the same canonical client surface. ## Complete private-swap example This example shows the full EVM path. The UI must render the quote before calling placeOrder. ```ts twoslash import { AmountOutOfRangeError, DepositSimulationError, HttpError, createSilentSwapClient, } from '@silentswap/sdk'; import { createWalletClient, custom, parseUnits, type Address, } from 'viem'; import { mainnet } from 'viem/chains'; declare const provider: Parameters[0]; declare const account: Address; const walletClient = createWalletClient({ account, chain: mainnet, transport: custom(provider), }); const client = createSilentSwapClient({ walletClient, }); export async function startSwap(): Promise<() => void> { try { const quote = await client.quote({ privacy: true, inputAddress: account, sourceAsset: 'USDC', sourceChainId: 1, outputs: [{ address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', chainId: 8453, amount: parseUnits('25', 6), }], }); // Await a real UI review here, then optionally narrate approval. if (!(await client.hasAllowance(quote))) { await client.ensureAllowance(quote); } const order = await client.placeOrder(quote); return client.trackOrderViaWebSocket( order.reference, (state) => console.log(state.status), console.error, ); } catch (error) { if (error instanceof AmountOutOfRangeError) { throw new Error(error.message, { cause: error }); } if (error instanceof DepositSimulationError) { throw new Error('Nothing was broadcast.', { cause: error }); } if (error instanceof HttpError) { throw new Error(`SilentSwap API: HTTP ${error.status}`, { cause: error }); } throw error; } } ``` ## Errors The SDK throws errors. It does not return V2-style \[error, value] tuples. ```ts twoslash import { AmountOutOfRangeError, DepositRevertedError, DepositSimulationError, HttpError, MaintenanceModeError, RefundNotAllowedError, RefundRevertedError, } from '@silentswap/sdk'; export function describeSilentSwapError(error: unknown): string { if (error instanceof MaintenanceModeError) return error.message; if (error instanceof AmountOutOfRangeError) { return `${error.code}: ${error.limitAmount ?? 'unknown limit'}`; } if (error instanceof DepositSimulationError) return error.message; if (error instanceof DepositRevertedError) return `Reverted: ${error.depositTxHash}`; if (error instanceof RefundNotAllowedError) return error.message; if (error instanceof RefundRevertedError) return `Reverted: ${error.refundTxHash}`; if (error instanceof HttpError) return `HTTP ${error.status}`; return error instanceof Error ? error.message : 'Unknown SilentSwap error'; } ``` A DepositSimulationError means no transaction was sent. DepositRevertedError and RefundRevertedError preserve hashes for explorer links. HttpError.body carries the API error payload. Detect wallet user rejection separately from protocol failures so a cancelled signature is not reported as an API incident. ## Installation Install the SDK and viem together: ```sh npm install @silentswap/sdk viem ``` The public package graph is @silentswap/sdk → @silentswap/assets → @silentswap/chains. All three packages ship at version 2.0.0. They share viem ^2.21.55 as a peer to preserve one Chain type identity. ### Runtime support The package is ESM and exports JavaScript, declarations, source maps, and TypeScript source. Use a modern browser or current Node.js runtime with fetch. Browser tracking uses EventSource; Node.js automatically falls back to polling. ### Verify the install ```ts twoslash import { createSilentSwapClient, type SilentSwapClient, } from '@silentswap/sdk'; import { bsc } from 'viem/chains'; const client: SilentSwapClient = createSilentSwapClient({ baseUrl: 'http://localhost:3001', chain: bsc, }); client.getOrder; ``` ## Integrator fees Set integratorFee in USDC base units. A positive fee requires integratorAddress. ```ts twoslash import type { QuoteRequest } from '@silentswap/sdk'; const intent: QuoteRequest = { privacy: true, inputAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', sourceAsset: 'USDC', sourceChainId: 56, outputs: [{ address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', chainId: 8453, amount: 100_000_000n, }], integratorFee: 500_000n, integratorAddress: '0x90F79bf6EB2c4f870365E785982E1f101E93b906', }; void intent; ``` This example charges 0.5 USDC. Display quote.integratorFee and quote.integratorAddress on the review surface. The user funds output payouts plus service and integrator fees. V2's pro and integratorId fields are removed. ## Order statuses The main path is: AWAITING\_DEPOSIT → OPEN → FULFILLING → FULFILLED → CLAIM\_READY → COMPLETED | Status | Meaning | | ------------------------------ | ---------------------------------------------------------------------------------------------- | | AWAITING\_DEPOSIT | Ticket exists; the quoted deposit has not been observed. | | OPEN | Deposit is confirmed and fulfillment can begin. | | FULFILLING | One or more destination payments are in progress. | | FULFILLED | Destination delivery completed; notary work remains. | | CLAIM\_READY | The claim is signed and ready to submit. | | COMPLETED | Claim completed; the order is terminal. | | DROPPED | The funding window expired; a late deposit can still resurrect the order to OPEN. | | FAILED | Fulfillment failed; recovery/refund rules apply. | | ABORTED | Failure recovery finished; the order is terminal. | ```ts twoslash import type { OrderStatus } from '@silentswap/sdk'; const watcherTerminal = new Set([ 'COMPLETED', 'FAILED', 'ABORTED', ]); function isWatcherTerminal(status: OrderStatus) { return watcherTerminal.has(status); } ``` For split orders, render each OrderStateRecipient.status as well as the top-level state. A top-level label alone can hide a partially delivered split. ## SDK quickstart Create a viem wallet client for the active account, then bind SilentSwap to the gateway chain. ```ts twoslash import { createSilentSwapClient, } from '@silentswap/sdk'; import { createWalletClient, custom, parseUnits, type Address, } from 'viem'; import { mainnet } from 'viem/chains'; declare const provider: Parameters[0]; declare const account: Address; const walletClient = createWalletClient({ account, chain: mainnet, transport: custom(provider), }); const client = createSilentSwapClient({ walletClient, }); const quote = await client.quote({ privacy: true, inputAddress: account, sourceAsset: 'USDC', sourceChainId: 1, outputs: [{ address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', chainId: 8453, amount: parseUnits('25', 6), }], }); // Render every output and fee before this line. const order = await client.placeOrder(quote); const stop = client.trackOrderViaWebSocket( order.reference, (state) => console.log(state.status), console.error, ); stop; ``` Keep the unsubscribe function for component cleanup. For a narrated approval step, call hasAllowance, then ensureAllowance, before placeOrder; the latter safely rechecks allowance. ## Quotes quote accepts an outputs\[] array. Private wallet-funded requests allow 1–20 outputs; address-funded and Simple Bridge requests require exactly one. ### Curated input asset Use sourceAsset for an asset from @silentswap/assets. ```ts twoslash import type { QuoteRequest, SilentSwapClient, } from '@silentswap/sdk'; declare const client: SilentSwapClient; const intent: QuoteRequest = { privacy: true, inputAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', sourceAsset: 'ETH', sourceChainId: 1, outputs: [{ address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', chainId: 8453, amount: 100_000_000n, }], }; const quote = await client.quote(intent); ``` ### Discovered input token Use sourceToken for a token outside the curated registry. Supply the live address/mint, decimals, and symbol from a token-data source. Never set both sourceAsset and sourceToken. ### Arbitrary output asset Set outputs\[0].dest with the destination asset ID, live decimals, kind, and slippage. The quote returns dest.minDestAmount; display it as the minimum received. Arbitrary-output routes are currently single-recipient. ```ts twoslash import type { QuoteRequest } from '@silentswap/sdk'; const outputIntent: QuoteRequest = { privacy: true, inputAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', sourceAsset: 'USDC', sourceChainId: 56, outputs: [{ address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', chainId: 1, amount: 50_000_000n, dest: { kind: 'evm', chainId: 1, assetId: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', assetSymbol: 'WETH', assetDecimals: 18, slippageBps: 100, }, }], }; void outputIntent; ``` ## Refunds executeRefund is for private EVM-origin orders whose gateway refund window has opened. It fetches fresh order state, validates preconditions, dry-runs the contract call, broadcasts, and waits for the receipt. ```ts twoslash import { RefundNotAllowedError, RefundRevertedError, type SilentSwapClient, } from '@silentswap/sdk'; import type { PrivateOrderReference } from '@silentswap/sdk'; declare const client: SilentSwapClient; declare const reference: PrivateOrderReference; try { const { refundTxHash } = await client.executeRefund(reference); console.log(refundTxHash); } catch (error) { if (error instanceof RefundNotAllowedError) { console.warn(error.message); } else if (error instanceof RefundRevertedError) { console.error(error.refundTxHash); } else { throw error; } } ``` Refunds are rejected for non-EVM origins, non-OPEN/FAILED states, unexpired orders, a different depositor wallet, already fulfilled/refunded orders, or any split where a recipient is paid or in flight. Do not implement a local timer as the authorization source. Let the fresh API state and the SDK simulation decide whether the wallet may open. ## Split payouts Put 1–20 entries in outputs. Each amount is the USDC payout in base units. The user funds the sum of payouts plus service and integrator fees. ```ts twoslash import type { QuoteRequest } from '@silentswap/sdk'; const split: QuoteRequest = { privacy: true, inputAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', sourceAsset: 'USDC', sourceChainId: 56, outputs: [ { address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', chainId: 8453, amount: 40_000_000n, }, { address: '0x90F79bf6EB2c4f870365E785982E1f101E93b906', chainId: 56, amount: 60_000_000n, }, ], }; void split; ``` Render the requested outputs alongside quote.recipients\[] during review and state.recipients\[] during tracking. Do not rely on the deprecated top-level recipient, payoutAmount, or recipientTxHash mirrors. A split cannot currently include an arbitrary-asset dest. Request separate orders if recipients need different output assets. ## Tracking trackOrderViaWebSocket accepts a serializable reference and emits the current state plus later status or transaction-hash changes. The compatibility name is stable; private mode uses browser SSE with a polling backstop, while Simple Bridge uses status polling. ```ts twoslash import type { PrivateOrderReference, SilentSwapClient, } from '@silentswap/sdk'; declare const client: SilentSwapClient; declare const reference: PrivateOrderReference; const unsubscribe = client.trackOrderViaWebSocket( reference, (state) => { console.log(state.status); }, (error: unknown) => { console.error('tracking failed', error); }, ); unsubscribe; ``` Store and call unsubscribe when the owning screen unmounts. The watcher also closes on COMPLETED, FAILED, or ABORTED, and after its two-hour safety limit. It deliberately keeps watching DROPPED orders because a late source-chain deposit can resurrect them to OPEN. Use getOrder(reference) for one-shot private recovery after navigation. Do not start a second custom polling loop beside trackOrderViaWebSocket. ## Deposits Inspect the quoted deposit shape before opening a wallet. ```ts twoslash import { isBtcPsbtDeposit, isUtxoDeposit, type PrivateQuote, } from '@silentswap/sdk'; declare const quote: PrivateQuote; if (isUtxoDeposit(quote.deposit)) { console.log(quote.deposit.payinAddress, quote.deposit.expectedAmount); } else if (isBtcPsbtDeposit(quote.deposit)) { console.log(quote.deposit.psbt, quote.deposit.expectedSats); } else { switch (quote.deposit.txKind ?? 'evm') { case 'svm': console.log(quote.deposit.solanaTx); break; case 'ton': console.log(quote.deposit.tonMessages); break; case 'tron': console.log(quote.deposit.tronTransaction); break; default: console.log(quote.deposit.to, quote.deposit.data); } } ``` ### EVM deposits After review, placeOrder checks allowance, simulates the deposit, broadcasts it on deposit.chainId, and waits for the receipt. It rejects approval calldata, empty calldata, and zero-address destinations before signing. For native tokens no approval is needed. For ERC-20 inputs, show the approval and deposit as distinct wallet steps. ### Native-wallet deposits Supply narrow Solana, TON, TRON, or Bitcoin adapters beside the viem wallet. The SDK verifies the active address and payload before it invokes an adapter. It never discovers an injected wallet or chooses between brands. ## Solana deposits A Solana-source quote has txKind: 'svm' and a base64 solanaTx. The API builds the versioned transaction; the active Solana wallet reviews and signs it. ```ts twoslash import type { PrivateQuote } from '@silentswap/sdk'; declare const quote: PrivateQuote; if (!('type' in quote.deposit) && quote.deposit.txKind === 'svm') { const encodedTransaction = quote.deposit.solanaTx; if (!encodedTransaction) throw new Error('Missing quoted Solana transaction'); // placeOrder validates the payer, then invokes the configured Solana adapter. console.log(encodedTransaction, quote.ticket); } ``` The SDK requires the adapter address to match inputAddress. Phantom must use Phantom's provider; MetaMask multichain must use its own session. Never fall back to a generic window\.solana provider, because that can expose another wallet brand's account. The SDK accepts the serialized solanaTx and rejects a mismatched payer before the wallet opens. ## TON and TRON deposits Account-chain quotes are signed outside viem. ```ts twoslash import type { PrivateQuote } from '@silentswap/sdk'; declare const quote: PrivateQuote; if (!('type' in quote.deposit)) { if (quote.deposit.txKind === 'ton') { const messages = quote.deposit.tonMessages; if (!messages?.length) throw new Error('Missing TON messages'); console.log(messages); } if (quote.deposit.txKind === 'tron') { const transaction = quote.deposit.tronTransaction; if (!transaction) throw new Error('Missing TRON transaction'); console.log(transaction); } } ``` For wallet-backed routes, configure the corresponding narrow adapter. The SDK verifies its active address against inputAddress, then submits all quoted TON messages in order or the single quoted TRON transaction without changing calldata or value. Some TON/TRON inputs use an external pay-in-address shape instead. Detect that with isUtxoDeposit and show the returned address and amount. ## Bitcoin and Litecoin deposits SilentSwap supports two UTXO deposit shapes. ### Pay-in address A utxo deposit is an external transfer. Show the exact address, currency, and amount. The funds may come from a wallet or exchange. ```ts twoslash import { isUtxoDeposit, type PrivateQuote, type SilentSwapClient, } from '@silentswap/sdk'; declare const client: SilentSwapClient; declare const quote: PrivateQuote; if (isUtxoDeposit(quote.deposit)) { const payment = { address: quote.deposit.payinAddress, amount: quote.deposit.expectedAmount, currency: quote.deposit.currency, }; const order = await client.placeOrder(quote); console.log(payment, order.reference); } ``` Without a matching wallet capability, placeOrder returns awaiting-external-deposit with the same pay-in instructions. ### Bitcoin PSBT A btc-psbt deposit spends the quoted refundTo account's UTXOs. The SDK audits every output, the refund address, inputs, absolute fee, and fee rate before invoking the Bitcoin adapter. The adapter receives the decoded audit so it can display the exact spend. If signing or broadcast fails, keep the ticket and the rejection capability so the UI can recover or explicitly abandon the still-unfunded order.