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

# Send tokens across wallets

> Send USDC from one dev-controlled wallet to another, then verify the transaction status and recipient balance.

Developer-controlled wallets can send tokens to any address on the same
blockchain. After completing this tutorial, you'll have sent USDC from one
developer-controlled wallet to\
another. The examples use Arc Testnet, but you can send tokens on any
[supported blockchain](/wallets/supported-blockchains).

<Tip>
  Circle offers [Gas Station](/wallets/gas-station) if you want to sponsor gas
  instead of funding the sender wallet directly. See [Send a Gasless
  Transaction](/wallets/gas-station/send-a-gasless-transaction).
</Tip>

## Prerequisites

Before you begin, ensure you have:

* Obtained an [API key](/api-reference/keys) from the
  [Circle Console](https://console.circle.com/).
* Registered an [entity secret](/wallets/dev-controlled/register-entity-secret).
* Completed the
  [Create a dev-controlled wallet](/wallets/dev-controlled/create-your-first-wallet)
  quickstart and created two wallets (a source wallet and a destination wallet).
* Funded the source wallet with testnet USDC from the
  [Circle Faucet](https://faucet.circle.com/).
* Installed [Node.js 22+](https://nodejs.org/) or
  [Python 3.11+](https://www.python.org/).

## Step 1. Set up your project

Reuse the `dev-controlled-projects` folder you created in the
[Create a dev-controlled wallet](/wallets/dev-controlled/create-your-first-wallet)
quickstart.

### 1.1. Prepare your project

Add the transfer run command:

<CodeGroup>
  ```shell Node.js theme={null}
  # Add a script for the transfer quickstart
  npm pkg set scripts.send-tokens="tsx --env-file=.env send-tokens.ts"
  ```

  ```shell Python theme={null}
  # Create or reactivate the virtual environment
  python3 -m venv .venv
  source .venv/bin/activate

  # Install runtime dependencies
  pip install circle-developer-controlled-wallets python-dotenv
  ```
</CodeGroup>

### 1.2. Set environment variables

Add your API key and entity secret to `.env`:

```text .env theme={null}
CIRCLE_API_KEY=YOUR_API_KEY
CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
```

* `CIRCLE_API_KEY` is your Circle Developer API key.
* `CIRCLE_ENTITY_SECRET` is your registered entity secret.

<Tip>
  Open `.env` in your editor rather than writing values with shell commands, and
  add `.env` to your `.gitignore`. This prevents credentials from leaking into
  your shell history or version control.
</Tip>

## Step 2. Send USDC between wallets

Write a script that sends USDC from the source wallet, polls for completion,
then checks the recipient balance.

### 2.1. Create the script

Create a `send-tokens.ts` (or `send_tokens.py`) file and add the following code.
The script calls `createTransaction()` to initiate the transfer, then polls
`getTransaction()` until the transaction reaches a terminal state: `COMPLETE`,
`FAILED`, `CANCELLED`, or `DENIED`.

<CodeGroup>
  ```ts send-tokens.ts theme={null}
  import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";

  // Replace the source and destination constants with your own wallet values
  const SOURCE_WALLET_ADDRESS: string = "0x..."; // Used with blockchain to identify the source wallet
  const SOURCE_WALLET_BLOCKCHAIN = "0x..."; // Used with blockchain to identify the source wallet
  const DESTINATION_WALLET_ADDRESS: string = "0x..."; // Recipient wallet address
  const DESTINATION_WALLET_ID: string = "..."; // Used for post-transfer balance check only
  const ARC_TESTNET_USDC: string = "0x3600000000000000000000000000000000000000";
  const TRANSFER_AMOUNT_USDC: string = "5"; // Token quantity as a string

  // Initialize the wallets client
  const client = initiateDeveloperControlledWalletsClient({
    apiKey: process.env.CIRCLE_API_KEY!,
    entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
  });

  async function main() {
    // Validate the wallet inputs
    if (
      SOURCE_WALLET_ADDRESS === "YOUR_SOURCE_WALLET_ADDRESS" ||
      DESTINATION_WALLET_ID === "YOUR_DESTINATION_WALLET_ID" ||
      DESTINATION_WALLET_ADDRESS === "YOUR_DESTINATION_WALLET_ADDRESS"
    ) {
      throw new Error(
        "Replace the wallet constants at the top of send-tokens.ts before running the script.",
      );
    }

    // Create the transfer transaction
    const transferResponse = await client.createTransaction({
      blockchain: SOURCE_WALLET_BLOCKCHAIN,
      walletAddress: SOURCE_WALLET_ADDRESS,
      tokenAddress: ARC_TESTNET_USDC, // USDC contract address on Arc Testnet; replace for other chains
      destinationAddress: DESTINATION_WALLET_ADDRESS,
      amount: [TRANSFER_AMOUNT_USDC],
      fee: {
        type: "level",
        config: { feeLevel: "MEDIUM" }, // Gas fee strategy: LOW, MEDIUM, or HIGH
      },
    });

    const transactionId = transferResponse.data?.id;
    let currentState = transferResponse.data?.state ?? "";

    if (!transactionId) {
      throw new Error("Transaction creation failed: no ID returned");
    }

    console.log("Transfer response:", transferResponse.data);

    // Wait for the transfer to finish
    const terminalStates = new Set(["COMPLETE", "FAILED", "CANCELLED", "DENIED"]);

    while (!terminalStates.has(currentState)) {
      await new Promise((resolve) => setTimeout(resolve, 3000));
      const pollResponse = await client.getTransaction({ id: transactionId });
      const tx = pollResponse.data?.transaction;
      currentState = tx?.state ?? "";
      console.log("Transaction response:", pollResponse.data);

      if (currentState === "COMPLETE") break;
    }

    if (currentState !== "COMPLETE") {
      throw new Error(`Transaction ended in state: ${currentState}`);
    }

    // Check the recipient balance
    const destinationBalanceResponse = await client.getWalletTokenBalance({
      id: DESTINATION_WALLET_ID,
    });

    console.log("Wallet balance response:", destinationBalanceResponse.data);
  }

  main().catch((err) => {
    console.error("Error:", err.message || err);
    process.exit(1);
  });
  ```

  ```python send_tokens.py theme={null}
  from circle.web3 import utils, developer_controlled_wallets
  from dotenv import load_dotenv
  import os
  import time
  import json

  load_dotenv()

  # Set transfer inputs
  SOURCE_WALLET_ADDRESS = "YOUR_SOURCE_WALLET_ADDRESS"
  SOURCE_WALLET_BLOCKCHAIN = "ARC-TESTNET"
  DESTINATION_WALLET_ID = "YOUR_DESTINATION_WALLET_ID"
  DESTINATION_WALLET_ADDRESS = "YOUR_DESTINATION_WALLET_ADDRESS"
  ARC_TESTNET_USDC = "0x3600000000000000000000000000000000000000"
  TRANSFER_AMOUNT_USDC = "5"

  # Initialize the wallets client
  client = utils.init_developer_controlled_wallets_client(
      api_key=os.getenv("CIRCLE_API_KEY"),
      entity_secret=os.getenv("CIRCLE_ENTITY_SECRET")
  )

  transactions_api = developer_controlled_wallets.TransactionsApi(client)
  wallets_api = developer_controlled_wallets.WalletsApi(client)

  # Validate the wallet inputs
  if (
      SOURCE_WALLET_ADDRESS == "YOUR_SOURCE_WALLET_ADDRESS"
      or DESTINATION_WALLET_ID == "YOUR_DESTINATION_WALLET_ID"
      or DESTINATION_WALLET_ADDRESS == "YOUR_DESTINATION_WALLET_ADDRESS"
  ):
      raise ValueError(
          "Replace the wallet constants at the top of send_tokens.py before running the script."
      )

  try:
      # Create the transfer transaction
      request = developer_controlled_wallets.CreateTransferTransactionForDeveloperRequest.from_dict({
          "walletAddress": SOURCE_WALLET_ADDRESS,
          "blockchain": SOURCE_WALLET_BLOCKCHAIN,
          "destinationAddress": DESTINATION_WALLET_ADDRESS,
          "tokenAddress": ARC_TESTNET_USDC,
          "amounts": [TRANSFER_AMOUNT_USDC],
          "feeLevel": "MEDIUM"
      })

      transfer_response = transactions_api.create_developer_transaction_transfer(request)
      transfer_data = transfer_response.data.to_dict()
      transaction_id = transfer_data["id"]
      current_state = transfer_data["state"]

      print(json.dumps(json.loads(transfer_response.model_dump_json()), indent=2))

      # Wait for the transfer to finish
      terminal_states = {"COMPLETE", "FAILED", "CANCELLED", "DENIED"}

      while current_state not in terminal_states:
          time.sleep(3)
          poll_response = transactions_api.get_transaction(id=transaction_id)
          transaction = poll_response.data.to_dict()["transaction"]
          current_state = transaction["state"]
          print(json.dumps(json.loads(poll_response.model_dump_json()), indent=2))

      if current_state != "COMPLETE":
          raise RuntimeError(f"Transaction ended in state: {current_state}")

      # Check the recipient balance
      destination_balance_response = wallets_api.list_wallet_balance(id=DESTINATION_WALLET_ID)
      print(json.dumps(json.loads(destination_balance_response.model_dump_json()), indent=2))
  except developer_controlled_wallets.ApiException as e:
      print("Exception when calling the Circle Wallets API: %s\n" % e)
  ```
</CodeGroup>

<Note>
  If you're calling the API directly instead of using the SDK, use
  [Create Transfer Transaction](/api-reference/wallets/developer-controlled-wallets/create-developer-transaction-transfer),
  [Get Transaction](/api-reference/wallets/developer-controlled-wallets/get-transaction),
  and
  [List Wallet Balance](/api-reference/wallets/developer-controlled-wallets/list-wallet-balance).
  Be sure to replace the entity secret ciphertext and idempotency key in your
  request. If you're using the SDKs, this is handled automatically for you.
</Note>

### 2.2. Run the script

Run the script from your project directory:

<CodeGroup>
  ```shell Node.js theme={null}
  npm run send-tokens
  ```

  ```shell Python theme={null}
  python send_tokens.py
  ```
</CodeGroup>

The output looks similar to:

<CodeGroup>
  ```text Node.js theme={null}
  Transfer response: {
    id: "6f10...",
    state: "INITIATED"
  }
  Transaction response: {
    transaction: {
      id: "6f10...",
      state: "COMPLETE",
      blockchain: "ARC-TESTNET",
      txHash: "0x..."
    }
  }
  Wallet balance response: {
    tokenBalances: [
      {
        token: [Object],
        amount: "5"
      }
    ]
  }
  ```

  ```text Python theme={null}
  {
    "data": {
      "id": "6f10...",
      "state": "INITIATED"
    }
  }
  {
    "data": {
      "transaction": {
        "id": "6f10...",
        "state": "COMPLETE",
        "blockchain": "ARC-TESTNET",
        "tx_hash": "0x..."
      }
    }
  }
  {
    "data": {
      "token_balances": [
        {
          "amount": "5",
          "token": {
            "symbol": "USDC"
          }
        }
      ]
    }
  }
  ```
</CodeGroup>

<Note>
  You can also monitor the transfer through [webhook
  notifications](/api-reference/webhooks) or by polling [Get
  Transaction](/api-reference/wallets/developer-controlled-wallets/get-transaction).
</Note>

## Next steps

* **Build payment workflows with [Arc App Kit](https://docs.arc.io/app-kit)**:
  Use the
  [Circle Wallets adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups#circle-wallets)
  to add token transfers, swaps, bridging, and chain-agnostic unified balances
  to your app without building each integration yourself.
