Prerequisites
- A wallet with ETH on Base Sepolia for gas (faucet)
- Node.js 18+ and npm
- A marketplace operator where your address is configured as the arbiter (see Deploy an Operator)
1. Install Dependencies
2. Create an Arbiter Client
import { createPublicClient, createWalletClient, http } from 'viem'
import { baseSepolia } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { createArbiterClient } from '@x402r/sdk'
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const arbiter = createArbiterClient({
publicClient: createPublicClient({ chain: baseSepolia, transport: http() }),
walletClient: createWalletClient({
account,
chain: baseSepolia,
transport: http(),
}),
operatorAddress: '0x...', // from deploy result
refundRequestAddress: '0x...', // from deploy result
refundRequestEvidenceAddress: '0x...', // from deploy result
escrowPeriodAddress: '0x...',
freezeAddress: '0x...',
})
3. Check for Pending Refund Requests
import type { PaymentInfo } from '@x402r/sdk'
const paymentInfo: PaymentInfo = { /* ... */ }
const hasRefund = await arbiter.refund?.has(paymentInfo)
if (hasRefund) {
const request = await arbiter.refund?.get(paymentInfo)
console.log('Amount:', request?.amount)
console.log('Status:', request?.status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Cancelled, 4 = Refused
}
To list all refund requests for your operator:
const requests = await arbiter.refund?.getOperatorRequests(
arbiter.config.operatorAddress,
0n, // offset
10n, // count
)
console.log('Pending requests:', requests)
4. Review Evidence
Both payers and merchants can submit evidence as IPFS CIDs. Read all entries before making a decision:
const count = await arbiter.evidence?.count(paymentInfo)
console.log('Evidence entries:', count)
const batch = await arbiter.evidence?.getBatch(paymentInfo, 0n, count!)
for (const entry of batch!.entries) {
console.log('CID:', entry.cid)
console.log('Submitter:', entry.submitter)
console.log('Timestamp:', entry.timestamp)
}
5. Approve a Refund
refundInEscrow() auto-approves the pending RefundRequest. There is no undo.
const tx = await arbiter.payment.refundInEscrow(paymentInfo, request!.amount)
console.log('Refund approved:', tx)
// Verify
const approved = await arbiter.refund?.get(paymentInfo)
console.log('Approved amount:', approved?.approvedAmount)
console.log('Status:', approved?.status) // 1 = Approved
6. Deny a Refund Request
deny() = you reviewed and rejected the claim. refuse() = you decline to rule (e.g. conflict of interest).
const denyTx = await arbiter.refund?.deny(paymentInfo)
console.log('Refund denied:', denyTx)
Or decline to rule entirely:
const refuseTx = await arbiter.refund?.refuse(paymentInfo)
console.log('Declined to rule:', refuseTx)
7. Unfreeze a Payment
If the payer froze the payment during the dispute, unfreeze it after resolution:
const frozen = await arbiter.freeze?.isFrozen(paymentInfo)
if (frozen) {
const tx = await arbiter.freeze?.unfreeze(paymentInfo)
console.log('Unfrozen:', tx)
}
8. Distribute Accumulated Fees
Protocol fees accumulate on the operator when payments are released:
import { getChainConfig } from '@x402r/sdk'
const config = getChainConfig(84532)
const fees = await arbiter.operator.getAccumulatedProtocolFees(config.usdc)
console.log('Accumulated fees:', fees)
if (fees > 0n) {
const tx = await arbiter.operator.distributeFees(config.usdc)
console.log('Fees distributed:', tx)
}
Next Steps
Delivery Protection
Automated evaluation for every transaction.
Deploy Operator
How your address gets configured as arbiter on an operator.
Examples
Full dispute resolution scenario end-to-end.