Skip to main content
All five payment query methods are currently stubbed. They throw a NotImplementedError when called.The PaymentOperator contract does not store payment state on-chain in a queryable mapping. Payment state is derived from the escrow contract and event logs. A subgraph or indexer is required to make these methods functional. Until then, use the workarounds described below for each method.
The Client SDK defines five payment query methods on X402rClient. These represent the planned API for querying payment information once a subgraph is deployed. This page documents each method’s intended behavior and provides working alternatives you can use today.

getPaymentState

Query the current lifecycle state of a payment.
import { PaymentState } from '@x402r/core';

// Planned API (currently throws NotImplementedError)
const state = await client.getPaymentState(paymentInfo);

// PaymentState enum:
// 0 = NonExistent  - Payment has never been authorized
// 1 = InEscrow     - Funds locked, capturableAmount > 0
// 2 = Released     - Funds released to receiver, may still be refundable
// 3 = Settled      - No funds in escrow or refundable
// 4 = Expired      - Authorization expired, payer can reclaim

Signature

getPaymentState(paymentInfo: PaymentInfo): Promise<PaymentState>
Workaround: Use merchant.getPaymentAmounts(paymentInfo) from the Merchant SDK. If capturable > 0, the payment is effectively in escrow. If both capturable and refundable are 0, the payment is settled.
const amounts = await merchant.getPaymentAmounts(paymentInfo);

if (amounts.capturable > 0n) {
  console.log('Payment is in escrow');
} else if (amounts.refundable > 0n) {
  console.log('Payment released but still refundable');
} else {
  console.log('Payment is settled');
}

paymentExists

Check whether a payment has been authorized through the operator.
// Planned API (currently throws NotImplementedError)
const exists = await client.paymentExists(paymentInfoHash);
if (exists) {
  console.log('Payment found');
}

Signature

paymentExists(paymentInfoHash: `0x${string}`): Promise<boolean>
Workaround: Watch for AuthorizationCreated events on the PaymentOperator contract. You can query past logs using viem’s getContractEvents:
import { PaymentOperatorABI } from '@x402r/core';

const events = await publicClient.getContractEvents({
  address: operatorAddress,
  abi: PaymentOperatorABI,
  eventName: 'AuthorizationCreated',
  fromBlock: 'earliest',
});

const exists = events.some(
  (e) => e.args.paymentInfoHash === paymentInfoHash
);

isInEscrow

Check if a payment is currently held in escrow with capturable funds.
// Planned API (currently throws NotImplementedError)
const inEscrow = await client.isInEscrow(paymentInfoHash);
if (inEscrow) {
  console.log('Payment can be refunded');
}

Signature

isInEscrow(paymentInfoHash: `0x${string}`): Promise<boolean>
Workaround: Use the working escrow period methods instead. If isDuringEscrowPeriod returns true, the payment is still in its escrow window:
const escrowPeriodAddress = '0x...'; // EscrowPeriod contract address

const duringEscrow = await client.isDuringEscrowPeriod(
  paymentInfo,
  escrowPeriodAddress
);

if (duringEscrow) {
  console.log('Still in escrow period - refund possible');
}

getPaymentDetails

Retrieve the full PaymentInfo struct from a payment hash.
// Planned API (currently throws NotImplementedError)
const details = await client.getPaymentDetails(paymentInfoHash);

console.log('Payer:', details.payer);
console.log('Receiver:', details.receiver);
console.log('Amount:', details.maxAmount);
console.log('Token:', details.token);

Signature

getPaymentDetails(paymentInfoHash: `0x${string}`): Promise<PaymentInfo>
Workaround: Store the PaymentInfo struct locally when you first create the payment. The hash is deterministic (it is the keccak256 of the struct), so you can maintain your own mapping of hash to PaymentInfo in a local database or cache.
// When payment is created, store the PaymentInfo
const paymentInfoMap = new Map<string, PaymentInfo>();
paymentInfoMap.set(paymentInfoHash, paymentInfo);

// Later, retrieve it
const details = paymentInfoMap.get(paymentInfoHash);

getMyPayments

List all payment hashes where the connected wallet is the payer.
// Planned API (currently throws NotImplementedError)
const { hashes } = await client.getMyPayments();

console.log(`You have ${hashes.length} payments`);

for (const hash of hashes) {
  console.log(`Payment: ${hash}`);
}

Signature

getMyPayments(): Promise<{ hashes: readonly `0x${string}`[] }>
Workaround: Use watchMyPayments to track payments in real-time and accumulate them, or query AuthorizationCreated logs filtered by the payer address:
import { PaymentOperatorABI } from '@x402r/core';

const events = await publicClient.getContractEvents({
  address: operatorAddress,
  abi: PaymentOperatorABI,
  eventName: 'AuthorizationCreated',
  args: {
    payer: walletClient.account.address,
  },
  fromBlock: 'earliest',
});

const hashes = events.map((e) => e.args.paymentInfoHash!);
console.log(`Found ${hashes.length} payments`);

Why a Subgraph Is Needed

The PaymentOperator contract is designed for gas efficiency. It does not maintain queryable mappings of all payments. Payment lifecycle data is emitted as events (AuthorizationCreated, ReleaseExecuted, RefundInEscrowExecuted, etc.) but not stored in contract storage. To provide these query methods, the SDK needs a subgraph (e.g., The Graph) or an indexer that:
  1. Indexes all PaymentOperator events
  2. Maintains a derived state machine for each payment
  3. Tracks payer-to-payment relationships
  4. Exposes a GraphQL API for efficient querying
Once the subgraph is deployed, these methods will work without any code changes on your side. The API signatures will remain the same.

Next Steps