Skip to main content

Pay with Trails

Trails revolutionizes crypto purchases by enabling users to buy NFTs, RWAs, or any other asset using any token a user holds from any supported chain. The payee simply specifies the settlement asset that they would like the payment to be reconciled in or to successfully complete the purchase, for example USDC, ETH, or any other token and the payer can select how they’d like to pay from their aggregated balance. Accept USDC, USDT, or any token as payment. Your user pays with whatever they have—Trails handles the conversion automatically and any executions automatically.
You Settle InUser Pays WithTrails Handles
USDC on PolygonETH on ArbitrumBridge + Swap
USDC on PolygonPOL on PolygonSwap
USDC on BaseUSDC on BaseNormal Execution

Use Cases

Pay flows in Trails are modeled as exact output by default. This means the developer simply defines the total amount the payee should receive for the payment and Trails will automatically include any additional fees to successfully fulfill the payment. For example, “I want to receive exactly 1 USDC tokens on the destination chain, so the user will send 1.01 USDC on the origin chain.” Ideal for use cases such as the following:
  • Purchase and/or mint an NFT directly from a marketplace on any chain
  • Use crypto to make a purchase at an ecommerce platform
  • Transfer a specific amount of funds to a user
  • Make an x402 payment in any token from any chain
  • Execute smart contract calls with an attached payment

Stablecoin Payment Examples

Accept USDC on Polygon

Settle in USDC on Polygon—users pay from any chain:
import { TrailsWidget } from '0xtrails/widget'

<TrailsWidget
  apiKey="YOUR_API_KEY"
  mode="pay"
  toAddress="0xYOUR_MERCHANT_ADDRESS"
  toAmount="25"
  toChainId={137} // Polygon
  toToken="USDC"
  onCheckoutComplete={({ sessionId }) => {
    // Mark order as paid
    updateOrderStatus(orderId, 'paid', sessionId)
  }}
>
  <button>Pay $25</button>
</TrailsWidget>

Cross-Chain USDC with CCTP (Polygon Settlement)

Use Circle’s Cross-Chain Transfer Protocol for native USDC bridging:
<TrailsWidget
  apiKey="YOUR_API_KEY"
  mode="pay"
  toAddress="0xYOUR_MERCHANT_ADDRESS"
  toAmount="500"
  toChainId={137} // Polygon
  toToken="USDC"
  bridgeProvider="CCTP" // Native USDC bridging
  onCheckoutComplete={({ sessionId }) => {
    processLargePayment(sessionId)
  }}
>
  <button>Pay $500</button>
</TrailsWidget>

Purchasing an NFT on Arbitrum

This example shows how to use the Trails widget to purchase an NFT on Arbitrum, where the user can pay with any token from any chain that automatically gets converted to ETH to fulfill the purchase:
import { TrailsWidget } from '0xtrails/widget'
import { encodeFunctionData } from 'viem'
import { nftABI } from './abi.ts'

export const CrossChainNFTPurchase = () => {
  // NFT contract address on Arbitrum
  const NFT_CONTRACT = "0xAA3df3c86EdB6aA4D03b75092b4dd0b99515EC83"
  
  // NFT purchase calldata
  const purchaseCalldata = encodeFunctionData({
    abi: nftABI,
    functionName: 'mint',
    args: [
      "0x97c4a952b46becad0663f76357d3776ba11566e1", // recipient address
    ],
  })

  return (
    <TrailsWidget
      apiKey="YOUR_API_KEY"
      mode="pay"
      toAddress={NFT_CONTRACT}
      toAmount="0.00002"
      toChainId={42161} // Arbitrum
      toToken="ETH"
      toCalldata={purchaseCalldata}
      onCheckoutComplete={({ sessionId }) => {
        console.log('NFT purchase completed:', sessionId)
        // Handle successful purchase
      }}
      onCheckoutError={({ sessionId, error }) => {
        console.error('Purchase failed:', sessionId, error)
      }}
      theme="auto"
    >
      <button className="nft-purchase-button">
        Buy NFT (0.00002 ETH)
      </button>
    </TrailsWidget>
  )
}

Merchant Integration Pattern

Complete ecommerce checkout flow with order verification:
import { TrailsWidget } from '0xtrails/widget'
import { useState } from 'react'

export function CheckoutButton({ order }) {
  const [status, setStatus] = useState('pending')

  return (
    <TrailsWidget
      apiKey={process.env.TRAILS_API_KEY}
      mode="pay"
      toAddress={process.env.MERCHANT_WALLET}
      toAmount={order.total.toString()}
      toChainId={137} // Polygon
      toToken="USDC"
      onCheckoutStart={({ sessionId }) => {
        // Create pending payment record
        createPaymentRecord({
          orderId: order.id,
          sessionId,
          amount: order.total,
          status: 'processing'
        })
        setStatus('processing')
      }}
      onCheckoutComplete={async ({ sessionId }) => {
        // Verify payment on your backend
        const verified = await verifyPayment(sessionId)
        if (verified) {
          await fulfillOrder(order.id)
          setStatus('complete')
        }
      }}
      onCheckoutError={({ sessionId, error }) => {
        logPaymentError(sessionId, error)
        setStatus('failed')
      }}
    >
      <button disabled={status === 'processing'}>
        {status === 'processing' ? 'Processing...' : `Pay $${order.total}`}
      </button>
    </TrailsWidget>
  )
}

Backend Payment Verification

Verify payments server-side before fulfilling orders:
// api/verify-payment.ts
import { Trails } from '@0xtrails/api'

const trails = new Trails({ accessKey: process.env.TRAILS_ACCESS_KEY })

export async function verifyPayment(sessionId: string, expectedAmount: string) {
  const receipt = await trails.getIntentReceipt({ intentId: sessionId })

  // Verify the payment completed successfully
  if (receipt.status !== 'COMPLETED') {
    return { verified: false, reason: 'Payment not completed' }
  }

  // Verify the amount matches
  const receivedAmount = receipt.destinationTransaction?.amount
  if (receivedAmount !== expectedAmount) {
    return { verified: false, reason: 'Amount mismatch' }
  }

  return { verified: true, receipt }
}

API-Based Fee Estimation

For server-side fee estimation or custom UIs:
const quote = 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's funds on Polygon
    originTokenAddress: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC on Polygon
    destinationChainId: 8453, // Settle on Base
    destinationTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
    destinationToAddress: merchantAddress,
    destinationTokenAmount: '100000000', // 100 USDC (6 decimals)
    tradeType: 'EXACT_OUTPUT',
    options: {
      slippageTolerance: 0.005 // 0.5%
    }
  })
})

const { intent } = await quote.json()

// Extract fee information
const fees = {
  gasFee: intent.gasFee,
  bridgeFee: intent.bridgeFee,
  totalInput: intent.originAmount,
  estimatedOutput: intent.destinationAmount
}

Next Steps