Skip to main content

Prerequisites

  • A wallet with ETH on Base Sepolia for gas (faucet)
  • Node.js 18+ and npm
  • Operator and escrow addresses from the Merchant Setup
There is a full AI garbage detector example that implements this pattern with heuristic + LLM evaluation.

1. Install Dependencies

npm install @x402r/sdk @x402r/helpers

2. Create the Arbiter Client

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

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

const arbiter = createArbiterClient({
  publicClient: createPublicClient({ chain: baseSepolia, transport: http() }),
  walletClient: createWalletClient({
    account,
    chain: baseSepolia,
    transport: http(),
  }),
  operatorAddress: process.env.OPERATOR_ADDRESS as `0x${string}`,
  escrowPeriodAddress: process.env.ESCROW_PERIOD_ADDRESS as `0x${string}`,
})

3. Handle the Verify Endpoint

The merchant’s forwardToArbiter() hook POSTs to /verify. Use parseForwardedPayload() to extract typed PaymentInfo with BigInt fields restored:
import { parseForwardedPayload } from '@x402r/helpers'
import express from 'express'

const app = express()
app.use(express.json())

app.post('/verify', async (req, res) => {
  const { responseBody, paymentInfo, network, transaction } =
    parseForwardedPayload(req.body)

  const chainId = fromNetworkId(network) // "eip155:84532" -> 84532

  // Your evaluation logic
  const passed = await evaluate(responseBody)

  if (passed) {
    const amounts = await arbiter.payment.getAmounts(paymentInfo)
    await arbiter.payment.release(paymentInfo, amounts.capturableAmount)
    res.json({ verdict: 'PASS' })
  } else {
    // Do nothing. Funds auto-refund after escrow expires.
    res.json({ verdict: 'FAIL' })
  }
})

app.listen(3001)

4. Implement Your Evaluation Logic

The evaluate() function is where your logic lives. It could be:
  • Heuristic checks: HTTP status code, response size, content-type validation
  • AI evaluation: send response body to an LLM and ask “is this a valid response?”
  • Schema validation: check if the response matches an expected JSON schema
async function evaluate(responseBody: string): Promise<boolean> {
  // Example: reject empty or error responses
  if (!responseBody || responseBody.length < 10) return false
  if (responseBody.includes('"error"')) return false

  // Example: LLM evaluation
  // const result = await llm.evaluate(responseBody)
  // return result.verdict === 'PASS'

  return true
}

5. What Happens on Failure

If your service goes down, no payments get evaluated and funds stay in escrow until timeout. The escrow period protects payers, but add uptime monitoring and alerting.

Next Steps

Merchant Setup

Deploy the operator and configure forwardToArbiter().

Dispute Resolution

For human-reviewed disputes instead of automated evaluation.

Examples

Runnable examples for every SDK operation.