Skip to main content
The Arbiter SDK provides batch operations for processing multiple refund requests in a single call. Both batchApprove and batchDeny accept an array of { paymentInfo, nonce } objects.
Batch items are processed sequentially, not atomically. If one item fails mid-batch, all previously processed items will not be rolled back. Design your error handling accordingly.

Batch Approve

Approve multiple refund requests in one call:
const items = [
  { paymentInfo: paymentInfo1, nonce: 0n },
  { paymentInfo: paymentInfo2, nonce: 0n },
  { paymentInfo: paymentInfo3, nonce: 1n },
];

const results = await arbiter.batchApprove(items);

for (const { txHash } of results) {
  console.log('Approved:', txHash);
}

Batch Deny

Deny multiple refund requests in one call:
const items = [
  { paymentInfo: paymentInfo4, nonce: 0n },
  { paymentInfo: paymentInfo5, nonce: 0n },
];

const results = await arbiter.batchDeny(items);

for (const { txHash } of results) {
  console.log('Denied:', txHash);
}

Empty Batch Handling

Both batch methods safely handle empty arrays and return an empty results array:
const results = await arbiter.batchApprove([]);
console.log(results.length); // 0

Item Format

Each item in the batch array must include both the paymentInfo struct and the nonce:
interface BatchItem {
  /** The full PaymentInfo struct identifying the payment */
  paymentInfo: PaymentInfo;
  /** The record index (nonce) from PaymentIndexRecorder */
  nonce: bigint;
}
The nonce identifies which specific charge record the refund request targets. For most single-charge payments, this is 0n.

Example: Triage and Batch Process Pending Cases

Fetch all pending cases, evaluate each one, then batch approve and deny:
import { X402rArbiter } from '@x402r/arbiter';
import { RequestStatus } from '@x402r/core';
import type { PaymentInfo, RefundRequestData } from '@x402r/core';

async function triageAndProcess(
  arbiter: X402rArbiter,
  receiverAddress: `0x${string}`,
  lookupPaymentInfo: (hash: `0x${string}`) => Promise<PaymentInfo>
) {
  // Step 1: Fetch pending cases
  const { keys, total } = await arbiter.getPendingRefundRequests(0n, 100n, receiverAddress);
  console.log(`Processing ${total} pending cases`);

  const toApprove: Array<{ paymentInfo: PaymentInfo; nonce: bigint }> = [];
  const toDeny: Array<{ paymentInfo: PaymentInfo; nonce: bigint }> = [];

  // Step 2: Evaluate each case
  for (const key of keys) {
    const request = await arbiter.getRefundRequestByKey(key);

    if (request.status !== RequestStatus.Pending) {
      continue;
    }

    const paymentInfo = await lookupPaymentInfo(request.paymentInfoHash);
    const item = { paymentInfo, nonce: request.nonce };

    if (shouldApprove(request)) {
      toApprove.push(item);
    } else {
      toDeny.push(item);
    }
  }

  // Step 3: Batch process decisions
  const approveResults = await arbiter.batchApprove(toApprove);
  const denyResults = await arbiter.batchDeny(toDeny);

  console.log(`Approved: ${approveResults.length}, Denied: ${denyResults.length}`);

  return { approved: approveResults, denied: denyResults };
}

function shouldApprove(request: RefundRequestData): boolean {
  // Your decision logic here
  return request.amount < BigInt('10000000'); // Auto-approve < 10 USDC
}

Example: Batch Approve with Refund Execution

After batch approving, execute refunds individually for each approved payment:
import { X402rArbiter } from '@x402r/arbiter';
import type { PaymentInfo } from '@x402r/core';

async function batchApproveAndExecute(
  arbiter: X402rArbiter,
  items: Array<{ paymentInfo: PaymentInfo; nonce: bigint }>
) {
  // Step 1: Batch approve all items
  const approveResults = await arbiter.batchApprove(items);
  console.log(`Approved ${approveResults.length} refund requests`);

  // Step 2: Execute refunds individually
  const executeResults: Array<{ txHash: `0x${string}` }> = [];

  for (const { paymentInfo } of items) {
    try {
      const { txHash } = await arbiter.executeRefundInEscrow(paymentInfo);
      executeResults.push({ txHash });
      console.log('Refund executed:', txHash);
    } catch (error) {
      console.error(`Failed to execute refund for ${paymentInfo.payer}:`, error);
    }
  }

  return {
    approved: approveResults,
    executed: executeResults,
  };
}

Performance Considerations

Each item in a batch results in a separate on-chain transaction. Gas costs scale linearly with the number of items. Plan batch sizes around your RPC provider’s rate limits.
FactorDetail
Transaction orderingItems are processed sequentially to ensure correct nonce ordering.
Gas costsEach item is a separate transaction. Batch methods save on SDK overhead, not gas.
Partial failuresIf one transaction fails, previous ones remain on-chain. Handle partial failures in your logic.
Rate limitingLarge batches may hit RPC rate limits. Consider adding delays for 50+ item batches.

Next Steps

AI Integration

Automate decisions with AI evaluation hooks.

Event Subscriptions

Watch for new cases in real-time.

Decision Submission

Individual approve/deny methods and executeRefundInEscrow.

Arbiter Quickstart

Review the complete arbiter setup guide.