> ## Documentation Index
> Fetch the complete documentation index at: https://docs.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](https://docs.dynamic.xyz/docs/javascript/building-ui/connecting-external-wallets.md)) on the network you're transacting on (see [Network switching & validation](https://docs.dynamic.xyz/docs/javascript/building-ui/network-switching.md)).

> [!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.

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

> [tab] 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] 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`.

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

> [tab] 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] 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>
>   );
> }
> ```

## Signing a message

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

> [tab] 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] 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>
>   );
> }
> ```

## 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](https://docs.dynamic.xyz/docs/javascript/building-ui/token-balances-display.md) — reflect balance changes after a transaction
* [Network switching & validation](https://docs.dynamic.xyz/docs/javascript/building-ui/network-switching.md) — make sure the wallet is on the right network first
* Building a swap UI — a higher-level flow built on the same primitives


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.