Skip to main content

Prerequisites

  • A wallet with ETH on Base Sepolia for gas (faucet)
  • Node.js 18+ and npm
  • A deployed operator with escrow support (see Deploy an Operator)
There are pre-configured examples in the x402r-sdk repo, including merchant examples and full scenario scripts.

1. Install Dependencies

npm install @x402r/sdk

2. Create a Merchant Client

import { createPublicClient, createWalletClient, http } from 'viem'
import { baseSepolia } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { createMerchantClient } from '@x402r/sdk'

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)

const merchant = createMerchantClient({
  publicClient: createPublicClient({ chain: baseSepolia, transport: http() }),
  walletClient: createWalletClient({
    account,
    chain: baseSepolia,
    transport: http(),
  }),
  operatorAddress: '0x...',              // from deploy result
  escrowPeriodAddress: '0x...',          // from deploy result
  refundRequestAddress: '0x...',         // from deploy result
  freezeAddress: '0x...',               // from deploy result
  refundRequestEvidenceAddress: '0x...', // from deploy result
})

3. Check Payment State

import type { PaymentInfo } from '@x402r/sdk'

// paymentInfo comes from the facilitator callback or your payment records
const paymentInfo: PaymentInfo = { /* ... */ }

const amounts = await merchant.payment.getAmounts(paymentInfo)
console.log('Collected:', amounts.hasCollectedPayment)     // true
console.log('Capturable:', amounts.capturableAmount)       // 1000000n
console.log('Refundable:', amounts.refundableAmount)       // 1000000n

const inEscrow = await merchant.escrow?.isDuringEscrow(paymentInfo)
console.log('In escrow:', inEscrow)                        // true

4. Release Funds After Escrow

release() reverts if called during escrow. Check escrow.isDuringEscrow() first. Pass a smaller amount to release partially.
const releaseTx = await merchant.payment.release(paymentInfo, 1_000_000n)
console.log('Released:', releaseTx)

// Verify
const after = await merchant.payment.getAmounts(paymentInfo)
console.log('Capturable after release:', after.capturableAmount) // 0n

5. Handle Refund Requests (Optional)

If a payer disputes, check for pending refund requests:
const hasRefund = await merchant.refund?.has(paymentInfo)

if (hasRefund) {
  const request = await merchant.refund?.get(paymentInfo)
  console.log('Refund amount:', request?.amount)
  console.log('Status:', request?.status)  // 0 = Pending

  // Approve by executing refundInEscrow (recorder auto-approves)
  const refundTx = await merchant.payment.refundInEscrow(
    paymentInfo,
    request!.amount,
  )
  console.log('Refunded:', refundTx)
}

Next Steps

Deploy Operator

Full deployment config, slot details, and preview addresses.

Protocol Overview

How escrow, capture, and void work under the hood.

Examples

Full scenario scripts to copy from.