Guides
Send Tokens

Send Tokens

Transfer tokens from your Veridex vault to any address.

Prerequisites

  • Wallet connected (see Create Wallet)
  • Tokens in your vault (use testnet faucets)
  • SDK initialized

Basic Transfer

Prepare and Execute

import { createSDK } from '@veridex/sdk';
import { ethers } from 'ethers';
 
const sdk = createSDK('base', {
  network: 'testnet',
  relayerUrl: 'https://relayer.veridex.network',
});
 
// Authenticate first (shows passkey picker)
const { credential } = await sdk.passkey.authenticate();
 
// Step 1: Prepare the transfer (shows gas cost before signing)
const prepared = await sdk.prepareTransfer({
  targetChain: 10004,  // Base Sepolia Wormhole chain ID
  token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // USDC
  recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234',
  amount: ethers.parseUnits('10', 6), // 10 USDC
});
 
console.log('Gas cost:', prepared.formattedCost);
console.log('Expires:', new Date(prepared.expiresAt));
 
// Step 2: Get human-readable summary
const summary = await sdk.getTransactionSummary(prepared);
console.log(summary.title);       // "Transfer"
console.log(summary.description); // "Send 10.0 USDC to 0x742d...5A234"
 
// Step 3: Execute with a signer (signs with passkey, then dispatches)
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const result = await sdk.executeTransfer(prepared, signer);
 
console.log('Transaction hash:', result.transactionHash);
console.log('Sequence:', result.sequence);

Native Token Transfer

const prepared = await sdk.prepareTransfer({
  targetChain: 10004,
  token: 'native',
  recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234',
  amount: ethers.parseEther('0.01'), // 0.01 ETH
});
 
const result = await sdk.executeTransfer(prepared, signer);
console.log('ETH transfer:', result.transactionHash);

Agent SDK Payments

For AI agents, the Agent SDK provides a simpler payment interface:

import { createAgentWallet } from '@veridex/agentic-payments';
 
const agent = await createAgentWallet({
  masterCredential: { /* passkey credential */ },
  session: {
    dailyLimitUSD: 50,
    perTransactionLimitUSD: 10,
    expiryHours: 24,
    allowedChains: [10004], // Base Sepolia
  },
});
 
// Direct payment — session key handles signing
const receipt = await agent.pay({
  chain: 10004,          // Base Sepolia
  token: 'USDC',
  amount: '10000000',    // 10 USDC (6 decimals)
  recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234',
});
 
console.log('Tx:', receipt.txHash);
console.log('Status:', receipt.status);

Check Spending Limits Before Transfer

import { ethers } from 'ethers';
 
// Check if a transfer amount is within limits
const check = await sdk.checkSpendingLimit(ethers.parseUnits('100', 6));
 
if (check.allowed) {
  console.log('Transfer allowed!');
} else {
  console.log('Blocked:', check.message);
  console.log('Suggestions:', check.suggestions);
}
 
// Get formatted limits for UI
const formatted = await sdk.getFormattedSpendingLimits();
console.log(`${formatted.dailyUsedPercentage}% of daily limit used`);

Check Balance Before Transfer

// Get vault balances on the current chain
const portfolio = await sdk.getVaultBalances();
 
for (const entry of portfolio.tokens) {
  console.log(`${entry.token.symbol}: ${entry.formatted}`);
}
console.log('Total USD:', portfolio.totalUsdValue);

Transaction Tracking

Track transaction status after execution:

const result = await sdk.executeTransfer(prepared, signer);
 
// Wait for confirmation
const state = await sdk.waitForTransaction(result.transactionHash);
console.log('Status:', state.status);
console.log('Confirmations:', state.confirmations);
 
console.log('Tx Hash:', result.transactionHash);
console.log('Sequence:', result.sequence);

React Example

import { useState } from 'react';
import { createSDK } from '@veridex/sdk';
import { ethers } from 'ethers';
 
const sdk = createSDK('base', { 
  network: 'testnet',
  relayerUrl: 'https://relayer.veridex.network',
});
 
const USDC = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
 
export function SendForm() {
  const [recipient, setRecipient] = useState('');
  const [amount, setAmount] = useState('');
  const [loading, setLoading] = useState(false);
  const [txHash, setTxHash] = useState<string | null>(null);
  const [summary, setSummary] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);
 
  const handleSend = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    setTxHash(null);
 
    try {
      // Prepare transfer
      const prepared = await sdk.prepareTransfer({
        targetChain: 10004,
        token: USDC,
        recipient,
        amount: ethers.parseUnits(amount, 6),
      });
 
      // Show summary
      const txSummary = await sdk.getTransactionSummary(prepared);
      setSummary(`${txSummary.description} (Gas: ${prepared.formattedCost})`);
 
      // Execute
      const provider = new ethers.BrowserProvider(window.ethereum);
      const signer = await provider.getSigner();
      const result = await sdk.executeTransfer(prepared, signer);
 
      setTxHash(result.transactionHash);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Transfer failed');
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <form onSubmit={handleSend} className="space-y-4">
      <div>
        <label className="block text-sm font-medium">Recipient</label>
        <input
          type="text"
          value={recipient}
          onChange={(e) => setRecipient(e.target.value)}
          placeholder="0x..."
          className="mt-1 block w-full rounded-md border p-2"
          required
        />
      </div>
 
      <div>
        <label className="block text-sm font-medium">Amount (USDC)</label>
        <input
          type="number"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          placeholder="10.00"
          step="0.01"
          className="mt-1 block w-full rounded-md border p-2"
          required
        />
      </div>
 
      <button
        type="submit"
        disabled={loading}
        className="w-full bg-blue-600 text-white py-2 rounded-lg"
      >
        {loading ? 'Sending...' : 'Send USDC'}
      </button>
 
      {summary && <p className="text-gray-600 text-sm">{summary}</p>}
 
      {txHash && (
        <p className="text-green-600">
          Success!{' '}
          <a 
            href={`https://sepolia.basescan.org/tx/${txHash}`}
            target="_blank"
            rel="noopener noreferrer"
            className="underline"
          >
            View on Explorer
          </a>
        </p>
      )}
 
      {error && <p className="text-red-600">{error}</p>}
    </form>
  );
}

Common Token Addresses

Base Sepolia (Testnet)

TokenAddress
USDC0x036CbD53842c5426634e7929541eC2318f3dCF7e
WETH0x4200000000000000000000000000000000000006

Base Mainnet

TokenAddress
USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
WETH0x4200000000000000000000000000000000000006

Error Handling

try {
  const prepared = await sdk.prepareTransfer({ /* ... */ });
  const result = await sdk.executeTransfer(prepared, signer);
} catch (error: any) {
  if (error.message?.includes('No credential set')) {
    console.log('Please register or login first');
  } else if (error.message?.includes('expired')) {
    console.log('Prepared transfer expired, please prepare again');
  } else {
    console.error('Transfer failed:', error);
  }
}
 
// For Agent SDK payments
import { AgentPaymentError, AgentPaymentErrorCode } from '@veridex/agentic-payments';
 
try {
  await agent.pay({ chain: 10004, token: 'USDC', amount: '1000000', recipient: '0x...' });
} catch (error) {
  if (error instanceof AgentPaymentError) {
    if (error.code === AgentPaymentErrorCode.INSUFFICIENT_BALANCE) {
      console.log('Not enough tokens:', error.suggestion);
    } else if (error.code === AgentPaymentErrorCode.LIMIT_EXCEEDED) {
      console.log('Spending limit exceeded');
    }
  }
}

Next Steps