Skip to main content
This is an enterprise-only feature. Please contact us to enable.
Flow lets you send a crypto payment from any supported chain and have it settled in a specific token on a specific chain. This guide walks through the complete flow, from creating a flow to polling for settlement completion.

Prerequisites

Before starting a flow on the client, create one from your backend. The flow creation endpoint is server-side only, authenticated by an API token with flow.write scope — this is where amount, currency, settlement, and destination are fixed.
# Create a flow from your backend (returns a flowId)
curl --request POST \
  --url https://app.dynamic.xyz/api/v0/server/<YOUR_ENVIRONMENT_ID>/flow/payment \
  --header 'Authorization: Bearer <YOUR_API_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data '{
    "amount": "25.00",
    "currency": "USD",
    "settlementConfig": {
      "strategy": "cheapest",
      "settlements": [
        {
          "chainName": "EVM",
          "tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
          "chainId": "1",
          "symbol": "USDC",
          "tokenDecimals": 18
        }
      ]
    },
    "destinationConfig": {
      "destinations": [
        {
          "chainName": "EVM",
          "type": "address",
          "identifier": "0xYourSettlementAddress"
        }
      ]
    },
    "memo": {
      "description": "Payment for order #1234"
    }
  }'
The response includes flow.id — pass this as the flowId to your frontend when calling the SDK functions below.

Supported Chains

Flow supports the following chains:
  • BTC
  • EVM
  • SOL
  • SUI
  • TRON
TRON cross-chain swaps currently route through intent-based bridges and may incur higher gas costs relative to the transfer amount.
The SDK signs internally, so the same flow works regardless of the payer’s chain — pass a wallet account on the relevant chain. The full example below pays from EVM; to pay from Solana, attach a Solana source (fromChainName: 'SOL', fromChainId: '101') and quote with a Solana token mint (or 11111111111111111111111111111111 for native SOL). submitFlowTransaction handles the Solana versioned-transaction signing for you. Except for the EVM chains listed below, only mainnet is supported:
NameID
Base Sepolia Testnet"84532"
Arbitrum Sepolia Testnet"421614"
Arc Testnet"5042002"
OP Sepolia Testnet"11155420"

Overview

The flow follows these steps:
  1. Attach the source wallet with attachFlowSource
  2. Quote the conversion with getFlowQuote
  3. Submit the transaction with submitFlowTransaction (prepares, signs, and broadcasts)
  4. Poll for completion with getFlow

Full example

import {
  attachFlowSource,
  getFlowQuote,
  submitFlowTransaction,
  getFlow,
} from '@dynamic-labs-sdk/client';

const pay = async (flowId, walletAccount) => {
  // Step 1: Attach the wallet source
  await attachFlowSource({
    flowId,
    fromAddress: walletAccount.address,
    fromChainId: '1',
    fromChainName: walletAccount.chain,
    sourceType: 'wallet',
  });

  // Step 2: Get a quote
  const quoted = await getFlowQuote({
    flowId,
    fromTokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
  });

  console.log('Quote:', quoted.quote);

  // Step 3: Submit (prepare + sign + broadcast)
  await submitFlowTransaction({
    flowId,
    walletAccount,
  });

  // Step 4: Poll for completion
  const final = await getFlow({ flowId });
  console.log('Final state:', final.settlementState);
};

Exchange source

To accept payment from a Coinbase exchange account instead of a wallet, attach an exchange source:
import { attachFlowSource, broadcastFlow } from '@dynamic-labs-sdk/client';

// Attach exchange source — returns the flow with exchange metadata
const { flow } = await attachFlowSource({
  flowId,
  sourceType: 'exchange',
  exchangeProvider: 'coinbase',
});

// The exchange buy URL is in flow.exchangeSource.metadata.url
// Open it in an iframe or redirect the user to complete payment on Coinbase
const exchangeUrl = flow.exchangeSource?.metadata?.url;
window.open(exchangeUrl, '_blank');

// After the user completes payment on the exchange, notify the backend
await broadcastFlow({ flowId });

Handling cancellation and errors

import { submitFlowTransaction, cancelFlow } from '@dynamic-labs-sdk/client';

try {
  await submitFlowTransaction({
    flowId,
    walletAccount,
  });
} catch (error) {
  if (error.message.includes('rejected')) {
    // User rejected the signing request in their wallet
    await cancelFlow({ flowId });
    console.log('Flow cancelled');
  } else {
    console.error('Flow failed:', error.message);
  }
}

Error reference

Each SDK function can throw errors with an HTTP status code and a message. The table below lists common errors by step, their cause, and how to resolve them.

Create flow (backend)

StatusErrorCauseFix
400Invalid flow mode. Expected one of: payment, deposit, withdrawThe mode path parameter is not a valid flow mode.Use payment, deposit, or withdraw.
401UnauthorizedMissing or invalid API token, or the token does not have flow.write scope.Verify the Authorization: Bearer dyn_... header and the token scope in the dashboard.
422Each chain should only have one destination configDuplicate chain entries in destinationConfig.destinations.Remove duplicate chain entries so each chain appears once.
422Settlement for token SYMBOL: ...A settlement chain is not supported, or the token address is invalid for that chain.Verify the chain/token pair. Use a valid EVM address for EVM chains, a Solana mint for SOL, etc.
422All settlement chains must have a destination config. Missing: ...A chain in settlementConfig has no matching entry in destinationConfig.Add a destination for every settlement chain.

Attach source (attachFlowSource)

StatusErrorCauseFix
400fromAddress, fromChainId, and fromChainName are required for wallet sourcesMissing source fields when sourceType is wallet.Pass fromAddress, fromChainId, and fromChainName from the wallet account.
400Chain not supportedThe source chain/chainId combination is not supported for swaps.Use a supported chain. See Supported Chains.
403Flow is blocked by sanctionsThe source address failed sanctions screening.Use a different source wallet.
404Flow not foundThe flowId does not exist, the flow has been deleted, or the environment ID is wrong.Verify the flowId and environment.
409State transition errorThe flow is not in a state that allows attaching a source (e.g. already broadcasted).Check executionState with getFlow and start a new flow if needed.

Get quote (getFlowQuote)

StatusErrorCauseFix
400fromChainName is required / fromChainId is required / fromAddress is requiredSource was not attached before requesting a quote.Call attachFlowSource first.
400fromTokenAddress is required and could not be resolved for the specified chainThe token could not be determined for the source chain.Pass fromTokenAddress explicitly in the quote request.
403Flow is blocked by risk screeningThe flow’s risk screening flagged the source.Cancel the flow and retry with a different source.
409State transition errorThe flow is not in a valid state for quoting (e.g. already signed).Check executionState. You can re-quote from source_attached or quoted states.
422No quotes available for any settlement token.No swap provider returned a valid quote. The response includes a structured quoteFailures array with per-settlement failure details.Inspect the quoteFailures array. See the reason codes below.
Quote failure response format: When no quotes are available, the 422 response includes a quoteFailures array:
{
  "error": "No quotes available for any settlement token.",
  "quoteFailures": [
    {
      "chainId": "728126428",
      "chainName": "TRON",
      "reason": "unknown",
      "symbol": "USDT"
    },
    {
      "chainId": "8453",
      "chainName": "EVM",
      "reason": "amount_below_minimum",
      "symbol": "USDC"
    }
  ]
}
Each entry identifies the settlement token that failed (symbol on chainName/chainId) and the failure reason. Quote failure reasons:
ReasonMeaningFix
amount_below_minimumThe flow amount is too low for the selected token/route.Increase the amount or choose a different source token.
amount_above_maximumThe flow amount exceeds the route’s maximum.Decrease the amount or split across multiple flows.
insufficient_liquidityNot enough liquidity on the swap route.Try a different source token, chain, or reduce the amount.
unsupported_chain_pairNo swap route exists between the source and settlement chains.Use a different source chain or settlement chain.
token_not_foundThe source or settlement token is not recognized by the swap provider.Verify the token address.
token_price_unavailableThe swap provider cannot price the token for conversion.Try a different source token.
slippage_too_tightThe slippage tolerance is too narrow for market conditions.Increase slippage in the quote request (e.g. 0.5 for 0.5%).
provider_rate_limitedThe swap provider rate-limited the request.Wait a moment and retry.
provider_unavailableThe swap provider is temporarily down.Retry after a short delay.
unknownThe swap provider returned an unclassified error. Often means the amount is too low for conversion on that route.Try increasing the amount or using a different source token.

Prepare signing (prepareFlowSigning / submitFlowTransaction)

StatusErrorCauseFix
409State transition errorThe flow is not in the quoted state.Ensure a quote was fetched first.
422Quote has expired; request a new quote before signingThe quote is older than 60 seconds.Call getFlowQuote again, then retry.
422Flow risk screening has not been clearedRisk screening is still pending or the flow was flagged.Wait for riskState to become cleared, or cancel.
422Insufficient balance ...The wallet does not have enough tokens or gas to cover the transaction. Includes required and available amounts.Fund the wallet or choose a smaller amount.
409Flow was modified by another requestA concurrent request changed the flow state.Re-fetch the flow and retry from the current state.

Broadcast (broadcastFlow)

StatusErrorCauseFix
400txHash is required for non-exchange sourcesWallet sources must include the transaction hash.This is handled automatically by submitFlowTransaction. If calling broadcastFlow directly, include txHash.
409Transaction hash already recordedThe same txHash was submitted twice.The broadcast already succeeded — no action needed.
409State transition errorThe flow is not in the signing state.Check executionState before calling broadcast.

Cancel (cancelFlow)

StatusErrorCauseFix
404Flow not foundThe flow does not exist or was already deleted.Verify the flowId.
409State transition errorThe flow has already been broadcasted and cannot be cancelled.Flows can only be cancelled before broadcast.

Polling for status

After submission, poll getFlow to track progress through execution and settlement states:
import { getFlow } from '@dynamic-labs-sdk/client';

const TERMINAL_EXECUTION = ['cancelled', 'expired', 'failed'];
const TERMINAL_SETTLEMENT = ['completed', 'failed'];

const pollFlow = async (flowId) => {
  const poll = async () => {
    const flow = await getFlow({ flowId });

    if (
      TERMINAL_EXECUTION.includes(flow.executionState) ||
      TERMINAL_SETTLEMENT.includes(flow.settlementState)
    ) {
      return flow;
    }

    await new Promise((resolve) => setTimeout(resolve, 3000));
    return poll();
  };

  return poll();
};