Multi-Chain Payment App

This tutorial shows how to build an application that accepts payments from multiple blockchain networks simultaneously.

Overview

A multi-chain payment app allows users to pay with their preferred network:

  • EVM chains: Ethereum, Base, Arbitrum, etc.
  • Non-EVM chains: TON, TRON, Solana, NEAR, Aptos, Tezos, Polkadot, Stacks, Cosmos
  • Automatic routing: Best network selection

Architecture

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Client    │────▶│    Server    │────▶│ Facilitator │
│  (Browser)  │     │   (API)      │     │  (t402.io)  │
└─────────────┘     └──────────────┘     └─────────────┘
       │                   │                    │
       │    ┌──────────────┼──────────────┐    │
       ▼    ▼              ▼              ▼    ▼
    ┌─────┐ ┌─────┐   ┌─────┐   ┌─────┐ ┌─────┐
    │ EVM │ │ TON │   │TRON │   │ SVM │ │ ... │
    └─────┘ └─────┘   └─────┘   └─────┘ └─────┘

Server Setup

Configure your server to accept multiple payment networks.

import express from 'express';
import { paymentMiddleware } from '@t402/express';
import { t402ResourceServer, createFacilitatorClient } from '@t402/core/server';
import { registerExactEvmScheme } from '@t402/evm/exact/server';
import { registerExactTonScheme } from '@t402/ton/exact/server';
import { registerExactTronScheme } from '@t402/tron/exact/server';
import { registerExactSvmScheme } from '@t402/svm/exact/server';
 
const app = express();
 
// Create facilitator client
const facilitator = createFacilitatorClient({
  url: 'https://facilitator.t402.io',
});
 
// Create t402 server with all mechanisms
const server = new t402ResourceServer(facilitator);
registerExactEvmScheme(server, {});
registerExactTonScheme(server, {});
registerExactTronScheme(server, {});
registerExactSvmScheme(server, {});
 
// Define routes with multiple payment options
const routes = {
  '/api/premium/*': {
    accepts: [
      // EVM chains
      { scheme: 'exact', network: 'eip155:8453', payTo: '0xYourEvmAddress', price: '$0.01' },
      { scheme: 'exact', network: 'eip155:42161', payTo: '0xYourEvmAddress', price: '$0.01' },
      // TON
      { scheme: 'exact', network: 'ton:mainnet', payTo: 'EQYourTonAddress', price: '$0.01' },
      // TRON
      { scheme: 'exact', network: 'tron:mainnet', payTo: 'TYourTronAddress', price: '$0.01' },
      // Solana
      { scheme: 'exact', network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', payTo: 'YourSolanaAddress', price: '$0.01' },
    ],
    description: 'Premium API - Pay with any supported chain',
  },
};
 
app.use(paymentMiddleware(routes, server));
 
app.get('/api/premium/data', (req, res) => {
  res.json({ data: 'Premium multi-chain content' });
});
 
app.listen(3000);

Client Setup with Multiple Wallets

Create a client that can pay with any available wallet.

import { t402Client } from '@t402/core/client';
import { ExactEvmScheme } from '@t402/evm/exact/client';
import { ExactTonScheme } from '@t402/ton';
import { ExactTronScheme } from '@t402/tron';
import { ExactSvmScheme } from '@t402/svm';
 
// Register all available wallets
const client = new t402Client();
 
// EVM (if wallet connected)
if (evmWallet) {
  client.register('eip155:*', new ExactEvmScheme(evmSigner));
}
 
// TON (if wallet connected)
if (tonWallet) {
  client.register('ton:mainnet', new ExactTonScheme(tonSigner));
}
 
// TRON (if wallet connected)
if (tronWallet) {
  client.register('tron:mainnet', new ExactTronScheme(tronSigner));
}
 
// Solana (if wallet connected)
if (solanaWallet) {
  client.register('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', new ExactSvmScheme(svmSigner));
}

Smart Network Selection

Implement automatic network selection based on user preferences or availability:

import { SelectPaymentRequirements } from '@t402/core/client';
 
// Custom selector: prefer user's connected wallet network
const selectPayment: SelectPaymentRequirements = (requirements, context) => {
  // Get user's preferred network (e.g., from connected wallet)
  const preferredNetwork = getUserPreferredNetwork();
 
  // Try to find matching requirement
  const preferred = requirements.find(r => r.network === preferredNetwork);
  if (preferred) return preferred;
 
  // Fallback to lowest fee network
  const sorted = requirements.sort((a, b) => {
    return estimateFee(a.network) - estimateFee(b.network);
  });
 
  return sorted[0];
};
 
const client = new t402Client({ selectPaymentRequirements: selectPayment });

React Multi-Wallet Component

Create a UI that shows all payment options:

import { useState } from 'react';
import { usePaymentRequired, PaymentButton } from '@t402/react';
import { getNetworkDisplayName, isEvmNetwork, isTonNetwork } from '@t402/react';
 
function MultiChainPayment({ paymentRequired }) {
  const [selectedNetwork, setSelectedNetwork] = useState(null);
 
  const requirements = paymentRequired.accepts;
 
  return (
    <div className="payment-modal">
      <h2>Choose Payment Network</h2>
 
      <div className="network-list">
        {requirements.map((req, i) => (
          <button
            key={i}
            className={`network-option ${selectedNetwork === i ? 'selected' : ''}`}
            onClick={() => setSelectedNetwork(i)}
          >
            <NetworkIcon network={req.network} />
            <span>{getNetworkDisplayName(req.network)}</span>
            <span className="price">{req.maxAmountRequired} USDT</span>
          </button>
        ))}
      </div>
 
      {selectedNetwork !== null && (
        <PaymentButton
          onClick={() => handlePayment(requirements[selectedNetwork])}
          size="lg"
        >
          Pay with {getNetworkDisplayName(requirements[selectedNetwork].network)}
        </PaymentButton>
      )}
    </div>
  );
}
 
function NetworkIcon({ network }) {
  if (isEvmNetwork(network)) return <EvmIcon />;
  if (isTonNetwork(network)) return <TonIcon />;
  // ... other networks
}

Wallet Connection Manager

Manage multiple wallet connections:

import { useState, useEffect } from 'react';
 
function useMultiWallet() {
  const [wallets, setWallets] = useState({
    evm: null,
    ton: null,
    tron: null,
    solana: null,
  });
 
  // Connect EVM wallet (MetaMask, etc.)
  const connectEvm = async () => {
    if (window.ethereum) {
      const accounts = await window.ethereum.request({
        method: 'eth_requestAccounts',
      });
      setWallets(w => ({ ...w, evm: accounts[0] }));
    }
  };
 
  // Connect TON wallet (Tonkeeper, etc.)
  const connectTon = async () => {
    // TON Connect implementation
  };
 
  // Connect TRON wallet (TronLink)
  const connectTron = async () => {
    if (window.tronWeb) {
      const address = window.tronWeb.defaultAddress.base58;
      setWallets(w => ({ ...w, tron: address }));
    }
  };
 
  // Connect Solana wallet (Phantom)
  const connectSolana = async () => {
    if (window.solana?.isPhantom) {
      const response = await window.solana.connect();
      setWallets(w => ({ ...w, solana: response.publicKey.toString() }));
    }
  };
 
  return {
    wallets,
    connectEvm,
    connectTon,
    connectTron,
    connectSolana,
    connectedNetworks: Object.entries(wallets)
      .filter(([_, v]) => v)
      .map(([k]) => k),
  };
}

Best Practices

Price Consistency

Keep prices consistent across networks:

const PRICE_USD = '0.01';
 
const routes = {
  '/api/premium/*': {
    accepts: [
      { scheme: 'exact', network: 'eip155:8453', payTo: evmAddress, price: PRICE_USD },
      { scheme: 'exact', network: 'ton:mainnet', payTo: tonAddress, price: PRICE_USD },
      // ... all use same price
    ],
  },
};

Address Management

Use environment variables for addresses:

const addresses = {
  evm: process.env.EVM_PAY_TO_ADDRESS,
  ton: process.env.TON_PAY_TO_ADDRESS,
  tron: process.env.TRON_PAY_TO_ADDRESS,
  solana: process.env.SOLANA_PAY_TO_ADDRESS,
};

Error Handling

Handle network-specific errors:

try {
  await pay(selectedRequirement);
} catch (error) {
  if (isEvmNetwork(selectedRequirement.network)) {
    handleEvmError(error);
  } else if (isTonNetwork(selectedRequirement.network)) {
    handleTonError(error);
  }
  // ... other networks
}

Testing

Test with multiple testnets:

const testnetRoutes = {
  '/api/test/*': {
    accepts: [
      { scheme: 'exact', network: 'eip155:84532', payTo: '0x...', price: '$0.001' }, // Base Sepolia
      { scheme: 'exact', network: 'ton:testnet', payTo: 'kQ...', price: '$0.001' },
      { scheme: 'exact', network: 'tron:nile', payTo: 'T...', price: '$0.001' },
      { scheme: 'exact', network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', payTo: '...', price: '$0.001' },
    ],
  },
};