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 Account Requirements
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
Smart Contract Information
Step 1: Request Delegation Token
Call your backend to generate a single-use delegation token from the API.
Backend Implementation
TypeScript
Python
JavaScript
cURL
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:
Connect User's Wallet
Use Web3 provider to connect to user’s wallet application
Request Network Switch
Ensure user is on correct blockchain network (if needed)
Request Approval Transaction
Call token contract’s approval function with spending limit
Wait for Confirmation
Monitor blockchain for transaction confirmation
Request Message Signature
Have user sign a message to prove wallet ownership
Collect Transaction Data
Gather all required fields for Step 3 submission
Frontend Implementation (EVM Example)
TypeScript (ethers.js v6)
TypeScript (wagmi + viem)
JavaScript (web3.js)
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
Transaction Preview
Progress Indicator
Error Handling
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 >
Keep users informed during the multi-step process: < div className = "progress-steps" >
< div className = "step completed" >
< span className = "icon" > ✅ </ span >
< span > Get Token </ span >
</ div >
< div className = "step active" >
< span className = "icon" > 🔄 </ span >
< span > Connect Wallet </ span >
< div className = "sub-steps" >
< div className = "sub-step completed" > ✅ Connect wallet </ div >
< div className = "sub-step active" > ⏳ Approve transaction </ div >
< div className = "sub-step" > ⏸️ Sign message </ div >
</ div >
</ div >
< div className = "step pending" >
< span className = "icon" > ⏸️ </ span >
< span > Finalize </ span >
</ div >
</ div >
Handle common user scenarios gracefully: try {
const data = await performWalletDelegation ( ... );
// Proceed to Step 3
} catch ( error : any ) {
if ( error . code === 4001 ) {
showError ( 'Transaction rejected. Click "Retry" when ready to approve.' );
} else if ( error . message . includes ( 'insufficient funds' )) {
showError ( 'Insufficient balance for gas fees. Please add funds to your wallet.' );
} else if ( error . message . includes ( 'wrong network' )) {
showError ( 'Please switch to the correct network in your wallet.' );
} else {
showError ( `Transaction failed: ${ error . message } ` );
}
}
Step 3: Submit Delegation Proof
Call your backend to submit the delegation proof and finalize wallet registration.
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
What Happens
API validates address format (EVM vs Solana)
API validates transaction hash format
API verifies signature matches wallet address
API validates blockchain transaction exists and is confirmed
External wallet is registered and linked to user account
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:
TypeScript (Complete Flow)
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
Frontend Requirements
Smart Contract Integration
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
User Rejected Transaction
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