Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"pages": [
"sdk/merchant/getting-started",
"sdk/helpers/refundable",
"sdk/helpers/forward-to-arbiter",
"sdk/merchant/quickstart",
"sdk/merchant/refund-handling"
]
Expand Down
140 changes: 140 additions & 0 deletions sdk/helpers/forward-to-arbiter.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
title: "forwardToArbiter()"
description: "Forward settlement data to an arbiter service for quality evaluation"
icon: "arrow-right"
---

The `forwardToArbiter()` function creates an `onAfterSettle` hook that forwards the response body and payment payload to an arbiter service. It runs fire-and-forget so it never blocks the response to the client.

- Only fires for successful **commerce** scheme settlements
- POSTs `{ responseBody, transaction, paymentPayload }` to `{arbiterUrl}/verify`
- Errors are silently caught so an unavailable arbiter cannot break the payment flow

## Usage

```typescript
import { forwardToArbiter } from '@x402r/helpers';

const resourceServer = new x402ResourceServer(facilitatorClient)
.register(networkId, new EscrowServerScheme())
.onAfterSettle(
forwardToArbiter('http://arbiter:3001')
);
```

## Function signature

```typescript
function forwardToArbiter(
arbiterUrl: string,
options?: ForwardToArbiterOptions
): (context: SettleResultContext) => Promise<void>
```

### 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;
}
```

## Payload shape

When a commerce settlement succeeds, the hook POSTs the following JSON to `{arbiterUrl}/verify`:

```typescript
{
responseBody: string; // UTF-8 encoded response body
transaction: string; // Settlement transaction hash
paymentPayload: {
x402Version: number;
accepted: {
scheme: string; // e.g. "commerce"
network: string; // e.g. "eip155:84532"
// ...other accepted fields
};
payload: {
paymentInfo: {
operator: string;
payer: string;
receiver: string;
// ...full PaymentInfo
};
};
};
}
```

<Tip>
Arbiters that need `paymentInfo` for `release()` can read it directly from `paymentPayload.payload.paymentInfo` — no extra RPC call needed.
</Tip>

## Error handling

By default, fetch errors are logged with `console.warn`. You can override this with a custom handler:

```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),
})
);
```

Errors are wrapped in an `X402rError` with the arbiter URL and request details for easier debugging.

## Skipped scenarios

The hook silently returns without making a request when:

- The settlement was not successful (`context.result.success === false`)
- The scheme is not `commerce` (e.g. direct or other custom schemes)
- No response body is available in the transport context

## Address re-exports

The `@x402r/helpers` package re-exports chain-invariant address constants from `@x402r/core` for convenience:

```typescript
import {
authCaptureEscrow,
tokenCollector,
protocolFeeConfig,
arbiterRegistry,
receiverRefundCollector,
usdcTvlLimit,
factories,
conditions,
getChainConfig,
supportedChainIds,
} from '@x402r/helpers';
```

These are the same CREATE3 addresses available from `@x402r/core`. You can import from either package depending on which you already have installed.

## Next steps

<CardGroup cols={2}>
<Card title="refundable()" icon="wrench" href="/sdk/helpers/refundable">
Configure escrow options and fee bounds.
</Card>
<Card title="Arbiter SDK" icon="gavel" href="/sdk/arbiter/quickstart">
Build an arbiter that processes forwarded settlements.
</Card>
<Card title="Examples" icon="code" href="/sdk/examples">
See working merchant server examples.
</Card>
</CardGroup>
3 changes: 3 additions & 0 deletions sdk/helpers/refundable.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ app.get('/api/resource', (req, res) => {
## Next Steps

<CardGroup cols={2}>
<Card title="forwardToArbiter()" icon="arrow-right" href="/sdk/helpers/forward-to-arbiter">
Forward settlement data to an arbiter for evaluation.
</Card>
<Card title="Deploy Operator" icon="rocket" href="/sdk/deploy-operator">
Deploy a PaymentOperator to use with refundable().
</Card>
Expand Down
43 changes: 34 additions & 9 deletions sdk/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,43 @@ Create `publicClient` and `walletClient` using [viem](https://viem.sh/docs/clien

## Contract Addresses

Get the deployed contract addresses from the network config:
All protocol contracts use CREATE3 addresses that are the same on every supported chain. You can import them directly as named constants or look them up from a chain config:

```typescript
import { getNetworkConfig } from '@x402r/core';
<Tabs>
<Tab title="Named constants">
```typescript
import {
authCaptureEscrow,
tokenCollector,
protocolFeeConfig,
arbiterRegistry,
receiverRefundCollector,
usdcTvlLimit,
factories,
conditions,
} from '@x402r/core';

console.log(authCaptureEscrow); // 0xBC15…
console.log(factories.paymentOperator); // 0x3Cd5…
console.log(conditions.payer); // 0x808b…
```
</Tab>
<Tab title="Chain config">
```typescript
import { getChainConfig } from '@x402r/core';

const config = getNetworkConfig('eip155:84532'); // Base Sepolia
const config = getChainConfig(84532); // Base Sepolia

console.log(config.authCaptureEscrow); // Escrow contract
console.log(config.arbiterRegistry); // ArbiterRegistry contract
console.log(config.usdc); // USDC token address (chain-specific)
```
</Tab>
</Tabs>

console.log(config.authCaptureEscrow); // Escrow contract
console.log(config.refundRequest); // RefundRequest contract
console.log(config.arbiterRegistry); // ArbiterRegistry contract
console.log(config.usdc); // USDC token address
```
<Tip>
The named constants and `getChainConfig()` return identical addresses. Use named constants when you don't need the chain-specific USDC address.
</Tip>

<Note>
Network identifiers use the [EIP-155](https://eips.ethereum.org/EIPS/eip-155) format: `eip155:<chainId>`. For Base Sepolia, use `'eip155:84532'`. For Base Mainnet, use `'eip155:8453'`.
Expand Down
2 changes: 1 addition & 1 deletion sdk/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The SDK is organized into packages designed for specific roles in the payment ec
SDK for arbiters to resolve disputes and manage refund decisions.
</Card>
<Card title="@x402r/helpers" icon="wrench" href="/sdk/helpers/refundable">
Framework-agnostic helper to mark x402 payment options as refundable with escrow configuration.
Framework-agnostic helpers: `refundable()` for escrow configuration, `forwardToArbiter()` for arbiter integration, and re-exported address constants.
</Card>
</CardGroup>

Expand Down
Loading