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.