The Client SDK provides complete refund management capabilities for payers. All refund methods documented on this page are fully functional and interact directly with the RefundRequest contract on-chain.
About the nonce parameter: Every refund method requires a nonce: bigint parameter. This is the record index from the PaymentIndexRecorder and identifies which charge within a payment you are requesting a refund for. For the first (and most common) charge, use 0n.
requestRefund
Submit a refund request for a payment that is in escrow. The request goes on-chain and is visible to the merchant and any assigned arbiter.
const { txHash } = await client.requestRefund(
paymentInfo,
BigInt('1000000'), // amount to refund (e.g., 1 USDC with 6 decimals)
0n // nonce: first charge
);
console.log(`Refund requested: ${txHash}`);
Signature
requestRefund(
paymentInfo: PaymentInfo,
amount: bigint,
nonce: bigint
): Promise<{ txHash: `0x${string}` }>
You can only request a refund if:
- The payment is in escrow (has capturable funds)
- No existing refund request exists for this payment and nonce combination
- You are the payer for this payment
- A
walletClient and refundRequestAddress are configured on the client
cancelRefundRequest
Cancel a pending refund request that you submitted. Only the original requester (payer) can cancel, and only while the request status is Pending.
const { txHash } = await client.cancelRefundRequest(paymentInfo, 0n);
console.log(`Refund request cancelled: ${txHash}`);
Signature
cancelRefundRequest(
paymentInfo: PaymentInfo,
nonce: bigint
): Promise<{ txHash: `0x${string}` }>
You can only cancel a refund request if:
- You are the original requester (payer)
- The request status is still
Pending
hasRefundRequest
Check whether a refund request exists for a given payment and nonce.
const hasRequest = await client.hasRefundRequest(paymentInfo, 0n);
if (hasRequest) {
console.log('Refund request already exists');
} else {
console.log('No refund request yet - can submit one');
}
Signature
hasRefundRequest(
paymentInfo: PaymentInfo,
nonce: bigint
): Promise<boolean>
getRefundStatus
Get the current status of a refund request. Returns a RequestStatus enum value.
import { RequestStatus } from '@x402r/core';
const status = await client.getRefundStatus(paymentInfo, 0n);
switch (status) {
case RequestStatus.Pending: // 0
console.log('Waiting for merchant/arbiter decision');
break;
case RequestStatus.Approved: // 1
console.log('Refund approved');
break;
case RequestStatus.Denied: // 2
console.log('Refund denied');
break;
case RequestStatus.Cancelled: // 3
console.log('Refund was cancelled by payer');
break;
}
Signature
getRefundStatus(
paymentInfo: PaymentInfo,
nonce: bigint
): Promise<RequestStatus>
getRefundRequest
Get the full refund request data including amount, status, payment hash, and nonce.
const request = await client.getRefundRequest(paymentInfo, 0n);
console.log('Payment hash:', request.paymentInfoHash);
console.log('Nonce:', request.nonce);
console.log('Amount:', request.amount);
console.log('Status:', request.status);
Signature
getRefundRequest(
paymentInfo: PaymentInfo,
nonce: bigint
): Promise<RefundRequestData>
The RefundRequestData type contains:
interface RefundRequestData {
paymentInfoHash: `0x${string}`;
nonce: bigint;
amount: bigint;
status: RequestStatus;
}
getMyRefundRequests
Get a paginated list of composite keys for all refund requests submitted by the connected wallet. Use these keys with getRefundRequestByKey to fetch full details.
const { keys, total } = await client.getMyRefundRequests(0n, 10n);
console.log(`Showing ${keys.length} of ${total} total refund requests`);
for (const key of keys) {
const request = await client.getRefundRequestByKey(key);
console.log(`Amount: ${request.amount}, Status: ${request.status}`);
}
Signature
getMyRefundRequests(
offset: bigint,
count: bigint
): Promise<{ keys: readonly `0x${string}`[]; total: bigint }>
Use offset and count for pagination. For example, to fetch page 2 with 10 items per page, use getMyRefundRequests(10n, 10n).
getMyRefundRequestCount
Get the total number of refund requests submitted by the connected wallet.
const count = await client.getMyRefundRequestCount();
console.log(`Total refund requests: ${count}`);
Signature
getMyRefundRequestCount(): Promise<bigint>
getRefundRequestByKey
Look up a refund request by its composite key. The composite key is keccak256(paymentInfoHash, nonce) and is returned by getMyRefundRequests.
const request = await client.getRefundRequestByKey(compositeKey);
console.log('Payment hash:', request.paymentInfoHash);
console.log('Nonce:', request.nonce);
console.log('Amount:', request.amount);
console.log('Status:', request.status);
Signature
getRefundRequestByKey(
compositeKey: `0x${string}`
): Promise<RefundRequestData>
Complete Example: Refund Request Flow
This example shows the full lifecycle: check if a request exists, submit a refund, then poll for status updates.
import { X402rClient } from '@x402r/client';
import { RequestStatus } from '@x402r/core';
import type { PaymentInfo } from '@x402r/core';
async function requestRefundWithTracking(
client: X402rClient,
paymentInfo: PaymentInfo,
refundAmount: bigint
) {
const nonce = 0n; // First charge
// Step 1: Check if a refund request already exists
const hasRequest = await client.hasRefundRequest(paymentInfo, nonce);
if (hasRequest) {
const existingStatus = await client.getRefundStatus(paymentInfo, nonce);
console.log(`Refund request already exists with status: ${existingStatus}`);
if (existingStatus === RequestStatus.Pending) {
console.log('Request is still pending - waiting for decision');
return;
}
if (existingStatus === RequestStatus.Denied) {
console.log('Previous request was denied');
return;
}
}
// Step 2: Submit the refund request
const { txHash } = await client.requestRefund(
paymentInfo,
refundAmount,
nonce
);
console.log(`Refund requested: ${txHash}`);
// Step 3: Watch for status updates
const { unsubscribe } = client.watchRefundRequests((event) => {
console.log(`Refund event: ${event.eventName}`);
if (event.eventName === 'RefundRequestStatusUpdated') {
const newStatus = event.args.status;
if (newStatus === RequestStatus.Approved) {
console.log('Refund approved - funds will be returned');
unsubscribe();
} else if (newStatus === RequestStatus.Denied) {
console.log('Refund denied by merchant or arbiter');
unsubscribe();
}
}
});
// Step 4: Alternatively, poll for status
const pollInterval = setInterval(async () => {
const status = await client.getRefundStatus(paymentInfo, nonce);
if (status !== RequestStatus.Pending) {
console.log(`Refund resolved with status: ${status}`);
clearInterval(pollInterval);
unsubscribe();
}
}, 10_000); // Poll every 10 seconds
}
Refund Request Lifecycle
Listing All Refund Requests
async function listAllRefundRequests(client: X402rClient) {
const total = await client.getMyRefundRequestCount();
console.log(`Total refund requests: ${total}`);
// Fetch in pages of 10
const pageSize = 10n;
let offset = 0n;
while (offset < total) {
const { keys } = await client.getMyRefundRequests(offset, pageSize);
for (const key of keys) {
const request = await client.getRefundRequestByKey(key);
console.log(
` Hash: ${request.paymentInfoHash}, ` +
`Amount: ${request.amount}, ` +
`Status: ${request.status}`
);
}
offset += pageSize;
}
}
Next Steps