> ## Documentation Index
> Fetch the complete documentation index at: https://docs.baanx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# EVM Chains (Linea & Ethereum)

> EVM-specific delegation implementation for Linea and Ethereum networks

## Overview

This guide covers EVM-specific implementation details for delegating wallets on Linea and Ethereum networks. Both networks follow the same ERC20 approval pattern with slight differences in gas costs and confirmation times.

<Note>
  Use the [`/v1/delegation/evm/post-approval`](/api-reference/delegation/evm-post-approval) endpoint for both Linea and Ethereum delegations.
</Note>

## Supported EVM Networks

<Tabs>
  <Tab title="Linea">
    **Network Name**: Linea
    **Chain ID**: 59144 (0xe708)
    **RPC URL**: [https://rpc.linea.build](https://rpc.linea.build)
    **Block Explorer**: [https://lineascan.build](https://lineascan.build)
    **Currencies**: USDC, USDT

    **Advantages**:

    * Significantly lower gas fees than Ethereum mainnet
    * Faster block times (\~2-3 seconds)
    * Primary network recommended for most integrations
    * US routing available with `x-us-env: true` header

    **Typical Gas Cost**: $0.01 - $0.10 per transaction
  </Tab>

  <Tab title="Ethereum">
    **Network Name**: Ethereum Mainnet
    **Chain ID**: 1 (0x1)
    **RPC URL**: [https://eth.llamarpc.com](https://eth.llamarpc.com)
    **Block Explorer**: [https://etherscan.io](https://etherscan.io)
    **Currencies**: USDC, USDT

    **Advantages**:

    * Maximum security and decentralization
    * Broadest wallet support
    * Most mature ecosystem

    **Typical Gas Cost**: $5 - $50+ depending on network congestion
  </Tab>
</Tabs>

## ERC20 Approval Pattern

EVM delegation uses the standard ERC20 `approve()` function to grant spending authority.

### The approve() Function

```solidity theme={null}
function approve(address spender, uint256 amount) public returns (bool)
```

**Parameters**:

* `spender`: Platform's smart contract address that will spend tokens
* `amount`: Maximum amount the spender can transfer (in token's smallest unit)

**Returns**: `true` if approval succeeded

### How It Works

<Steps>
  <Step title="User Calls approve()">
    User's wallet calls the `approve()` function on the token contract (USDC or USDT)
  </Step>

  <Step title="Allowance Updated">
    Token contract updates the allowance mapping:

    ```solidity theme={null}
    allowances[userAddress][platformSpender] = amount
    ```
  </Step>

  <Step title="Platform Can Spend">
    Platform's smart contract can now call `transferFrom()` to spend up to the approved amount
  </Step>

  <Step title="Allowance Decreases">
    Each time platform spends, the allowance decreases by the spent amount
  </Step>
</Steps>

## Implementation

### Contract Addresses

Obtain contract addresses dynamically by calling the chain configuration endpoint **before** starting the delegation flow:

<CodeGroup>
  ```typescript Fetch Chain Configuration theme={null}
  const response = await fetch('https://api.baanx.com/v1/delegation/chain/config', {
    headers: {
      'x-client-key': process.env.CLIENT_PUBLIC_KEY!,
      'Authorization': `Bearer ${userAccessToken}`
    }
  });

  const config = await response.json();

  const TOKEN_ADDRESSES = {
    linea: {
      chainId: config.linea.chain_id,         // 59144
      usdc: config.linea.usdc_address,        // Linea USDC contract
      usdt: config.linea.usdt_address,        // Linea USDT contract
      spender: config.linea.spender_address   // Platform spender for Linea
    },
    ethereum: {
      chainId: config.ethereum.chain_id,      // 1
      usdc: config.ethereum.usdc_address,     // Ethereum USDC
      usdt: config.ethereum.usdt_address,     // Ethereum USDT
      spender: config.ethereum.spender_address // Platform spender for Ethereum
    }
  };
  ```

  ```typescript Static Fallback (Not Recommended) theme={null}
  const TOKEN_ADDRESSES = {
    linea: {
      usdc: '0x...', // Linea USDC contract
      usdt: '0x...'  // Linea USDT contract
    },
    ethereum: {
      usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // Ethereum USDC
      usdt: '0xdAC17F958D2ee523a2206206994597C13D831ec7'  // Ethereum USDT
    }
  };

  const PLATFORM_SPENDER = '0x...'; // Platform's spender contract address
  ```
</CodeGroup>

<Note>
  **Recommended:** Always fetch addresses dynamically using `GET /v1/delegation/chain/config`. This ensures you have the latest contract addresses, especially after platform upgrades or network migrations.
</Note>

<Warning>
  Always verify contract addresses are correct before approving. Approving the wrong address could result in loss of funds.
</Warning>

### Frontend Implementation

<CodeGroup>
  ```typescript Complete EVM Delegation Flow theme={null}
  import { BrowserProvider, Contract, parseUnits } from 'ethers';

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

  interface EVMDelegationParams {
    network: 'linea' | 'ethereum';
    currency: 'usdc' | 'usdt';
    amount: string;
    tokenAddress: string;
    spenderAddress: string;
    delegationToken: string;
  }

  async function delegateEVMWallet(
    params: EVMDelegationParams
  ): Promise<{
    address: string;
    network: string;
    currency: string;
    amount: string;
    txHash: string;
    sigHash: string;
    sigMessage: string;
    token: string;
  }> {

    if (!window.ethereum) {
      throw new Error('No Ethereum wallet detected. Please install MetaMask.');
    }

    const provider = new BrowserProvider(window.ethereum);

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

    const network = await provider.getNetwork();
    const expectedChainId = params.network === 'linea' ? 59144n : 1n;

    if (network.chainId !== expectedChainId) {
      await switchNetwork(provider, params.network);
    }

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

    console.log(`Connected wallet: ${address}`);

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

    const decimals = params.currency === 'usdc' || params.currency === 'usdt' ? 6 : 18;
    const amountWei = parseUnits(params.amount, decimals);

    console.log(
      `Requesting approval for ${params.amount} ${params.currency.toUpperCase()} ` +
      `on ${params.network}`
    );

    const approveTx = await tokenContract.approve(
      params.spenderAddress,
      amountWei
    );

    console.log('Transaction submitted:', approveTx.hash);
    console.log('Waiting for confirmation...');

    const receipt = await approveTx.wait();

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

    console.log('Transaction confirmed in block:', receipt.blockNumber);

    const siweMessage = generateSIWEMessage(
      address,
      network.chainId.toString()
    );

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

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

  async function switchNetwork(
    provider: BrowserProvider,
    network: 'linea' | 'ethereum'
  ) {
    const chainId = network === 'linea' ? '0xe708' : '0x1';

    try {
      await provider.send('wallet_switchEthereumChain', [{ chainId }]);
    } catch (error: any) {
      if (error.code === 4902) {
        if (network === 'linea') {
          await provider.send('wallet_addEthereumChain', [
            {
              chainId: '0xe708',
              chainName: 'Linea',
              nativeCurrency: {
                name: 'Ether',
                symbol: 'ETH',
                decimals: 18
              },
              rpcUrls: ['https://rpc.linea.build'],
              blockExplorerUrls: ['https://lineascan.build']
            }
          ]);
        } else {
          throw error;
        }
      } else {
        throw error;
      }
    }
  }

  function generateSIWEMessage(address: string, chainId: string): string {
    const domain = window.location.host;
    const nonce = generateNonce();
    const issuedAt = new Date().toISOString();
    const expirationTime = new Date(Date.now() + 30 * 60000).toISOString();

    return `${domain} wants you to sign in with your Ethereum account:
  ${address}

  Prove wallet ownership for delegation

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

  function generateNonce(): string {
    return Math.random().toString(36).substring(2, 15) +
           Math.random().toString(36).substring(2, 15);
  }
  ```

  ```typescript Using wagmi + viem theme={null}
  import {
    useAccount,
    useWriteContract,
    useWaitForTransactionReceipt,
    useSignMessage,
    useSwitchChain,
    useChainId
  } from 'wagmi';
  import { parseUnits } from 'viem';
  import { linea, mainnet } from 'wagmi/chains';

  const ERC20_ABI = [
    {
      name: 'approve',
      type: 'function',
      stateMutability: 'nonpayable',
      inputs: [
        { name: 'spender', type: 'address' },
        { name: 'amount', type: 'uint256' }
      ],
      outputs: [{ type: 'bool' }]
    }
  ] as const;

  function useDelegateEVM() {
    const { address } = useAccount();
    const chainId = useChainId();
    const { switchChainAsync } = useSwitchChain();
    const { writeContractAsync } = useWriteContract();
    const { signMessageAsync } = useSignMessage();

    async function delegate(
      network: 'linea' | 'ethereum',
      currency: 'usdc' | 'usdt',
      amount: string,
      tokenAddress: `0x${string}`,
      spenderAddress: `0x${string}`,
      delegationToken: string
    ) {
      if (!address) {
        throw new Error('Wallet not connected');
      }

      const targetChain = network === 'linea' ? linea : mainnet;

      if (chainId !== targetChain.id) {
        await switchChainAsync({ chainId: targetChain.id });
      }

      const decimals = 6;
      const amountWei = parseUnits(amount, decimals);

      const hash = await writeContractAsync({
        address: tokenAddress,
        abi: ERC20_ABI,
        functionName: 'approve',
        args: [spenderAddress, amountWei]
      });

      const siweMessage = generateSIWEMessage(address, targetChain.id.toString());

      const signature = await signMessageAsync({ message: siweMessage });

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

    return { delegate };
  }
  ```

  ```typescript React Component Example theme={null}
  import { useState } from 'react';
  import { useDelegateEVM } from './hooks/useDelegateEVM';

  function DelegateWalletButton() {
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const { delegate } = useDelegateEVM();

    async function handleDelegate() {
      setLoading(true);
      setError(null);

      try {
        const delegationToken = await fetch('/api/delegation/token')
          .then(r => r.json())
          .then(d => d.token);

        const proof = await delegate(
          'linea',
          'usdc',
          '5000',
          '0x...', // Token address
          '0x...', // Spender address
          delegationToken
        );

        await fetch('/api/delegation/submit', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(proof)
        });

        alert('Delegation successful!');

      } catch (err: any) {
        console.error('Delegation failed:', err);
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    return (
      <div>
        <button onClick={handleDelegate} disabled={loading}>
          {loading ? 'Delegating...' : 'Delegate Wallet'}
        </button>
        {error && <p className="error">{error}</p>}
      </div>
    );
  }
  ```
</CodeGroup>

## SIWE (Sign-In with Ethereum)

SIWE is the standard format for proving wallet ownership through message signing.

### SIWE Message Format

```
{domain} wants you to sign in with your Ethereum account:
{address}

{statement}

URI: {uri}
Version: {version}
Chain ID: {chainId}
Nonce: {nonce}
Issued At: {issuedAt}
Expiration Time: {expirationTime}
```

### Field Descriptions

| Field            | Required | Description                               | Example                                 |
| ---------------- | -------- | ----------------------------------------- | --------------------------------------- |
| `domain`         | Yes      | Domain requesting signature               | `yourplatform.com`                      |
| `address`        | Yes      | User's Ethereum address                   | `0x3a11a86cf...`                        |
| `statement`      | No       | Human-readable statement                  | `Prove wallet ownership for delegation` |
| `uri`            | Yes      | Full URI of requesting application        | `https://yourplatform.com`              |
| `version`        | Yes      | SIWE version (always "1")                 | `1`                                     |
| `chainId`        | Yes      | Blockchain chain ID                       | `59144` (Linea) or `1` (Ethereum)       |
| `nonce`          | Yes      | Random string to prevent replay attacks   | `abc123xyz789`                          |
| `issuedAt`       | Yes      | ISO 8601 timestamp                        | `2024-10-08T12:00:00Z`                  |
| `expirationTime` | No       | ISO 8601 timestamp when signature expires | `2024-10-08T12:30:00Z`                  |

<Warning>
  The SIWE message format must be exact. Extra whitespace, missing fields, or incorrect formatting will cause signature verification to fail.
</Warning>

### SIWE Implementation

<CodeGroup>
  ```typescript Generate SIWE Message theme={null}
  function generateSIWEMessage(
    address: string,
    chainId: string,
    domain: string = window.location.host
  ): string {
    const nonce = crypto.randomUUID().replace(/-/g, '');
    const issuedAt = new Date().toISOString();
    const expirationTime = new Date(Date.now() + 30 * 60000).toISOString();

    return `${domain} wants you to sign in with your Ethereum account:
  ${address}

  Prove wallet ownership for delegation

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

  ```typescript Sign SIWE Message theme={null}
  import { BrowserProvider } from 'ethers';

  async function signSIWE(message: string): Promise<string> {
    const provider = new BrowserProvider(window.ethereum);
    const signer = await provider.getSigner();

    const signature = await signer.signMessage(message);

    return signature;
  }
  ```

  ```typescript Verify SIWE Signature (Client-side check) theme={null}
  import { verifyMessage } from 'ethers';

  function verifySIWESignature(
    message: string,
    signature: string,
    expectedAddress: string
  ): boolean {
    try {
      const recoveredAddress = verifyMessage(message, signature);
      return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase();
    } catch (error) {
      return false;
    }
  }
  ```
</CodeGroup>

## Gas Optimization

### Check Existing Allowance

Before requesting a new approval, check if sufficient allowance already exists:

<CodeGroup>
  ```typescript Check Allowance theme={null}
  async function checkAllowance(
    tokenAddress: string,
    ownerAddress: string,
    spenderAddress: string
  ): Promise<bigint> {
    const provider = new BrowserProvider(window.ethereum);

    const tokenContract = new Contract(
      tokenAddress,
      ['function allowance(address owner, address spender) view returns (uint256)'],
      provider
    );

    const allowance = await tokenContract.allowance(ownerAddress, spenderAddress);
    return allowance;
  }

  async function requestApprovalIfNeeded(
    tokenAddress: string,
    spenderAddress: string,
    amount: string
  ): Promise<string | null> {
    const provider = new BrowserProvider(window.ethereum);
    const signer = await provider.getSigner();
    const address = await signer.getAddress();

    const existingAllowance = await checkAllowance(
      tokenAddress,
      address,
      spenderAddress
    );

    const requiredAmount = parseUnits(amount, 6);

    if (existingAllowance >= requiredAmount) {
      console.log('Sufficient allowance already exists');
      return null; // No new transaction needed
    }

    const tokenContract = new Contract(tokenAddress, ERC20_ABI, signer);
    const tx = await tokenContract.approve(spenderAddress, requiredAmount);
    const receipt = await tx.wait();

    return receipt.hash;
  }
  ```
</CodeGroup>

### Gas Price Strategies

<Tabs>
  <Tab title="Standard">
    Let wallet handle gas price estimation (recommended):

    ```typescript theme={null}
    const tx = await tokenContract.approve(spender, amount);
    // Wallet will estimate gas automatically
    ```
  </Tab>

  <Tab title="Custom Gas Price">
    Set custom gas parameters for faster confirmation:

    ```typescript theme={null}
    const feeData = await provider.getFeeData();

    const tx = await tokenContract.approve(spender, amount, {
      maxFeePerGas: feeData.maxFeePerGas! * 120n / 100n, // 20% higher
      maxPriorityFeePerGas: feeData.maxPriorityFeePerGas! * 120n / 100n
    });
    ```
  </Tab>

  <Tab title="Gas Estimation">
    Estimate gas before sending:

    ```typescript theme={null}
    const gasEstimate = await tokenContract.approve.estimateGas(
      spender,
      amount
    );

    console.log(`Estimated gas: ${gasEstimate.toString()}`);

    const tx = await tokenContract.approve(spender, amount, {
      gasLimit: gasEstimate * 120n / 100n // 20% buffer
    });
    ```
  </Tab>
</Tabs>

## Submit to API

After collecting all data from the blockchain, submit to the EVM-specific endpoint:

### API Request

```typescript theme={null}
interface EVMDelegationProof {
  address: string;          // Must start with 0x, 42 characters total
  network: 'linea' | 'ethereum';
  currency: 'usdc' | 'usdt';
  amount: string;
  txHash: string;           // Must start with 0x, 66 characters total
  sigHash: string;          // Must start with 0x, 132 characters total
  sigMessage: string;       // SIWE formatted message
  token: string;            // From Step 1
}

async function submitEVMDelegation(
  proof: EVMDelegationProof,
  userAccessToken: string
): 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(error.message || 'Delegation failed');
  }

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

**API Reference:** [`POST /v1/delegation/evm/post-approval`](/api-reference/delegation/evm-post-approval)

### Validation Rules

The API performs strict EVM-specific validation:

<AccordionGroup>
  <Accordion title="Address Format">
    * Must start with `0x`
    * Must be exactly 42 characters long
    * Must contain only hexadecimal characters (0-9, a-f)
    * Case-insensitive, but checksummed addresses are preferred

    **Valid**: `0x3a11a86cf218c448be519728cd3ac5c741fb3424`
    **Invalid**: `3a11a86cf218c448be519728cd3ac5c741fb3424` (missing 0x)
  </Accordion>

  <Accordion title="Transaction Hash">
    * Must start with `0x`
    * Must be exactly 66 characters long
    * Must contain only hexadecimal characters
    * Must be a confirmed transaction on the specified network

    **Valid**: `0xb92de09d893e8162b0861c0f7321f68df02212efbc58f208839ae3f176d89638`
  </Accordion>

  <Accordion title="Signature Hash">
    * Must start with `0x`
    * Must be exactly 132 characters long
    * Must contain only hexadecimal characters
    * Must be valid ECDSA signature that recovers to the provided address

    **Valid**: `0x2039b9765a4df76e8bae80f3bbc640e8ae6acc81f7a5cc96fe91ccc1844b6f7d4c3e8f1a2b...`
  </Accordion>

  <Accordion title="SIWE Message">
    * Must follow SIWE format exactly
    * Must contain the provided address
    * Must contain correct chain ID for network
    * Must not be expired (check expirationTime if present)
  </Accordion>
</AccordionGroup>

## Error Handling

### Common EVM Errors

<CodeGroup>
  ```typescript Error Handler theme={null}
  async function handleEVMDelegation() {
    try {
      await delegateEVMWallet(params);
    } catch (error: any) {
      if (error.code === 4001) {
        console.error('User rejected transaction');
        alert('Please approve the transaction in your wallet to continue');
      }
      else if (error.code === -32603) {
        console.error('Insufficient funds for gas');
        alert('You need more ETH in your wallet to pay for gas fees');
      }
      else if (error.message?.includes('wrong network')) {
        console.error('Wrong network');
        alert('Please switch to the correct network in your wallet');
      }
      else if (error.message?.includes('insufficient allowance')) {
        console.error('Approval failed or insufficient');
        alert('Please try approving the transaction again');
      }
      else if (error.code === -32002) {
        console.error('Request already pending');
        alert('Please check your wallet for a pending request');
      }
      else {
        console.error('Unknown error:', error);
        alert(`Delegation failed: ${error.message}`);
      }
    }
  }
  ```
</CodeGroup>

### API Error Responses

| Status | Error                    | Cause                                      | Solution                                      |
| ------ | ------------------------ | ------------------------------------------ | --------------------------------------------- |
| 400    | Invalid address format   | Address doesn't match EVM format           | Ensure address starts with 0x and is 42 chars |
| 400    | Invalid transaction hash | Transaction hash not found or wrong format | Wait for confirmation, verify network         |
| 400    | Invalid signature        | Signature doesn't match address            | Re-sign message, ensure correct wallet        |
| 400    | Expired token            | Delegation token expired                   | Generate new token from Step 1                |
| 400    | SIWE validation failed   | Message format incorrect                   | Use exact SIWE format                         |
| 429    | Rate limited             | Too many requests                          | Wait and retry with exponential backoff       |

## Testing

### Test on Linea Testnet

Before going to production, test on Linea Goerli testnet:

<CodeGroup>
  ```typescript Testnet Configuration theme={null}
  const TESTNET_CONFIG = {
    chainId: 59140, // Linea Goerli
    chainIdHex: '0xe704',
    rpcUrl: 'https://rpc.goerli.linea.build',
    blockExplorer: 'https://goerli.lineascan.build',
    nativeCurrency: {
      name: 'Ether',
      symbol: 'ETH',
      decimals: 18
    }
  };

  async function addLineaTestnet() {
    await window.ethereum.request({
      method: 'wallet_addEthereumChain',
      params: [{
        chainId: TESTNET_CONFIG.chainIdHex,
        chainName: 'Linea Goerli',
        nativeCurrency: TESTNET_CONFIG.nativeCurrency,
        rpcUrls: [TESTNET_CONFIG.rpcUrl],
        blockExplorerUrls: [TESTNET_CONFIG.blockExplorer]
      }]
    });
  }
  ```
</CodeGroup>

Get testnet ETH from:

* Linea Goerli Faucet: [https://faucet.goerli.linea.build](https://faucet.goerli.linea.build)
* Goerli ETH Faucet: [https://goerlifaucet.com](https://goerlifaucet.com)

## Next Steps

<CardGroup cols={2}>
  <Card title="Solana Implementation" icon="circle-s" href="/guides/delegation/solana">
    Learn how delegation works on Solana
  </Card>

  <Card title="Priority Management" icon="ranking-star" href="/guides/delegation/priority">
    Configure wallet charging priority
  </Card>

  <Card title="Redelegation" icon="rotate" href="/guides/delegation/redelegation">
    Update allowances and contracts
  </Card>

  <Card title="Implementation Guide" icon="code" href="/guides/delegation/implementation">
    Complete 3-step delegation workflow
  </Card>
</CardGroup>
