Skip to main content
A delegate wallet can deposit into a user’s Unified Balance and spend from it after the user gives authorization. In this quickstart, you’ll need two wallets: a user wallet that owns the Unified Balance and a delegate wallet that deposits funds and signs spends on the user’s behalf. You’ll write scripts that deposit from Base Sepolia, authorize the delegate on Base Sepolia, check the user’s Unified Balance, and spend on Arc Testnet. These are examples only. You can use any of the supported blockchains and fund the Unified Balance from as many sources as you need. The scripts use built-in public RPC URLs, which may be rate-limited or unreliable. For a more stable connection, you can configure a custom RPC.

Prerequisites

Before you begin, ensure that you’ve:
  • Installed Node.js v22+.
  • Created two EVM wallets (delegate and user) using a wallet provider such as MetaMask and added the Base Sepolia and Arc Testnet networks.
  • Funded the delegate wallet with testnet tokens:
    • Get testnet USDC from the Circle Faucet on Base Sepolia.
    • Get testnet ETH on Base Sepolia from a public faucet (needed for deposit and spend transactions on Base Sepolia).
  • Funded the user wallet with testnet ETH on Base Sepolia (needed for gas to authorize the delegate on Base Sepolia).
  • Fund wallets on Arc Testnet if needed (USDC there can cover gas for the destination credit when the delegate spends on Arc Testnet).
  • Obtained a recipient address on Arc Testnet that will receive the USDC.

Step 1. Set up your project

1.1. Create the project and install dependencies

Create a new directory and install App Kit and its dependencies:
Shell
mkdir unified-balance-delegate
cd unified-balance-delegate
npm init -y
npm pkg set type=module

npm install @circle-fin/app-kit @circle-fin/adapter-viem-v2 viem
npm install --save-dev typescript tsx @types/node
Only need a Unified Balance and want a lighter install than the full App Kit? Install the standalone package instead: @circle-fin/unified-balance-kit

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

1.3. Set environment variables

Create a .env file in the project directory:
.env
DELEGATE_EVM_PRIVATE_KEY=0xYOUR_DELEGATE_PRIVATE_KEY
USER_EVM_PRIVATE_KEY=0xYOUR_USER_PRIVATE_KEY
USER_EVM_ADDRESS=0xYOUR_USER_ADDRESS
EVM_RECIPIENT_ADDRESS=0xYOUR_RECIPIENT_ADDRESS
  • Replace 0xYOUR_DELEGATE_PRIVATE_KEY with the private key for the delegate wallet that holds USDC on Base Sepolia, signs deposits, and spends on the user’s behalf.
  • Replace 0xYOUR_USER_PRIVATE_KEY with the private key for the user wallet that owns the Unified Balance and authorizes the delegate.
  • Replace 0xYOUR_USER_ADDRESS with the user wallet’s public address.
  • Replace 0xYOUR_RECIPIENT_ADDRESS with the address that should receive USDC on Arc Testnet when the delegate spends.
If you use MetaMask, follow their guide for how to find and export your private key.
Edit .env files in your IDE or editor so credentials are not leaked to your shell history.

Step 2. Deposit into the user’s Unified Balance

In this step, the delegate funds the user’s Unified Balance from Base Sepolia.

2.1. Create the deposit script

Create a delegate-deposit.ts file. In this script, the delegate deposits 2.00 USDC from the delegate’s Base Sepolia wallet into the user’s Unified Balance.
delegate-deposit.ts
import { AppKit } from "@circle-fin/app-kit";
import { inspect } from "node:util";
import { createViemAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2";

const DEPOSIT_AMOUNT = "2.00";

const kit = new AppKit();

const delegateAdapter = createViemAdapterFromPrivateKey({
  privateKey: process.env.DELEGATE_EVM_PRIVATE_KEY as `0x${string}`,
});

async function main() {
  // depositFor: credit the user's balance; depositAccount is their address
  const result = await kit.unifiedBalance.depositFor({
    from: { adapter: delegateAdapter, chain: "Base_Sepolia" },
    amount: DEPOSIT_AMOUNT,
    token: "USDC",
    depositAccount: process.env.USER_EVM_ADDRESS as string,
  });

  console.log("Result:", inspect(result, false, null, true));
}

void main();
depositFor is permissionless. Any wallet can fund another user’s Unified Balance. The delegate does not need prior authorization to deposit.

2.2. Run the deposit script

In your terminal, run:
Shell
npx tsx --env-file=.env delegate-deposit.ts
You’ll see output like:
Shell
Result:
{
  amount: '2.00',
  token: 'USDC',
  chain: 'Base_Sepolia',
  txHash: '0x...',
  explorerUrl: 'https://sepolia.basescan.org/tx/0x...',
  ...
}

2.3. Verify the deposit

Open the explorerUrl from the deposit result to confirm the onchain transaction on Base Sepolia.

Step 3. Authorize the delegate

In this step, the user grants the delegate permission to spend from their Unified Balance on a specific blockchain.

3.1. Create the authorize script

Create a delegate-authorize.ts file. In this script, the user wallet authorizes the delegate to spend from their Unified Balance on Base Sepolia:
delegate-authorize.ts
import { AppKit } from "@circle-fin/app-kit";
import { inspect } from "node:util";
import { createViemAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2";
import { privateKeyToAccount } from "viem/accounts";

const kit = new AppKit();

const userAdapter = createViemAdapterFromPrivateKey({
  privateKey: process.env.USER_EVM_PRIVATE_KEY as `0x${string}`,
});

const delegateAddress = privateKeyToAccount(
  process.env.DELEGATE_EVM_PRIVATE_KEY as `0x${string}`,
).address;

async function main() {
  const status = await kit.unifiedBalance.getDelegateStatus({
    from: { adapter: userAdapter, chain: "Base_Sepolia" },
    delegateAddress,
  });

  if (status === "ready") {
    console.log(
      `Delegate ${delegateAddress} is already authorized on Base_Sepolia.`,
    );
    return;
  }

  if (status === "pending") {
    console.log(
      `Delegate ${delegateAddress} is still pending on Base_Sepolia. Wait and run this script again.`,
    );
    return;
  }

  // addDelegate: user-signed transaction granting the delegate spend rights
  const result = await kit.unifiedBalance.addDelegate({
    from: { adapter: userAdapter, chain: "Base_Sepolia" },
    delegateAddress,
  });

  console.log("Result:", inspect(result, false, null, true));
}

void main();
addDelegate is an onchain transaction signed by the user. Once authorized, the delegate can spend repeatedly on the same blockchain without reauthorization. Authorization is per-blockchain. See Manage Delegates for details.

3.2. Run the authorize script

In your terminal, run:
Shell
npx tsx --env-file=.env delegate-authorize.ts
You’ll see output like:
Shell
Result:
{
  txHash: '0x...',
  explorerUrl: 'https://sepolia.basescan.org/tx/0x...',
  ...
}
If status is already 'ready', the script exits without calling addDelegate. If status is 'pending', it asks you to wait and run the script again. Otherwise it submits addDelegate.

Step 4. Check the user’s Unified Balance

In this step, the delegate checks the user’s Unified Balance by address.

4.1. Create the balance check script

Create a delegate-check-balance.ts file. This script prints the user’s confirmed and pending Unified Balance totals:
delegate-check-balance.ts
import { AppKit } from "@circle-fin/app-kit";
import { inspect } from "node:util";

const kit = new AppKit();

async function main() {
  const balances = await kit.unifiedBalance.getBalances({
    // Query by account address instead of the delegate's adapter
    sources: { address: process.env.USER_EVM_ADDRESS as string },
    networkType: "testnet",
    includePending: true,
  });

  console.log("Result:", inspect(balances, false, null, true));
}

void main();

4.2. Run the balance check script

In your terminal, run:
Shell
npx tsx --env-file=.env delegate-check-balance.ts
You’ll see output like:
Shell
Result:
{
  token: 'USDC',
  totalConfirmedBalance: '2.00',
  totalPendingBalance: '0.00',
  breakdown: [
    {
      depositor: '0x...',
      totalConfirmed: '2.00',
      totalPending: '0.00',
      breakdown: [{ chain: 'Base_Sepolia', confirmedBalance: '2.00', ... }]
    }
  ]
}
After a deposit, funds can appear in totalPendingBalance before they are reflected in totalConfirmedBalance. Wait until the user’s totalConfirmedBalance is high enough for the spend you plan to make before you continue.

Step 5. Spend from the user’s balance

In this step, the delegate spends from the user’s Unified Balance on Arc Testnet for the recipient.

5.1. Create the spend script

Create a delegate-spend.ts file. This script spends 0.50 USDC from the user’s Unified Balance on Arc Testnet for the recipient, signed by the delegate. App Kit chooses how much USDC to use from each blockchain.
delegate-spend.ts
import { AppKit } from "@circle-fin/app-kit";
import { inspect } from "node:util";
import { createViemAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2";

const SPEND_AMOUNT = "0.50";

const kit = new AppKit();

const delegateAdapter = createViemAdapterFromPrivateKey({
  privateKey: process.env.DELEGATE_EVM_PRIVATE_KEY as `0x${string}`,
});

async function main() {
  const recipientAddress = process.env.EVM_RECIPIENT_ADDRESS as string;

  console.log(
    `Spending ${SPEND_AMOUNT} USDC on Arc_Testnet for ${recipientAddress}...\n`,
  );

  const result = await kit.unifiedBalance.spend({
    amount: SPEND_AMOUNT,
    token: "USDC",
    from: [
      {
        adapter: delegateAdapter,
        // Spend from the user's balance; delegateAdapter only signs
        sourceAccount: process.env.USER_EVM_ADDRESS as string,
      },
    ],
    to: {
      adapter: delegateAdapter,
      chain: "Arc_Testnet",
      recipientAddress,
    },
  });

  console.log("Result:", inspect(result, false, null, true));
}

void main();
You can customize your Unified Balance to collect a custom fee from end users, estimate fees before spending, select source blockchains and allocations to fund a balance, or use the Forwarding Service.

5.2. Run the spend script

In your terminal, run:
Shell
npx tsx --env-file=.env delegate-spend.ts
When the script completes, you should see output similar to:
Shell
Spending 0.50 USDC on Arc_Testnet for 0x...

Result:
{ recipientAddress: '0x...', destinationChain: 'Arc Testnet', txHash: '0x...', ... }

5.3. Verify the spend

Use the explorerUrl from the spend result to confirm that USDC arrived at the recipient address on Arc Testnet. The received amount can be less than the requested spend after fees. For more on fees, see How Unified Balance fees work.