Skip to main content
There is no prebuilt widget for withdrawals. To move funds out of an embedded wallet, call the headless core directly with the embedded wallet as the source. The flow is the same five calls every time:
  1. Trustware.getBalances() to load what the wallet can send
  2. Trustware.buildRoute() to get a signable route
  3. Trustware.useWallet() to attach the embedded wallet
  4. Trustware.sendRouteTransaction() to sign and broadcast
  5. Trustware.submitReceipt() to confirm the transaction and enable status tracking
The embedded wallet signs the withdrawal, and funds move through the routing provider’s contracts to the destination the user chooses. Trustware builds the route and tracks settlement; it never holds the funds.
This guide covers users withdrawing funds held in an embedded wallet your app provisioned. It is unrelated to paymaster fund management in the Client Dashboard.

When to use this pattern

Choose this pattern when:
  • your app provisions embedded wallets and users hold balances in them
  • users need to move those funds to an external wallet or another chain
  • you want to build the withdrawal UI in your own design system

Prerequisites

  • An adapted embedded wallet. Follow use Trustware with embedded wallets to wrap the wallet’s EIP-1193 provider with useEIP1193.
  • An initialized SDK. Either mount TrustwareProvider with your config, or call Trustware.init(config) before the calls below.
import { Trustware, type TrustwareConfigOptions } from "@trustware/sdk";

const config = {
  apiKey: process.env.NEXT_PUBLIC_TRUSTWARE_API_KEY!,
} satisfies TrustwareConfigOptions;

await Trustware.init(config);

1. Load embedded wallet balances

getBalances returns the token balances for an address on one chain. Filter to funded rows to drive your source-token picker:
const rows = await Trustware.getBalances(fromChain, embeddedAddress, {
  forceRefresh: true,
});

const funded = rows.filter((row) => BigInt(row.balance || "0") > 0n);
The SDK caches getBalances results per chain and address. Pass forceRefresh: true to force a fresh on-chain scan, for example right after a deposit lands; omit it to reuse the cache when the user is only switching between chains. Each row is a BalanceRow:
type BalanceRow = {
  chain_key: string;
  category: "native" | "erc20" | "spl" | "btc";
  contract?: string;
  address?: string;
  symbol?: string;
  decimals: number;
  balance: string; // base units
  name?: string;
  logoURI?: string;
  usdPrice?: number;
};

2. Build the withdrawal route

Build a route with the embedded wallet as fromAddress and the user’s chosen destination as toAddress:
const route = await Trustware.buildRoute({
  fromChain,
  toChain,
  fromToken,
  toToken,
  fromAmount, // base units, e.g. "1500000" for 1.5 USDC
  fromAddress: embeddedAddress,
  toAddress: destinationAddress,
  slippageBps: 100, // 1% slippage tolerance
});
fromAmount is in the token’s smallest unit. Convert human-readable input with the token’s decimals before calling the SDK, for example with viem’s parseUnits. See headless core for the full BuildRouteBody and BuildRouteResult shapes.

3. Attach the embedded wallet and send

Attach the adapted wallet, then sign and broadcast the route. sendRouteTransaction switches the wallet to the route’s chain if needed and returns the transaction hash:
Trustware.useWallet(wallet);

const txHash = await Trustware.sendRouteTransaction(route, fromChain);

4. Submit the receipt and track status

Submit the transaction hash so Trustware can track settlement, then poll until the route resolves:
await Trustware.submitReceipt(route.intentId, txHash);

const result = await Trustware.pollStatus(route.intentId);

if (result.status === "success") {
  console.log("Withdrawal settled:", result.destTxHash);
}
pollStatus resolves when the route reaches success or failed. Use Trustware.getStatus(route.intentId) instead if you want to poll on your own schedule.

Gas reserve for native withdrawals

When the user withdraws the chain’s native token, the wallet still needs gas to send the transaction. A “Use max” control should fill the balance minus a small reserve rather than the full balance. Size the reserve for the chain you target: a tiny flat reserve covers a transfer on an L2 like Base, while L1 mainnet needs a larger one for its higher and more volatile gas costs.

Complete example

A complete Next.js implementation of this flow, including balance display, amount entry with max handling, and route sending, is available in the Trustware examples repo alongside the deposit flow from the embedded wallets guide.