> ## 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.

# Transaction confirmation & simulation

> Simulate a transaction, show the user what it will do (assets, fees, security), send it, and wait for confirmation.

## Prerequisites

Before this page: have a connected wallet (see [Connecting external wallets](/docs/javascript/building-ui/connecting-external-wallets)) on the network you're transacting on (see [Network switching & validation](/docs/javascript/building-ui/network-switching)).

<Note>
  Transactions are chain-specific. The examples here use the EVM extension (`@dynamic-labs-sdk/evm`). The pattern — simulate, confirm with the user, send, wait — is the same on other chains with that chain's functions.
</Note>

## What you'll build

A trustworthy "review & send" step:

1. **Simulate** the transaction to preview asset changes, fees, and a security verdict — before the user signs.
2. **Show** that preview and let the user approve or cancel.
3. **Send** the transaction with the wallet's client.
4. **Wait** for it to confirm and update your UI.

## Simulating before you send

`simulateEvmTransaction` returns the assets moving in and out, optional `feeData`, and a Blockaid `validation` verdict (`benign` / `warning` / `malicious`). Use it to warn — or block — before the user signs.

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

    type TransactionRequest = {
      from: `0x${string}`;
      to: `0x${string}`;
      value: bigint;
      networkId: string;
    };

    async function reviewTransaction(
      walletAccount: EvmWalletAccount,
      transaction: TransactionRequest,
    ) {
      const result = await simulateEvmTransaction({
        walletAccount,
        transaction,
        includeFees: true,
      });

      // Block anything flagged malicious.
      if (result.validation?.result === 'malicious') {
        // showBlocked is your implementation.
        showBlocked(result.validation.description ?? 'This transaction looks unsafe.');
        return;
      }

      // showReview renders the preview and resolves true if the user approves.
      const approved = await showReview({
        warning:
          result.validation?.result === 'warning'
            ? result.validation.reason
            : undefined,
        fee: result.feeData?.humanReadableAmount,
        feeUsd: result.feeData?.usdAmount,
        outAssets: result.outAssets,
        inAssets: result.inAssets,
      });

      if (approved) await send(walletAccount, transaction);
    }
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { simulateEvmTransaction } from '@dynamic-labs-sdk/evm';
    import type { EvmSimulationResult, EvmWalletAccount } from '@dynamic-labs-sdk/evm';
    import { useState } from 'react';

    type TransactionRequest = {
      from: `0x${string}`;
      to: `0x${string}`;
      value: bigint;
      networkId: string;
    };

    function useTransactionReview(walletAccount: EvmWalletAccount) {
      const [simulation, setSimulation] = useState<EvmSimulationResult | null>(null);
      const [isSimulating, setIsSimulating] = useState(false);

      const review = async (transaction: TransactionRequest) => {
        setIsSimulating(true);
        try {
          const result = await simulateEvmTransaction({
            walletAccount,
            transaction,
            includeFees: true,
          });
          setSimulation(result);
        } finally {
          setIsSimulating(false);
        }
      };

      const isMalicious = simulation?.validation?.result === 'malicious';
      return { simulation, isSimulating, isMalicious, review };
    }
    ```

    Render `simulation.outAssets` / `inAssets`, `simulation.feeData?.humanReadableAmount`, and gate the approve button on `!isMalicious`.
  </Tab>
</Tabs>

## Sending and confirming

Get a wallet client for the account, send the transaction to obtain a hash, then `confirmTransaction` resolves once it's confirmed on-chain.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"system"}
    import { createWalletClientForWalletAccount } from '@dynamic-labs-sdk/evm/viem';
    import { confirmTransaction } from '@dynamic-labs-sdk/client';
    import type { EvmWalletAccount } from '@dynamic-labs-sdk/evm';

    async function send(
      walletAccount: EvmWalletAccount,
      transaction: { to: `0x${string}`; value: bigint },
    ) {
      const walletClient = await createWalletClientForWalletAccount({
        walletAccount,
      });

      // The wallet prompts the user to sign here.
      const transactionHash = await walletClient.sendTransaction(transaction);

      // showPending / showConfirmed are your implementation.
      showPending(transactionHash);
      await confirmTransaction({ walletAccount, transactionHash });
      showConfirmed(transactionHash);
    }
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={"system"}
    import { createWalletClientForWalletAccount } from '@dynamic-labs-sdk/evm/viem';
    import type { EvmWalletAccount } from '@dynamic-labs-sdk/evm';
    import { useConfirmTransaction } from '@dynamic-labs-sdk/react-hooks';
    import { useState } from 'react';

    function SendButton({
      walletAccount,
      transaction,
    }: {
      walletAccount: EvmWalletAccount;
      transaction: { to: `0x${string}`; value: bigint };
    }) {
      const [hash, setHash] = useState<string | null>(null);
      const { mutateAsync: confirm, isPending: isConfirming } =
        useConfirmTransaction();

      const send = async () => {
        const walletClient = await createWalletClientForWalletAccount({
          walletAccount,
        });
        const transactionHash = await walletClient.sendTransaction(transaction);
        setHash(transactionHash);
        await confirm({ walletAccount, transactionHash });
      };

      return (
        <button type="button" onClick={send} disabled={isConfirming}>
          {isConfirming ? 'Confirming…' : hash ? 'Sent' : 'Send'}
        </button>
      );
    }
    ```
  </Tab>
</Tabs>

## Signing a message

For sign-in prompts, approvals, or off-chain proofs, `signMessage` returns a signature without a transaction.

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

    async function proveOwnership(walletAccount: WalletAccount) {
      const { signature } = await signMessage({
        walletAccount,
        message: "Sign to confirm it's you.",
      });
      // verifyOnServer is your implementation.
      await verifyOnServer(signature);
    }
    ```
  </Tab>

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

    function SignButton({ walletAccount }: { walletAccount: WalletAccount }) {
      const { mutateAsync: signMessage, isPending } = useSignMessage();

      return (
        <button
          type="button"
          disabled={isPending}
          onClick={() =>
            signMessage({ walletAccount, message: "Sign to confirm it's you." })
          }
        >
          {isPending ? 'Waiting for signature…' : 'Sign message'}
        </button>
      );
    }
    ```
  </Tab>
</Tabs>

## Handling errors

| Situation                              | What to do                                                                                                                                                                                          |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `validation.result === 'malicious'`    | Block the transaction — don't offer an approve button.                                                                                                                                              |
| `validation.result === 'warning'`      | Show the `reason`/`description` and require an explicit confirmation.                                                                                                                               |
| User rejects the wallet prompt         | `sendTransaction`/`signMessage` reject with the provider's own error (e.g. viem's rejection error), not the SDK's `UserRejectedError` — catch that and return to the review step; nothing was sent. |
| Send succeeds but confirmation is slow | Keep showing the pending state with the hash; `confirmTransaction` resolves when it lands.                                                                                                          |
| Simulation fails                       | Let the user proceed with a clear "couldn't preview this transaction" caveat, or block — your call based on risk tolerance.                                                                         |

## See also

* [Token balances & display](/docs/javascript/building-ui/token-balances-display) — reflect balance changes after a transaction
* [Network switching & validation](/docs/javascript/building-ui/network-switching) — make sure the wallet is on the right network first
* Building a swap UI — a higher-level flow built on the same primitives
