Guides
Cross-Chain

Cross-Chain Transfers

Send tokens from one blockchain to another using Veridex.

Overview

Veridex uses Wormhole for cross-chain messaging. When you transfer cross-chain:

  1. Hub (Base) creates authorization message
  2. Wormhole guardians attest the message
  3. Destination spoke executes the transfer

Basic Cross-Chain Transfer

import { createSDK } from '@veridex/sdk';
import { ethers } from 'ethers';
 
const sdk = createSDK('base', {
  network: 'testnet',
  relayerUrl: 'https://relayer.veridex.network',
});
 
const { credential } = await sdk.passkey.authenticate();
 
// Step 1: Estimate bridge fees
const fees = await sdk.getBridgeFees({
  sourceChain: 10004,       // Base Sepolia
  destinationChain: 10005,  // Optimism Sepolia
  token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
  recipient: sdk.getVaultAddress(),
  amount: 10000000n,
});
 
console.log('Bridge fees:', fees.formattedTotal, fees.currency);
console.log('Source gas:', fees.sourceGas);
console.log('Message fee:', fees.messageFee);
console.log('Relayer fee:', fees.relayerFee);
 
// Step 2: Prepare the bridge
const prepared = await sdk.prepareBridge({
  sourceChain: 10004,
  destinationChain: 10005,
  token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
  recipient: sdk.getVaultAddress(),
  amount: 10000000n,
});
 
// Step 3: Execute with progress tracking
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
 
const result = await sdk.executeBridge(prepared, signer, (progress) => {
  console.log(`Step ${progress.step}/${progress.totalSteps}: ${progress.message}`);
});
 
console.log('Bridge completed:', result.transactionHash);

Chain Configuration

Get Chain Info

import { getChainConfig, getSupportedChains } from '@veridex/sdk';
 
// Get all supported chains
const chains = getSupportedChains('testnet');
console.log(chains);
// ['base', 'optimism', 'arbitrum', 'solana', 'aptos', 'sui', 'starknet', 'stacks']
 
// Get specific chain config
const optimism = getChainConfig('optimism', 'testnet');
console.log(optimism);
// {
//   name: 'optimism',
//   chainId: 11155420,
//   wormholeChainId: 10005,
//   rpcUrl: 'https://sepolia.optimism.io',
//   contracts: { factory: '0x...', implementation: '0x...' }
// }

Wormhole Chain IDs

ChainTestnetMainnet
Base1000430
Optimism1000524
Arbitrum1000323
Solana11
Aptos2222
Sui2121
Starknet5000150002
Stacks500032147483648

EVM to EVM Transfer

// Base → Optimism
const result = await sdk.bridgeWithTracking(
  {
    sourceChain: 10004,
    destinationChain: 10005, // Optimism Sepolia
    token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
    recipient: sdk.getVaultAddress(), // Same address!
    amount: 10000000n,
  },
  signer,
  (p) => console.log(p.message)
);

EVM to Solana Transfer

// Base → Solana
const result = await sdk.bridgeWithTracking(
  {
    sourceChain: 10004,
    destinationChain: 1, // Solana
    token: USDC,
    recipient: 'DRpbCBMxVnDK7maPM5tGv6MvB3v1sRMC86PZ8okm21hy', // Solana address
    amount: 50000000n, // 50 USDC
  },
  signer,
  (p) => console.log(p.message)
);

EVM to Aptos Transfer

// Base → Aptos
const result = await sdk.bridgeWithTracking(
  {
    sourceChain: 10004,
    destinationChain: 22, // Aptos
    token: USDC,
    recipient: '0x1a89da9e9f8f0bc90d8d492890bd55fb261c6277d2a95dfcac70c268d0c23dcc',
    amount: 25000000n, // 25 USDC
  },
  signer,
  (p) => console.log(p.message)
);

Transfer Speed

Veridex supports two verification paths:

PathSpeedUse Case
Query (CCQ)~5-7 secondsBalance checks, low-value transfers
VAA~60 secondsHigh-value transfers, critical actions

The relayer automatically selects the optimal path based on transaction value.

Track Cross-Chain Status

The executeBridge and bridgeWithTracking methods provide real-time progress via callbacks:

const result = await sdk.executeBridge(prepared, signer, (progress) => {
  switch (progress.step) {
    case 1: console.log('Signing transaction...'); break;
    case 2: console.log('Dispatching to source chain...'); break;
    case 3: console.log('Waiting for confirmations...'); break;
    case 4: console.log('Fetching Wormhole VAA...'); break;
    case 5: console.log('Relaying to destination chain...'); break;
    case 6: console.log('Bridge completed!'); break;
  }
});
 
console.log('Source tx:', result.transactionHash);
console.log('Sequence:', result.sequence);

React Example

import { useState } from 'react';
import { createSDK, getSupportedChains } from '@veridex/sdk';
import { ethers } from 'ethers';
 
const sdk = createSDK('base', {
  network: 'testnet',
  relayerUrl: 'https://relayer.veridex.network',
});
 
const USDC = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
 
export function BridgeForm() {
  const [targetChain, setTargetChain] = useState('10005');
  const [amount, setAmount] = useState('');
  const [progress, setProgress] = useState<string | null>(null);
  const [fees, setFees] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
 
  const estimateFees = async () => {
    const feeEstimate = await sdk.getBridgeFees({
      sourceChain: 10004,
      destinationChain: parseInt(targetChain),
      token: USDC,
      recipient: sdk.getVaultAddress(),
      amount: ethers.parseUnits(amount || '0', 6),
    });
    setFees(`Estimated fees: ${feeEstimate.formattedTotal} ${feeEstimate.currency}`);
  };
 
  const handleBridge = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setProgress('Preparing bridge...');
 
    try {
      const provider = new ethers.BrowserProvider(window.ethereum);
      const signer = await provider.getSigner();
 
      const result = await sdk.bridgeWithTracking(
        {
          sourceChain: 10004,
          destinationChain: parseInt(targetChain),
          token: USDC,
          recipient: sdk.getVaultAddress(),
          amount: ethers.parseUnits(amount, 6),
        },
        signer,
        (p) => setProgress(`Step ${p.step}/${p.totalSteps}: ${p.message}`)
      );
 
      setProgress(`Bridge completed! Tx: ${result.transactionHash}`);
    } catch (error) {
      setProgress(`Error: ${error instanceof Error ? error.message : 'Bridge failed'}`);
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <form onSubmit={handleBridge} className="space-y-4">
      <div>
        <label className="block text-sm font-medium">Destination Chain</label>
        <select
          value={targetChain}
          onChange={(e) => setTargetChain(e.target.value)}
          className="mt-1 block w-full rounded-md border p-2"
        >
          <option value="10005">Optimism Sepolia</option>
          <option value="10003">Arbitrum Sepolia</option>
          <option value="1">Solana</option>
          <option value="22">Aptos</option>
          <option value="50003">Stacks Testnet</option>
        </select>
      </div>
 
      <div>
        <label className="block text-sm font-medium">Amount (USDC)</label>
        <input
          type="number"
          value={amount}
          onChange={(e) => { setAmount(e.target.value); setFees(null); }}
          onBlur={estimateFees}
          placeholder="10.00"
          step="0.01"
          className="mt-1 block w-full rounded-md border p-2"
          required
        />
      </div>
 
      {fees && <p className="text-sm text-gray-500">{fees}</p>}
 
      <button
        type="submit"
        disabled={loading}
        className="w-full bg-purple-600 text-white py-2 rounded-lg"
      >
        {loading ? 'Bridging...' : 'Bridge USDC'}
      </button>
 
      {progress && <p className="text-sm text-gray-600">{progress}</p>}
    </form>
  );
}

Agent SDK: Cross-Chain Router

The Agent SDK provides a CrossChainRouter for multi-chain agent operations:

import { createAgentWallet } from '@veridex/agentic-payments';
 
const agent = await createAgentWallet({
  masterCredential: { /* ... */ },
  session: {
    dailyLimitUSD: 100,
    perTransactionLimitUSD: 25,
    expiryHours: 24,
    allowedChains: [10004, 10005, 50003], // Base, Optimism, Stacks
  },
});
 
// Agent can pay on any allowed chain
await agent.pay({ chain: 10004, token: 'USDC', amount: '1000000', recipient: '0x...' });
await agent.pay({ chain: 10005, token: 'USDC', amount: '2000000', recipient: '0x...' });
 
// Multi-chain balance check
const portfolio = await agent.getMultiChainBalance();
console.log('Total USD:', portfolio.totalUsdValue);

Fee Estimation

const fees = await sdk.getBridgeFees({
  sourceChain: 10004,
  destinationChain: 10005,
  token: USDC,
  recipient: sdk.getVaultAddress(),
  amount: 10000000n,
});
 
console.log('Bridge fees:', fees.formattedTotal, fees.currency);
console.log('Source gas:', fees.sourceGas);
console.log('Message fee:', fees.messageFee);
console.log('Relayer fee:', fees.relayerFee);
console.log('Estimated time:', fees.estimatedTimeSeconds);

Error Handling

try {
  const result = await sdk.bridgeWithTracking(params, signer, onProgress);
} catch (error: any) {
  if (error.message?.includes('insufficient')) {
    console.log('Not enough tokens to bridge');
  } else if (error.message?.includes('unsupported')) {
    console.log('Chain not supported');
  } else if (error.message?.includes('VAA')) {
    console.log('Wormhole attestation failed, try again');
  } else {
    console.error('Bridge error:', error);
  }
}

Next Steps