Skip to main content

Overview

Implementing delegation requires coordination between your backend (API calls) and frontend (wallet interaction). This guide walks through all four steps with complete code examples.
Architecture Note: Steps 1, 3, and 4 are backend API calls. Step 2 happens entirely in your frontend with Web3 libraries and wallet providers.

Prerequisites

Before implementing delegation, ensure you have:
  • User must have completed registration including consent acceptance
  • User must have accepted terms of service and data processing consent
  • User must have completed identity verification (KYC)
  • See Consent Management for compliance tracking
  • Client public key (x-client-key header)
  • User access token (OAuth2 Bearer token)
  • Valid API credentials from your dashboard
  • Web3 library installed (ethers.js, web3.js, or @solana/web3.js)
  • Wallet provider support (MetaMask, WalletConnect, Phantom)
  • User has wallet installed and funded with desired currency
  • Token contract addresses for each network (USDC/USDT)
  • Platform spender addresses for approvals
  • Chain IDs for each supported network
  • Obtain dynamically: Call GET /v1/delegation/chain/config to retrieve all addresses and chain IDs before Step 1

Step 1: Request Delegation Token

Call your backend to generate a single-use delegation token from the API.

Backend Implementation

interface DelegationTokenResponse {
  token: string;
  nonce: string;
}

async function requestDelegationToken(
  userAccessToken: string
): Promise<{ token: string; nonce: string }> {
  const response = await fetch('https://api.yourplatform.com/v1/delegation/token', {
    method: 'GET',
    headers: {
      'x-client-key': process.env.CLIENT_PUBLIC_KEY!,
      'Authorization': `Bearer ${userAccessToken}`,
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    throw new Error(`Failed to get delegation token: ${response.statusText}`);
  }

  const data: DelegationTokenResponse = await response.json();
  return { token: data.token, nonce: data.nonce };
}
API Reference: GET /v1/delegation/token

Response

{
  "token": "100a99cf-f4d3-4fa1-9be9-2e9828b20ebc",
  "nonce": "5f8a9b2c4d3e1a7b9c6d8e2f"
}

Token Characteristics

  • Single-use only: Becomes invalid after use in Step 3
  • ~10 minute lifetime: Complete the flow before expiration
  • Temporary storage: Store in session/state, not database
  • Pass to frontend: Required for Step 3 submission
Do not reuse delegation tokens. Generate a new token for each delegation attempt.

Step 2: Frontend Wallet Interaction

This step happens entirely in your frontend application. The API does not provide this functionality.
Important: This is client-side code that runs in the browser. You’ll need to implement this using Web3 libraries and wallet provider integrations.

Architecture Overview

Required Tasks

Your frontend must complete these tasks:
1

Connect User's Wallet

Use Web3 provider to connect to user’s wallet application
2

Request Network Switch

Ensure user is on correct blockchain network (if needed)
3

Request Approval Transaction

Call token contract’s approval function with spending limit
4

Wait for Confirmation

Monitor blockchain for transaction confirmation
5

Request Message Signature

Have user sign a message to prove wallet ownership
6

Collect Transaction Data

Gather all required fields for Step 3 submission

Frontend Implementation (EVM Example)

import { BrowserProvider, Contract, parseUnits } from 'ethers';

interface DelegationData {
  address: string;
  network: 'linea' | 'ethereum';
  currency: 'usdc' | 'usdt';
  amount: string;
  txHash: string;
  sigHash: string;
  sigMessage: string;
  token: string;
}

const ERC20_ABI = [
  'function approve(address spender, uint256 amount) returns (bool)'
];

async function performWalletDelegation(
  delegationToken: string,
  network: 'linea' | 'ethereum',
  currency: 'usdc' | 'usdt',
  amount: string,
  tokenAddress: string,
  spenderAddress: string
): Promise<DelegationData> {

  if (!window.ethereum) {
    throw new Error('No Web3 wallet detected');
  }

  const provider = new BrowserProvider(window.ethereum);

  await provider.send('eth_requestAccounts', []);

  const signer = await provider.getSigner();
  const address = await signer.getAddress();

  const tokenContract = new Contract(tokenAddress, ERC20_ABI, signer);

  const amountWei = parseUnits(amount, 6);

  console.log(`Requesting approval for ${amount} ${currency.toUpperCase()}`);
  const tx = await tokenContract.approve(spenderAddress, amountWei);

  console.log('Waiting for transaction confirmation...');
  const receipt = await tx.wait();

  if (!receipt || receipt.status !== 1) {
    throw new Error('Transaction failed');
  }

  const txHash = receipt.hash;
  console.log('Transaction confirmed:', txHash);

  const chainId = (await provider.getNetwork()).chainId;
  const nonce = Math.random().toString(36).substring(7);
  const issuedAt = new Date().toISOString();
  const expirationTime = new Date(Date.now() + 30 * 60000).toISOString();

  const siweMessage =
`yourplatform.com wants you to sign in with your Ethereum account:
${address}

Prove wallet ownership for delegation

URI: https://yourplatform.com
Version: 1
Chain ID: ${chainId}
Nonce: ${nonce}
Issued At: ${issuedAt}
Expiration Time: ${expirationTime}`;

  console.log('Requesting signature for proof...');
  const sigHash = await signer.signMessage(siweMessage);

  return {
    address,
    network,
    currency,
    amount,
    txHash,
    sigHash,
    sigMessage: siweMessage,
    token: delegationToken
  };
}

Frontend Implementation (Solana Example)

import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import {
  createApproveInstruction,
  getAssociatedTokenAddress,
  TOKEN_PROGRAM_ID
} from '@solana/spl-token';
import { useWallet } from '@solana/wallet-adapter-react';

interface SolanaDelegationData {
  address: string;
  network: 'solana';
  currency: 'usdc' | 'usdt';
  amount: string;
  txHash: string;
  sigHash: string;
  sigMessage: string;
  token: string;
}

async function performSolanaWalletDelegation(
  wallet: any,
  delegationToken: string,
  currency: 'usdc' | 'usdt',
  amount: string,
  mintAddress: string,
  delegateAddress: string
): Promise<SolanaDelegationData> {

  if (!wallet.connected || !wallet.publicKey) {
    throw new Error('Wallet not connected');
  }

  const connection = new Connection('https://api.mainnet-beta.solana.com');
  const userPublicKey = wallet.publicKey;

  const tokenAccount = await getAssociatedTokenAddress(
    new PublicKey(mintAddress),
    userPublicKey
  );

  const amountBN = BigInt(parseFloat(amount) * 1_000_000);

  const approveInstruction = createApproveInstruction(
    tokenAccount,
    new PublicKey(delegateAddress),
    userPublicKey,
    amountBN,
    [],
    TOKEN_PROGRAM_ID
  );

  const transaction = new Transaction().add(approveInstruction);
  transaction.feePayer = userPublicKey;

  const { blockhash } = await connection.getLatestBlockhash();
  transaction.recentBlockhash = blockhash;

  const signedTx = await wallet.signTransaction(transaction);
  const txHash = await connection.sendRawTransaction(signedTx.serialize());

  await connection.confirmTransaction(txHash);

  const message = 'Prove wallet ownership for delegation';
  const messageBytes = new TextEncoder().encode(message);

  const signature = await wallet.signMessage(messageBytes);
  const sigHash = Buffer.from(signature).toString('base64');

  return {
    address: userPublicKey.toBase58(),
    network: 'solana',
    currency,
    amount,
    txHash,
    sigHash,
    sigMessage: message,
    token: delegationToken
  };
}

UI/UX Recommendations

Show users exactly what they’re approving before signing:
<div className="delegation-preview">
  <h3>Approve Wallet Delegation</h3>
  <div className="details">
    <div className="detail-row">
      <span>Network:</span>
      <span>{network.toUpperCase()}</span>
    </div>
    <div className="detail-row">
      <span>Currency:</span>
      <span>{currency.toUpperCase()}</span>
    </div>
    <div className="detail-row">
      <span>Spending Limit:</span>
      <span>{amount} {currency.toUpperCase()}</span>
    </div>
    <div className="detail-row">
      <span>Platform:</span>
      <span>YourPlatform</span>
    </div>
  </div>
  <p className="info">
    This allows YourPlatform to spend up to {amount} {currency.toUpperCase()}
    from your wallet for card purchases.
  </p>
  <div className="actions">
    <button onClick={onCancel}>Cancel</button>
    <button onClick={onApprove}>Approve in Wallet</button>
  </div>
</div>

Step 3: Submit Delegation Proof

Call your backend to submit the delegation proof and finalize wallet registration.
Use the blockchain-specific endpoint: /v1/delegation/evm/post-approval for EVM chains (Linea/Ethereum) or /v1/delegation/solana/post-approval for Solana.

Backend Implementation (EVM)

interface EVMDelegationProof {
  address: string;
  network: 'linea' | 'ethereum';
  currency: 'usdc' | 'usdt';
  amount: string;
  txHash: string;
  sigHash: string;
  sigMessage: string;
  token: string;
}

async function submitEVMDelegation(
  userAccessToken: string,
  proof: EVMDelegationProof
): Promise<boolean> {
  const response = await fetch(
    'https://api.yourplatform.com/v1/delegation/evm/post-approval',
    {
      method: 'POST',
      headers: {
        'x-client-key': process.env.CLIENT_PUBLIC_KEY!,
        'Authorization': `Bearer ${userAccessToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(proof)
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Delegation failed: ${error.message || response.statusText}`);
  }

  const data = await response.json();
  return data.success;
}

Backend Implementation (Solana)

interface SolanaDelegationProof {
  address: string;
  network: 'solana';
  currency: 'usdc' | 'usdt';
  amount: string;
  txHash: string;
  sigHash: string;
  sigMessage: string;
  token: string;
}

async function submitSolanaDelegation(
  userAccessToken: string,
  proof: SolanaDelegationProof
): Promise<boolean> {
  const response = await fetch(
    'https://api.yourplatform.com/v1/delegation/solana/post-approval',
    {
      method: 'POST',
      headers: {
        'x-client-key': process.env.CLIENT_PUBLIC_KEY!,
        'Authorization': `Bearer ${userAccessToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(proof)
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Delegation failed: ${error.message || response.statusText}`);
  }

  const data = await response.json();
  return data.success;
}
API Reference: POST /v1/delegation/evm/post-approval

Response

{
  "success": true
}

What Happens

  1. API validates address format (EVM vs Solana)
  2. API validates transaction hash format
  3. API verifies signature matches wallet address
  4. API validates blockchain transaction exists and is confirmed
  5. External wallet is registered and linked to user account
  6. User can now make card purchases using delegated wallet funds
If validation fails, you’ll receive a 400 error with details about what went wrong. Common issues include invalid signature, unconfirmed transaction, or expired token.

Step 4: Verify Registration

Confirm the wallet was successfully registered and view its details.

Backend Implementation

interface ExternalWallet {
  address: string;
  currency: string;
  balance: string;
  allowance: string;
  network: string;
}

async function verifyWalletRegistration(
  userAccessToken: string
): Promise<ExternalWallet[]> {
  const response = await fetch(
    'https://api.yourplatform.com/v1/wallet/external',
    {
      method: 'GET',
      headers: {
        'x-client-key': process.env.CLIENT_PUBLIC_KEY!,
        'Authorization': `Bearer ${userAccessToken}`
      }
    }
  );

  if (!response.ok) {
    throw new Error(`Failed to fetch wallets: ${response.statusText}`);
  }

  return await response.json();
}
API Reference: GET /v1/wallet/external

Response

[
  {
    "address": "0x3a11a86cf218c448be519728cd3ac5c741fb3424",
    "currency": "usdc",
    "balance": "5000.00",
    "allowance": "5000",
    "network": "linea"
  }
]

Complete Flow Example

Here’s a complete end-to-end implementation example:
async function completeDelegationFlow(
  userAccessToken: string,
  network: 'linea' | 'ethereum' | 'solana',
  currency: 'usdc' | 'usdt',
  amount: string
): Promise<void> {
  try {
    console.log('Step 1: Requesting delegation token...');
    const { token: delegationToken, nonce } = await requestDelegationToken(userAccessToken);
    console.log('Token received:', delegationToken);
    console.log('Nonce received:', nonce);

    console.log('Step 2: Initiating wallet connection...');
    let delegationData;

    if (network === 'solana') {
      delegationData = await performSolanaWalletDelegation(
        wallet,
        delegationToken,
        currency,
        amount,
        SOLANA_MINT_ADDRESSES[currency],
        SOLANA_DELEGATE_ADDRESS
      );
    } else {
      delegationData = await performWalletDelegation(
        delegationToken,
        network,
        currency,
        amount,
        EVM_TOKEN_ADDRESSES[network][currency],
        EVM_SPENDER_ADDRESS
      );
    }

    console.log('Wallet interaction complete');

    console.log('Step 3: Submitting delegation proof...');
    if (network === 'solana') {
      await submitSolanaDelegation(userAccessToken, delegationData);
    } else {
      await submitEVMDelegation(userAccessToken, delegationData);
    }

    console.log('Delegation submitted successfully');

    console.log('Step 4: Verifying registration...');
    const wallets = await verifyWalletRegistration(userAccessToken);

    const newWallet = wallets.find(
      w => w.address.toLowerCase() === delegationData.address.toLowerCase()
    );

    if (newWallet) {
      console.log('✅ Delegation successful!');
      console.log('Wallet details:', newWallet);
      return newWallet;
    } else {
      throw new Error('Wallet not found after registration');
    }

  } catch (error) {
    console.error('Delegation failed:', error);
    throw error;
  }
}

Implementation Checklist

Use this checklist to ensure you’ve implemented all required components:

Backend Requirements

  • Implement delegation token request endpoint integration
  • Implement blockchain-specific post-approval submission (EVM/Solana)
  • Store delegation tokens securely (short-lived, single-use)
  • Handle API errors gracefully with user feedback
  • Implement wallet verification after delegation
  • Add retry logic for failed API requests
  • Log delegation attempts for debugging

Frontend Requirements

  • Implement wallet connection UI (MetaMask, WalletConnect, Phantom)
  • Support multiple blockchain networks (Linea, Ethereum, Solana)
  • Display approval transaction details before signing
  • Show transaction confirmation and status
  • Implement signature collection (SIWE for EVM chains)
  • Handle wallet errors and rejections
  • Display current allowance and balance information
  • Implement loading states and progress indicators

Smart Contract Integration

  • Obtain correct token contract addresses for each network
  • Obtain platform spender addresses for approvals
  • Implement ERC20 approval flow for EVM chains
  • Implement token account delegation for Solana
  • Handle transaction confirmation and receipts
  • Monitor blockchain for transaction success/failure
  • Validate contract addresses before approval

Troubleshooting

Symptom: API returns error about expired token in Step 3Solution: Generate a new token and restart from Step 1. Tokens are valid for ~10 minutes.
Symptom: API can’t find the blockchain transactionSolutions:
  • Wait longer for transaction confirmation (especially on Ethereum mainnet)
  • Verify transaction hash is correct
  • Check transaction status on block explorer
  • Ensure you’re on the correct network
Symptom: API rejects signature in Step 3Solutions:
  • Ensure sigMessage format matches SIWE specification (for EVM)
  • Verify signature was created from the same wallet address
  • Check that message wasn’t modified after signing
Symptom: Wallet throws error code 4001Solution: Allow user to retry without regenerating token. Keep the delegation token and retry Steps 2-3.
Symptom: Wallet is on different network than requestedSolution: Implement network switching in your frontend:
await window.ethereum.request({
  method: 'wallet_switchEthereumChain',
  params: [{ chainId: '0xe708' }] // Linea: 0xe708 (59144)
});

Next Steps