diff --git a/docs.json b/docs.json
index 3ae8129..d9c2da8 100644
--- a/docs.json
+++ b/docs.json
@@ -101,7 +101,7 @@
"group": "Merchant",
"pages": [
"sdk/merchant/getting-started",
- "sdk/helpers/refundable",
+ "sdk/helpers/forward-to-arbiter",
"sdk/merchant/quickstart",
"sdk/merchant/refund-handling"
]
diff --git a/sdk/deploy-operator.mdx b/sdk/deploy-operator.mdx
index a97990b..e0dae0b 100644
--- a/sdk/deploy-operator.mdx
+++ b/sdk/deploy-operator.mdx
@@ -225,8 +225,8 @@ Deployment requires gas fees. Ensure your wallet has ETH on the target network.
See working merchant and client examples.
-
- Mark payment options as refundable with your operator.
+
+ Forward escrow settlements to an arbiter service.
Understand the underlying contract architecture.
diff --git a/sdk/examples.mdx b/sdk/examples.mdx
index 44df86f..bedb02c 100644
--- a/sdk/examples.mdx
+++ b/sdk/examples.mdx
@@ -16,10 +16,10 @@ The SDK includes working examples in the `x402r-sdk/` repository. Each is a stan
Deploy a complete marketplace operator with escrow, freeze, and arbiter support.
- Express merchant server using `EscrowServerScheme`, `HTTPFacilitatorClient`, and `refundable()` to accept escrow payments via x402 middleware.
+ Express merchant server using `EscrowServerScheme` and `HTTPFacilitatorClient` to accept escrow payments via x402 middleware.
- Hono merchant server using `EscrowServerScheme`, `HTTPFacilitatorClient`, and `refundable()` to accept escrow payments via x402 middleware.
+ Hono merchant server using `EscrowServerScheme` and `HTTPFacilitatorClient` to accept escrow payments via x402 middleware.
CLI tool for merchants to release payments, approve/deny refunds, and query escrow state.
@@ -92,9 +92,9 @@ See [Deploy an Operator](/sdk/deploy-operator) for the full guide.
## Server Examples
-Demonstrates minimal merchant servers (Express and Hono variants) that use `EscrowServerScheme`, `HTTPFacilitatorClient`, and `refundable()` via x402's standard middleware:
+Demonstrates minimal merchant servers (Express and Hono variants) that use `EscrowServerScheme` and `HTTPFacilitatorClient` via x402's standard middleware:
-1. Returns 402 with `refundable()` payment options
+1. Returns 402 with inline escrow payment options
2. Delegates payment verification to the facilitator via `HTTPFacilitatorClient`
3. Delegates on-chain settlement to the facilitator after the handler runs
4. Returns weather data after successful payment
@@ -105,8 +105,8 @@ Demonstrates minimal merchant servers (Express and Hono variants) that use `Escr
Deploy a PaymentOperator with escrow and freeze support.
-
- Mark payment options as refundable with escrow configuration.
+
+ Forward escrow settlements to an arbiter service.
Understand the payment lifecycle and key concepts.
diff --git a/sdk/helpers/forward-to-arbiter.mdx b/sdk/helpers/forward-to-arbiter.mdx
new file mode 100644
index 0000000..7984d2d
--- /dev/null
+++ b/sdk/helpers/forward-to-arbiter.mdx
@@ -0,0 +1,161 @@
+---
+title: "forwardToArbiter()"
+description: "Forward escrow settlement responses to an arbiter service for dispute evaluation"
+icon: "wrench"
+---
+
+The `@x402r/helpers` package provides `forwardToArbiter()` — an `onAfterSettle` hook that forwards the response body to an arbiter service after successful escrow settlements. It is fire-and-forget and does not block the client response.
+
+
+This function lives in `@x402r/helpers`. Install it separately:
+```bash
+npm install @x402r/helpers
+```
+
+Peer dependency: `@x402/core >=2.5.0`
+
+
+## Usage
+
+```typescript
+import { forwardToArbiter } from '@x402r/helpers';
+
+const resourceServer = new x402ResourceServer(facilitatorClient)
+ .register(networkId, new EscrowServerScheme())
+ .onAfterSettle(
+ forwardToArbiter('http://arbiter:3001')
+ );
+```
+
+After a successful escrow settlement, the hook POSTs `{ responseBody, network, transaction, scheme }` to `{arbiterUrl}/verify`. Non-escrow schemes, failed settlements, and missing response bodies are silently skipped.
+
+## Function signature
+
+```typescript
+function forwardToArbiter(
+ arbiterUrl: string,
+ options?: ForwardToArbiterOptions
+): (context: SettleResultContext) => Promise
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `arbiterUrl` | `string` | Base URL of your arbiter service (e.g., `http://arbiter:3001`) |
+| `options` | `ForwardToArbiterOptions` | Optional configuration (see below) |
+
+### Options
+
+```typescript
+interface ForwardToArbiterOptions {
+ /** Custom error handler. Defaults to `console.warn`. */
+ onError?: (error: unknown) => void;
+}
+```
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `onError` | `console.warn` | Called when the POST to the arbiter fails. Use this to integrate with your error tracking (e.g., Sentry). |
+
+### Return value
+
+Returns an async callback compatible with `onAfterSettle`. The callback:
+
+1. Skips if the settlement failed (`context.result.success === false`)
+2. Skips if the scheme is not `escrow`
+3. Skips if there is no response body in the transport context
+4. POSTs to `{arbiterUrl}/verify` with the settlement data
+5. Catches errors via the `onError` handler (default: `console.warn`)
+
+## Examples
+
+### Basic usage
+
+```typescript
+import { forwardToArbiter } from '@x402r/helpers';
+
+const resourceServer = new x402ResourceServer(facilitatorClient)
+ .register(networkId, new EscrowServerScheme())
+ .onAfterSettle(
+ forwardToArbiter('http://arbiter:3001')
+ );
+```
+
+### Custom error handling
+
+```typescript
+import { forwardToArbiter } from '@x402r/helpers';
+
+const resourceServer = new x402ResourceServer(facilitatorClient)
+ .register(networkId, new EscrowServerScheme())
+ .onAfterSettle(
+ forwardToArbiter('http://arbiter:3001', {
+ onError: (err) => sentry.captureException(err),
+ })
+ );
+```
+
+
+You should always provide an `onError` handler in production. The x402 `onAfterSettle` runner does not catch errors internally — without a handler, an unreachable arbiter could break your post-settlement flow.
+
+
+### POST payload
+
+The hook sends a JSON POST to `{arbiterUrl}/verify` with this shape:
+
+```json
+{
+ "responseBody": "",
+ "network": "eip155:84532",
+ "transaction": "0xabc...",
+ "scheme": "escrow"
+}
+```
+
+## Behavior details
+
+- **Escrow-only** — Only fires for `scheme: "escrow"` settlements. Non-escrow schemes (e.g., `exact`) are silently skipped.
+- **Fire-and-forget** — The POST happens asynchronously and does not block the HTTP response to the client.
+- **Trailing slash safe** — `forwardToArbiter('http://arbiter:3001/')` resolves to `http://arbiter:3001/verify`.
+
+## Migration from refundable()
+
+The `refundable()` helper has been removed. If you were using it to wrap payment options with escrow configuration, you should now inline the escrow `extra` config directly:
+
+```typescript
+// Before (removed)
+import { refundable } from '@x402r/helpers';
+const option = refundable(
+ { scheme: 'escrow', price: '$0.01', network: 'eip155:84532', payTo: address },
+ operatorAddress
+);
+
+// After — inline the extra config
+const option = {
+ scheme: 'escrow',
+ network: 'eip155:84532',
+ price: '$0.01',
+ payTo: address,
+ extra: {
+ escrowAddress: authCaptureEscrow, // from getChainConfig()
+ operatorAddress,
+ feeReceiver: operatorAddress,
+ maxFeeBps: 500,
+ },
+};
+```
+
+## Next steps
+
+
+
+ Build an arbiter service that receives forwarded settlements.
+
+
+ Deploy a PaymentOperator for escrow payments.
+
+
+ See merchant and arbiter examples.
+
+
diff --git a/sdk/helpers/refundable.mdx b/sdk/helpers/refundable.mdx
deleted file mode 100644
index 1d5fbfe..0000000
--- a/sdk/helpers/refundable.mdx
+++ /dev/null
@@ -1,167 +0,0 @@
----
-title: "refundable()"
-description: "Configure HTTP 402 responses with escrow-backed refundable payment options"
-icon: "wrench"
----
-
-The `@x402r/helpers` package provides the `refundable()` function — a framework-agnostic helper that adds escrow configuration to x402 payment options.
-
-
-This function lives in `@x402r/helpers`, not `@x402r/merchant`. Install it separately:
-```bash
-npm install @x402r/helpers @x402r/core
-```
-
-
-## Usage
-
-```typescript
-import { refundable } from '@x402r/helpers';
-
-const option = refundable(
- {
- scheme: 'escrow',
- network: 'eip155:84532',
- payTo: '0xMerchantAddress...',
- price: '$0.01',
- },
- '0xOperatorAddress...'
-);
-```
-
-This returns the original payment option with an `extra` field populated with escrow addresses and fee bounds.
-
-## Function Signature
-
-```typescript
-function refundable(
- option: T,
- operatorAddress: `0x${string}`,
- options?: RefundableOptions
-): T & { extra: EscrowExtra }
-```
-
-### Parameters
-
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `option` | `PaymentOption` | Base payment option (must include `network`) |
-| `operatorAddress` | `Address` | Your PaymentOperator contract address |
-| `options` | `RefundableOptions` | Optional overrides (see below) |
-
-### Options & Defaults
-
-| Option | Default | Description |
-|--------|---------|-------------|
-| `escrowAddress` | From network config | AuthCaptureEscrow contract address |
-| `tokenCollector` | From network config | ERC3009PaymentCollector contract address |
-| `minFeeBps` | `0` | Minimum acceptable fee (0% = accept zero fees) |
-| `maxFeeBps` | `1000` | Maximum acceptable fee (1000 bps = 10%) |
-
-### Return Value
-
-The function returns the original option object with an `extra` field added:
-
-```typescript
-interface EscrowExtra {
- escrowAddress: `0x${string}`;
- operatorAddress: `0x${string}`;
- tokenCollector: `0x${string}`;
- minFeeBps: number;
- maxFeeBps: number;
-}
-```
-
-## Examples
-
-### Basic usage (defaults)
-
-```typescript
-const option = refundable({
- scheme: 'escrow',
- network: 'eip155:84532',
- payTo: '0xMerchant...',
- price: '$0.01',
-}, '0xOperator...');
-
-// Result includes:
-// option.extra.escrowAddress → from network config
-// option.extra.operatorAddress → '0xOperator...'
-// option.extra.tokenCollector → from network config
-// option.extra.minFeeBps → 0
-// option.extra.maxFeeBps → 1000
-```
-
-### Custom fee bounds
-
-```typescript
-const option = refundable({
- scheme: 'escrow',
- network: 'eip155:84532',
- payTo: '0xMerchant...',
- price: '$10.00',
-}, '0xOperator...', {
- maxFeeBps: 500, // Accept up to 5% fee
-});
-```
-
-### Custom escrow address
-
-```typescript
-const option = refundable({
- scheme: 'escrow',
- network: 'eip155:84532',
- payTo: '0xMerchant...',
- price: '$100.00',
-}, '0xOperator...', {
- escrowAddress: '0xCustomEscrow...',
- tokenCollector: '0xCustomCollector...',
-});
-```
-
-## Supported Networks
-
-The function resolves addresses from the network config for all supported networks. See `getNetworkConfig()` for the full list (Base Sepolia, Base, Ethereum, Ethereum Sepolia, Arbitrum Sepolia, Polygon, Arbitrum, Optimism, Avalanche, Celo, Monad).
-
-```typescript
-refundable({ network: 'eip155:84532', ... }, '0x...'); // Base Sepolia
-refundable({ network: 'eip155:8453', ... }, '0x...'); // Base Mainnet
-refundable({ network: 'eip155:1', ... }, '0x...'); // Ethereum
-```
-
-## Integration with x402
-
-Use `refundable()` when constructing your 402 Payment Required response:
-
-```typescript
-import { refundable } from '@x402r/helpers';
-
-// In your server handler:
-app.get('/api/resource', (req, res) => {
- res.status(402).json({
- x402Version: 2,
- accepts: [
- refundable({
- scheme: 'escrow',
- network: 'eip155:84532',
- payTo: merchantAddress,
- price: '$1.00',
- }, operatorAddress),
- ],
- });
-});
-```
-
-## Next Steps
-
-
-
- Deploy a PaymentOperator to use with refundable().
-
-
- See merchant server examples using refundable().
-
-
- Understand the escrow scheme.
-
-
diff --git a/sdk/limitations.mdx b/sdk/limitations.mdx
index 7e3a23c..0d180c9 100644
--- a/sdk/limitations.mdx
+++ b/sdk/limitations.mdx
@@ -44,7 +44,7 @@ const details = await client.getPaymentDetails(hash, recentBlockNumber);
### No Express/Hono Middleware
-The `refundable()` helper in `@x402r/helpers` is framework-agnostic. There is no dedicated Express or Hono middleware — use `refundable()` directly when constructing payment options.
+The `forwardToArbiter()` hook in `@x402r/helpers` is framework-agnostic. There is no dedicated Express or Hono middleware — configure escrow payment options inline and use `forwardToArbiter()` as an `onAfterSettle` hook.
## Getting Updates
diff --git a/sdk/merchant/getting-started.mdx b/sdk/merchant/getting-started.mdx
index 9c75abb..67b7676 100644
--- a/sdk/merchant/getting-started.mdx
+++ b/sdk/merchant/getting-started.mdx
@@ -24,7 +24,7 @@ The full source code for this example is available on [GitHub](https://github.co
```bash
mkdir merchant-server && cd merchant-server
npm init -y
- npm install express @x402/core @x402/express @x402r/evm @x402r/helpers dotenv
+ npm install express @x402/core @x402/express @x402r/evm dotenv
```
@@ -35,6 +35,7 @@ The full source code for this example is available on [GitHub](https://github.co
ADDRESS=0xYourMerchantAddress
OPERATOR_ADDRESS=0xYourPaymentOperatorAddress
FACILITATOR_URL=http://localhost:4022
+
```
@@ -46,7 +47,7 @@ The full source code for this example is available on [GitHub](https://github.co
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { EscrowServerScheme } from "@x402r/evm/escrow/server";
- import { refundable } from "@x402r/helpers";
+ import { getChainConfig } from "@x402r/core";
import { HTTPFacilitatorClient } from "@x402/core/server";
const address = process.env.ADDRESS as `0x${string}`;
@@ -63,6 +64,9 @@ The full source code for this example is available on [GitHub](https://github.co
}
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
+ const networkId = "eip155:84532";
+ const { authCaptureEscrow } = getChainConfig(networkId);
+
const app = express();
app.use(
@@ -70,22 +74,25 @@ The full source code for this example is available on [GitHub](https://github.co
{
"GET /weather": {
accepts: [
- refundable(
- {
- scheme: "escrow",
- price: "$0.01",
- network: "eip155:84532",
- payTo: address,
+ {
+ scheme: "escrow",
+ price: "$0.01",
+ network: networkId,
+ payTo: address,
+ extra: {
+ escrowAddress: authCaptureEscrow,
+ operatorAddress,
+ feeReceiver: operatorAddress,
+ maxFeeBps: 500,
},
- operatorAddress,
- ),
+ },
],
description: "Weather data",
mimeType: "application/json",
},
},
new x402ResourceServer(facilitatorClient).register(
- "eip155:84532",
+ networkId,
new EscrowServerScheme() as never,
),
),
@@ -132,9 +139,8 @@ The full source code for this example is available on [GitHub](https://github.co
"extra": {
"escrowAddress": "0x...",
"operatorAddress": "0x...",
- "tokenCollector": "0x...",
- "minFeeBps": 0,
- "maxFeeBps": 1000
+ "feeReceiver": "0x...",
+ "maxFeeBps": 500
}
}]
}
@@ -144,7 +150,7 @@ The full source code for this example is available on [GitHub](https://github.co
## How it works
-- **`refundable()`** wraps a standard x402 payment option with escrow configuration (contract addresses, fee bounds) from the network config.
+- **Escrow `extra` config** specifies the escrow contract address, operator address, fee receiver, and maximum fee bounds directly on the payment option.
- **`EscrowServerScheme`** registers the escrow payment scheme with the x402 resource server so it can validate escrow-backed payments.
- **`paymentMiddleware`** intercepts requests, checks for a valid payment header, and returns 402 if no payment is provided.
- **`HTTPFacilitatorClient`** connects to the facilitator service that verifies and settles payments on-chain.
@@ -152,8 +158,8 @@ The full source code for this example is available on [GitHub](https://github.co
## Next Steps
-
- Configure escrow options and fee bounds.
+
+ Forward escrow settlements to an arbiter service.
Release payments, handle refunds, and manage escrow.
diff --git a/sdk/merchant/payment-operations.mdx b/sdk/merchant/payment-operations.mdx
index e5bdd20..9f28e64 100644
--- a/sdk/merchant/payment-operations.mdx
+++ b/sdk/merchant/payment-operations.mdx
@@ -262,7 +262,7 @@ flowchart TD
Understand the underlying PaymentOperator contract methods.
-
- Mark payment options as refundable with your operator.
+
+ Forward escrow settlements to an arbiter service.
diff --git a/sdk/merchant/quickstart.mdx b/sdk/merchant/quickstart.mdx
index 4929d3f..d8d19b9 100644
--- a/sdk/merchant/quickstart.mdx
+++ b/sdk/merchant/quickstart.mdx
@@ -13,7 +13,7 @@ The `@x402r/merchant` package provides everything merchants need for the post-pa
## Installation
```bash
-npm install @x402r/merchant @x402r/helpers @x402r/core viem
+npm install @x402r/merchant @x402r/core viem
```
## Setup
@@ -232,8 +232,8 @@ flowchart TD
Process incoming refund requests with approve/deny workflows.
-
- Mark payment options as refundable with your operator.
+
+ Forward escrow settlements to an arbiter service.
Understand the underlying PaymentOperator contract methods.
diff --git a/sdk/overview.mdx b/sdk/overview.mdx
index c0e9ceb..1637dd9 100644
--- a/sdk/overview.mdx
+++ b/sdk/overview.mdx
@@ -27,8 +27,8 @@ The SDK is organized into packages designed for specific roles in the payment ec
SDK for arbiters to resolve disputes and manage refund decisions.
-
- Framework-agnostic helper to mark x402 payment options as refundable with escrow configuration.
+
+ `onAfterSettle` hook to forward escrow settlement responses to an arbiter service.