Exact Scheme

The exact scheme enables fixed-amount payments where the cost is known upfront. The client signs an authorization for a specific amount, and the facilitator executes the transfer on-chain.

Overview

The exact scheme is the primary payment scheme in T402. The client pre-signs a payment authorization off-chain, which the facilitator later submits to the blockchain for settlement. The client never pays gas.

Use Cases

  • API Monetization: Fixed price per API call ($0.01/request)
  • Content Purchases: One-time payment for articles, images, datasets
  • Subscription Tokens: Pre-pay for access periods
  • Data Marketplace: Fixed price per data record
  • IoT Micropayments: Sensor readings at known cost

Payment Requirements

The server specifies the exact amount required:

{
  "scheme": "exact",
  "network": "eip155:8453",
  "amount": "10000",
  "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payTo": "0xMerchantAddress...",
  "maxTimeoutSeconds": 300,
  "extra": {
    "name": "USD Coin",
    "version": "2"
  }
}

Fields

FieldTypeRequiredDescription
schemestringYesAlways "exact"
networkstringYesCAIP-2 network ID (e.g., "eip155:8453")
amountstringYesExact payment amount (smallest units)
assetstringYesToken contract address or identifier
payTostringYesRecipient address
maxTimeoutSecondsnumberYesAuthorization validity period in seconds
extra.namestringEVM onlyToken EIP-712 domain name
extra.versionstringEVM onlyToken EIP-712 domain version
extra.feePayerstringSVM onlyFacilitator address for fee coverage

EVM Implementation (EIP-3009)

On EVM chains, the exact scheme uses EIP-3009 TransferWithAuthorization — a gasless transfer standard where the token holder signs a transfer authorization off-chain and a relayer executes it.

How It Works

  1. Client signs an EIP-712 typed data message authorizing a specific transfer
  2. Facilitator calls transferWithAuthorization(from, to, value, validAfter, validBefore, nonce, v, r, s) on the token contract
  3. The token contract verifies the signature and executes the transfer
  4. Client pays zero gas — the facilitator covers transaction costs

EIP-712 Typed Data

const domain = {
  name: "USD Coin",     // Token name (from extra.name)
  version: "2",         // Token version (from extra.version)
  chainId: 8453,        // From network ID
  verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
};
 
const types = {
  TransferWithAuthorization: [
    { name: "from", type: "address" },
    { name: "to", type: "address" },
    { name: "value", type: "uint256" },
    { name: "validAfter", type: "uint256" },
    { name: "validBefore", type: "uint256" },
    { name: "nonce", type: "bytes32" }
  ]
};
 
const message = {
  from: "0xClientAddress...",
  to: "0xMerchantAddress...",
  value: "10000",                    // Exact amount
  validAfter: "1700000000",          // Current time - 600s
  validBefore: "1700000300",         // Current time + maxTimeoutSeconds
  nonce: "0x1234567890abcdef..."     // Random 32-byte nonce
};

Payment Payload

{
  "t402Version": 2,
  "accepted": {
    "scheme": "exact",
    "network": "eip155:8453",
    "amount": "10000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0xMerchantAddress..."
  },
  "payload": {
    "signature": "0xabcdef1234...",
    "authorization": {
      "from": "0xClientAddress...",
      "to": "0xMerchantAddress...",
      "value": "10000",
      "validAfter": "1700000000",
      "validBefore": "1700000300",
      "nonce": "0x1234567890abcdef..."
    }
  }
}

Supported EVM Tokens

TokenEIP-3009Networks
USDT0Ethereum, Arbitrum, Ink, Berachain, Unichain
USDCEthereum, Base, Arbitrum, Optimism

EIP-3009 is supported by USDT0 (LayerZero OFT) and USDC. Both support gasless transferWithAuthorization. The extra.name and extra.version fields must match the token’s EIP-712 domain exactly.

Smart Wallet Support (EIP-6492)

The exact scheme supports smart contract wallets (Safe, ERC-4337 accounts) via EIP-6492 signature validation:

// Smart wallet signatures use EIP-6492 wrapper
// The facilitator automatically detects and validates:
// 1. Standard EOA signatures (ECDSA)
// 2. EIP-1271 smart contract signatures (deployed wallets)
// 3. EIP-6492 signatures (undeployed counterfactual wallets)
⚠️

For undeployed smart wallets, the facilitator must deploy the wallet before executing transferWithAuthorization. Set deployERC4337WithEIP6492: true in facilitator config.

Non-EVM Implementations

Non-EVM chains use the exact-direct variant where the client executes the transfer directly on-chain and provides the transaction hash as proof.

Exact vs Exact-Direct

Aspectexact (EVM)exact-direct (Non-EVM)
Who executes transferFacilitatorClient
Client pays gasNoYes
Authorization methodOff-chain signatureOn-chain transaction
ProofSigned EIP-712 messageTransaction hash
SettlementFacilitator calls contractAlready settled
Facilitator roleExecute + verifyVerify only

Solana (SPL Token Transfer)

{
  "scheme": "exact",
  "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
  "amount": "10000",
  "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "payTo": "FacilitatorPublicKey...",
  "extra": {
    "feePayer": "FacilitatorPublicKey..."
  }
}

The client creates a partially-signed TransferChecked instruction. The facilitator adds its signature (as fee payer) and submits.

TON (Jetton Transfer)

{
  "scheme": "exact",
  "network": "ton:mainnet",
  "amount": "10000",
  "asset": "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs",
  "payTo": "EQRecipientAddress...",
  "maxTimeoutSeconds": 300
}

The client signs an external message containing a Jetton transfer operation. The facilitator submits the signed BOC to the TON network.

TRON (TRC-20 Transfer)

{
  "scheme": "exact",
  "network": "tron:mainnet",
  "amount": "10000",
  "asset": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "payTo": "TRecipientAddress...",
  "maxTimeoutSeconds": 300
}

The client signs a TRC-20 transfer transaction with reference block bytes for replay protection.

NEAR, Aptos, Tezos, Polkadot, Stacks, Cosmos (Direct Transfer)

These chains use exact-direct — the client executes the transfer and returns the transaction hash:

{
  "payload": {
    "txHash": "abc123...",
    "from": "sender-address",
    "to": "recipient-address",
    "amount": "10000"
  }
}

See individual chain reference pages for details:

  • NEAR — NEP-141 ft_transfer
  • Aptos — Fungible Asset primary_fungible_store::transfer
  • Tezos — FA2 transfer entrypoint
  • Polkadotassets.transfer extrinsic
  • Stacks — SIP-010 transfer contract call
  • Cosmos — Bank MsgSend

Verification

The facilitator performs these checks before approving a payment:

CheckDescription
Payload structureAll required fields present and correctly typed
Scheme matchscheme equals "exact"
Network matchPayload network matches requirements
Signature validityEIP-712 signature recovers to from address
Recipient matchto matches payTo in requirements
Time windowvalidAfter ≤ now and validBefore > now
Balance checkClient has sufficient token balance
Amount checkvalue ≥ amount in requirements

Error Codes

CodeDescriptionRecovery
invalid_payload_structureMissing or malformed fieldsCheck payload format
unsupported_schemeScheme is not "exact"Use correct scheme
network_mismatchNetwork doesn’t matchUse correct network
invalid_exact_evm_payload_signatureSignature verification failedRe-sign with correct key
invalid_exact_evm_payload_recipient_mismatchto doesn’t match payToUse address from requirements
invalid_exact_evm_payload_authorization_valid_beforeAuthorization expiredCreate new authorization
invalid_exact_evm_payload_authorization_valid_afterNot yet validWait or adjust validAfter
insufficient_fundsToken balance too lowFund the wallet
invalid_exact_evm_payload_authorization_valueAmount too lowUse amount from requirements

SDK Usage

import { t402Client } from '@t402/fetch'
import { ExactEvmScheme } from '@t402/evm/exact/client'
import { privateKeyToAccount } from 'viem/accounts'
 
// Client: Register scheme and make payment
const client = new t402Client()
const account = privateKeyToAccount('0x...')
 
client.register('eip155:*', new ExactEvmScheme(account))
 
// Automatic 402 handling
const fetchWithPay = wrapFetchWithPayment(fetch, client)
const response = await fetchWithPay('https://api.example.com/data')
// Server: Define requirements
import { paymentMiddleware } from '@t402/express'
import { ExactEvmScheme } from '@t402/evm/exact/server'
 
app.use(paymentMiddleware({
  'GET /api/data': {
    accepts: [{
      scheme: 'exact',
      network: 'eip155:8453',
      price: '$0.01',
      payTo: '0xMerchantAddress...'
    }],
    description: 'Premium API data'
  }
}, server))
// Facilitator: Verify and settle
import { ExactEvmScheme } from '@t402/evm/exact/facilitator'
 
const facilitator = new ExactEvmScheme(facilitatorSigner)
 
const verifyResult = await facilitator.verify(payload, requirements)
// { isValid: true, payer: "0x...", network: "eip155:8453" }
 
const settleResult = await facilitator.settle(payload, requirements)
// { success: true, transaction: "0xabc...", blockNumber: 12345 }

Security Considerations

Replay Protection

ChainMechanism
EVMRandom 32-byte nonce (unique per authorization)
SolanaRecent blockhash (expires after ~2 minutes)
TONWallet seqno + query ID
TRONReference block bytes/hash + expiration
NEAR/Aptos/Tezos/Polkadot/Stacks/CosmosTransaction hash uniqueness

Time Window

The validAfter and validBefore fields create a bounded time window:

validAfter = now - 600s    (10 minute grace for clock skew)
validBefore = now + maxTimeoutSeconds
⚠️

Set maxTimeoutSeconds appropriately for your use case. Too short may cause legitimate payments to expire. Too long increases the window for potential issues. 300 seconds (5 minutes) is recommended for most API calls.

Amount Validation

  • The facilitator verifies value ≥ amount (not exact equality)
  • This allows clients to slightly overpay without rejection
  • Settlement always transfers the exact value specified in the authorization

Comparison with Up-To Scheme

Featureexactupto
Amount fieldamountmaxAmount
Settlement amountFixed (equals value)Variable (≤ maxAmount)
Client riskKnown exactlyMaximum exposure known
Server complexityLowerHigher (usage tracking)
EVM signatureEIP-3009 TransferWithAuthorizationEIP-2612 Permit
Token supportUSDT0, USDCUSDC, DAI (not USDT)
Best forFixed pricingUsage-based billing

Network Support

NetworkScheme VariantSignatureToken Standard
EVM (25 chains)exactEIP-712EIP-3009
SolanaexactEd25519SPL TransferChecked
TONexactEd25519Jetton transfer
TRONexactsecp256k1TRC-20 transfer
NEARexact-directEd25519NEP-141 ft_transfer
Aptosexact-directEd25519FA transfer
Tezosexact-directEd25519/secp256k1FA2 transfer
Polkadotexact-directSr25519assets.transfer
Stacksexact-directsecp256k1SIP-010 transfer
Cosmos (Noble)exact-directsecp256k1Bank MsgSend

Troubleshooting

Common Issues

“Signature verification failed”

  • Ensure extra.name and extra.version match the token’s EIP-712 domain
  • USDT0 uses name: "USDT0", version: "1"
  • USDC uses name: "USD Coin", version: "2"

“Authorization expired”

  • validBefore has passed — create a new payment payload
  • Check server/client clock synchronization

“Insufficient funds”

  • Client wallet needs sufficient token balance
  • Balance is checked at verification time, not signing time

“Recipient mismatch”

  • The to in authorization must exactly match payTo in requirements
  • Check for address checksum differences

“Nonce already used”

  • Each nonce can only be used once per (from, token) pair
  • The SDK generates random 32-byte nonces to avoid collisions

Further Reading