When to use this pattern
Choose this pattern when:- you want full control over the deposit UI
- your design system requires a custom amount entry or confirmation flow
- you want to embed deposit logic into an existing interface without a floating widget
- you need a flow the widget does not provide, such as withdrawing from an embedded wallet
- you need to deposit into a contract on the destination chain, such as depositing into a vault destination
Setup
The headless core still requiresTrustwareProvider for config context. Mount it once near the root of your app; you do not need to render TrustwareWidget.
Wallet setup
You have two options for attaching a wallet to the headless core.Bring your own wallet
Adapt an existing Wagmi wallet client into Trustware usinguseWallet:
Let Trustware detect wallets
If you do not manage wallet state, callautoDetect once at startup:
Core operations
1. Build a route
buildRoute constructs a route. Use this when you want to inspect route details before asking the user to confirm.
BuildRouteBody shape: (? indicates optional)
hooks.postHook executes a contract call on the destination chain once routed funds arrive, instead of paying the destination token to toAddress. It requires SDK 1.1.10 or later, is fully optional, and omitting hooks leaves route behavior unchanged. See PostHookRequest for the field reference and vault destinations for the end-to-end flow.
2. Inspect route details
The returnedBuildRouteResult contains exchange rate information you can display to the user before they confirm.
BuildRouteResult shape:
txReq type is not exported under its own name. Refer to it as BuildRouteResult["txReq"] rather than importing a named type.
3. Sign and send a route
sendRouteTransaction switches the wallet to the route’s chain if needed, grants any ERC-20 allowance the route requires, and then sends the route transaction. It returns the source transaction hash.
route.execution.approvals, the SDK reads the current allowance, submits an approval for the exact amount when one is missing, waits for that approval to confirm, and only then requests the route signature.
Plan your UI around two consequences:
- The user can see more than one wallet prompt for a single deposit. An ERC-20 source asset usually means an approval prompt followed by the deposit prompt.
- Each approval must confirm on chain before the route signature is requested, so the call can stay pending for a minute or more.
route.execution.approvals in the same order: read, approve, wait, then send.
4. Run the full flow
runTopUp handles route construction, wallet approval, transaction submission, and status polling in a single call. Use this when you want the SDK to orchestrate the full deposit path.
Only fromAmount is required; every other field is optional and falls back to the corresponding value in your TrustwareConfigOptions.routes config when omitted.
Working with chains and tokens
The same chain and token discovery the widget uses internally is available to your own UI through three helpers on theTrustware facade. All three respect the active TrustwareProvider config and reuse its cache.
Trustware.useChains()
React hook that returns the supported chain list, split into popular and other groups, with loading and error state. Use it to drive your own chain selector.
Trustware.useTokens(chainId)
React hook that returns the token list for a given chain with built-in pagination and search. Pass null to skip fetching.
Trustware.validateAddressForChain(address, chain)
Synchronous helper that returns { isValid: boolean; error?: string } for the given destination address against the rules of the selected chain (EVM checksum, Solana base58, Bitcoin, Cosmos prefixes, etc.). Use it for live form validation.
Scanning wallet balances
Three helpers read balances.Trustware.getBalances(chainRef, address) covers a single chain and is the one to use when you already know which chain you care about, as in embedded wallet withdrawals. The other two scan every chain compatible with the address format.
Trustware.getBalancesByAddress(address, options)
Resolves once with one WalletAddressBalanceWrapper per chain. Pass stream: true to have the SDK consume the streamed scan and merge the chunks for you. That changes the transport, not the timing: the promise still settles after the last chain reports, so use getBalancesByAddressStream below if you want to render progressively.
partial before you tell a user their wallet is empty: a chain that timed out and a chain with no holdings both come back as no rows. See BalanceStreamSummary for when the callback fires and what each field means.
Trustware.getBalancesByAddressStream(address, options)
Async generator that yields each chain’s balances as it lands, so you can paint a wallet that fills in rather than a spinner that sits.
chain_id instead of appending.
Aborting signal does not end the scan. The abort surfaces as a stream failure, so the SDK falls back and re-runs the scan against the buffered endpoint, which the signal does not reach. Stop consuming the generator if you need the results dropped.
Streaming is on by default from SDK 1.1.12 through
features.balanceStreaming.
Where a backend has streaming disabled, the request is aborted, or the runtime
has no readable response body (React Native, most notably), the generator emits
balance_stream_fallback and yields the buffered result as a single chunk.
Setting the flag to false does the same thing without the event. Either way
this code path still works, so it does not need a variant. See
BalanceStreamOptions and
GET /balances.Lifecycle callbacks
TrustwareConfigOptions exposes three optional callbacks you can pass alongside your routes config to react to SDK activity:
onEventfires for every lifecycle event the SDK emits. It receives the fullTrustwareEventdiscriminated union, which you can narrow bytype(transaction_started,transaction_success,wallet_connected, etc.).onSuccessfires once when a deposit settles on the destination chain. It receives the resolvedTransactionwithdestTxHashpopulated.onErrorfires for anyTrustwareErrorthrown during a route or transaction operation. Use it as a single place to log errors or push them into your UI state.RateLimitErrorextends nativeError(notTrustwareError) and does not flow through this callback; handle it with the imperativetry/catchpattern below.
onError runs in addition to any try/catch you wrap around individual core calls; see Error handling below for the imperative pattern. See lifecycle events for the full list of event types.
Error handling
Wrap core calls in try/catch. ImportRateLimitError to handle rate-limiting specifically:
retriesExhausted cases mean.
