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

# How-to: Transfer tokens

> Send tokens from a modular wallet, with gas optionally sponsored by Circle's paymaster.

Send tokens from a user's modular wallet. The examples below transfer 1 USDC on
Arc Testnet, with gas sponsored by [Gas Station](/wallets/gas-station).

<Info>
  For a complete working app that transfers tokens, see the [Circle Smart
  Account
  example](https://github.com/circlefin/modularwallets-web-sdk/tree/master/examples/circle-smart-account)
  in the modular wallets web SDK repository.
</Info>

## Before you begin

Before you begin, ensure that you've:

* [Created a modular wallet](/wallets/modular/create-a-modular-wallet) and have
  access to the `smartAccount` and `bundlerClient` from that tutorial.
* Funded the smart account address with USDC on Arc Testnet from
  [faucet.circle.com](https://faucet.circle.com).
* Configured a Gas Station policy in the
  [Circle Console](https://console.circle.com/) if you plan to sponsor gas on
  mainnet. Testnet usage is sponsored automatically.

<Note>
  Before shipping to production, also [set up passkey
  recovery](/wallets/modular/set-up-passkey-recovery) so users can restore
  access if they lose their passkey.
</Note>

## Steps

<Steps>
  <Step title="Send the user operation">
    A user operation packages a contract call, in this case an ERC-20 `transfer`,
    for the bundler to submit through the user's MSCA. Setting `paymaster: true` (or
    `Paymaster.True()` on iOS and Android) sponsors gas through your Gas Station
    policy.

    Testnet usage is sponsored automatically. For mainnet, configure a policy in the
    Circle Console first.

    <CodeGroup>
      ```typescript Web SDK (TypeScript) theme={null}
      import {
        encodeTransfer,
        ContractAddress,
      } from "@circle-fin/modular-wallets-core";
      import { parseUnits } from "viem";

      const recipient = "0x..."; // recipient address

      const { to, data } = encodeTransfer(
        recipient,
        ContractAddress.ArcTestnet_USDC,
        parseUnits("1", 6), // 1 USDC, 6 decimals
      );

      const userOpHash = await bundlerClient.sendUserOperation({
        // calls is an array. Pass multiple entries to batch them atomically.
        calls: [{ to, data }],
        paymaster: true,
      });
      ```

      ```swift iOS SDK (Swift) theme={null}
      import CircleModularWalletsCore

      Task {
          do {
              let recipient = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

              let result = Utils.encodeTransfer(
                  to: recipient,
                  token: ArcTestnetToken.USDC.name,
                  amount: BigInt(1_000_000) // 1 USDC, 6 decimals
              )

              let userOpHash = try await bundlerClient.sendUserOperation(
                  account: smartAccount,
                  calls: [
                      EncodeCallDataArg(
                          to: result.to,
                          value: BigInt(0),
                          data: result.data
                      )
                  ],
                  paymaster: Paymaster.True()
              )
          } catch {
              print(error)
          }
      }
      ```

      ```kotlin Android SDK (Kotlin) theme={null}
      CoroutineScope(Dispatchers.IO).launch {
          try {
              val recipient = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

              val result = encodeTransfer(
                  recipient,
                  Token.ArcTestnet_USDC.name,
                  parseUnits("1", 6), // 1 USDC, 6 decimals
              )

              val userOpHash = bundlerClient.sendUserOperation(
                  context,
                  smartAccount,
                  arrayOf(
                      EncodeCallDataArg(
                          to = result.to,
                          value = BigInteger.ZERO,
                          data = result.data,
                      ),
                  ),
                  paymaster = Paymaster.True(),
              )
          } catch (e: Exception) {
              e.printStackTrace()
          }
      }
      ```

      ```java Android SDK (Java) theme={null}
      try {
          String recipient = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

          EncodeTransferResult result = encodeTransfer(
              recipient,
              Token.ArcTestnet_USDC.name(),
              parseUnits("1", 6) // 1 USDC, 6 decimals
          );

          EncodeCallDataArg transferCall = new EncodeCallDataArg(
              result.getTo(),
              BigInteger.ZERO,
              result.getData()
          );

          CompletableFuture<String> userOpHashFuture = new CompletableFuture<>();
          bundlerClient.sendUserOperation(
              context,
              smartAccount,
              new EncodeCallDataArg[] { transferCall },
              new UserOperationV07(),
              new Paymaster.True(),
              new CustomContinuation<>(userOpHashFuture)
          );
          String userOpHash = userOpHashFuture.join();
      } catch (Exception e) {
          e.printStackTrace();
      }
      ```
    </CodeGroup>

    `sendUserOperation` returns a `userOpHash`, a 66-character hex string that
    identifies the user operation.

    `encodeTransfer` supports the tokens listed in the `ContractAddress` enum,
    including USDC. See the full list in the
    [Web](/sdks/modular/web-sdk#function-encodetransfer),
    [iOS](/sdks/modular/ios-sdk#function-encodetransfer), or
    [Android](/sdks/modular/android-sdk#function-encodetransfer) SDK reference.

    <Note>
      For tokens not listed in `ContractAddress`, or when batching multiple
      contract calls in a single user operation, build `calls` manually. Each
      entry is a `{ to, data }` pair executed by the MSCA. See
      [Batch and parallel user operations](/wallets/modular/batch-and-parallel-user-operations)
      for the pattern.
    </Note>
  </Step>

  <Step title="Wait for the receipt">
    Poll for the receipt to confirm the transfer landed onchain.

    <CodeGroup>
      ```typescript Web SDK (TypeScript) theme={null}
      const { receipt } = await bundlerClient.waitForUserOperationReceipt({
        hash: userOpHash,
      });

      console.log("Transaction hash:", receipt.transactionHash);
      ```

      ```swift iOS SDK (Swift) theme={null}
              let receipt = try await bundlerClient.waitForUserOperationReceipt(
                  userOpHash: userOpHash
              )

              print("Transaction hash: \(receipt.transactionHash)")
      ```

      ```kotlin Android SDK (Kotlin) theme={null}
              val receipt = bundlerClient.waitForUserOperationReceipt(userOpHash)

              println("Transaction hash: ${receipt.transactionHash}")
      ```

      ```java Android SDK (Java) theme={null}
          CompletableFuture<UserOperationReceipt> receiptFuture = new CompletableFuture<>();
          bundlerClient.waitForUserOperationReceipt(
              userOpHash,
              new CustomContinuation<>(receiptFuture)
          );
          UserOperationReceipt receipt = receiptFuture.join();

          System.out.println("Transaction hash: " + receipt.getTransactionHash());
      ```
    </CodeGroup>

    The receipt contains `transactionHash`, the onchain transaction hash. Look it up
    on the block explorer to confirm the transfer landed.

    <Note>
      `waitForUserOperationReceipt` polls locally. To be notified of transfer
      activity on the wallet asynchronously, including inbound transfers from
      external senders, [set up a webhook
      endpoint](/api-reference/webhook-endpoints).
    </Note>
  </Step>
</Steps>

You've now transferred 1 USDC on behalf of your user, with gas paid by your
paymaster policy.
