Skip to main content
This quickstart walks you through sending tokens from one wallet to another on the same blockchain. The example in this quickstart sends USDC on Arc Testnet, but you can use another supported token or blockchain.
Use this flow to send USDC from a browser wallet. The wallet runs in the browser, and the user signs transactions in the wallet extension, so connect the wallet first and call kit.send() only after the wallet is available.This example uses the Viem adapter to send USDC on an EVM-compatible blockchain in an existing browser app. The sample configuration uses Arc Testnet, but you can use any supported EVM chain and token.

Prerequisites

Before you begin, ensure that you have:

Step 1. Set up the project

1.1. Create the project and install dependencies

Create a new directory, install the App Kit packages, and add local browser demo tooling:
Shell
# Set up your directory and initialize a Node.js project
mkdir app-kit-send-browser-wallet
cd app-kit-send-browser-wallet
npm init -y
npm pkg set type=module

# Install App Kit packages
npm install @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem

# Install TypeScript and a local Vite dev server for the browser demo
npm install --save-dev typescript vite

1.2. Configure TypeScript (optional)

This step is optional. It helps prevent missing types in your IDE or editor.
Create a tsconfig.json file:
Shell
npx tsc --init
Then, update the tsconfig.json file:
Shell
cat <<'EOF' > tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "types": ["node"]
  }
}
EOF

Step 2. Connect a browser wallet

This step shows the core browser wallet integration flow: discover an EIP-6963 provider, create an App Kit adapter from the selected provider, and pass that adapter into an App Kit SDK method.The snippets below keep wallet discovery, wallet connection, and adapter setup in small helper functions for readability.

2.1. Discover a browser wallet with EIP-6963

This pattern is standards-based. The example uses MetaMask as the selected wallet, but the discovery flow works with any wallet that announces an EIP-6963 provider.
TypeScript
import type { EIP1193Provider } from "viem";

type EIP6963ProviderInfo = {
  uuid: string;
  name: string;
  icon: string;
  rdns: string;
};

type EIP6963ProviderDetail = {
  info: EIP6963ProviderInfo;
  provider: EIP1193Provider;
};

declare global {
  interface WindowEventMap {
    "eip6963:announceProvider": CustomEvent<EIP6963ProviderDetail>;
  }
}

async function discoverBrowserWallets(): Promise<EIP6963ProviderDetail[]> {
  const providers = new Map<string, EIP6963ProviderDetail>();

  const handleProviderAnnouncement = (
    event: WindowEventMap["eip6963:announceProvider"],
  ) => {
    providers.set(event.detail.info.uuid, event.detail);
  };

  window.addEventListener(
    "eip6963:announceProvider",
    handleProviderAnnouncement,
  );
  window.dispatchEvent(new Event("eip6963:requestProvider"));

  await new Promise((resolve) => window.setTimeout(resolve, 250));
  window.removeEventListener(
    "eip6963:announceProvider",
    handleProviderAnnouncement,
  );

  return [...providers.values()];
}

2.2. Connect the wallet and request account access

After you select a provider, request account access before attempting to bridge. This should happen in a user-triggered action such as a Connect wallet button.
TypeScript
async function connectWallet(provider: EIP1193Provider) {
  await provider.request({
    method: "eth_requestAccounts",
    params: undefined, // Required by the provider type even though this method has no params.
  });

  const accounts = (await provider.request({
    method: "eth_accounts",
    params: undefined, // Required by the provider type even though this method has no params.
  })) as string[];

  return {
    connectedAddress: accounts[0] ?? null,
  };
}
Keep wallet connection and bridging as separate user actions. This avoids overlapping wallet permission or chain-switch requests while a previous wallet prompt is still pending.

2.3. Create a Viem adapter from the selected wallet provider

Use the discovered provider to request account access, then create the App Kit adapter that signs bridge transactions in the browser:
TypeScript
import { createViemAdapterFromProvider } from "@circle-fin/adapter-viem-v2";

async function connectBrowserWallet() {
  const providers = await discoverBrowserWallets();
  const selectedWallet =
    providers.find(
      ({ info }) => info.rdns === "io.metamask" || info.name === "MetaMask",
    ) ?? providers[0];

  if (!selectedWallet) {
    throw new Error("No EIP-6963 browser wallet found");
  }

  const { connectedAddress } = await connectWallet(selectedWallet.provider);

  const adapter = await createViemAdapterFromProvider({
    provider: selectedWallet.provider,
  });

  return {
    adapter,
    connectedAddress,
    walletName: selectedWallet.info.name,
  };
}
If multiple EVM wallets are installed, explicitly choose the wallet you want to use instead of relying on the first announced provider. The browser demo used to validate this quickstart prefers MetaMask when it is available.

Step 3. Send USDC

3.1. Call kit.send()

This is the only App Kit-specific send call you need after the wallet is connected:
TypeScript
import { AppKit } from "@circle-fin/app-kit";
import type { SendParams } from "@circle-fin/app-kit";

const kit = new AppKit();

async function sendUSDCWithBrowserWallet() {
  const { adapter, connectedAddress, walletName } =
    await connectBrowserWallet();

  const sendParams: SendParams = {
    from: { adapter, chain: "Arc_Testnet" },
    to: "RECIPIENT_ADDRESS",
    amount: "1.00",
    token: "USDC",
  };

  const estimate = await kit.estimateSend(sendParams);
  const result = await kit.send(sendParams);

  console.log(`Submitted send with ${walletName}`, {
    connectedAddress,
    estimate,
    result,
  });
  return result;
}
Using another token or blockchain? Change the token and chain values in kit.send() and ensure the connected wallet holds enough funds to complete the transfer.

3.2. Verify the transaction

After kit.send() resolves, inspect the returned result. Use the transaction explorer URL to verify the amount and recipient on the blockchain.The following is an example of how the result of a successful send might look in the browser console. The values are used in this example only and are not a real transaction:
Browser console
{
  name: "transfer",
  state: "success",
  txHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  explorerUrl: "https://testnet.arcscan.app/tx/0x1234567890abcdef...",
}