Skip to main content

Fund with Trails

Traditional funding flows require users to:
  • Find an external bridge or fiat onramp provider supported by the chain / app
  • Swap to the required deposit token
  • Pay multiple gas fees across different networks
  • Wait for bridge confirmations
  • Navigate complex interfaces
Trails seamlessly enables users to deposit funds into your chain, app, or protocol with optimized UX flows and native integrations with multiple liquidity sources to optimize routes for low-slippage. Onramps, transfers from other dapps, wallet balances, and chain abstraction are all built in by default, so you do not need to implement or expose any separate funding path. Users can fund from any token they hold across any supported chain, eliminating the need for manual bridging and swapping - embedded seamlessly in the experience as a single transaction.

Use Cases

Funding flows in Trails are modeled as exact input by default. For example, “I have exactly 10 USDC tokens I want to send out from the origin chain, and I’ll receive 9.99 USDC on the destination.” Funding can done through a variety of methods such as the user’s connected wallet, an existing app they already have funds, or onramping from various fiat options. This is ideal for a variety of use cases and apps such as the following:
  • Transfer funds into a perpetual exchange deposit address.
  • Bootstrap liquidity for lending protocols by transferring funds and executing a vault deposit.
  • Add funds to your balance on an application with crypto support.
  • Swap and deposit funds into staking contracts.
  • Onboard users to new chains seamlessly from any origin chain.
  • Top up a user’s account on a prediction market.

Examples

Depositing USDC into Polygon

This example shows how to use the Trails widget to enable a user to deposit into a chain with any token, in this case USDC on Polygon with a fixed amount:
import { TrailsWidget } from '0xtrails/widget'

export const BaseDeposit = () => {
  return (
    <TrailsWidget
      apiKey="YOUR_API_KEY"
      mode="fund"
      toAddress="0x..." // Recipient Address
      toChainId={137} // Polygon
      toToken="USDC"
      toAmount="100" // 100 USDC
      onCheckoutComplete={({ sessionId }) => {
        console.log('Deposit completed:', sessionId)
      }}
    />
  )
}

Depositing Fixed Amount into a DeFi Yield Vault

Trails is able to call any smart contract function on the destination chain. A common pattern is to fund a DeFi vault, for example depositing into an Aave pool on Base:
import { TrailsWidget } from '0xtrails/widget'
import { encodeFunctionData } from 'viem'
import { aaveABI } from './abi.ts'

export const AaveDepositExample = () => {
  // Aave V3 Pool contract on Base
  const AAVE_POOL_CONTRACT = "0xa0d9C1E9E48Ca30c8d8C3B5D69FF5dc1f6DFfC24"
  
  // Encode the deposit function call on Aave
  const depositCalldata = encodeFunctionData({
    abi: aaveABI,
    functionName: 'depositETH',
    args: [
      "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", // pool address
      "0x97...", // receiver's address
      0, // referralCode
    ],
  })

  return (
    <TrailsWidget
      apiKey="YOUR_API_KEY"
      mode="fund"
      toAddress={AAVE_POOL_CONTRACT}
      toAmount="100" // 100 USDC
      toChainId={8453} // Base
      toToken="USDC"
      toCalldata={depositCalldata}
      theme="auto"
      onCheckoutComplete={({ sessionId }) => {
        console.log('Aave deposit completed:', sessionId)
      }}
    />
  )
}

Depositing variable amount into a DeFi Yield Vault

Additionally, Trails is easily integrated with virtually any ERC-4626 yield vault. For many use cases, you can simple encode the static calldata directly and pass it to Trails directly. However, for user-selected amounts where the amount is part of the calldata, you must encode it with a placeholder amount so the widget replaces it with the exact bridged/swapped output at execution time which we have a handy utility for to identify the data to replace. For example, a user can select any amount with any token to deposit into a USDC yearn vault on Katana:
import { TrailsWidget } from '0xtrails/widget'
import { TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from '0xtrails'
import { encodeFunctionData } from 'viem'
import { yearnVaultABI } from './abi.ts'

export const YearnDepositExample = () => {
  // ERC-4626 vault contract on destination chain
  const YEARN_VAULT_CONTRACT = "0x80c34BD3A3569E126e7055831036aa7b212cB159" // USDC Yearn Vault on Katana - replace with target vault address

  // Encode the deposit call with placeholder amount which will be replaced at execution time automatically
  const depositCalldata = encodeFunctionData({
    abi: yearnVaultABI,
    functionName: 'deposit',
    args: [
      TRAILS_ROUTER_PLACEHOLDER_AMOUNT, // amount (auto-filled at runtime based on user's input)
      "0x...", // receiver address
    ],
  })

  return (
    <TrailsWidget
      apiKey="YOUR_API_KEY"
      mode="fund"
      toAddress={YEARN_VAULT_CONTRACT}
      toChainId={747474} // Katana
      toToken="USDC"
      toCalldata={depositCalldata}
      theme="auto"
      onCheckoutComplete={({ sessionId }) => {
        console.log('Vault deposit completed:', sessionId)
      }}
    />
  )
}

Creating a trustless deposit address via API (quote → commit → execute)

Deposit addresses are common flows where users can simply deposit funds into based on their requested quote and Trails will automatically orchestrate. The funding mode of the widget handles this for you, but in some cases you may wish to use the API to create your own deposit flows. To create a Trails deposit address, use the backend endpoints to create a quote, commit it, and then execute after the user deposits to the intent address returned in the quote. This gives you a trustless, deterministic deposit address and an intent lifecycle you can track server-side.
const quoteResponse = await fetch('https://trails-api.sequence.app/rpc/Trails/QuoteIntent', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Access-Key': 'YOUR_ACCESS_KEY'
  },
  body: JSON.stringify({
    ownerAddress: userWallet,
    originChainId: 137, // User funds on Polygon
    originTokenAddress: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC on Polygon
    destinationChainId: 8453, // Settle on Base
    destinationTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
    destinationToAddress: appCreditAddress, // Address your app credits on destination
    destinationTokenAmount: '50000000', // 50 USDC (6 decimals)
    tradeType: 'EXACT_OUTPUT',
  })
})

const { intent } = await quoteResponse.json()
const intentAddress = intent.depositTransaction.toAddress

const commitResponse = await fetch('https://trails-api.sequence.app/rpc/Trails/CommitIntent', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Access-Key': 'YOUR_ACCESS_KEY'
  },
  body: JSON.stringify({ intent })
})

const { intentId } = await commitResponse.json()

// 1) Ask the user to deposit to the intent address returned in the quote
// 2) When the deposit is confirmed, execute with the deposit transaction hash
const executeResponse = await fetch('https://trails-api.sequence.app/rpc/Trails/ExecuteIntent', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Access-Key': 'YOUR_ACCESS_KEY'
  },
  body: JSON.stringify({
    intentId,
    depositTransactionHash: userDepositTxHash
  })
})

const { intentStatus } = await executeResponse.json()
Quotes expire after 10 minutes.

Next Steps

Explore more about configuring and customizing funding flows: