Skip to main content
A complete example of a server that accepts both Solana and EVM payments, with a client configured for both chains.

Server

server.ts
import express from "express"
import { createMiddleware } from "@faremeter/middleware/express"
import { xSolanaSettlement } from "@faremeter/info/solana"
import { x402Exact } from "@faremeter/info/evm"

const app = express()

const multiChainPayment = await createMiddleware({
  facilitatorURL: "https://facilitator.corbits.dev",
  accepts: [
    xSolanaSettlement({
      network: "devnet",
      payTo: process.env.SOLANA_ADDRESS,
      asset: "USDC",
      amount: "10000",
    }),
    x402Exact({
      network: "base-sepolia",
      payTo: process.env.EVM_ADDRESS,
      asset: "USDC",
      amount: "10000",
    }),
  ],
})

app.get("/api/data", multiChainPayment, (req, res) => {
  res.json({ data: "multi-chain protected content" })
})

app.listen(3000)

Client with rides

The simplest approach. Add wallets for both chains and rides selects the right one.
client-rides.ts
import "dotenv/config"
import { payer } from "@faremeter/rides"
import { getLogger } from "@faremeter/logs"

const logger = await getLogger(["client"])

await payer.addLocalWallet(process.env.PAYER_KEYPAIR_PATH)
await payer.addLocalWallet(process.env.EVM_PRIVATE_KEY)

const response = await payer.fetch("http://localhost:3000/api/data")
logger.info(JSON.stringify(await response.json()))

Client with fetch wrapper

For more control over chain selection.
client-fetch.ts
import "dotenv/config"
import { Keypair } from "@solana/web3.js"
import { wrap } from "@faremeter/fetch"
import type { PaymentExecer } from "@faremeter/types/client"
import { createPaymentHandler as createSolanaHandler } from "@faremeter/payment-solana/exact"
import { createPaymentHandler as createEVMHandler } from "@faremeter/payment-evm/exact"
import { createLocalWallet as createSolanaWallet } from "@faremeter/wallet-solana"
import { createLocalWallet as createEVMWallet } from "@faremeter/wallet-evm"
import { getLogger } from "@faremeter/logs"
import { readFileSync } from "node:fs"

const logger = await getLogger(["client"])

const solanaKeypairBytes = JSON.parse(readFileSync(process.env.PAYER_KEYPAIR_PATH, "utf-8"))
const solanaKeypair = Keypair.fromSecretKey(new Uint8Array(solanaKeypairBytes))
const solanaWallet = await createSolanaWallet("devnet", solanaKeypair)
const evmWallet = await createEVMWallet({ id: 84532, name: "Base Sepolia" }, process.env.EVM_PRIVATE_KEY)

const preferEVM = (execers: PaymentExecer[]) => {
  const evm = execers.find((e) => e.requirements.network.startsWith("base"))
  return evm ?? execers[0]
}

const fetchWithPayment = wrap(fetch, {
  handlers: [
    createSolanaHandler(solanaWallet, solanaWallet.mint),
    createEVMHandler(evmWallet),
  ],
  payerChooser: preferEVM,
})

const response = await fetchWithPayment("http://localhost:3000/api/data")
logger.info(JSON.stringify(await response.json()))

Running the example

# Terminal 1
SOLANA_ADDRESS=7xKX... EVM_ADDRESS=0x1234... pnpm tsx server.ts

# Terminal 2 (rides)
PAYER_KEYPAIR_PATH=~/.config/solana/id.json EVM_PRIVATE_KEY=0x... pnpm tsx client-rides.ts

# Terminal 2 (fetch wrapper with EVM preference)
PAYER_KEYPAIR_PATH=~/.config/solana/id.json EVM_PRIVATE_KEY=0x... pnpm tsx client-fetch.ts
The server accepts either chain. The rides client uses whichever wallet matches first. The fetch wrapper client explicitly prefers EVM via a custom payerChooser.