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)
1. Install Dependencies
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.