> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Building for Flow (deposits & payments)

> Build a deposit/payment UI with Fireblocks Flow — load a flow, attach a source, quote, sign, and track it to settlement.

<Note>
  Flow is an enterprise feature. [Contact us](https://www.dynamic.xyz/book-a-call) to enable it for your environment.
</Note>

## Prerequisites

Before this page: a connected wallet (see [Connecting external wallets](/docs/javascript/building-ui/connecting-external-wallets)) for the wallet payment path. Your **backend** creates the flow and passes its `flowId` to the frontend — flow creation is server-side; this page covers the frontend.

## What you'll build

A Flow deposit takes a payment from a user's **wallet** or an **exchange** and settles it to a destination. The frontend walks the flow through its lifecycle:

1. **Load** the flow and check it's still actionable.
2. **Attach a source** — a wallet or an exchange.
3. **Quote** — lock in amounts and fees.
4. **Sign & submit** (wallet) or **confirm** (exchange).
5. **Track** to settlement.

The flow's `executionState` and `settlementState` are string unions you branch on at every step.

## Load the flow

Read `flowId` (typically from the URL so a user can resume) and load it with `getFlow`. Skip the UI when the flow has already reached a terminal state.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import { getFlow } from '@dynamic-labs-sdk/client';

    // 'broadcasted' and 'source_confirmed' are mid-flight, not terminal --
    // the transaction landed on-chain but settlement can still be pending.
    // This must match the same check used in "Track to settlement" below.
    function isFlowFinished(flow: { executionState: string; settlementState: string }) {
      return (
        ['cancelled', 'expired', 'failed'].includes(flow.executionState) ||
        ['completed', 'failed'].includes(flow.settlementState)
      );
    }

    async function loadFlow(flowId: string) {
      const flow = await getFlow({ flowId });
      if (isFlowFinished(flow)) {
        showFinished(flow); // your implementation
        return null;
      }
      return flow;
    }
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { useGetFlow } from '@dynamic-labs-sdk/react-hooks';

    function FlowScreen({ flowId }: { flowId: string }) {
      const { data: flow, isLoading } = useGetFlow({ flowId });

      if (isLoading) return <span>Loading…</span>;
      if (!flow) return null;

      // Branch on flow.executionState to pick which step to render.
      // FlowSteps is your implementation.
      return <FlowSteps flow={flow} />;
    }
    ```
  </Tab>
</Tabs>

## Attach a payment source

`attachFlowSource` records where the payment comes from. For a **wallet**, pass the user's address and chain. For an **exchange**, pass the provider — the response's flow carries the hosted buy URL to open.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import { attachFlowSource, type WalletAccount } from '@dynamic-labs-sdk/client';

    async function attachWallet(flowId: string, walletAccount: WalletAccount) {
      await attachFlowSource({
        flowId,
        sourceType: 'wallet',
        fromAddress: walletAccount.address,
        fromChainName: walletAccount.chain,
      });
    }

    async function attachExchange(flowId: string) {
      const { flow } = await attachFlowSource({
        flowId,
        sourceType: 'exchange',
        exchangeProvider: 'coinbase',
      });
      return flow; // open the exchange buy URL from the flow's source metadata
    }
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { type WalletAccount } from '@dynamic-labs-sdk/client';
    import { useAttachFlowSource } from '@dynamic-labs-sdk/react-hooks';

    function AttachWalletButton({
      flowId,
      walletAccount,
    }: {
      flowId: string;
      walletAccount: WalletAccount;
    }) {
      const { mutateAsync: attach, isPending } = useAttachFlowSource();

      const onClick = async () => {
        await attach({
          flowId,
          sourceType: 'wallet',
          fromAddress: walletAccount.address,
          fromChainName: walletAccount.chain,
        });
      };

      return (
        <button type="button" onClick={onClick} disabled={isPending}>
          Pay with wallet
        </button>
      );
    }
    ```
  </Tab>
</Tabs>

## Quote the flow

`getFlowQuote` returns the flow with a `quote` — `fromAmount`, `toAmount`, `fees`, and `estimatedTimeSec`. Quotes expire (`quote.expiresAt`), so let the user re-quote before signing. Omit `fromTokenAddress` to pay in the chain's native token.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import { getFlowQuote } from '@dynamic-labs-sdk/client';

    async function quote(flowId: string, fromTokenAddress?: string) {
      const flow = await getFlowQuote({ flowId, fromTokenAddress });
      if (!flow.quote) return;

      showQuote({
        send: flow.quote.fromAmount,
        receive: flow.quote.toAmount,
        feesUsd: flow.quote.fees?.totalFeeUsd,
        expiresAt: flow.quote.expiresAt,
      }); // your implementation
      return flow;
    }
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { useGetFlowQuote } from '@dynamic-labs-sdk/react-hooks';

    function QuoteButton({ flowId }: { flowId: string }) {
      const { mutateAsync: getQuote, data, isPending } = useGetFlowQuote();
      const quote = data?.quote;

      return (
        <div>
          <button type="button" onClick={() => getQuote({ flowId })} disabled={isPending}>
            {isPending ? 'Refreshing…' : 'Get quote'}
          </button>
          {quote && (
            <p>
              Send {quote.fromAmount} → receive {quote.toAmount}
            </p>
          )}
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

## Sign and submit (wallet)

`submitFlowTransaction` runs the whole signing pipeline — prepare, approve (if the token needs an allowance), sign, and broadcast. `onStepChange` fires `'approval'` then `'transaction'` so you can show progress.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import { submitFlowTransaction, type WalletAccount } from '@dynamic-labs-sdk/client';

    async function submit(flowId: string, walletAccount: WalletAccount) {
      const flow = await submitFlowTransaction({
        flowId,
        walletAccount,
        // updateStep is your implementation — 'approval' | 'transaction'.
        onStepChange: (step) => updateStep(step),
      });
      return flow;
    }
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { type WalletAccount } from '@dynamic-labs-sdk/client';
    import { useSubmitFlowTransaction } from '@dynamic-labs-sdk/react-hooks';

    function SubmitButton({
      flowId,
      walletAccount,
    }: {
      flowId: string;
      walletAccount: WalletAccount;
    }) {
      const { mutateAsync: submit, isPending } = useSubmitFlowTransaction();

      const onClick = async () => {
        await submit({
          flowId,
          walletAccount,
          // updateStep is your implementation.
          onStepChange: (step) => updateStep(step),
        });
      };

      return (
        <button type="button" onClick={onClick} disabled={isPending}>
          Confirm & sign
        </button>
      );
    }
    ```
  </Tab>
</Tabs>

For an **exchange** source, the user pays on the exchange instead of signing. Once they're done, call `broadcastFlow({ flowId })` to tell the backend the transfer was made.

## Track to settlement

Poll `getFlow` until it settles. `executionState` covers the on-chain leg (`broadcasted` → `source_confirmed`); `settlementState` covers delivery (`settling` → `completed`). Stop when either terminates.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import { getFlow } from '@dynamic-labs-sdk/client';
    import type { Flow } from '@dynamic-labs-sdk/client';

    function isDone(flow: Flow) {
      return (
        ['cancelled', 'expired', 'failed'].includes(flow.executionState) ||
        ['completed', 'failed'].includes(flow.settlementState)
      );
    }

    async function poll(flowId: string) {
      const flow = await getFlow({ flowId });
      renderProgress(flow.executionState, flow.settlementState); // your implementation
      if (!isDone(flow)) setTimeout(() => poll(flowId), 3000);
    }
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { useGetFlow } from '@dynamic-labs-sdk/react-hooks';

    function FlowStatus({ flowId }: { flowId: string }) {
      const { data: flow } = useGetFlow({
        flowId,
        queryParams: {
          refetchInterval: (query) => {
            const f = query.state.data;
            if (!f) return 3000;
            const done =
              ['cancelled', 'expired', 'failed'].includes(f.executionState) ||
              ['completed', 'failed'].includes(f.settlementState);
            return done ? false : 3000;
          },
        },
      });

      if (!flow) return null;
      if (flow.settlementState === 'completed') return <span>Payment complete</span>;
      if (flow.executionState === 'failed' || flow.settlementState === 'failed')
        return <span>Payment failed</span>;
      return <span>Processing…</span>;
    }
    ```
  </Tab>
</Tabs>

## Cancelling

Before broadcast, a user can back out with `cancelFlow({ flowId })`. After broadcast the transaction is on-chain and can't be reversed — hide the cancel entry point once `executionState` reaches `broadcasted`.

## Handling errors

| Situation                        | What to do                                                  |
| -------------------------------- | ----------------------------------------------------------- |
| Flow in a terminal state on load | Show a finished/expired screen instead of the payment UI.   |
| Quote expired before signing     | Re-quote with `getFlowQuote`, then let the user sign again. |
| User rejected the wallet prompt  | No funds moved — keep the submit entry point to retry.      |
| `settlementState === 'failed'`   | Surface the failure; inspect `flow.failure` for context.    |

## See also

* [Connecting external wallets](/docs/javascript/building-ui/connecting-external-wallets) — connect the wallet used to pay
* [Network switching & validation](/docs/javascript/building-ui/network-switching) — put the wallet on the right network
* [Token balances & display](/docs/javascript/building-ui/token-balances-display) — pick the token to pay with
