For the complete documentation index, see llms.txt. This page is also available as Markdown.

Examples

Complete, runnable code examples for all three integration flows. Copy any example into a Node.js script, set your environment variables, and run it.

Setup

npm install ethers dotenv

Create a .env file:

IVORYPAY_API_KEY=your_merchant_api_key_here
EVM_PRIVATE_KEY=0xYourWalletPrivateKey

Example A — Client-Side EIP-712 (USDC on Base)

Builds and signs the EIP-712 hash entirely in client code. No /build call. Use this for USDC on Base, Polygon, or Lisk.

import 'dotenv/config';
import { ethers } from 'ethers';

const BASE_URL    = 'https://facilitator.ivorypay.io';
const API_KEY     = process.env.IVORYPAY_API_KEY;
const PRIVATE_KEY = process.env.EVM_PRIVATE_KEY;

const EIP3009_TRANSFER_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' },
  ],
};

async function payViaClientSideEIP712() {
  // ── Step 1: Initiate ─────────────────────────────────────────────────────────
  const initiateRes = await fetch(`${BASE_URL}/api/x402/initiate`, {
    method: 'POST',
    headers: { 'Authorization': API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      amount: 0.01,
      token: 'USDC',
      network: 'BASE_MAINNET',
      resourceUrl: 'https://yourapp.com/premium-content',
      resourcesDescription: 'Access to premium content',
    }),
  });
  const initiateBody = await initiateRes.json();

  // ── Step 2: Decode PAYMENT-REQUIRED ──────────────────────────────────────────
  const paymentRequired = JSON.parse(
    Buffer.from(initiateBody.headers['PAYMENT-REQUIRED'], 'base64').toString('utf8')
  );
  const accepted = paymentRequired.accepts[0];

  // ── Step 3: Build EIP-712 hash locally ───────────────────────────────────────
  const wallet  = new ethers.Wallet(PRIVATE_KEY);
  const chainId = parseInt(accepted.network.split(':')[1]);

  const now         = Math.floor(Date.now() / 1000);
  const validAfter  = now;
  const validBefore = now + accepted.maxTimeoutSeconds;
  const nonce       = ethers.hexlify(ethers.randomBytes(32));

  const domain = {
    name:              accepted.extra.name,
    version:           accepted.extra.version,
    chainId:           chainId,
    verifyingContract: accepted.asset,
  };

  const message = {
    from:        ethers.getAddress(wallet.address),
    to:          ethers.getAddress(accepted.payTo),
    value:       BigInt(Math.round(parseFloat(accepted.amount) * 1_000_000)),
    validAfter:  BigInt(validAfter),
    validBefore: BigInt(validBefore),
    nonce:       nonce,
  };

  const typedDataHash = ethers.TypedDataEncoder.hash(domain, EIP3009_TRANSFER_TYPES, message);

  // ── Step 4: Sign ─────────────────────────────────────────────────────────────
  const sig = wallet.signingKey.sign(typedDataHash);
  const signature = '0x' + sig.r.slice(2) + sig.s.slice(2) + Buffer.from([sig.v]).toString('hex');

  // ── Step 5: Build payment-signature header ────────────────────────────────────
  const signaturePayload = {
    x402Version: 2,
    resource: paymentRequired.resource,
    accepted: {
      scheme:            accepted.scheme,
      network:           accepted.network,
      amount:            accepted.amount,
      asset:             accepted.asset,
      payTo:             accepted.payTo,
      maxTimeoutSeconds: accepted.maxTimeoutSeconds,
      extra:             accepted.extra,
    },
    payload: {
      signature,
      authorization: {
        from:        wallet.address,
        to:          accepted.payTo,
        value:       accepted.amount,
        validAfter:  String(validAfter),
        validBefore: String(validBefore),
        nonce,
      },
    },
  };

  const paymentSignatureHeader = Buffer.from(JSON.stringify(signaturePayload), 'utf8').toString('base64');

  // ── Step 6: Submit ────────────────────────────────────────────────────────────
  const submitRes = await fetch(`${BASE_URL}/api/x402/submit`, {
    method: 'POST',
    headers: { 'Authorization': API_KEY, 'payment-signature': paymentSignatureHeader },
  });

  const result = await submitRes.json();
  console.log('Session ID:', result.sessionId);
}

payViaClientSideEIP712().catch(console.error);

Example B — Facilitator-Assisted Build (USDC on Base)

Delegates hash computation to the facilitator via POST /build. Use this for USDC on Base, Polygon, or Lisk when you want simpler code.


Example C — Permit2 (USDT on BSC)

Use this for BSC, Ethereum, or any non-USDC token. Before running, ensure the payer wallet has approved the Permit2 contract — see Permit2 Approval.


Utility Helpers


Verify a Transaction

Last updated