Skip to main content
The X402rMerchant class provides three subscription methods for watching blockchain events in real-time. Each returns an object with an unsubscribe function for cleanup.

Watch Refund Requests

Use watchRefundRequests() to subscribe to RefundRequested events emitted by the RefundRequest contract. The callback receives a RefundRequestEventLog object for each event.
const { unsubscribe } = merchant.watchRefundRequests((event) => {
  console.log('Event:', event.eventName);
  console.log('Payment hash:', event.args.paymentInfoHash);
  console.log('Payer:', event.args.payer);
  console.log('Receiver:', event.args.receiver);
  console.log('Amount:', event.args.amount);
  console.log('Nonce:', event.args.nonce);
  console.log('Block:', event.blockNumber);
  console.log('Tx hash:', event.transactionHash);
});

// Later: stop watching
unsubscribe();
The RefundRequestEventLog type has the following shape:
interface RefundRequestEventLog {
  eventName: 'RefundRequested' | 'RefundRequestStatusUpdated' | 'RefundRequestCancelled';
  args: {
    paymentInfoHash?: `0x${string}`;
    payer?: `0x${string}`;
    receiver?: `0x${string}`;
    amount?: bigint;
    nonce?: bigint;
    status?: number;
  };
  address: `0x${string}`;
  blockNumber: bigint;
  transactionHash: `0x${string}`;
  logIndex: number;
}
watchRefundRequests() requires the refundRequestAddress to be configured when creating the X402rMerchant instance.

Example: Auto-respond to Small Refund Requests

import { X402rMerchant } from '@x402r/merchant';
import { RequestStatus } from '@x402r/core';

const AUTO_APPROVE_THRESHOLD = BigInt('5000000'); // 5 USDC

const { unsubscribe } = merchant.watchRefundRequests(async (event) => {
  const amount = event.args.amount;
  const paymentHash = event.args.paymentInfoHash;

  console.log(`New refund request: ${paymentHash}, amount: ${amount}`);

  if (amount && amount < AUTO_APPROVE_THRESHOLD) {
    console.log('Auto-approving small refund request');
    // You would look up the paymentInfo from your database
    // and call merchant.approveRefundRequest(paymentInfo, nonce)
  } else {
    console.log('Queuing for manual review');
  }
});

Watch Releases

Use watchReleases() to subscribe to ReleaseExecuted events emitted by the PaymentOperator contract. The callback receives a PaymentOperatorEventLog object for each event.
const { unsubscribe } = merchant.watchReleases((event) => {
  console.log('Release executed!');
  console.log('Payment hash:', event.args.paymentInfoHash);
  console.log('Amount:', event.args.amount);
  console.log('Payer:', event.args.payer);
  console.log('Receiver:', event.args.receiver);
  console.log('Block:', event.blockNumber);
  console.log('Tx hash:', event.transactionHash);
});

// Later: stop watching
unsubscribe();
The PaymentOperatorEventLog type has the following shape:
interface PaymentOperatorEventLog {
  eventName: 'ReleaseExecuted' | 'RefundInEscrowExecuted' | 'RefundPostEscrowExecuted'
    | 'AuthorizationCreated' | 'ChargeExecuted';
  args: {
    paymentInfoHash?: `0x${string}`;
    payer?: `0x${string}`;
    receiver?: `0x${string}`;
    amount?: bigint;
  };
  address: `0x${string}`;
  blockNumber: bigint;
  transactionHash: `0x${string}`;
  logIndex: number;
}

Example: Revenue Tracking

let totalReleased = 0n;

const { unsubscribe } = merchant.watchReleases((event) => {
  const amount = event.args.amount ?? 0n;
  totalReleased += amount;

  console.log(`Release: +${amount} tokens`);
  console.log(`Total released: ${totalReleased}`);
});

Watch Freeze Events

Use watchFreezeEvents() to subscribe to PaymentFrozen and PaymentUnfrozen events from a specific Freeze contract. You must provide the Freeze contract address as the first argument.
const freezeAddress: `0x${string}` = '0xFreezeContract...';

const { unsubscribe } = merchant.watchFreezeEvents(
  freezeAddress,
  (event) => {
    if (event.eventName === 'PaymentFrozen') {
      console.log('Payment FROZEN:', event.args.paymentInfoHash);
      console.log('Frozen by:', event.args.caller);
      // Alert: a dispute may be in progress
    } else if (event.eventName === 'PaymentUnfrozen') {
      console.log('Payment UNFROZEN:', event.args.paymentInfoHash);
      console.log('Unfrozen by:', event.args.caller);
      // The payment can now be released
    }
  }
);

// Later: stop watching
unsubscribe();
The FreezeEventLog type has the following shape:
interface FreezeEventLog {
  eventName: 'PaymentFrozen' | 'PaymentUnfrozen';
  args: {
    paymentInfoHash?: `0x${string}`;
    caller?: `0x${string}`;
  };
  address: `0x${string}`;
  blockNumber: bigint;
  transactionHash: `0x${string}`;
  logIndex: number;
}
The freezeAddress parameter is the address of the Freeze condition contract, not the PaymentOperator. You can retrieve it from your operator config via merchant.getOperatorConfig().

Event Types Reference

MethodEvent NameCallback TypeUse Case
watchRefundRequestsRefundRequestedRefundRequestEventLogDetect incoming refund requests
watchReleasesReleaseExecutedPaymentOperatorEventLogTrack revenue and release confirmations
watchFreezeEventsPaymentFrozen / PaymentUnfrozenFreezeEventLogMonitor dispute-related freezes
All subscription methods use viem’s watchContractEvent under the hood. For reliable real-time delivery, configure your publicClient with a WebSocket transport.

Next Steps

Merchant SDK

Release funds, charge, and query escrow state.

Arbiter SDK

Learn about dispute resolution from the arbiter perspective.

Refund Handling

Process refund requests with approve/deny workflows.

Client Subscriptions

See how clients subscribe to the same events.