Open your platform wallet
Sign in and Dynamic provisions an embedded wallet you control. Deposit funds, then withdraw to any external wallet — pick the settlement token + chain when you do.
Pull from a Fireblocks vault, embedded wallet, or any external source and settle directly to the user's wallet of choice. Fireblocks Flow provides the swap, settlement, and webhook infrastructure[†].
Sign in and Dynamic provisions an embedded wallet you control. Deposit funds, then withdraw to any external wallet — pick the settlement token + chain when you do.
Your application should make clear to end-users that asset conversion and cross-chain routing are executed by independent third-party providers. Users keep full control of their assets and must explicitly sign each transfer. On-chain transactions are final and cannot be reversed.
By continuing, you agree to our Terms of Service and Privacy Policy.
Flow runs on mainnet networks only. Use a sandbox environment id with real mainnet addresses for development.
Server-side, one per withdraw. The destination address comes from the user, so each withdraw mints its own Flow via POST /environments/{envId}/checkouts with the user's address in destinationConfig.destinations[0].identifier. Dynamic's API only accepts mode: "payment" | "deposit" — a withdraw is sent as "deposit" because it's conceptually a deposit into the user's destination wallet.
// Runs on your server — one-time per Flow config.
const res = await fetch(
`https://api.dynamic.xyz/v0/environments/${process.env.ENV_ID}/checkouts`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DYNAMIC_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
// Dynamic's API only accepts "payment" | "deposit" —
// a withdraw is modeled as a deposit into the user's
// destination wallet.
mode: "deposit",
settlementConfig: {
strategy: "preferred_order",
settlements: [
{
chainName: "SOL",
chainId: "101",
symbol: "USDC",
tokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
tokenDecimals: 6,
},
],
},
destinationConfig: {
destinations: [
{
chainName: "SOL",
type: "address",
identifier: "0xUSER_EXTERNAL_WALLET",
},
],
},
}),
},
);
const { id: flowId } = await res.json();Opens a transaction against the freshly-minted Flow id and returns a session token that authenticates the rest of this withdraw's lifecycle.
import { addEvmExtension } from "@dynamic-labs-sdk/evm";
import { createCheckoutTransaction } from "@dynamic-labs-sdk/client";
addEvmExtension();
// Open a transaction against the Flow id returned in Step 01. Returns
// both the transaction and a one-time sessionToken — store the latter.
const { transaction, sessionToken } = await createCheckoutTransaction({
checkoutId: flowId,
amount: "5.00",
currency: "USD",
});Declare the wallet funds are being withdrawn from. For this demo it's the embedded wallet Dynamic provisioned during connect; Dynamic still runs risk + sanctions screening here, so a 403 means the source is blocked even when it's user-controlled.
import {
attachCheckoutTransactionSource,
getActiveNetworkData,
getPrimaryWalletAccount,
} from "@dynamic-labs-sdk/client";
const wallet = getPrimaryWalletAccount()!;
const { networkData } = await getActiveNetworkData({ walletAccount: wallet });
await attachCheckoutTransactionSource({
transactionId: transaction.id,
fromAddress: wallet.address,
fromChainId: String(networkData.networkId),
fromChainName: wallet.chain,
});
// 403 → source blocked by risk/sanctions screening.Quote the swap from the picked source token in the embedded wallet to the settlement asset. Quotes expire in 60 seconds — re-quote if the user takes longer to confirm.
import {
getBalances,
getCheckoutTransactionQuote,
} from "@dynamic-labs-sdk/client";
// First non-zero balance — production would let the user pick which
// token to pay with.
const [fromToken] = await getBalances({ walletAccount: wallet });
const quoted = await getCheckoutTransactionQuote({
transactionId: transaction.id,
fromTokenAddress: fromToken.address,
slippage: 0.005, // 0.5%
});
// quoted.quote.expiresAt — re-quote if the user takes longer than 60s.The embedded wallet signs the on-chain transfer; Dynamic broadcasts and tracks settlement to the user-supplied destination address. SDK collapses prepare + sign + broadcast into one call; REST keeps them separate so a custom wallet client can hook in.
import { submitCheckoutTransaction } from "@dynamic-labs-sdk/client";
// One helper handles all three sub-stages:
// 1. prepare → fetch the signing payload
// 2. sign → wallet popup appears here
// 3. broadcast → notify Dynamic of the on-chain txHash
await submitCheckoutTransaction({
transactionId: transaction.id,
walletAccount: wallet,
});We poll the transaction endpoint every ~3 seconds so the settlement lifecycle stays visible in the demo. Production should subscribe to the checkout.transaction.settlement.updated webhook instead — push-driven, no idle polling. On completed, the destination wallet has received the funds; on failed, expose a retry with a fresh Checkout (the original one is single-use).
import {
getCheckoutTransaction,
isTerminalState,
} from "@dynamic-labs-sdk/client";
// Poll every 3s — or subscribe via the lifecycle webhook.
let tx = await getCheckoutTransaction({ transactionId: transaction.id });
while (!isTerminalState(tx)) {
await new Promise((r) => setTimeout(r, 3000));
tx = await getCheckoutTransaction({ transactionId: transaction.id });
}
// tx.settlementState === "completed" | "failed"Dynamic does not control the swap, bridge, or routing protocols used to convert and deliver assets. Rates and fees are sourced from third-party providers and may change between quote and execution.
Cross-chain transfers carry risk — including slippage, partial fills, and failed conversions. On-chain transactions are final and cannot be reversed.
These materials are not investment, financial, legal, or tax advice. You are responsible for evaluation at your own discretion. Please review Dynamic's terms and conditions for full details on acceptable use.