Examples
Setup
npm install ethers dotenvIVORYPAY_API_KEY=your_merchant_api_key_here
EVM_PRIVATE_KEY=0xYourWalletPrivateKeyExample A — Client-Side EIP-712 (USDC on Base)
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)
Example C — Permit2 (USDT on BSC)
Utility Helpers
Verify a Transaction
Last updated