Skip to main content

Overview

Earn mode enables users to deposit into DeFi protocols for yield generation and staking. It supports both pre-integrated protocols (Aave, Morpho) and custom contract integrations with arbitrary calldata.

Configuration

Required Props

PropTypeDescription
mode"earn"Sets the widget to earn mode

Required Props (Custom Protocol Integration)

PropTypeDescription
toAddressstringProtocol contract address
toChainIdnumberProtocol’s chain ID
toTokenstringToken to deposit
toCalldatastringEncoded function call

Optional Props (Custom Integration)

PropTypeDescription
toAmountstringFixed deposit amount
slippageTolerancestring | numberSlippage tolerance

Implementation

Protocol Selection Widget

Use pre-integrated protocols (Aave, Morpho, etc.):
import { TrailsWidget } from '0xtrails/widget'

<TrailsWidget
  apiKey="YOUR_API_KEY"
  mode="earn"
  onCheckoutComplete={({ sessionId }) => {
    console.log('Deposit completed:', sessionId)
  }}
/>

Fixed Amount Protocol Deposit

Deposit a specific amount into a protocol:
import { TrailsWidget } from '0xtrails/widget'
import { encodeFunctionData } from 'viem'

const AAVE_POOL = '0x794a61358D6845594F94dc1DB02A252b5b4814aD' // Arbitrum
const USDC_ADDRESS = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'

const supplyCalldata = encodeFunctionData({
  abi: [{
    name: 'supply',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'asset', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'onBehalfOf', type: 'address' },
      { name: 'referralCode', type: 'uint16' },
    ],
    outputs: [],
  }],
  functionName: 'supply',
  args: [
    USDC_ADDRESS,
    '1000000', // 1 USDC (6 decimals)
    '0x97c4A952b46bEcaD0663f76357d3776ba11566E1', // User address
    0, // No referral code
  ],
})

<TrailsWidget
  apiKey="YOUR_API_KEY"
  mode="earn"
  toAddress={AAVE_POOL}
  toAmount="1"
  toChainId={42161} // Arbitrum
  toToken="USDC"
  toCalldata={supplyCalldata}
>
  <button>Deposit 1 USDC to Aave</button>
</TrailsWidget>

Dynamic Amount Deposits

Use placeholder amount for user-selected deposit amounts:
import { TrailsWidget, TRAILS_CONTRACT_PLACEHOLDER_AMOUNT } from '0xtrails'
import { encodeFunctionData } from 'viem'

const STAKING_CONTRACT = '0x...'

// Encode stake function with placeholder amount
const stakeCalldata = encodeFunctionData({
  abi: [{
    name: 'stake',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [{ name: 'amount', type: 'uint256' }],
    outputs: [],
  }],
  functionName: 'stake',
  args: [TRAILS_CONTRACT_PLACEHOLDER_AMOUNT], // Replaced at execution
})

<TrailsWidget
  apiKey="YOUR_API_KEY"
  mode="earn"
  toAddress={STAKING_CONTRACT}
  toChainId={1}
  toToken="ETH"
  toCalldata={stakeCalldata}
>
  <button>Stake ETH (Choose Amount)</button>
</TrailsWidget>

ERC-4626 Vault Deposit

Standard vault deposit pattern:
import { TrailsWidget, TRAILS_CONTRACT_PLACEHOLDER_AMOUNT } from '0xtrails'
import { encodeFunctionData } from 'viem'

const VAULT_ADDRESS = '0x...'
const USER_ADDRESS = '0x...'

const depositCalldata = encodeFunctionData({
  abi: [{
    name: 'deposit',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'assets', type: 'uint256' },
      { name: 'receiver', type: 'address' },
    ],
    outputs: [{ name: 'shares', type: 'uint256' }],
  }],
  functionName: 'deposit',
  args: [
    TRAILS_CONTRACT_PLACEHOLDER_AMOUNT,
    USER_ADDRESS,
  ],
})

<TrailsWidget 
  apiKey="YOUR_API_KEY"
  mode="earn"
  toAddress={VAULT_ADDRESS}
  toChainId={1}
  toToken="USDC"
  toCalldata={depositCalldata}
>
  <button>Deposit to Vault</button>
</TrailsWidget>

Calldata Patterns

When to Use TRAILS_CONTRACT_PLACEHOLDER_AMOUNT

Use the placeholder when:
  • Deposit amount is a function parameter
  • User selects the deposit amount
  • Amount needs to reflect post-swap/bridge value
// ✅ Correct - dynamic amount in calldata
encodeFunctionData({
  functionName: 'deposit',
  args: [TRAILS_CONTRACT_PLACEHOLDER_AMOUNT, userAddress],
})

When Static Values Work

Use static values when:
  • Function doesn’t take amount as parameter
  • Amount is fixed and known in advance
  • Contract reads balance internally
// ✅ Correct - depositAll() reads balance
encodeFunctionData({
  functionName: 'depositAll',
  args: [userAddress],
})

Protocol-Specific Examples

Aave V3 Deposit

const aaveSupply = encodeFunctionData({
  abi: aavePoolABI,
  functionName: 'supply',
  args: [
    tokenAddress,        // Asset to supply
    amount,              // Amount to supply
    onBehalfOf,          // Who receives aTokens
    referralCode,        // Referral code (usually 0)
  ],
})

Morpho Vault Deposit

const morphoDeposit = encodeFunctionData({
  abi: morphoVaultABI,
  functionName: 'deposit',
  args: [
    assets,              // Amount of assets
    receiver,            // Who receives shares
  ],
})

Yearn Vault Deposit

const yearnDeposit = encodeFunctionData({
  abi: yearnVaultABI,
  functionName: 'deposit',
  args: [
    amount,              // Amount to deposit (use placeholder)
    receiver,            // Share recipient
  ],
})

Liquid Staking (Lido)

const lidoSubmit = encodeFunctionData({
  abi: lidoABI,
  functionName: 'submit',
  args: [
    referralAddress,     // Referral (use zero address if none)
  ],
})
// Note: Amount sent via msg.value, not parameter

Event Handling

<TrailsWidget
  apiKey="YOUR_API_KEY"
  mode="earn"
  toAddress="0x..."
  toChainId={1}
  toToken="USDC"
  toCalldata={calldata}
  onCheckoutStart={({ sessionId }) => {
    console.log('Deposit started:', sessionId)
  }}
  onCheckoutComplete={({ sessionId }) => {
    console.log('Deposit completed:', sessionId)
    // Refresh user's vault balance
    // Update TVL display
    // Show success notification
  }}
  onCheckoutError={({ sessionId, error }) => {
    console.error('Deposit failed:', error)
    // Handle error state
  }}
/>

Supported Protocols

Pre-integrated

Trails has built-in support for:
  • Aave V3: Lending and borrowing across multiple chains
  • Morpho: Optimized lending pools

Custom Integration

Any protocol with a deposit/stake function can be integrated:
  • Yearn Finance vaults
  • Compound V3
  • Convex Finance
  • Curve pools
  • Custom staking contracts
  • Any ERC-4626 vault

Use Cases

  • Lending Protocol Deposits: Supply assets to earn interest (Aave, Compound, Morpho)
  • Yield Vault Deposits: Deposit into automated yield strategies (Yearn)
  • Liquid Staking: Stake ETH while maintaining liquidity (Lido, Rocket Pool)
  • LP Token Staking: Stake LP tokens in farming contracts
  • Governance Staking: Lock tokens for governance rights
  • Auto-compounding Vaults: Deposit into vaults that auto-compound rewards

Technical Notes

  • Earn mode handles token approvals automatically
  • Supports both ERC-20 tokens and native tokens (ETH, MATIC, etc.)
  • All cross-chain swapping and bridging is handled
  • Calldata execution is atomic with the deposit
  • TRAILS_CONTRACT_PLACEHOLDER_AMOUNT = uint256.max internally
  • Works with any contract that has onBehalfOf or receiver parameter

Error Handling

import { getIsUserRejectionError, InsufficientBalanceError } from '0xtrails'

<TrailsWidget
  apiKey="YOUR_API_KEY"
  mode="earn"
  toAddress="0x..."
  toCalldata={calldata}
  onCheckoutError={({ sessionId, error }) => {
    if (getIsUserRejectionError(error)) {
      // User cancelled the transaction
      showNotification('Transaction cancelled')
    } else if (error.includes('insufficient balance')) {
      showNotification('Insufficient balance for deposit')
    } else if (error.includes('slippage')) {
      showNotification('Price impact too high, try smaller amount')
    } else {
      showNotification('Deposit failed, please try again')
    }
  }}
/>

See Also