Skip to main content
The Arbiter SDK provides methods for reviewing refund requests, making decisions, and executing refunds for disputed payments.

Approve a Refund Request

Approve a pending refund request. This updates the on-chain status but does not transfer funds.
const { txHash } = await arbiter.approveRefundRequest(paymentInfo, 0n);
console.log('Refund approved:', txHash);
Approving a refund request updates the request status to Approved but does not transfer funds. You must call executeRefundInEscrow() separately to move funds back to the payer.

Deny a Refund Request

Deny a pending refund request:
const { txHash } = await arbiter.denyRefundRequest(paymentInfo, 0n);
console.log('Refund denied:', txHash);

Execute Refund in Escrow

After approving a refund request, execute the actual fund transfer back to the payer:
// Full refund (defaults to paymentInfo.maxAmount)
const { txHash } = await arbiter.executeRefundInEscrow(paymentInfo);
console.log('Full refund executed:', txHash);

// Partial refund
const partialAmount = BigInt('500000'); // 0.5 USDC
const { txHash: partialTx } = await arbiter.executeRefundInEscrow(paymentInfo, partialAmount);
console.log('Partial refund executed:', partialTx);
When no amount is provided, executeRefundInEscrow defaults to paymentInfo.maxAmount, issuing a full refund.

Check If a Refund Request Exists

Verify whether a refund request has been submitted for a given payment and nonce:
const hasRequest = await arbiter.hasRefundRequest(paymentInfo, 0n);

if (!hasRequest) {
  console.log('No refund request found for this payment');
  return;
}

Get Refund Request Data

Retrieve the full refund request data, including amount and status:
import { RequestStatus } from '@x402r/core';

const request = await arbiter.getRefundRequest(paymentInfo, 0n);

console.log('Payment hash:', request.paymentInfoHash);
console.log('Nonce:', request.nonce);
console.log('Refund amount:', request.amount);
console.log('Status:', RequestStatus[request.status]);
The RefundRequestData type contains:
interface RefundRequestData {
  paymentInfoHash: `0x${string}`;
  nonce: bigint;
  amount: bigint;
  status: RequestStatus;
}

Get Refund Request Status

Query the current status of a specific refund request:
import { RequestStatus } from '@x402r/core';

const status = await arbiter.getRefundStatus(paymentInfo, 0n);

switch (status) {
  case RequestStatus.Pending:
    console.log('Awaiting decision');
    break;
  case RequestStatus.Approved:
    console.log('Already approved');
    break;
  case RequestStatus.Denied:
    console.log('Already denied');
    break;
  case RequestStatus.Cancelled:
    console.log('Cancelled by payer');
    break;
}

Get Pending Refund Requests (Paginated)

Retrieve a paginated list of refund request keys for a receiver. You can optionally filter by receiver address:
// Get the first 10 pending requests for a specific receiver
const { keys, total } = await arbiter.getPendingRefundRequests(
  0n,    // offset
  10n,   // count
  '0xReceiverAddress...' // optional: defaults to the arbiter's wallet address
);

console.log(`${total} total cases, showing first ${keys.length}`);

// Look up each request by its composite key
for (const key of keys) {
  const request = await arbiter.getRefundRequestByKey(key);
  console.log(`Amount: ${request.amount}, Status: ${RequestStatus[request.status]}`);
}

Get Refund Request Count

Get the total number of refund requests for a receiver:
const count = await arbiter.getRefundRequestCount('0xReceiverAddress...');
console.log(`Total refund requests: ${count}`);

// Defaults to the arbiter's wallet address if not specified
const myCount = await arbiter.getRefundRequestCount();
console.log(`My refund requests: ${myCount}`);

Get Refund Request by Composite Key

Look up a specific refund request using its keccak256(paymentInfoHash, nonce) composite key:
const request = await arbiter.getRefundRequestByKey(compositeKey);

console.log('Payment hash:', request.paymentInfoHash);
console.log('Amount:', request.amount);
console.log('Status:', RequestStatus[request.status]);

Check If a Payment Is Frozen

Verify whether a payment is currently frozen by a Freeze condition contract:
const frozen = await arbiter.isFrozen(paymentInfo, freezeAddress);

if (frozen) {
  console.log('Payment is frozen - dispute in progress');
} else {
  console.log('Payment is not frozen');
}

Complete Decision Workflow

This example shows the full arbiter workflow: fetching pending cases, reviewing them, making a decision, and executing the refund.
import { X402rArbiter } from '@x402r/arbiter';
import { RequestStatus } from '@x402r/core';
import type { PaymentInfo } from '@x402r/core';

async function processAllPendingCases(arbiter: X402rArbiter, receiverAddress: `0x${string}`) {
  // Step 1: Get all pending refund requests
  const { keys, total } = await arbiter.getPendingRefundRequests(0n, 50n, receiverAddress);
  console.log(`Found ${total} pending cases`);

  for (const key of keys) {
    // Step 2: Retrieve request details
    const request = await arbiter.getRefundRequestByKey(key);

    // Step 3: Skip if already decided
    if (request.status !== RequestStatus.Pending) {
      console.log(`Skipping ${request.paymentInfoHash} - already ${RequestStatus[request.status]}`);
      continue;
    }

    // Step 4: Apply your decision logic
    const shouldApprove = await evaluateCase(request);

    if (shouldApprove) {
      // Step 5a: Approve and execute the refund
      // NOTE: You need the full PaymentInfo struct to call these methods.
      // Retrieve it from your application's database or event logs.
      const paymentInfo = await lookupPaymentInfo(request.paymentInfoHash);

      const { txHash: approveTx } = await arbiter.approveRefundRequest(paymentInfo, request.nonce);
      console.log(`Approved: ${approveTx}`);

      const { txHash: executeTx } = await arbiter.executeRefundInEscrow(paymentInfo);
      console.log(`Refund executed: ${executeTx}`);
    } else {
      // Step 5b: Deny the refund
      const paymentInfo = await lookupPaymentInfo(request.paymentInfoHash);

      const { txHash } = await arbiter.denyRefundRequest(paymentInfo, request.nonce);
      console.log(`Denied: ${txHash}`);
    }
  }
}

Decision Flow Diagram

Error Handling

import { X402rArbiter } from '@x402r/arbiter';
import { RequestStatus } from '@x402r/core';
import type { PaymentInfo } from '@x402r/core';

async function safeProcessCase(
  arbiter: X402rArbiter,
  paymentInfo: PaymentInfo,
  nonce: bigint
) {
  try {
    // Check that a refund request exists
    const hasRequest = await arbiter.hasRefundRequest(paymentInfo, nonce);
    if (!hasRequest) {
      return { error: 'No refund request found' };
    }

    // Check that it is still pending
    const status = await arbiter.getRefundStatus(paymentInfo, nonce);
    if (status !== RequestStatus.Pending) {
      return { error: `Request already ${RequestStatus[status]}` };
    }

    // Approve and execute
    const { txHash: approveTx } = await arbiter.approveRefundRequest(paymentInfo, nonce);
    const { txHash: executeTx } = await arbiter.executeRefundInEscrow(paymentInfo);

    return { success: true, approveTx, executeTx };
  } catch (error) {
    console.error('Failed to process case:', error);
    return { error: (error as Error).message };
  }
}

Next Steps