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

# End to End Token Transfer Flow

> A complete example of preparing, signing, and sending an ERC20 token transfer on Ethereum using the WalletSuite SDK.

<Info>
  This walkthrough uses a local private key from an imported mnemonic so every step is reproducible. In production, you'll keep the same prepare → sign → broadcast shape, but sign with your existing key-management system (KMS, HSM, custodian) instead of the SDK's private-key helper. See [Bring your own signer](#bring-your-own-signer) below.
</Info>

<Steps>
  <Step>
    #### Initialize

    ```ts theme={null}
    import { WalletSuiteSDK } from "@walletsuite/wallet-sdk";

    const sdk = new WalletSuiteSDK({
      apiKey: process.env.WALLETSUITE_API_KEY!,
      env: "prod",
    });

    await sdk.init();
    ```
  </Step>

  <Step>
    #### Import wallet and get Ethereum account

    ```ts theme={null}
    const mnemonic = process.env.MNEMONIC as string;
    if (!mnemonic) throw new Error("MNEMONIC is required");

    const wallet = await sdk.importWallet(mnemonic);

    // Default account for Ethereum (m/44'/60'/0'/0/0)
    const eth = await wallet.deriveEthereum();
    console.log("ETH address:", eth.address);
    ```
  </Step>

  <Step>
    #### Read USDT balance on Ethereum

    ```ts theme={null}
    const usdtContract = "0xdAC17F958D2ee523a2206206994597C13D831ec7";

    const before = await sdk.api.getAssetBalances(eth.address, {
      chain: "ethereum",
      fiat: "USD",
      assetIds: [usdtContract],
      includeNative: false,
    });

    console.log("USDT balance (before):", before.data);
    ```

    Response fields

    * `amount` is human-readable (decimals applied)
    * `smallestUnit` is the raw token integer balance
  </Step>

  <Step>
    #### Prepare USDT transfer (fees, nonce, calldata, simulation)

    This stage produces a signing payload that your UI can display and confirm.

    **Server side actions**

    * Resolve nonce if not provided
    * Build ERC20 calldata
    * Select fee mode (EIP1559 by default, legacy fallback when required)
    * Run a simulation check

    **Client side actions**

    * Display fee information to the user
    * If user confirms, sign the payload locally using the SDK signer

    ```ts theme={null}
    const to = process.env.TO as string;
    if (!to) throw new Error("TO is required");

    // Send 1 USDT
    const amountSmallest = "1000000"; // 1 * 10^6

    const prepared = await sdk.api.prepareTransferSign({
      chain: "ethereum",
      txType: "TRANSFER_TOKEN",
      from: eth.address,
      to,
      tokenContract: usdtContract,
      amountWei: amountSmallest,

      // Optional fee tuning
      // priorityFeeMultiplier: 1.0,
      // maxPriorityFeePerGasWei: "2000000000",

      // Optional explicit nonce (otherwise backend resolves it)
      // nonce: "12",
    });
    ```

    <Warning>
      If the simulation fails, `prepareTransferSign` throws a structured SDK error with backend code `SIMULATION_FAILED`.
    </Warning>
  </Step>

  <Step>
    #### Sign transaction

    In this walkthrough, signing happens entirely on the client using a raw private key - this is the SDK's [External Signing (BYO)](/core-concepts/architecture#external-signing-byo) path, useful for tests and integration spikes. For MPC threshold signing, use the [WalletSuite MCP server](/ai-agents/overview) instead.

    * No network requests are made during signing
    * Private keys are provided by the client application at call time
    * WalletSuite SDK does not persist keys

    <Info>
      The SDK's `signEvmTransaction` / `signTronTransaction` helpers accept a raw private key — useful for tests and integration spikes. If your signer lives in a KMS, HSM, or custodian, skip these helpers: pass `prepared.data` to your signer, take the signed hex back, and continue to the broadcast step. `sendSignedTransaction` accepts any valid signed hex. See [Security Best Practices → Key Management](/sdk/security-best-practices#key-management) for backend key-storage guidance.
    </Info>

    ```ts theme={null}
    // Sign locally — no network requests, private key never leaves the client
    const signedTx = sdk.signEvmTransaction(prepared.data, eth.privateKeyHex);

    // 0x-prefixed signed raw transaction
    console.log("signedTx:", signedTx.slice(0, 20) + "...");
    ```
  </Step>

  <Step>
    #### Broadcast signed transaction

    ```ts theme={null}
    const sent = await sdk.api.sendSignedTransaction({
      chain: "ethereum",
      signedTx,
    });

    console.log("tx hash:", sent.data.hash);
    ```
  </Step>

  <Step>
    #### Check transaction status

    The SDK does not provide any waiting or polling helpers.

    After broadcasting the transaction, the client application is responsible for deciding when and how to check the status. A common approach is to wait \~10 seconds (Ethereum) or few seconds other chains before the first check and then poll periodically until a terminal state is reached.

    ```ts theme={null}
    // Example: single status check after a delay handled by the client
    const status = await sdk.api.getTransactionStatus(sent.data.hash, "ethereum");

    console.log("status:", status.status);
    console.log("success:", status.success);
    console.log("block:", status.blockNumber ?? "-");
    ```
  </Step>

  <Step>
    #### Read USDT balance again

    ```ts theme={null}
    const after = await sdk.api.getAssetBalances(eth.address, {
      chain: "ethereum",
      fiat: "USD",
      assetIds: [usdtContract],
      includeNative: false,
    });

    console.log("USDT balance (after):", after.data);
    ```

    Notes

    * Balance updates depend on chain finality and node/indexer freshness.
    * If you transfer the full balance, account level gas costs are paid in ETH and do not affect USDT amount.
  </Step>
</Steps>

<Warning>
  **Common failure modes — Simulation failed.**

  * Typical causes: insufficient token balance, invalid recipient, token restrictions
  * Expected SDK error: a structured error with backend code `SIMULATION_FAILED`
</Warning>

***

## Bring your own signer

The prepare-sign → broadcast surface of the SDK is signer-agnostic. `prepareTransferSign` returns a typed unsigned payload and `sendSignedTransaction` accepts any valid signed hex — how you sign in between is up to you.

### Local private key

For tests, integration spikes, or cases where the private key legitimately belongs in your application's process. `sdk.transfer()` handles the whole prepare → sign → broadcast flow in one call:

```ts theme={null}
const result = await sdk.transfer({
  chain: "ethereum",
  from: eth.address,
  to: "0xRecipient",
  amount: "1000000",                                          // smallest unit
  privateKey: eth.privateKeyHex,
  txType: "TRANSFER_TOKEN",                                   // optional, auto-detected from tokenContract
  tokenContract: "0xdAC17F958D2ee523a2206206994597C13D831ec7", // omit for native transfers
  priorityFeeMultiplier: 1.0,                                 // optional, default 1.0
});

console.log("tx hash:", result.hash);
```

Use the step-by-step flow above when you need to display fee details to the user before signing, or when you want to split preparation and signing across processes.

### External signer (KMS, HSM, custodian)

The production pattern, equivalent to [External Signing](/core-concepts/architecture#external-signing-byo) in the architecture model. Prepare with `sdk.api.prepareTransferSign`, hand the payload to your existing signer, broadcast with `sdk.api.sendSignedTransaction`. The SDK never sees the key.

```ts theme={null}
// 1. Prepare the unsigned payload with the SDK
const prepared = await sdk.api.prepareTransferSign({
  chain: "ethereum",
  txType: "TRANSFER_TOKEN",
  from: fromAddress,
  to: recipient,
  tokenContract: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  amountWei: "1000000",
});

// 2. Sign with your own infrastructure — shape varies per signer
const signedTx = await yourSigner.signEvmTransaction(prepared.data);

// 3. Broadcast
const sent = await sdk.api.sendSignedTransaction({
  chain: "ethereum",
  signedTx,
});
```

See [Add Wallet Operations to Your Application](/getting-started/use-case-guides/add-wallet-to-backend) for a worked backend-integration example.
