From 18bf6d320e46a56afa1ef72d783e37adf85d845a Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 16:56:20 -0700 Subject: [PATCH 01/23] Rewrite V2 SDK Getting Started pages with editorial voice Replace mechanical AI-generated pages with docs that lead with motivation, use real addresses, and vary structure per page type: - overview: landing page with x402 vs x402r contrast - installation: shortest page, straight to install command - create-client: decision tree between full client and role presets - typescript: reference sheet with role narrowing tables Follows DOCS_WRITING_GUIDE.md anti-slop rules. All signatures verified against types.ts. Broken-links check passes. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 6 +- sdk/create-client.mdx | 167 ++++++++++++++++++++++++++++++++++++++++++ sdk/installation.mdx | 146 +++++++++++++++++++++++------------- sdk/overview.mdx | 120 +++++++++++++++++++++--------- sdk/typescript.mdx | 148 +++++++++++++++++++++++++++++++++++++ 5 files changed, 501 insertions(+), 86 deletions(-) create mode 100644 sdk/create-client.mdx create mode 100644 sdk/typescript.mdx diff --git a/docs.json b/docs.json index 3ae8129..10377c2 100644 --- a/docs.json +++ b/docs.json @@ -91,10 +91,10 @@ "pages": [ "sdk/overview", "sdk/installation", - "sdk/concepts", + "sdk/create-client", + "sdk/typescript", "sdk/deploy-operator", - "sdk/examples", - "sdk/limitations" + "sdk/examples" ] }, { diff --git a/sdk/create-client.mdx b/sdk/create-client.mdx new file mode 100644 index 0000000..7ed47b0 --- /dev/null +++ b/sdk/create-client.mdx @@ -0,0 +1,167 @@ +--- +title: "Create a Client" +description: "Choose between the full client and role presets, then configure" +icon: "plug" +--- + +The SDK creates clients two ways: `createX402r()` gives you every action group with no type restrictions. The role presets — `createPayerClient()`, `createMerchantClient()`, `createArbiterClient()` — narrow TypeScript types so you only see methods relevant to your role. + +**Use a role preset** when your application has a single role. Most apps fall here. +**Use `createX402r()`** when you need multi-role access (e.g., a backend that authorizes payments *and* resolves disputes), read-only access without a `walletClient`, or admin tooling. + +## Role Presets + +### Payer + +```typescript +import { createPayerClient } from '@x402r/sdk' + +const payer = createPayerClient({ + publicClient, + walletClient, + operatorAddress: '0x...', // from deployMarketplaceOperator() + refundRequestAddress: '0x...', // from deploy result + escrowPeriodAddress: '0x...', // from deploy result + freezeAddress: '0x...', // from deploy result +}) + +const { capturableAmount } = await payer.payment.getAmounts(paymentInfo) +await payer.refund?.request(paymentInfo, capturableAmount) +``` + +Autocomplete shows `refund.request()` and `freeze.freeze()` but not `payment.authorize()` — payers don't authorize payments, merchants do. + +### Merchant + +```typescript +import { createMerchantClient } from '@x402r/sdk' + +const merchant = createMerchantClient({ + publicClient, + walletClient, + operatorAddress: '0x...', + escrowPeriodAddress: '0x...', +}) + +const inEscrow = await merchant.escrow?.isDuringEscrow(paymentInfo) +if (!inEscrow) { + await merchant.payment.release(paymentInfo, amount) +} +``` + +Shows `payment.release()`, `payment.charge()`, and `payment.authorize()` but not `refund.request()`. + +### Arbiter + +```typescript +import { createArbiterClient } from '@x402r/sdk' + +const arbiter = createArbiterClient({ + publicClient, + walletClient, + operatorAddress: '0x...', + refundRequestAddress: '0x...', + refundRequestEvidenceAddress: '0x...', + freezeAddress: '0x...', +}) + +await arbiter.payment.refundInEscrow(paymentInfo, amount) +// RefundRequest recorder auto-approves — no separate approve() call needed +``` + +Shows `refund.deny()`, `refund.refuse()`, `freeze.unfreeze()`, and `evidence.submit()`. Does not show `payment.authorize()` or `payment.charge()`. + + +All three presets require `walletClient`. Omit it and you get `ValidationError: walletClient is required for createPayerClient`. For read-only access, use `createX402r()` without a `walletClient`. + + +## Full Client + +```typescript +import { createX402r } from '@x402r/sdk' + +const client = createX402r({ + publicClient, + walletClient, // optional — omit for read-only + operatorAddress: '0x...', + refundRequestAddress: '0x...', + refundRequestEvidenceAddress: '0x...', + escrowPeriodAddress: '0x...', + freezeAddress: '0x...', + paymentIndexRecorderAddress: '0x...', +}) +``` + +Every action group is available (subject to addresses being provided). No type narrowing. + +## Config Reference + +| Field | Type | Required | Notes | +|-------|------|:--------:|-------| +| `publicClient` | `PublicClient` | Yes | viem public client for reads | +| `walletClient` | `WalletClient` | No | Required for writes. Role presets throw without it. | +| `operatorAddress` | `Address` | Yes | Your deployed PaymentOperator. From `deployMarketplaceOperator()`. | +| `chainId` | `number` | No | Auto-detected from `publicClient.chain`. Override for chains without viem definitions. | +| `network` | `string` | No | CAIP-2 fallback (`'eip155:84532'`). Used only if `chainId` is absent. | +| `refundRequestAddress` | `Address` | No | Activates `refund` group | +| `refundRequestEvidenceAddress` | `Address` | No | Activates `evidence` group. Requires `refundRequestAddress`. | +| `escrowPeriodAddress` | `Address` | No | Activates `escrow` group | +| `freezeAddress` | `Address` | No | Activates `freeze` group | +| `paymentIndexRecorderAddress` | `Address` | No | Activates `query` group | +| `paymentStore` | `PaymentStore` | No | Pluggable payment store for `query` lookups | +| `eventFromBlock` | `bigint` | No | Starting block for event-based payment lookups. Set to operator deploy block. | + +The optional address fields (`escrowPeriodAddress`, `freezeAddress`, etc.) are per-operator — they come from your deploy result, not from chain config. Omit any you didn't deploy and the corresponding action group is `undefined` on the client. + +## Check Conditions + +```typescript +const allowed = await client.canExecute('release', paymentInfo, amount) +``` + +Calls `ICondition.check()` on the condition configured for that slot. Returns `true` if no condition is set (zero address). Check before submitting transactions to avoid reverts. + +## Payment Store + +```typescript +import { createX402r, createMemoryStore } from '@x402r/sdk' + +const client = createX402r({ + publicClient, + operatorAddress: '0x...', + paymentStore: createMemoryStore(), +}) +``` + +`createMemoryStore()` is an in-memory `PaymentStore` for development. Data is lost when the process exits. For production, implement the `PaymentStore` interface with your database. + +## Extend + +Add custom action groups or fill undefined slots with `.extend()`: + +```typescript +import { createX402r, queryActions } from '@x402r/sdk' + +const client = createX402r({ publicClient, operatorAddress: '0x...' }) + +// query group was undefined because no paymentIndexRecorderAddress was provided +const extended = client.extend( + queryActions('0xRecorderAddress...', { eventFromBlock: 100000n }) +) + +// extended.query is now defined +const payments = await extended.query.getPayerPayments(payerAddress) +``` + +Built-in properties cannot be overridden by extensions. Extensions can only fill slots that are `undefined` on the base client. + +## Next Steps + + + + Role narrowing details, type imports, and error types. + + + Get operator and condition addresses for your config. + + diff --git a/sdk/installation.mdx b/sdk/installation.mdx index 7485080..1220b2e 100644 --- a/sdk/installation.mdx +++ b/sdk/installation.mdx @@ -1,70 +1,116 @@ --- title: "Installation" -description: "Install and configure the X402r SDK packages" +description: "Install the x402r SDK and set up viem clients" icon: "download" --- -## Install Packages - -Install only the packages you need for your use case: - - - - ```bash - npm install @x402r/client @x402r/core viem - ``` - - - ```bash - npm install @x402r/merchant @x402r/helpers @x402r/core viem - ``` - - - ```bash - npm install @x402r/arbiter @x402r/core viem - ``` - - - ```bash - npm install @x402r/helpers @x402r/core viem - ``` - - - -## Setup viem Clients - -Create `publicClient` and `walletClient` using [viem](https://viem.sh/docs/clients/public). All SDK classes require these as constructor arguments. - -## Contract Addresses - -Get the deployed contract addresses from the network config: + +```bash npm +npm install @x402r/sdk viem +``` +```bash pnpm +pnpm add @x402r/sdk viem +``` +```bash bun +bun add @x402r/sdk viem +``` + + +This installs the full SDK and [viem](https://viem.sh), the only peer dependency. + + + Low-level access to types, ABIs, and deploy utilities without the client factory: + + + ```bash npm + npm install @x402r/core viem + ``` + ```bash pnpm + pnpm add @x402r/core viem + ``` + ```bash bun + bun add @x402r/core viem + ``` + + + x402 server integration — marks payment options as refundable: + + + ```bash npm + npm install @x402r/helpers @x402r/evm + ``` + ```bash pnpm + pnpm add @x402r/helpers @x402r/evm + ``` + ```bash bun + bun add @x402r/helpers @x402r/evm + ``` + + + + `@x402r/evm` is a peer dependency of `@x402r/helpers`. It provides escrow scheme types used by the `refundable()` helper. + + + +## Set Up viem Clients + +The SDK operates through viem's `PublicClient` (reads) and `WalletClient` (writes). Create both for your target chain: ```typescript -import { getNetworkConfig } from '@x402r/core'; +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' -const config = getNetworkConfig('eip155:84532'); // Base Sepolia +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) -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 -``` +const publicClient = createPublicClient({ + chain: baseSepolia, + transport: http(), +}) - -Network identifiers use the [EIP-155](https://eips.ethereum.org/EIPS/eip-155) format: `eip155:`. For Base Sepolia, use `'eip155:84532'`. For Base Mainnet, use `'eip155:8453'`. - +const walletClient = createWalletClient({ + account, + chain: baseSepolia, + transport: http(), +}) +``` -Never commit private keys to version control. Use environment variables or a secrets manager. +Never commit private keys. Use environment variables or a secrets manager. +For custom transports, batch config, or other client options, see the [viem client docs](https://viem.sh/docs/clients/public). + +## Get Chain Config + +```typescript +import { getChainConfig } from '@x402r/sdk' + +const config = getChainConfig(84532) // Base Sepolia +``` + +Returns protocol addresses, USDC, factory addresses, and condition singletons for the chain: + +```typescript +config.usdc // 0x036CbD53842c5426634e7929541eC2318f3dCF7e +config.authCaptureEscrow // escrow contract (same on every chain) +config.factories // 12 factory addresses (same on every chain) +config.conditions // 3 condition singletons (same on every chain) +``` + +USDC is the only address that differs between chains. Everything else is identical via CREATE3. + + +V2 uses numeric chain IDs (`84532`). The CAIP-2 format (`'eip155:84532'`) is only needed for `toNetworkId()` / `fromNetworkId()` interop. + + ## Next Steps - - Learn about payment states, escrow, and the refund lifecycle. + + Pick between `createX402r()` and role presets. - - Working examples for merchants, clients, and arbiters. + + Type imports, role narrowing, and error types. diff --git a/sdk/overview.mdx b/sdk/overview.mdx index c0e9ceb..95edb9e 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -1,49 +1,103 @@ --- title: "SDK Overview" -description: "TypeScript SDK for the X402r refundable payments protocol (experimental)" +description: "TypeScript SDK for the x402r refundable payments protocol" icon: "cube" --- - -The X402r SDK is in active development (v0.0.2). APIs may change between releases. Always test on Base Sepolia before using real funds on mainnet. - +x402 handles instant payments. x402r adds what happens *after* payment: escrow holds, partial captures, refund requests, dispute resolution, and freeze mechanics. If you need refund windows or arbitrated disputes on top of x402, this is the SDK. -The X402r SDK provides a complete TypeScript implementation for integrating with the X402r refundable payments protocol. It enables clients, merchants, and arbiters to interact with smart contracts for payment authorization, escrow management, and dispute resolution. +```typescript +import { createMerchantClient } from '@x402r/sdk' -## Packages +const merchant = createMerchantClient({ + publicClient, + walletClient, + operatorAddress: '0x...', // from deployMarketplaceOperator() — see Deploy an Operator guide +}) + +await merchant.payment.release(paymentInfo, amount) +``` -The SDK is organized into packages designed for specific roles in the payment ecosystem: +Three lines to set up, one line to release escrowed funds. The client narrows TypeScript types to merchant-relevant methods — `refund.request()` won't appear in autocomplete because merchants don't request refunds. Payers do. + +## Packages - - Shared types, ABIs, network config, deploy utilities, and condition builders. + + Client factory, role presets, 8 action groups, event watchers, and `.extend()` plugins. The only package most developers need. - - SDK for payers to request refunds, freeze payments, and manage escrow. + + Types, ABIs, chain config, deploy utilities, and low-level viem actions. Installed automatically as a dependency of `@x402r/sdk`. - - SDK for merchants to release payments, charge, and handle refunds. + + `refundable()` helper for marking x402 payment options as refundable. One function that attaches escrow config to a standard x402 payment request. - - SDK for arbiters to resolve disputes and manage refund decisions. + + +## Action Groups + +Every method on the client belongs to an action group. Three groups are always available; the rest activate when you pass the corresponding contract address to the config. + +| Group | Methods | Requirement | +|-------|---------|-------------| +| `payment` | 9 | Always available — authorize, charge, release, refund in/post escrow, query state | +| `operator` | 8 | Always available — fee calculation, slot config, fee distribution | +| `watch` | 4 | Always available — subscribe to payment, refund, and fee events | +| `escrow` | 3 | `escrowPeriodAddress` — check escrow status, authorization time, duration | +| `refund` | 15 | `refundRequestAddress` — request, cancel, deny, refuse, query refund state | +| `evidence` | 4 | `refundRequestEvidenceAddress` — submit and query IPFS evidence CIDs | +| `freeze` | 3 | `freezeAddress` — freeze, unfreeze, check frozen status | +| `query` | 3 | `paymentIndexRecorderAddress` — look up payments by payer or receiver | + +Skip addresses you don't need. If your operator has no freeze contract, omit `freezeAddress` — the `freeze` group won't exist on your client type and TypeScript will enforce that. + +## Role Presets + +Three factory functions narrow the client type to a specific role: + +- **`createPayerClient()`** — query payment state, request refunds, freeze payments, submit evidence +- **`createMerchantClient()`** — authorize, charge, release, handle post-escrow refunds, read operator config +- **`createArbiterClient()`** — deny/refuse refund requests, unfreeze, review evidence, distribute fees + +Autocomplete only shows methods your role can call. This is a DX convenience, not a security boundary — on-chain [conditions](/contracts/conditions/overview) enforce access control regardless of which client you use. + +## Supported Chains + +All protocol contracts are deployed via CREATE3 to identical addresses on every chain. Only the USDC address differs. + +### Testnets + +| Chain | Chain ID | USDC | +|-------|----------|------| +| Base Sepolia | `84532` | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | +| Ethereum Sepolia | `11155111` | `0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238` | +| Arbitrum Sepolia | `421614` | `0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d` | + +### Mainnets + +| Chain | Chain ID | USDC | +|-------|----------|------| +| Ethereum | `1` | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | +| Base | `8453` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | +| Polygon | `137` | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` | +| Arbitrum One | `42161` | `0xaf88d065e77c8cC2239327C5EDb3A432268e5831` | +| Optimism | `10` | `0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85` | +| Celo | `42220` | `0xcebA9300f2b948710d2653dD7B07f33A8B32118C` | +| Avalanche C-Chain | `43114` | `0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E` | +| Monad | `143` | `0x754704Bc059F8C67012fEd69BC8a327a5aafb603` | +| Linea | `59144` | `0x176211869cA2b568f2A7D4EE941E073a821EE1ff` | + + +Base Sepolia is the primary testnet and the most battle-tested deployment. Mainnet contracts are live but have seen less production traffic. Run integration tests on your target chain before handling real funds. + + +## Next Steps + + + + Install packages and set up viem clients. - - Framework-agnostic helper to mark x402 payment options as refundable with escrow configuration. + + Pick between the full client and role presets. - -## Network Support - -| Network | Chain ID | Status | -|---------|----------|--------| -| Base Sepolia | 84532 | Tested | -| Base Mainnet | 8453 | Deployed, not yet tested | -| Ethereum | 1 | Deployed, not yet tested | -| Ethereum Sepolia | 11155111 | Deployed, not yet tested | -| Arbitrum Sepolia | 421614 | Deployed, not yet tested | -| Polygon | 137 | Deployed, not yet tested | -| Arbitrum | 42161 | Deployed, not yet tested | -| Optimism | 10 | Deployed, not yet tested | -| Avalanche | 43114 | Deployed, not yet tested | -| Celo | 42220 | Deployed, not yet tested | -| Monad | 143 | Deployed, not yet tested | diff --git a/sdk/typescript.mdx b/sdk/typescript.mdx new file mode 100644 index 0000000..79669e6 --- /dev/null +++ b/sdk/typescript.mdx @@ -0,0 +1,148 @@ +--- +title: "TypeScript" +description: "Type imports, role narrowing tables, and error types" +icon: "code" +--- + +TypeScript 5.0+ with `strict: true` and `moduleResolution: "bundler"` or `"node16"`. + +## Type Imports + +Client types and action group interfaces from `@x402r/sdk`: + +```typescript +import type { + X402r, + X402rConfig, + PayerClient, + MerchantClient, + ArbiterClient, + PaymentActions, + RefundActions, + EscrowActions, + EvidenceActions, + FreezeActions, + QueryActions, + OperatorActions, + WatchActions, + PaymentStore, + ResolvedConfig, +} from '@x402r/sdk' +``` + +Domain types re-exported from `@x402r/core`: + +```typescript +import type { + PaymentInfo, + PaymentAmounts, + ConditionSlot, + OperatorSlots, + FeeAddresses, + FeeCalculationResult, + RefundRequestData, + EvidenceEntry, + X402rChainConfig, +} from '@x402r/sdk' // re-exported — no need to depend on @x402r/core directly +``` + +## Role Narrowing + +Each role preset restricts which methods appear in autocomplete via TypeScript `Pick` types. The runtime client is identical — narrowing is a DX convenience only, not a security boundary. + +### PayerClient + +| Group | Available Methods | +|-------|-------------------| +| `payment` | `getState`, `getAmounts` | +| `refund` | `request`, `cancel`, `get`, `getByKey`, `getStatus`, `has`, `getStoredPaymentInfo`, `getPayerRequests`, `getCancelCount`, `getCancelledAmount` | +| `freeze` | `freeze`, `isFrozen` | +| `evidence` | All | +| `escrow` | All | +| `query` | All | +| `operator` | `getConfig`, `getFeeAddresses` | +| `watch` | All | + +### MerchantClient + +| Group | Available Methods | +|-------|-------------------| +| `payment` | `authorize`, `charge`, `release`, `refundInEscrow`, `refundPostEscrow`, `approvePostEscrowRefund`, `getPostEscrowRefundAllowance`, `getState`, `getAmounts` | +| `refund` | `get`, `getByKey`, `getStatus`, `has`, `getStoredPaymentInfo`, `getReceiverRequests`, `getCancelCount`, `getCancelledAmount` | +| `freeze` | `isFrozen` | +| `evidence` | All | +| `escrow` | All | +| `query` | All | +| `operator` | All | +| `watch` | All | + +### ArbiterClient + +| Group | Available Methods | +|-------|-------------------| +| `payment` | `refundInEscrow`, `getState`, `getAmounts` | +| `refund` | `deny`, `refuse`, `get`, `getByKey`, `getStatus`, `has`, `getStoredPaymentInfo`, `getOperatorRequests`, `getCancelCount`, `getCancelledAmount` | +| `freeze` | `unfreeze`, `isFrozen` | +| `evidence` | All | +| `escrow` | All | +| `query` | All | +| `operator` | `getConfig`, `getFeeAddresses`, `getAccumulatedProtocolFees`, `distributeFees` | +| `watch` | All | + + +A payer *could* use `createX402r()` and call `payment.authorize()`, but the on-chain condition would revert the transaction. Type narrowing prevents mistakes; [conditions](/contracts/conditions/overview) enforce access. + + +## Conditional Action Groups + +`escrow`, `refund`, `evidence`, `freeze`, and `query` are typed as `T | undefined`. They are `undefined` when the corresponding address is not in the config: + +```typescript +const client = createX402r({ + publicClient, + operatorAddress: '0x...', + // no escrowPeriodAddress +}) + +client.escrow // EscrowActions | undefined + +// Use optional chaining +const inEscrow = await client.escrow?.isDuringEscrow(paymentInfo) +``` + +This reflects the protocol: these contracts are periphery. Not every operator deploys all of them. If you know the address exists, provide it in the config and the group will be defined. + +`payment`, `operator`, and `watch` are always defined. + +## Error Types + +Three error classes, all extending `X402rError`: + +| Error | When | +|-------|------| +| `ValidationError` | Missing `walletClient` on a role preset. `refundRequestEvidenceAddress` without `refundRequestAddress`. Chain cannot be determined from config. | +| `ConfigError` | `chainId` is not in the supported chains list. | +| `ContractCallError` | A contract read or write call fails on-chain (revert, out of gas, etc.). | + +```typescript +import { ValidationError, ConfigError, ContractCallError } from '@x402r/sdk' + +try { + const payer = createPayerClient({ publicClient, operatorAddress: '0x...' }) +} catch (e) { + if (e instanceof ValidationError) { + // "walletClient is required for createPayerClient" + } +} +``` + +## Next Steps + + + + Get operator and condition addresses for your client config. + + + On-chain architecture, conditions, and recorders. + + From d08da77c2b48fbd8cc5e1ca11756cc32c7d63d91 Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 17:18:39 -0700 Subject: [PATCH 02/23] Remove V1 SDK pages from nav Hide stale V1 pages (Merchant, Client, Arbiter, Facilitator, Experimental groups) from the sidebar. Files remain on disk for the cleanup PR which will delete them and add redirects. SDK tab now shows only V2 Getting Started pages. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 46 +--------------------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/docs.json b/docs.json index 10377c2..7969149 100644 --- a/docs.json +++ b/docs.json @@ -92,51 +92,7 @@ "sdk/overview", "sdk/installation", "sdk/create-client", - "sdk/typescript", - "sdk/deploy-operator", - "sdk/examples" - ] - }, - { - "group": "Merchant", - "pages": [ - "sdk/merchant/getting-started", - "sdk/helpers/refundable", - "sdk/merchant/quickstart", - "sdk/merchant/refund-handling" - ] - }, - { - "group": "Client", - "pages": [ - "sdk/client/quickstart", - "sdk/client/payment-queries", - "sdk/client/escrow-management", - "sdk/client/refund-operations" - ] - }, - { - "group": "Arbiter", - "pages": [ - "sdk/arbiter/quickstart", - "sdk/arbiter/decision-submission", - "sdk/arbiter/registry" - ] - }, - { - "group": "Facilitator", - "pages": [ - "sdk/facilitator/getting-started" - ] - }, - { - "group": "Experimental", - "pages": [ - "sdk/client/subscriptions", - "sdk/merchant/subscriptions", - "sdk/arbiter/subscriptions", - "sdk/arbiter/batch-operations", - "sdk/arbiter/ai-integration" + "sdk/typescript" ] } ] From f743b457f8f55c49a6a60ac9671eb4dd3dd35b67 Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 17:25:10 -0700 Subject: [PATCH 03/23] Rewrite SDK docs as step-by-step quickstart tutorials MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace descriptive reference pages with code-heavy tutorials matching the x402 getting-started pattern: - overview: brief intro + links to quickstarts - merchant-quickstart: install → deploy → create client → authorize → release - payer-quickstart: create client → check state → request refund → freeze → evidence Numbered steps, mostly code, minimal prose between blocks. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 5 +- sdk/merchant-quickstart.mdx | 194 ++++++++++++++++++++++++++++++++++++ sdk/overview.mdx | 94 +++++------------ sdk/payer-quickstart.mdx | 146 +++++++++++++++++++++++++++ 4 files changed, 365 insertions(+), 74 deletions(-) create mode 100644 sdk/merchant-quickstart.mdx create mode 100644 sdk/payer-quickstart.mdx diff --git a/docs.json b/docs.json index 7969149..02153be 100644 --- a/docs.json +++ b/docs.json @@ -90,9 +90,8 @@ "group": "Getting Started", "pages": [ "sdk/overview", - "sdk/installation", - "sdk/create-client", - "sdk/typescript" + "sdk/merchant-quickstart", + "sdk/payer-quickstart" ] } ] diff --git a/sdk/merchant-quickstart.mdx b/sdk/merchant-quickstart.mdx new file mode 100644 index 0000000..1343a1e --- /dev/null +++ b/sdk/merchant-quickstart.mdx @@ -0,0 +1,194 @@ +--- +title: "Quickstart for Merchants" +description: "Deploy an operator, accept a payment into escrow, and release funds — step by step." +--- + +### Prerequisites + +* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) +* Node.js 18+ and npm +* USDC on Base Sepolia for testing ([faucet](https://faucet.circle.com/)) + +**Note**\ +There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [merchant examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/merchant) and full [scenario scripts](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios). + +### 1. Install Dependencies + +```bash +npm install @x402r/sdk @x402r/core viem +``` + +### 2. Create viem Clients + +```typescript +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) + +const publicClient = createPublicClient({ + chain: baseSepolia, + transport: http(), +}) + +const walletClient = createWalletClient({ + account, + chain: baseSepolia, + transport: http(), +}) +``` + +### 3. Deploy an Operator + +Every x402r payment flows through a `PaymentOperator` contract. Deploy one with the marketplace preset: + +```typescript +import { deployMarketplaceOperator } from '@x402r/core' + +const deployment = await deployMarketplaceOperator(walletClient, publicClient, { + chainId: 84532, + feeRecipient: account.address, // receives operator fees + arbiter: '0xYourArbiterAddress', // resolves disputes + escrowPeriodSeconds: 604_800n, // 7 days + freezeDurationSeconds: 604_800n, // 7 days (set 0 to disable freeze) + operatorFeeBps: 50n, // 0.5% +}) + +console.log('Operator:', deployment.operatorAddress) +console.log('EscrowPeriod:', deployment.escrowPeriodAddress) +console.log('RefundRequest:', deployment.refundRequestAddress) +console.log('Freeze:', deployment.freezeAddress) +``` + +This deploys a `PaymentOperator` with escrow, freeze, and dispute resolution contracts. Save these addresses — you need them to create SDK clients. + +### 4. Create a Merchant Client + +```typescript +import { createMerchantClient } from '@x402r/sdk' + +const merchant = createMerchantClient({ + publicClient, + walletClient, + operatorAddress: deployment.operatorAddress, + escrowPeriodAddress: deployment.escrowPeriodAddress, + refundRequestAddress: deployment.refundRequestAddress, + freezeAddress: deployment.freezeAddress, + refundRequestEvidenceAddress: deployment.refundRequestEvidenceAddress, +}) +``` + +`createMerchantClient` restricts TypeScript types to merchant-relevant methods. `payment.release()` shows up in autocomplete; `refund.request()` does not — that is a payer action. + +### 5. Authorize a Payment + +In the x402 flow, the **payer signs** an ERC-3009 authorization and the **merchant submits** it to the operator: + +```typescript +import { signReceiveAuthorization, type PaymentInfo } from '@x402r/core' + +// The payer builds and signs +const paymentInfo: PaymentInfo = { + operator: deployment.operatorAddress, + payer: '0xPayerAddress', + receiver: account.address, // merchant receives funds + token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // USDC on Base Sepolia + maxAmount: 1_000_000n, // 1 USDC (6 decimals) + preApprovalExpiry: 281_474_976_710_655, // far future + authorizationExpiry: 281_474_976_710_655, + refundExpiry: 281_474_976_710_655, + minFeeBps: 0, + maxFeeBps: 500, + feeReceiver: deployment.operatorAddress, + salt: 1n, +} + +const { tokenCollector, collectorData } = await signReceiveAuthorization({ + account: payerAccount, // payer's viem account + chainId: 84532, + paymentInfo, +}) + +// The merchant submits the authorization +const authTx = await merchant.payment.authorize( + paymentInfo, + 1_000_000n, // 1 USDC + tokenCollector, + collectorData, +) +console.log('Authorized:', authTx) +``` + +Funds are now locked in escrow. + +### 6. Check Payment State + +```typescript +const amounts = await merchant.payment.getAmounts(paymentInfo) +console.log('Collected:', amounts.hasCollectedPayment) // true +console.log('Capturable:', amounts.capturableAmount) // 1000000n +console.log('Refundable:', amounts.refundableAmount) // 1000000n + +const inEscrow = await merchant.escrow?.isDuringEscrow(paymentInfo) +console.log('In escrow:', inEscrow) // true +``` + +### 7. Release Funds After Escrow + +Once the escrow period expires, release funds to the receiver (merchant): + +```typescript +const releaseTx = await merchant.payment.release(paymentInfo, 1_000_000n) +console.log('Released:', releaseTx) + +// Verify +const after = await merchant.payment.getAmounts(paymentInfo) +console.log('Capturable after release:', after.capturableAmount) // 0n +``` + +### 8. Handle Refund Requests (Optional) + +If a payer disputes, check for pending refund requests: + +```typescript +// Check if a refund request exists +const hasRefund = await merchant.refund?.has(paymentInfo) + +if (hasRefund) { + const request = await merchant.refund?.get(paymentInfo) + console.log('Refund amount:', request?.amount) + console.log('Status:', request?.status) // 0 = Pending + + // Approve by executing refundInEscrow (recorder auto-approves) + const refundTx = await merchant.payment.refundInEscrow( + paymentInfo, + request!.amount, + ) + console.log('Refunded:', refundTx) +} +``` + +### Summary + +* Install `@x402r/sdk`, `@x402r/core`, and `viem` +* Deploy a `PaymentOperator` with `deployMarketplaceOperator()` +* Create a merchant client with the deployment addresses +* Payer signs authorization, merchant submits with `payment.authorize()` +* Check state with `payment.getAmounts()` and `escrow.isDuringEscrow()` +* Release after escrow with `payment.release()` +* Handle disputes with `refund.get()` and `payment.refundInEscrow()` + +--- + +**Next Steps:** + +* [Quickstart for Payers](/sdk/payer-quickstart) — request refunds, freeze payments, submit evidence +* [Smart Contracts](/contracts/overview) — on-chain architecture, conditions, and recorders +* [Protocol Overview](/x402-integration/overview) — how x402r extends the x402 payment protocol + +**References:** + +* [@x402r/sdk on npm](https://www.npmjs.com/package/@x402r/sdk) +* [@x402r/core on npm](https://www.npmjs.com/package/@x402r/core) +* [x402r-sdk on GitHub](https://github.com/BackTrackCo/x402r-sdk) diff --git a/sdk/overview.mdx b/sdk/overview.mdx index 95edb9e..f9f1753 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -1,82 +1,49 @@ --- title: "SDK Overview" -description: "TypeScript SDK for the x402r refundable payments protocol" +description: "TypeScript SDK for adding escrow, refunds, and dispute resolution to x402 payments" icon: "cube" --- -x402 handles instant payments. x402r adds what happens *after* payment: escrow holds, partial captures, refund requests, dispute resolution, and freeze mechanics. If you need refund windows or arbitrated disputes on top of x402, this is the SDK. +x402 payments are instant and irreversible. x402r adds escrow holds, refund windows, and dispute resolution on top. -```typescript -import { createMerchantClient } from '@x402r/sdk' +Three roles interact with the protocol: -const merchant = createMerchantClient({ - publicClient, - walletClient, - operatorAddress: '0x...', // from deployMarketplaceOperator() — see Deploy an Operator guide -}) +- **Merchants** authorize payments into escrow and release funds after delivery +- **Payers** can request refunds, freeze payments, and submit evidence during disputes +- **Arbiters** review disputes and approve or deny refund requests -await merchant.payment.release(paymentInfo, amount) +### Packages + +```bash +npm install @x402r/sdk viem ``` -Three lines to set up, one line to release escrowed funds. The client narrows TypeScript types to merchant-relevant methods — `refund.request()` won't appear in autocomplete because merchants don't request refunds. Payers do. +`@x402r/sdk` is the only package most developers need. It includes role-scoped client factories, 8 action groups (payment, escrow, refund, evidence, freeze, query, operator, watch), and an `.extend()` plugin system. -## Packages +For low-level access to contract ABIs and deploy utilities: `npm install @x402r/core viem` - - - Client factory, role presets, 8 action groups, event watchers, and `.extend()` plugins. The only package most developers need. - - - Types, ABIs, chain config, deploy utilities, and low-level viem actions. Installed automatically as a dependency of `@x402r/sdk`. +For x402 server integration: `npm install @x402r/helpers @x402r/evm` + +### Quickstarts + + + + Deploy an operator, accept a payment, release funds from escrow. - - `refundable()` helper for marking x402 payment options as refundable. One function that attaches escrow config to a standard x402 payment request. + + Check payment state, request a refund, submit evidence. -## Action Groups - -Every method on the client belongs to an action group. Three groups are always available; the rest activate when you pass the corresponding contract address to the config. - -| Group | Methods | Requirement | -|-------|---------|-------------| -| `payment` | 9 | Always available — authorize, charge, release, refund in/post escrow, query state | -| `operator` | 8 | Always available — fee calculation, slot config, fee distribution | -| `watch` | 4 | Always available — subscribe to payment, refund, and fee events | -| `escrow` | 3 | `escrowPeriodAddress` — check escrow status, authorization time, duration | -| `refund` | 15 | `refundRequestAddress` — request, cancel, deny, refuse, query refund state | -| `evidence` | 4 | `refundRequestEvidenceAddress` — submit and query IPFS evidence CIDs | -| `freeze` | 3 | `freezeAddress` — freeze, unfreeze, check frozen status | -| `query` | 3 | `paymentIndexRecorderAddress` — look up payments by payer or receiver | - -Skip addresses you don't need. If your operator has no freeze contract, omit `freezeAddress` — the `freeze` group won't exist on your client type and TypeScript will enforce that. - -## Role Presets - -Three factory functions narrow the client type to a specific role: - -- **`createPayerClient()`** — query payment state, request refunds, freeze payments, submit evidence -- **`createMerchantClient()`** — authorize, charge, release, handle post-escrow refunds, read operator config -- **`createArbiterClient()`** — deny/refuse refund requests, unfreeze, review evidence, distribute fees - -Autocomplete only shows methods your role can call. This is a DX convenience, not a security boundary — on-chain [conditions](/contracts/conditions/overview) enforce access control regardless of which client you use. - -## Supported Chains +### Supported Chains -All protocol contracts are deployed via CREATE3 to identical addresses on every chain. Only the USDC address differs. - -### Testnets +All contracts are deployed to identical addresses on every chain via CREATE3. Only USDC differs. | Chain | Chain ID | USDC | |-------|----------|------| | Base Sepolia | `84532` | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | | Ethereum Sepolia | `11155111` | `0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238` | | Arbitrum Sepolia | `421614` | `0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d` | - -### Mainnets - -| Chain | Chain ID | USDC | -|-------|----------|------| | Ethereum | `1` | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | | Base | `8453` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | | Polygon | `137` | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` | @@ -86,18 +53,3 @@ All protocol contracts are deployed via CREATE3 to identical addresses on every | Avalanche C-Chain | `43114` | `0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E` | | Monad | `143` | `0x754704Bc059F8C67012fEd69BC8a327a5aafb603` | | Linea | `59144` | `0x176211869cA2b568f2A7D4EE941E073a821EE1ff` | - - -Base Sepolia is the primary testnet and the most battle-tested deployment. Mainnet contracts are live but have seen less production traffic. Run integration tests on your target chain before handling real funds. - - -## Next Steps - - - - Install packages and set up viem clients. - - - Pick between the full client and role presets. - - diff --git a/sdk/payer-quickstart.mdx b/sdk/payer-quickstart.mdx new file mode 100644 index 0000000..1bd644d --- /dev/null +++ b/sdk/payer-quickstart.mdx @@ -0,0 +1,146 @@ +--- +title: "Quickstart for Payers" +description: "Check payment state, request a refund, freeze a payment, and submit evidence — step by step." +--- + +### Prerequisites + +* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) +* Node.js 18+ and npm +* An authorized payment on an x402r operator (see [Quickstart for Merchants](/sdk/merchant-quickstart)) + +**Note**\ +There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [payer examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts). + +### 1. Install Dependencies + +```bash +npm install @x402r/sdk viem +``` + +### 2. Create a Payer Client + +```typescript +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { createPayerClient } from '@x402r/sdk' + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) + +const payer = createPayerClient({ + publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), + walletClient: createWalletClient({ + account, + chain: baseSepolia, + transport: http(), + }), + operatorAddress: '0x...', // your operator address + refundRequestAddress: '0x...', // from deploy result + refundRequestEvidenceAddress: '0x...', + escrowPeriodAddress: '0x...', + freezeAddress: '0x...', +}) +``` + +`createPayerClient` restricts TypeScript types to payer-relevant methods. `refund.request()` shows up in autocomplete; `payment.authorize()` does not — that is a merchant action. + +### 3. Check Payment State + +```typescript +import type { PaymentInfo } from '@x402r/sdk' + +// paymentInfo is the same struct used during authorization +const paymentInfo: PaymentInfo = { /* ... */ } + +const amounts = await payer.payment.getAmounts(paymentInfo) +console.log('Collected:', amounts.hasCollectedPayment) +console.log('Capturable:', amounts.capturableAmount) +console.log('Refundable:', amounts.refundableAmount) + +const inEscrow = await payer.escrow?.isDuringEscrow(paymentInfo) +console.log('In escrow:', inEscrow) +``` + +### 4. Request a Refund + +Request a refund while the payment is still in escrow: + +```typescript +// Check if a refund request already exists +const hasExisting = await payer.refund?.has(paymentInfo) +if (hasExisting) { + console.log('Refund already requested') +} else { + const tx = await payer.refund?.request(paymentInfo, 1_000_000n) // 1 USDC + console.log('Refund requested:', tx) +} + +// Check refund status +const status = await payer.refund?.getStatus(paymentInfo) +console.log('Status:', status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Refused +``` + +### 5. Freeze a Payment (Optional) + +Freezing blocks the merchant from releasing funds while the dispute is active: + +```typescript +const frozen = await payer.freeze?.isFrozen(paymentInfo) +if (!frozen) { + const tx = await payer.freeze?.freeze(paymentInfo) + console.log('Payment frozen:', tx) +} +``` + +### 6. Submit Evidence (Optional) + +Attach evidence to a refund request. Evidence is stored on-chain as IPFS CIDs: + +```typescript +// Upload your evidence to IPFS first, then submit the CID +const tx = await payer.evidence?.submit(paymentInfo, 'QmYourEvidenceCID...') +console.log('Evidence submitted:', tx) + +// Read back evidence +const count = await payer.evidence?.count(paymentInfo) +console.log('Evidence entries:', count) + +for (let i = 0n; i < count!; i++) { + const entry = await payer.evidence?.get(paymentInfo, i) + console.log(` [${i}] CID: ${entry?.cid} from ${entry?.submitter}`) +} +``` + +### 7. Cancel a Refund Request (Optional) + +If the issue is resolved directly with the merchant: + +```typescript +const cancelTx = await payer.refund?.cancel(paymentInfo) +console.log('Refund cancelled:', cancelTx) +``` + +### Summary + +* Install `@x402r/sdk` and `viem` +* Create a payer client with the operator's deployment addresses +* Check payment state with `payment.getAmounts()` and `escrow.isDuringEscrow()` +* Request a refund with `refund.request()` +* Optionally freeze the payment with `freeze.freeze()` to block release +* Submit IPFS evidence with `evidence.submit()` +* Cancel with `refund.cancel()` if the issue is resolved + +--- + +**Next Steps:** + +* [Quickstart for Merchants](/sdk/merchant-quickstart) — deploy an operator, authorize payments, release funds +* [Smart Contracts](/contracts/overview) — conditions, recorders, and the operator architecture +* [Protocol Overview](/x402-integration/overview) — escrow scheme and payment lifecycle + +**References:** + +* [Dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) +* [Payer examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer) +* [@x402r/sdk on npm](https://www.npmjs.com/package/@x402r/sdk) From 4cf2ed2162f6f955ce36e61f8ce47391911cbd99 Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 17:29:17 -0700 Subject: [PATCH 04/23] Remove em dashes, add CodeGroup to all install commands - Add no-em-dash and CodeGroup rules to CLAUDE.md - Replace all em dashes with commas, periods, or rewrites - Wrap all install commands in CodeGroup (npm/pnpm/bun) Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 4 +++- sdk/merchant-quickstart.mdx | 22 ++++++++++++++------- sdk/overview.mdx | 38 ++++++++++++++++++++++++++++++++++--- sdk/payer-quickstart.mdx | 20 +++++++++++++------ 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index af30ffd..d587c8a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,9 @@ npx mint dev # Preview at localhost:3000 - YAML frontmatter required on all MDX files (`title`, `description`) - Second person ("You can..."), active voice, short paragraphs (2-4 sentences) +- **Never use em dashes (`—`).** Use a comma, period, or rewrite the sentence instead. - Use Mermaid for diagrams, never ASCII art - Relative paths for internal links, alt text on images - Test all code examples before including, use realistic values -- Components: `Note`, `Tip`, `Warning`, `Info`, `Steps`, `CardGroup`, `Tabs`, `Accordion` +- All install commands must be copy-pasteable. Use `CodeGroup` with npm/pnpm/bun tabs for every install command. +- Components: `Note`, `Tip`, `Warning`, `Info`, `Steps`, `CardGroup`, `Tabs`, `Accordion`, `CodeGroup` diff --git a/sdk/merchant-quickstart.mdx b/sdk/merchant-quickstart.mdx index 1343a1e..b4080f6 100644 --- a/sdk/merchant-quickstart.mdx +++ b/sdk/merchant-quickstart.mdx @@ -1,6 +1,6 @@ --- title: "Quickstart for Merchants" -description: "Deploy an operator, accept a payment into escrow, and release funds — step by step." +description: "Deploy an operator, accept a payment into escrow, and release funds." --- ### Prerequisites @@ -14,9 +14,17 @@ There are pre-configured [examples in the x402r-sdk repo](https://github.com/Bac ### 1. Install Dependencies -```bash + +```bash npm npm install @x402r/sdk @x402r/core viem ``` +```bash pnpm +pnpm add @x402r/sdk @x402r/core viem +``` +```bash bun +bun add @x402r/sdk @x402r/core viem +``` + ### 2. Create viem Clients @@ -61,7 +69,7 @@ console.log('RefundRequest:', deployment.refundRequestAddress) console.log('Freeze:', deployment.freezeAddress) ``` -This deploys a `PaymentOperator` with escrow, freeze, and dispute resolution contracts. Save these addresses — you need them to create SDK clients. +This deploys a `PaymentOperator` with escrow, freeze, and dispute resolution contracts. Save these addresses. You need them to create SDK clients. ### 4. Create a Merchant Client @@ -79,7 +87,7 @@ const merchant = createMerchantClient({ }) ``` -`createMerchantClient` restricts TypeScript types to merchant-relevant methods. `payment.release()` shows up in autocomplete; `refund.request()` does not — that is a payer action. +`createMerchantClient` restricts TypeScript types to merchant-relevant methods. `payment.release()` shows up in autocomplete; `refund.request()` does not because that is a payer action. ### 5. Authorize a Payment @@ -183,9 +191,9 @@ if (hasRefund) { **Next Steps:** -* [Quickstart for Payers](/sdk/payer-quickstart) — request refunds, freeze payments, submit evidence -* [Smart Contracts](/contracts/overview) — on-chain architecture, conditions, and recorders -* [Protocol Overview](/x402-integration/overview) — how x402r extends the x402 payment protocol +* [Quickstart for Payers](/sdk/payer-quickstart): request refunds, freeze payments, submit evidence +* [Smart Contracts](/contracts/overview): on-chain architecture, conditions, and recorders +* [Protocol Overview](/x402-integration/overview): how x402r extends the x402 payment protocol **References:** diff --git a/sdk/overview.mdx b/sdk/overview.mdx index f9f1753..0847825 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -14,15 +14,47 @@ Three roles interact with the protocol: ### Packages -```bash + +```bash npm npm install @x402r/sdk viem ``` +```bash pnpm +pnpm add @x402r/sdk viem +``` +```bash bun +bun add @x402r/sdk viem +``` + `@x402r/sdk` is the only package most developers need. It includes role-scoped client factories, 8 action groups (payment, escrow, refund, evidence, freeze, query, operator, watch), and an `.extend()` plugin system. -For low-level access to contract ABIs and deploy utilities: `npm install @x402r/core viem` +For low-level access to contract ABIs and deploy utilities: + + +```bash npm +npm install @x402r/core viem +``` +```bash pnpm +pnpm add @x402r/core viem +``` +```bash bun +bun add @x402r/core viem +``` + + +For x402 server integration: -For x402 server integration: `npm install @x402r/helpers @x402r/evm` + +```bash npm +npm install @x402r/helpers @x402r/evm +``` +```bash pnpm +pnpm add @x402r/helpers @x402r/evm +``` +```bash bun +bun add @x402r/helpers @x402r/evm +``` + ### Quickstarts diff --git a/sdk/payer-quickstart.mdx b/sdk/payer-quickstart.mdx index 1bd644d..cded013 100644 --- a/sdk/payer-quickstart.mdx +++ b/sdk/payer-quickstart.mdx @@ -1,6 +1,6 @@ --- title: "Quickstart for Payers" -description: "Check payment state, request a refund, freeze a payment, and submit evidence — step by step." +description: "Check payment state, request a refund, freeze a payment, and submit evidence." --- ### Prerequisites @@ -14,9 +14,17 @@ There are pre-configured [examples in the x402r-sdk repo](https://github.com/Bac ### 1. Install Dependencies -```bash + +```bash npm npm install @x402r/sdk viem ``` +```bash pnpm +pnpm add @x402r/sdk viem +``` +```bash bun +bun add @x402r/sdk viem +``` + ### 2. Create a Payer Client @@ -43,7 +51,7 @@ const payer = createPayerClient({ }) ``` -`createPayerClient` restricts TypeScript types to payer-relevant methods. `refund.request()` shows up in autocomplete; `payment.authorize()` does not — that is a merchant action. +`createPayerClient` restricts TypeScript types to payer-relevant methods. `refund.request()` shows up in autocomplete; `payment.authorize()` does not because that is a merchant action. ### 3. Check Payment State @@ -135,9 +143,9 @@ console.log('Refund cancelled:', cancelTx) **Next Steps:** -* [Quickstart for Merchants](/sdk/merchant-quickstart) — deploy an operator, authorize payments, release funds -* [Smart Contracts](/contracts/overview) — conditions, recorders, and the operator architecture -* [Protocol Overview](/x402-integration/overview) — escrow scheme and payment lifecycle +* [Quickstart for Merchants](/sdk/merchant-quickstart): deploy an operator, authorize payments, release funds +* [Smart Contracts](/contracts/overview): conditions, recorders, and the operator architecture +* [Protocol Overview](/x402-integration/overview): escrow scheme and payment lifecycle **References:** From ed13e139d1e6018ebe1d0d4b5d2b095c0b238f1b Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 17:40:45 -0700 Subject: [PATCH 05/23] Add arbiter quickstart, examples page, update deploy-operator - arbiter-quickstart: 8 numbered steps covering dispute review, approve/deny, unfreeze, and fee distribution - examples: links to all SDK examples with run commands - deploy-operator: fix V2 API (chainId instead of network string), add refundRequest/evidence to return type, fix broken nav links - docs.json: add arbiter quickstart + Resources group Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 10 +- sdk/arbiter-quickstart.mdx | 182 +++++++++++++++++++++++++++++++++++++ sdk/deploy-operator.mdx | 114 +++++++++++------------ sdk/examples.mdx | 137 ++++++++-------------------- 4 files changed, 286 insertions(+), 157 deletions(-) create mode 100644 sdk/arbiter-quickstart.mdx diff --git a/docs.json b/docs.json index 02153be..d71aa00 100644 --- a/docs.json +++ b/docs.json @@ -91,7 +91,15 @@ "pages": [ "sdk/overview", "sdk/merchant-quickstart", - "sdk/payer-quickstart" + "sdk/payer-quickstart", + "sdk/arbiter-quickstart" + ] + }, + { + "group": "Resources", + "pages": [ + "sdk/deploy-operator", + "sdk/examples" ] } ] diff --git a/sdk/arbiter-quickstart.mdx b/sdk/arbiter-quickstart.mdx new file mode 100644 index 0000000..037c6fd --- /dev/null +++ b/sdk/arbiter-quickstart.mdx @@ -0,0 +1,182 @@ +--- +title: "Quickstart for Arbiters" +description: "Review disputes, approve or deny refunds, and distribute fees." +--- + +### Prerequisites + +* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) +* Node.js 18+ and npm +* An operator where your address is configured as the arbiter (see [Deploy an Operator](/sdk/deploy-operator)) + +**Note**\ +There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts). + +### 1. Install Dependencies + + +```bash npm +npm install @x402r/sdk viem +``` +```bash pnpm +pnpm add @x402r/sdk viem +``` +```bash bun +bun add @x402r/sdk viem +``` + + +### 2. Create an Arbiter Client + +```typescript +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { createArbiterClient } from '@x402r/sdk' + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) + +const arbiter = createArbiterClient({ + publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), + walletClient: createWalletClient({ + account, + chain: baseSepolia, + transport: http(), + }), + operatorAddress: '0x...', // your operator address + refundRequestAddress: '0x...', // from deploy result + refundRequestEvidenceAddress: '0x...', // from deploy result + escrowPeriodAddress: '0x...', + freezeAddress: '0x...', +}) +``` + +`createArbiterClient` restricts TypeScript types to arbiter-relevant methods. `refund.deny()` and `freeze.unfreeze()` show up in autocomplete; `payment.authorize()` does not because that is a merchant action. + +### 3. Check for Pending Refund Requests + +```typescript +import type { PaymentInfo } from '@x402r/sdk' + +const paymentInfo: PaymentInfo = { /* ... */ } + +const hasRefund = await arbiter.refund?.has(paymentInfo) +if (hasRefund) { + const request = await arbiter.refund?.get(paymentInfo) + console.log('Amount:', request?.amount) + console.log('Status:', request?.status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Refused +} +``` + +To list all refund requests for your operator: + +```typescript +const requests = await arbiter.refund?.getOperatorRequests( + arbiter.config.operatorAddress, + 0n, // offset + 10n, // count +) +console.log('Pending requests:', requests) +``` + +### 4. Review Evidence + +Both payers and merchants can submit evidence as IPFS CIDs. Read all entries before making a decision: + +```typescript +const count = await arbiter.evidence?.count(paymentInfo) +console.log('Evidence entries:', count) + +const batch = await arbiter.evidence?.getBatch(paymentInfo, 0n, count!) + +for (const entry of batch!.entries) { + console.log('CID:', entry.cid) + console.log('Submitter:', entry.submitter) + console.log('Timestamp:', entry.timestamp) +} +``` + +### 5. Approve a Refund + +Call `payment.refundInEscrow()` to approve. The RefundRequest recorder auto-approves the pending request, so there is no separate `approve()` call: + +```typescript +const tx = await arbiter.payment.refundInEscrow(paymentInfo, request!.amount) +console.log('Refund approved:', tx) + +// Verify +const approved = await arbiter.refund?.get(paymentInfo) +console.log('Approved amount:', approved?.approvedAmount) +console.log('Status:', approved?.status) // 1 = Approved +``` + +### 6. Deny a Refund Request + +If the evidence does not support a refund: + +```typescript +const denyTx = await arbiter.refund?.deny(paymentInfo) +console.log('Refund denied:', denyTx) +``` + +Or decline to rule entirely: + +```typescript +const refuseTx = await arbiter.refund?.refuse(paymentInfo) +console.log('Declined to rule:', refuseTx) +``` + +### 7. Unfreeze a Payment + +If the payer froze the payment during the dispute, unfreeze it after resolution: + +```typescript +const frozen = await arbiter.freeze?.isFrozen(paymentInfo) +if (frozen) { + const tx = await arbiter.freeze?.unfreeze(paymentInfo) + console.log('Unfrozen:', tx) +} +``` + +### 8. Distribute Accumulated Fees + +Protocol fees accumulate on the operator when payments are released. Any role can distribute them: + +```typescript +import { getChainConfig } from '@x402r/sdk' + +const config = getChainConfig(84532) + +const fees = await arbiter.operator.getAccumulatedProtocolFees(config.usdc) +console.log('Accumulated fees:', fees) + +if (fees > 0n) { + const tx = await arbiter.operator.distributeFees(config.usdc) + console.log('Fees distributed:', tx) +} +``` + +### Summary + +* Install `@x402r/sdk` and `viem` +* Create an arbiter client with the operator's deployment addresses +* Check for pending refund requests with `refund.has()` and `refund.get()` +* Review evidence with `evidence.getBatch()` +* Approve with `payment.refundInEscrow()` (recorder auto-approves) +* Deny with `refund.deny()` or decline with `refund.refuse()` +* Unfreeze payments with `freeze.unfreeze()` +* Distribute fees with `operator.distributeFees()` + +--- + +**Next Steps:** + +* [Quickstart for Merchants](/sdk/merchant-quickstart): deploy an operator, authorize payments, release funds +* [Quickstart for Payers](/sdk/payer-quickstart): request refunds, freeze payments, submit evidence +* [Smart Contracts](/contracts/overview): conditions, recorders, and the operator architecture + +**References:** + +* [Dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) +* [Arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) +* [@x402r/sdk on npm](https://www.npmjs.com/package/@x402r/sdk) diff --git a/sdk/deploy-operator.mdx b/sdk/deploy-operator.mdx index a97990b..8edc029 100644 --- a/sdk/deploy-operator.mdx +++ b/sdk/deploy-operator.mdx @@ -14,12 +14,12 @@ The `@x402r/core` package includes deployment utilities that handle the full lif A complete marketplace operator deployment includes: -1. **EscrowPeriod** — Records authorization time, enforces waiting period before release -2. **Freeze** — Allows payer to freeze payment during escrow, receiver to unfreeze -3. **StaticAddressCondition** — Restricts refund approval to the designated arbiter -4. **OrCondition** — Allows either the receiver OR the arbiter to approve in-escrow refunds -5. **StaticFeeCalculator** — Optional operator fee (basis points) -6. **PaymentOperator** — The main contract tying everything together +1. **EscrowPeriod** Records authorization time, enforces waiting period before release +2. **Freeze** Allows payer to freeze payment during escrow, receiver to unfreeze +3. **StaticAddressCondition** Restricts refund approval to the designated arbiter +4. **OrCondition** Allows either the receiver OR the arbiter to approve in-escrow refunds +5. **StaticFeeCalculator** Optional operator fee (basis points) +6. **PaymentOperator** The main contract tying everything together All contracts are deployed via factories using CREATE2, so identical configurations produce identical addresses across deployments. @@ -44,11 +44,11 @@ All contracts are deployed via factories using CREATE2, so identical configurati cp .env.example .env ``` - Edit `.env` with your values. Only `PRIVATE_KEY` is required — everything else has sensible defaults: + Edit `.env` with your values. Only `PRIVATE_KEY` is required everything else has sensible defaults: | Variable | Default | Description | |----------|---------|-------------| - | `PRIVATE_KEY` | — | Deployer wallet (required) | + | `PRIVATE_KEY` | (required) | Deployer wallet private key | | `ARBITER` | deployer address | Dispute resolver | | `FEE_RECIPIENT` | deployer address | Receives operator fees | | `ESCROW_PERIOD` | `604800` (7 days) | Escrow period in seconds | @@ -90,38 +90,35 @@ If you want to integrate deployment into your own code: import { createPublicClient, createWalletClient, http } from 'viem'; import { baseSepolia } from 'viem/chains'; import { privateKeyToAccount } from 'viem/accounts'; -import { deployMarketplaceOperator } from '@x402r/core/deploy'; +import { deployMarketplaceOperator } from '@x402r/core' const publicClient = createPublicClient({ chain: baseSepolia, transport: http(), -}); +}) -const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) const walletClient = createWalletClient({ account, chain: baseSepolia, transport: http(), -}); - -const result = await deployMarketplaceOperator( - walletClient, - publicClient, - 'eip155:84532', // Base Sepolia - { - feeRecipient: account.address, // receives operator fees - arbiter: '0xArbiterAddress...', // dispute resolver - escrowPeriodSeconds: 604800n, // 7 days - freezeDurationSeconds: 259200n, // 3 days max freeze - operatorFeeBps: 100n, // 1% fee (optional) - } -); - -console.log('Operator:', result.operatorAddress); -console.log('EscrowPeriod:', result.escrowPeriodAddress); -console.log('Freeze:', result.freezeAddress); -console.log('New deployments:', result.summary.newDeployments); -console.log('Existing (reused):', result.summary.existingContracts); +}) + +const result = await deployMarketplaceOperator(walletClient, publicClient, { + chainId: 84532, // Base Sepolia + feeRecipient: account.address, // receives operator fees + arbiter: '0xArbiterAddress...', // dispute resolver + escrowPeriodSeconds: 604_800n, // 7 days + freezeDurationSeconds: 259_200n, // 3 days max freeze + operatorFeeBps: 100n, // 1% fee (optional) +}) + +console.log('Operator:', result.operatorAddress) +console.log('EscrowPeriod:', result.escrowPeriodAddress) +console.log('RefundRequest:', result.refundRequestAddress) +console.log('Freeze:', result.freezeAddress) +console.log('New deployments:', result.summary.newDeployments) +console.log('Existing (reused):', result.summary.existingContracts) ``` ## Configuration Options @@ -140,22 +137,24 @@ The `deployMarketplaceOperator` function returns: ```typescript interface MarketplaceOperatorDeployment { - operatorAddress: Address; // The PaymentOperator - escrowPeriodAddress: Address; // EscrowPeriod recorder/condition - freezeAddress: Address; // Freeze condition - arbiterConditionAddress: Address; // StaticAddressCondition for arbiter - refundInEscrowCondition: Address; // OR(Receiver, Arbiter) - feeCalculatorAddress: Address | null; // null if no fee - txHashes: Hash[]; // All deployment tx hashes + operatorAddress: Address; // The PaymentOperator + escrowPeriodAddress: Address; // EscrowPeriod recorder/condition + freezeAddress: Address; // Freeze condition + refundRequestAddress: Address; // RefundRequest recorder + refundRequestEvidenceAddress: Address; // Evidence storage + arbiterConditionAddress: Address; // StaticAddressCondition for arbiter + refundInEscrowConditionAddress: Address; // OR(Receiver, Arbiter) + feeCalculatorAddress: Address | null; // null if no fee + txHashes: Hash[]; // All deployment tx hashes summary: { - newDeployments: number; // Newly deployed contracts - existingContracts: number; // Reused existing contracts + newDeployments: number; // Newly deployed contracts + existingContracts: number; // Reused existing contracts }; } ``` -Because all contracts use CREATE2, redeploying with the same parameters is idempotent — it will detect existing contracts and skip them. The `summary` tells you what was new vs reused. +Because all contracts use CREATE2, redeploying with the same parameters is idempotent it will detect existing contracts and skip them. The `summary` tells you what was new vs reused. ## Preview Addresses (No Deploy) @@ -163,17 +162,14 @@ Because all contracts use CREATE2, redeploying with the same parameters is idemp You can preview what addresses will be created without actually deploying: ```typescript -import { previewMarketplaceOperator } from '@x402r/core/deploy'; - -const preview = await previewMarketplaceOperator( - publicClient, - 'eip155:84532', - { - feeRecipient: '0xYourAddress...', - arbiter: '0xArbiterAddress...', - escrowPeriodSeconds: 604800n, - } -); +import { previewMarketplaceOperator } from '@x402r/core' + +const preview = await previewMarketplaceOperator(publicClient, { + chainId: 84532, + feeRecipient: '0xYourAddress...', + arbiter: '0xArbiterAddress...', + escrowPeriodSeconds: 604_800n, +}) console.log('Operator will be at:', preview.operatorAddress); console.log('EscrowPeriod will be at:', preview.escrowPeriodAddress); @@ -219,16 +215,16 @@ Deployment requires gas fees. Ensure your wallet has ETH on the target network. ## Next Steps - - Clone and run the deploy-operator example. + + Authorize payments, release funds from escrow. - - See working merchant and client examples. + + Request refunds, freeze payments, submit evidence. - - Mark payment options as refundable with your operator. + + Runnable examples for every SDK operation. - Understand the underlying contract architecture. + On-chain architecture, conditions, and recorders. diff --git a/sdk/examples.mdx b/sdk/examples.mdx index 44df86f..bde5978 100644 --- a/sdk/examples.mdx +++ b/sdk/examples.mdx @@ -1,117 +1,60 @@ --- title: "Examples" -description: "Working examples for deploying operators, building merchants, clients, and arbiters" +description: "Runnable examples for every SDK operation." icon: "code" --- -The SDK includes working examples in the `x402r-sdk/` repository. Each is a standalone project that demonstrates a specific integration pattern. +The [x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples) includes runnable examples for every role. Each example starts a local Anvil fork, deploys contracts, and runs. No wallet or testnet funds needed. -## Examples - - - - Operator-agnostic HTTP service implementing x402's facilitator protocol for escrow payments. Handles signature verification and on-chain settlement. - - - 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. - - - Hono merchant server using `EscrowServerScheme`, `HTTPFacilitatorClient`, and `refundable()` to accept escrow payments via x402 middleware. - - - CLI tool for merchants to release payments, approve/deny refunds, and query escrow state. - - - CLI tool for payers to `pay`, `preview-fee`, request refunds, freeze payments, and check status. - - - CLI tool for arbiters to review cases, make decisions, and manage registry. - - - Shared utilities used by the CLI examples: `parsePaymentInfo`, `shortAddress`, `formatUSDC`. - - - -## Running Examples - - -All examples require a private key with Base Sepolia ETH and USDC. See [Base network faucets](https://docs.base.org/base-chain/tools/network-faucets) for testnet tokens. - - -The full payment flow requires the facilitator to be running before the merchant server: +### Running ```bash -# All commands run from the x402r-sdk/ root directory - -# 1. Set up environment files -cp examples/facilitator/basic/.env-local examples/facilitator/basic/.env -# Edit .env — set PRIVATE_KEY - -cp examples/servers/express/.env-local examples/servers/express/.env -# Edit .env — set ADDRESS, OPERATOR_ADDRESS, FACILITATOR_URL - -# 2. Start the facilitator (port 4022) -pnpm example:facilitator - -# 3. Start the merchant server (new terminal, port 4021) -pnpm example:server:express -# Or: pnpm example:server:hono - -# 4. Make a payment (new terminal) -pnpm example:client-cli pay --url http://localhost:4021/weather +git clone https://github.com/BackTrackCo/x402r-sdk.git +cd x402r-sdk +pnpm install && pnpm build ``` - -The facilitator must be running before the merchant server (Express or Hono) starts, as the merchant delegates payment verification and settlement to it. - +Then run any example: -## deploy-operator +```bash +# Per-action examples +pnpm example:payer:request-refund +pnpm example:merchant:charge +pnpm example:arbiter:approve-refund + +# Multi-role scenarios +pnpm scenario:release +pnpm scenario:dispute +``` -Deploys a complete marketplace operator using `deployMarketplaceOperator()`: +### Payer -```typescript -import { deployMarketplaceOperator } from '@x402r/core/deploy'; +| Example | What it does | +|---------|-------------| +| [`payer/request-refund.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer/request-refund.ts) | Request a refund for a payment in escrow | +| [`payer/submit-evidence.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer/submit-evidence.ts) | Submit an IPFS evidence CID for a dispute | +| [`payer/freeze-payment.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer/freeze-payment.ts) | Freeze a payment to block release during investigation | -const result = await deployMarketplaceOperator( - walletClient, - publicClient, - 'eip155:84532', - { - feeRecipient: account.address, - arbiter: arbiterAddress, - escrowPeriodSeconds: 604800n, // 7 days - operatorFeeBps: 100n, // 1% - } -); -``` +### Merchant -See [Deploy an Operator](/sdk/deploy-operator) for the full guide. +| Example | What it does | +|---------|-------------| +| [`merchant/charge-payment.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/merchant/charge-payment.ts) | Charge an authorized payment (no escrow) | +| [`merchant/release-escrow.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/merchant/release-escrow.ts) | Release remaining funds after escrow expires | -## Server Examples +### Arbiter -Demonstrates minimal merchant servers (Express and Hono variants) that use `EscrowServerScheme`, `HTTPFacilitatorClient`, and `refundable()` via x402's standard middleware: +| Example | What it does | +|---------|-------------| +| [`arbiter/approve-refund.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter/approve-refund.ts) | Approve a payer's refund request | +| [`arbiter/review-evidence.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter/review-evidence.ts) | Review all submitted evidence for a dispute | +| [`arbiter/distribute-fees.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter/distribute-fees.ts) | Distribute accumulated protocol fees | -1. Returns 402 with `refundable()` 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 +### Scenarios -## Next Steps +Full multi-role integration tests running against a local Anvil fork. - - - Deploy a PaymentOperator with escrow and freeze support. - - - Mark payment options as refundable with escrow configuration. - - - Understand the payment lifecycle and key concepts. - - - Browse all examples on GitHub. - - +| Scenario | What it does | +|----------|-------------| +| [`scenarios/happy-path-release.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/happy-path-release.ts) | Authorize, wait for escrow, release (2 roles: payer + merchant) | +| [`scenarios/dispute-resolution.ts`](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) | Full dispute lifecycle with evidence and arbitration (3 roles) | From 87eef31f5184efa6c8241de62178015d195b4a6b Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 17:45:58 -0700 Subject: [PATCH 06/23] Clean up page layout: icons, cards, remove cruft - Add icons to all quickstart pages (store, user, gavel) - Rename "SDK Overview" to "Overview" - Add arbiter to overview's Quickstarts CardGroup - Remove Summary sections (just repeat the steps) - Remove References sections (npm/GitHub links) - Replace Next Steps bullet lists with CardGroup cards - Remove duplicate network table from deploy-operator, link to overview as single source of truth Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/arbiter-quickstart.mdx | 38 ++++++++++++++----------------------- sdk/deploy-operator.mdx | 18 +----------------- sdk/merchant-quickstart.mdx | 37 ++++++++++++++---------------------- sdk/overview.mdx | 11 +++++++---- sdk/payer-quickstart.mdx | 37 ++++++++++++++---------------------- 5 files changed, 50 insertions(+), 91 deletions(-) diff --git a/sdk/arbiter-quickstart.mdx b/sdk/arbiter-quickstart.mdx index 037c6fd..1cf4af1 100644 --- a/sdk/arbiter-quickstart.mdx +++ b/sdk/arbiter-quickstart.mdx @@ -1,6 +1,7 @@ --- title: "Quickstart for Arbiters" description: "Review disputes, approve or deny refunds, and distribute fees." +icon: "gavel" --- ### Prerequisites @@ -156,27 +157,16 @@ if (fees > 0n) { } ``` -### Summary - -* Install `@x402r/sdk` and `viem` -* Create an arbiter client with the operator's deployment addresses -* Check for pending refund requests with `refund.has()` and `refund.get()` -* Review evidence with `evidence.getBatch()` -* Approve with `payment.refundInEscrow()` (recorder auto-approves) -* Deny with `refund.deny()` or decline with `refund.refuse()` -* Unfreeze payments with `freeze.unfreeze()` -* Distribute fees with `operator.distributeFees()` - ---- - -**Next Steps:** - -* [Quickstart for Merchants](/sdk/merchant-quickstart): deploy an operator, authorize payments, release funds -* [Quickstart for Payers](/sdk/payer-quickstart): request refunds, freeze payments, submit evidence -* [Smart Contracts](/contracts/overview): conditions, recorders, and the operator architecture - -**References:** - -* [Dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) -* [Arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) -* [@x402r/sdk on npm](https://www.npmjs.com/package/@x402r/sdk) +## Next Steps + + + + Deploy an operator, authorize payments, release funds. + + + Request refunds, freeze payments, submit evidence. + + + Runnable examples for every SDK operation. + + diff --git a/sdk/deploy-operator.mdx b/sdk/deploy-operator.mdx index 8edc029..1b8a385 100644 --- a/sdk/deploy-operator.mdx +++ b/sdk/deploy-operator.mdx @@ -190,23 +190,7 @@ The deployed operator has the following slot configuration: | `FEE_CALCULATOR` | StaticFeeCalculator | Fixed percentage fee | | `FEE_RECIPIENT` | Your address | Receives fees | -## Network Support - -Deployment is supported on all configured networks: - -| Network | Chain ID | EIP-155 ID | -|---------|----------|------------| -| Base Sepolia | 84532 | `eip155:84532` | -| Base Mainnet | 8453 | `eip155:8453` | -| Ethereum | 1 | `eip155:1` | -| Ethereum Sepolia | 11155111 | `eip155:11155111` | -| Arbitrum Sepolia | 421614 | `eip155:421614` | -| Polygon | 137 | `eip155:137` | -| Arbitrum | 42161 | `eip155:42161` | -| Optimism | 10 | `eip155:10` | -| Avalanche | 43114 | `eip155:43114` | -| Celo | 42220 | `eip155:42220` | -| Monad | 143 | `eip155:143` | +Deployment is supported on all [supported chains](/sdk/overview#supported-chains). Pass the numeric `chainId` in the options. Deployment requires gas fees. Ensure your wallet has ETH on the target network. On Base Sepolia, you can get testnet ETH from [Base network faucets](https://docs.base.org/base-chain/tools/network-faucets). diff --git a/sdk/merchant-quickstart.mdx b/sdk/merchant-quickstart.mdx index b4080f6..908bcdc 100644 --- a/sdk/merchant-quickstart.mdx +++ b/sdk/merchant-quickstart.mdx @@ -1,6 +1,7 @@ --- title: "Quickstart for Merchants" description: "Deploy an operator, accept a payment into escrow, and release funds." +icon: "store" --- ### Prerequisites @@ -177,26 +178,16 @@ if (hasRefund) { } ``` -### Summary - -* Install `@x402r/sdk`, `@x402r/core`, and `viem` -* Deploy a `PaymentOperator` with `deployMarketplaceOperator()` -* Create a merchant client with the deployment addresses -* Payer signs authorization, merchant submits with `payment.authorize()` -* Check state with `payment.getAmounts()` and `escrow.isDuringEscrow()` -* Release after escrow with `payment.release()` -* Handle disputes with `refund.get()` and `payment.refundInEscrow()` - ---- - -**Next Steps:** - -* [Quickstart for Payers](/sdk/payer-quickstart): request refunds, freeze payments, submit evidence -* [Smart Contracts](/contracts/overview): on-chain architecture, conditions, and recorders -* [Protocol Overview](/x402-integration/overview): how x402r extends the x402 payment protocol - -**References:** - -* [@x402r/sdk on npm](https://www.npmjs.com/package/@x402r/sdk) -* [@x402r/core on npm](https://www.npmjs.com/package/@x402r/core) -* [x402r-sdk on GitHub](https://github.com/BackTrackCo/x402r-sdk) +## Next Steps + + + + Request refunds, freeze payments, submit evidence. + + + Review disputes, approve or deny refunds. + + + Runnable examples for every SDK operation. + + diff --git a/sdk/overview.mdx b/sdk/overview.mdx index 0847825..def57b9 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -1,5 +1,5 @@ --- -title: "SDK Overview" +title: "Overview" description: "TypeScript SDK for adding escrow, refunds, and dispute resolution to x402 payments" icon: "cube" --- @@ -58,13 +58,16 @@ bun add @x402r/helpers @x402r/evm ### Quickstarts - - + + Deploy an operator, accept a payment, release funds from escrow. - + Check payment state, request a refund, submit evidence. + + Review disputes, approve or deny refunds, distribute fees. + ### Supported Chains diff --git a/sdk/payer-quickstart.mdx b/sdk/payer-quickstart.mdx index cded013..cdaa62b 100644 --- a/sdk/payer-quickstart.mdx +++ b/sdk/payer-quickstart.mdx @@ -1,6 +1,7 @@ --- title: "Quickstart for Payers" description: "Check payment state, request a refund, freeze a payment, and submit evidence." +icon: "user" --- ### Prerequisites @@ -129,26 +130,16 @@ const cancelTx = await payer.refund?.cancel(paymentInfo) console.log('Refund cancelled:', cancelTx) ``` -### Summary - -* Install `@x402r/sdk` and `viem` -* Create a payer client with the operator's deployment addresses -* Check payment state with `payment.getAmounts()` and `escrow.isDuringEscrow()` -* Request a refund with `refund.request()` -* Optionally freeze the payment with `freeze.freeze()` to block release -* Submit IPFS evidence with `evidence.submit()` -* Cancel with `refund.cancel()` if the issue is resolved - ---- - -**Next Steps:** - -* [Quickstart for Merchants](/sdk/merchant-quickstart): deploy an operator, authorize payments, release funds -* [Smart Contracts](/contracts/overview): conditions, recorders, and the operator architecture -* [Protocol Overview](/x402-integration/overview): escrow scheme and payment lifecycle - -**References:** - -* [Dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) -* [Payer examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer) -* [@x402r/sdk on npm](https://www.npmjs.com/package/@x402r/sdk) +## Next Steps + + + + Deploy an operator, authorize payments, release funds. + + + Review disputes, approve or deny refunds. + + + Runnable examples for every SDK operation. + + From 62eda3d53a544979eecf27983720727231ff134a Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 17:47:51 -0700 Subject: [PATCH 07/23] Use Mintlify Note component instead of bold Note pattern Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/arbiter-quickstart.mdx | 3 ++- sdk/merchant-quickstart.mdx | 3 ++- sdk/payer-quickstart.mdx | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/sdk/arbiter-quickstart.mdx b/sdk/arbiter-quickstart.mdx index 1cf4af1..2709b86 100644 --- a/sdk/arbiter-quickstart.mdx +++ b/sdk/arbiter-quickstart.mdx @@ -10,8 +10,9 @@ icon: "gavel" * Node.js 18+ and npm * An operator where your address is configured as the arbiter (see [Deploy an Operator](/sdk/deploy-operator)) -**Note**\ + There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts). + ### 1. Install Dependencies diff --git a/sdk/merchant-quickstart.mdx b/sdk/merchant-quickstart.mdx index 908bcdc..f1df679 100644 --- a/sdk/merchant-quickstart.mdx +++ b/sdk/merchant-quickstart.mdx @@ -10,8 +10,9 @@ icon: "store" * Node.js 18+ and npm * USDC on Base Sepolia for testing ([faucet](https://faucet.circle.com/)) -**Note**\ + There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [merchant examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/merchant) and full [scenario scripts](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios). + ### 1. Install Dependencies diff --git a/sdk/payer-quickstart.mdx b/sdk/payer-quickstart.mdx index cdaa62b..578770c 100644 --- a/sdk/payer-quickstart.mdx +++ b/sdk/payer-quickstart.mdx @@ -10,8 +10,9 @@ icon: "user" * Node.js 18+ and npm * An authorized payment on an x402r operator (see [Quickstart for Merchants](/sdk/merchant-quickstart)) -**Note**\ + There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [payer examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts). + ### 1. Install Dependencies From 0f33b70f3a27d2817231f0308a45451a3eca780f Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 17:49:35 -0700 Subject: [PATCH 08/23] Fix Next Steps links to point where readers actually go next - Merchant: Deploy Operator (full config), Protocol Overview, Examples - Payer: Arbiter Quickstart (what happens to your refund), Protocol Overview, Examples - Arbiter: Deploy Operator (how you're configured), Smart Contracts (access control), Examples Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/arbiter-quickstart.mdx | 10 +++++----- sdk/merchant-quickstart.mdx | 10 +++++----- sdk/payer-quickstart.mdx | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/sdk/arbiter-quickstart.mdx b/sdk/arbiter-quickstart.mdx index 2709b86..246edae 100644 --- a/sdk/arbiter-quickstart.mdx +++ b/sdk/arbiter-quickstart.mdx @@ -161,13 +161,13 @@ if (fees > 0n) { ## Next Steps - - Deploy an operator, authorize payments, release funds. + + How your address gets configured as arbiter on an operator. - - Request refunds, freeze payments, submit evidence. + + Conditions and recorders that control on-chain access. - Runnable examples for every SDK operation. + Full dispute resolution scenario end-to-end. diff --git a/sdk/merchant-quickstart.mdx b/sdk/merchant-quickstart.mdx index f1df679..0b8a18c 100644 --- a/sdk/merchant-quickstart.mdx +++ b/sdk/merchant-quickstart.mdx @@ -182,13 +182,13 @@ if (hasRefund) { ## Next Steps - - Request refunds, freeze payments, submit evidence. + + Full deployment config, slot details, and preview addresses. - - Review disputes, approve or deny refunds. + + How escrow, capture, and void work under the hood. - Runnable examples for every SDK operation. + Full scenario scripts to copy from. diff --git a/sdk/payer-quickstart.mdx b/sdk/payer-quickstart.mdx index 578770c..6d57f6c 100644 --- a/sdk/payer-quickstart.mdx +++ b/sdk/payer-quickstart.mdx @@ -134,13 +134,13 @@ console.log('Refund cancelled:', cancelTx) ## Next Steps - - Deploy an operator, authorize payments, release funds. - - Review disputes, approve or deny refunds. + What happens after you submit a refund request. + + + Escrow timing, capture, and the payment lifecycle. - Runnable examples for every SDK operation. + Full dispute resolution scenario end-to-end. From fecbb689f481f3a1e6a87469b82dcee6abe1de52 Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 22:57:23 -0700 Subject: [PATCH 09/23] Remove viem from install commands viem is a regular dependency of @x402r/sdk (not a peer dep), so it installs automatically. No need to list it explicitly. Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/arbiter-quickstart.mdx | 6 +++--- sdk/merchant-quickstart.mdx | 6 +++--- sdk/overview.mdx | 12 ++++++------ sdk/payer-quickstart.mdx | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/sdk/arbiter-quickstart.mdx b/sdk/arbiter-quickstart.mdx index 246edae..8edb490 100644 --- a/sdk/arbiter-quickstart.mdx +++ b/sdk/arbiter-quickstart.mdx @@ -18,13 +18,13 @@ There are pre-configured [examples in the x402r-sdk repo](https://github.com/Bac ```bash npm -npm install @x402r/sdk viem +npm install @x402r/sdk ``` ```bash pnpm -pnpm add @x402r/sdk viem +pnpm add @x402r/sdk ``` ```bash bun -bun add @x402r/sdk viem +bun add @x402r/sdk ``` diff --git a/sdk/merchant-quickstart.mdx b/sdk/merchant-quickstart.mdx index 0b8a18c..386dec7 100644 --- a/sdk/merchant-quickstart.mdx +++ b/sdk/merchant-quickstart.mdx @@ -18,13 +18,13 @@ There are pre-configured [examples in the x402r-sdk repo](https://github.com/Bac ```bash npm -npm install @x402r/sdk @x402r/core viem +npm install @x402r/sdk @x402r/core ``` ```bash pnpm -pnpm add @x402r/sdk @x402r/core viem +pnpm add @x402r/sdk @x402r/core ``` ```bash bun -bun add @x402r/sdk @x402r/core viem +bun add @x402r/sdk @x402r/core ``` diff --git a/sdk/overview.mdx b/sdk/overview.mdx index def57b9..27af086 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -16,13 +16,13 @@ Three roles interact with the protocol: ```bash npm -npm install @x402r/sdk viem +npm install @x402r/sdk ``` ```bash pnpm -pnpm add @x402r/sdk viem +pnpm add @x402r/sdk ``` ```bash bun -bun add @x402r/sdk viem +bun add @x402r/sdk ``` @@ -32,13 +32,13 @@ For low-level access to contract ABIs and deploy utilities: ```bash npm -npm install @x402r/core viem +npm install @x402r/core ``` ```bash pnpm -pnpm add @x402r/core viem +pnpm add @x402r/core ``` ```bash bun -bun add @x402r/core viem +bun add @x402r/core ``` diff --git a/sdk/payer-quickstart.mdx b/sdk/payer-quickstart.mdx index 6d57f6c..50d99de 100644 --- a/sdk/payer-quickstart.mdx +++ b/sdk/payer-quickstart.mdx @@ -18,13 +18,13 @@ There are pre-configured [examples in the x402r-sdk repo](https://github.com/Bac ```bash npm -npm install @x402r/sdk viem +npm install @x402r/sdk ``` ```bash pnpm -pnpm add @x402r/sdk viem +pnpm add @x402r/sdk ``` ```bash bun -bun add @x402r/sdk viem +bun add @x402r/sdk ``` From 8a2b99f3a85fb3a0980a94df7d8131c56d966648 Mon Sep 17 00:00:00 2001 From: vraspar Date: Sat, 28 Mar 2026 23:13:35 -0700 Subject: [PATCH 10/23] Remove deploy and authorize steps from merchant quickstart Deploy is a one-time setup (has its own page). Authorization is handled by the payer/facilitator, not the merchant directly. Merchant quickstart now starts from "payment arrived in escrow" which is the merchant's actual first interaction. Install simplified to just @x402r/sdk. Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/merchant-quickstart.mdx | 120 ++++++++---------------------------- 1 file changed, 25 insertions(+), 95 deletions(-) diff --git a/sdk/merchant-quickstart.mdx b/sdk/merchant-quickstart.mdx index 386dec7..916a7ae 100644 --- a/sdk/merchant-quickstart.mdx +++ b/sdk/merchant-quickstart.mdx @@ -1,6 +1,6 @@ --- title: "Quickstart for Merchants" -description: "Deploy an operator, accept a payment into escrow, and release funds." +description: "Accept a payment into escrow, check state, and release funds." icon: "store" --- @@ -8,7 +8,7 @@ icon: "store" * A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) * Node.js 18+ and npm -* USDC on Base Sepolia for testing ([faucet](https://faucet.circle.com/)) +* A deployed operator with escrow support (see [Deploy an Operator](/sdk/deploy-operator)) There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [merchant examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/merchant) and full [scenario scripts](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios). @@ -18,121 +18,52 @@ There are pre-configured [examples in the x402r-sdk repo](https://github.com/Bac ```bash npm -npm install @x402r/sdk @x402r/core +npm install @x402r/sdk ``` ```bash pnpm -pnpm add @x402r/sdk @x402r/core +pnpm add @x402r/sdk ``` ```bash bun -bun add @x402r/sdk @x402r/core +bun add @x402r/sdk ``` -### 2. Create viem Clients +### 2. Create a Merchant Client ```typescript import { createPublicClient, createWalletClient, http } from 'viem' import { baseSepolia } from 'viem/chains' import { privateKeyToAccount } from 'viem/accounts' +import { createMerchantClient } from '@x402r/sdk' const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) -const publicClient = createPublicClient({ - chain: baseSepolia, - transport: http(), -}) - -const walletClient = createWalletClient({ - account, - chain: baseSepolia, - transport: http(), -}) -``` - -### 3. Deploy an Operator - -Every x402r payment flows through a `PaymentOperator` contract. Deploy one with the marketplace preset: - -```typescript -import { deployMarketplaceOperator } from '@x402r/core' - -const deployment = await deployMarketplaceOperator(walletClient, publicClient, { - chainId: 84532, - feeRecipient: account.address, // receives operator fees - arbiter: '0xYourArbiterAddress', // resolves disputes - escrowPeriodSeconds: 604_800n, // 7 days - freezeDurationSeconds: 604_800n, // 7 days (set 0 to disable freeze) - operatorFeeBps: 50n, // 0.5% -}) - -console.log('Operator:', deployment.operatorAddress) -console.log('EscrowPeriod:', deployment.escrowPeriodAddress) -console.log('RefundRequest:', deployment.refundRequestAddress) -console.log('Freeze:', deployment.freezeAddress) -``` - -This deploys a `PaymentOperator` with escrow, freeze, and dispute resolution contracts. Save these addresses. You need them to create SDK clients. - -### 4. Create a Merchant Client - -```typescript -import { createMerchantClient } from '@x402r/sdk' - const merchant = createMerchantClient({ - publicClient, - walletClient, - operatorAddress: deployment.operatorAddress, - escrowPeriodAddress: deployment.escrowPeriodAddress, - refundRequestAddress: deployment.refundRequestAddress, - freezeAddress: deployment.freezeAddress, - refundRequestEvidenceAddress: deployment.refundRequestEvidenceAddress, + publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), + walletClient: createWalletClient({ + account, + chain: baseSepolia, + transport: http(), + }), + operatorAddress: '0x...', // from deploy result + escrowPeriodAddress: '0x...', // from deploy result + refundRequestAddress: '0x...', // from deploy result + freezeAddress: '0x...', // from deploy result + refundRequestEvidenceAddress: '0x...', // from deploy result }) ``` `createMerchantClient` restricts TypeScript types to merchant-relevant methods. `payment.release()` shows up in autocomplete; `refund.request()` does not because that is a payer action. -### 5. Authorize a Payment +### 3. Check Payment State -In the x402 flow, the **payer signs** an ERC-3009 authorization and the **merchant submits** it to the operator: +In the x402 flow, the payer signs an ERC-3009 authorization, the facilitator settles it on-chain, and funds land in escrow on your operator. Your first interaction as a merchant is checking what arrived: ```typescript -import { signReceiveAuthorization, type PaymentInfo } from '@x402r/core' - -// The payer builds and signs -const paymentInfo: PaymentInfo = { - operator: deployment.operatorAddress, - payer: '0xPayerAddress', - receiver: account.address, // merchant receives funds - token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // USDC on Base Sepolia - maxAmount: 1_000_000n, // 1 USDC (6 decimals) - preApprovalExpiry: 281_474_976_710_655, // far future - authorizationExpiry: 281_474_976_710_655, - refundExpiry: 281_474_976_710_655, - minFeeBps: 0, - maxFeeBps: 500, - feeReceiver: deployment.operatorAddress, - salt: 1n, -} - -const { tokenCollector, collectorData } = await signReceiveAuthorization({ - account: payerAccount, // payer's viem account - chainId: 84532, - paymentInfo, -}) - -// The merchant submits the authorization -const authTx = await merchant.payment.authorize( - paymentInfo, - 1_000_000n, // 1 USDC - tokenCollector, - collectorData, -) -console.log('Authorized:', authTx) -``` - -Funds are now locked in escrow. +import type { PaymentInfo } from '@x402r/sdk' -### 6. Check Payment State +// paymentInfo comes from the facilitator callback or your payment records +const paymentInfo: PaymentInfo = { /* ... */ } ```typescript const amounts = await merchant.payment.getAmounts(paymentInfo) @@ -144,7 +75,7 @@ const inEscrow = await merchant.escrow?.isDuringEscrow(paymentInfo) console.log('In escrow:', inEscrow) // true ``` -### 7. Release Funds After Escrow +### 4. Release Funds After Escrow Once the escrow period expires, release funds to the receiver (merchant): @@ -157,12 +88,11 @@ const after = await merchant.payment.getAmounts(paymentInfo) console.log('Capturable after release:', after.capturableAmount) // 0n ``` -### 8. Handle Refund Requests (Optional) +### 5. Handle Refund Requests (Optional) If a payer disputes, check for pending refund requests: ```typescript -// Check if a refund request exists const hasRefund = await merchant.refund?.has(paymentInfo) if (hasRefund) { From 05840cb6d141e3d2d9d6c4640210a371122806a4 Mon Sep 17 00:00:00 2001 From: vraspar Date: Sun, 29 Mar 2026 00:00:22 -0700 Subject: [PATCH 11/23] Address PR review: fix broken fence, wrong types, orphaned pages Review fixes: 1. Fix broken code fence in merchant-quickstart (nested opening) 2. Fix MarketplaceOperatorDeployment interface to match source: summary.newCount/existingCount, txHashes inside summary, remove non-existent arbiterConditionAddress 3. Delete orphaned pages (create-client, installation, typescript) that were not in nav, fixing em dash violations in those files 4. Fix "merchants authorize" to "merchants receive" in overview (payer signs, facilitator submits, not the merchant) 5. Fix Monad USDC checksum (lowercase a to uppercase A) 6. Redirect 7 broken /sdk/installation links to /sdk/overview Co-Authored-By: Claude Opus 4.6 (1M context) --- contracts/factories.mdx | 2 +- contracts/periphery/overview.mdx | 2 +- roadmap.mdx | 2 +- sdk/create-client.mdx | 167 ----------------------------- sdk/deploy-operator.mdx | 29 ++--- sdk/installation.mdx | 116 -------------------- sdk/merchant-quickstart.mdx | 1 - sdk/merchant/quickstart.mdx | 2 +- sdk/overview.mdx | 4 +- sdk/typescript.mdx | 148 ------------------------- x402-integration/comparison.mdx | 2 +- x402-integration/escrow-scheme.mdx | 2 +- x402-integration/overview.mdx | 2 +- 13 files changed, 24 insertions(+), 455 deletions(-) delete mode 100644 sdk/create-client.mdx delete mode 100644 sdk/installation.mdx delete mode 100644 sdk/typescript.mdx diff --git a/contracts/factories.mdx b/contracts/factories.mdx index 3a0b77e..dd8e5ff 100644 --- a/contracts/factories.mdx +++ b/contracts/factories.mdx @@ -632,7 +632,7 @@ const deployments = { Use the SDK's `deployMarketplaceOperator()` for simplified deployment. - + Install the SDK packages. diff --git a/contracts/periphery/overview.mdx b/contracts/periphery/overview.mdx index ee95400..454046b 100644 --- a/contracts/periphery/overview.mdx +++ b/contracts/periphery/overview.mdx @@ -52,7 +52,7 @@ All periphery contracts use **unified CREATE3 addresses** — the same address o | AlwaysTrueCondition | `0xb295df7E7f786fd84D614AB26b1f2e86026C3483` | -All addresses are available programmatically via `@x402r/core`'s `getChainConfig(chainId)`. See [SDK Installation](/sdk/installation) for details. +All addresses are available programmatically via `@x402r/core`'s `getChainConfig(chainId)`. See [SDK Overview](/sdk/overview) for details. ## Next Steps diff --git a/roadmap.mdx b/roadmap.mdx index 290045a..29575ba 100644 --- a/roadmap.mdx +++ b/roadmap.mdx @@ -112,7 +112,7 @@ All contracts use **unified CREATE3 addresses** — same address on every suppor | Condition singletons (Payer, Receiver, AlwaysTrue) | Deployed | -All contract addresses are available in `@x402r/core` via `getChainConfig(chainId)`. See the [Installation](/sdk/installation) page for details. +All contract addresses are available in `@x402r/core` via `getChainConfig(chainId)`. See the [SDK Overview](/sdk/overview) page for details. ## Get Involved diff --git a/sdk/create-client.mdx b/sdk/create-client.mdx deleted file mode 100644 index 7ed47b0..0000000 --- a/sdk/create-client.mdx +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: "Create a Client" -description: "Choose between the full client and role presets, then configure" -icon: "plug" ---- - -The SDK creates clients two ways: `createX402r()` gives you every action group with no type restrictions. The role presets — `createPayerClient()`, `createMerchantClient()`, `createArbiterClient()` — narrow TypeScript types so you only see methods relevant to your role. - -**Use a role preset** when your application has a single role. Most apps fall here. -**Use `createX402r()`** when you need multi-role access (e.g., a backend that authorizes payments *and* resolves disputes), read-only access without a `walletClient`, or admin tooling. - -## Role Presets - -### Payer - -```typescript -import { createPayerClient } from '@x402r/sdk' - -const payer = createPayerClient({ - publicClient, - walletClient, - operatorAddress: '0x...', // from deployMarketplaceOperator() - refundRequestAddress: '0x...', // from deploy result - escrowPeriodAddress: '0x...', // from deploy result - freezeAddress: '0x...', // from deploy result -}) - -const { capturableAmount } = await payer.payment.getAmounts(paymentInfo) -await payer.refund?.request(paymentInfo, capturableAmount) -``` - -Autocomplete shows `refund.request()` and `freeze.freeze()` but not `payment.authorize()` — payers don't authorize payments, merchants do. - -### Merchant - -```typescript -import { createMerchantClient } from '@x402r/sdk' - -const merchant = createMerchantClient({ - publicClient, - walletClient, - operatorAddress: '0x...', - escrowPeriodAddress: '0x...', -}) - -const inEscrow = await merchant.escrow?.isDuringEscrow(paymentInfo) -if (!inEscrow) { - await merchant.payment.release(paymentInfo, amount) -} -``` - -Shows `payment.release()`, `payment.charge()`, and `payment.authorize()` but not `refund.request()`. - -### Arbiter - -```typescript -import { createArbiterClient } from '@x402r/sdk' - -const arbiter = createArbiterClient({ - publicClient, - walletClient, - operatorAddress: '0x...', - refundRequestAddress: '0x...', - refundRequestEvidenceAddress: '0x...', - freezeAddress: '0x...', -}) - -await arbiter.payment.refundInEscrow(paymentInfo, amount) -// RefundRequest recorder auto-approves — no separate approve() call needed -``` - -Shows `refund.deny()`, `refund.refuse()`, `freeze.unfreeze()`, and `evidence.submit()`. Does not show `payment.authorize()` or `payment.charge()`. - - -All three presets require `walletClient`. Omit it and you get `ValidationError: walletClient is required for createPayerClient`. For read-only access, use `createX402r()` without a `walletClient`. - - -## Full Client - -```typescript -import { createX402r } from '@x402r/sdk' - -const client = createX402r({ - publicClient, - walletClient, // optional — omit for read-only - operatorAddress: '0x...', - refundRequestAddress: '0x...', - refundRequestEvidenceAddress: '0x...', - escrowPeriodAddress: '0x...', - freezeAddress: '0x...', - paymentIndexRecorderAddress: '0x...', -}) -``` - -Every action group is available (subject to addresses being provided). No type narrowing. - -## Config Reference - -| Field | Type | Required | Notes | -|-------|------|:--------:|-------| -| `publicClient` | `PublicClient` | Yes | viem public client for reads | -| `walletClient` | `WalletClient` | No | Required for writes. Role presets throw without it. | -| `operatorAddress` | `Address` | Yes | Your deployed PaymentOperator. From `deployMarketplaceOperator()`. | -| `chainId` | `number` | No | Auto-detected from `publicClient.chain`. Override for chains without viem definitions. | -| `network` | `string` | No | CAIP-2 fallback (`'eip155:84532'`). Used only if `chainId` is absent. | -| `refundRequestAddress` | `Address` | No | Activates `refund` group | -| `refundRequestEvidenceAddress` | `Address` | No | Activates `evidence` group. Requires `refundRequestAddress`. | -| `escrowPeriodAddress` | `Address` | No | Activates `escrow` group | -| `freezeAddress` | `Address` | No | Activates `freeze` group | -| `paymentIndexRecorderAddress` | `Address` | No | Activates `query` group | -| `paymentStore` | `PaymentStore` | No | Pluggable payment store for `query` lookups | -| `eventFromBlock` | `bigint` | No | Starting block for event-based payment lookups. Set to operator deploy block. | - -The optional address fields (`escrowPeriodAddress`, `freezeAddress`, etc.) are per-operator — they come from your deploy result, not from chain config. Omit any you didn't deploy and the corresponding action group is `undefined` on the client. - -## Check Conditions - -```typescript -const allowed = await client.canExecute('release', paymentInfo, amount) -``` - -Calls `ICondition.check()` on the condition configured for that slot. Returns `true` if no condition is set (zero address). Check before submitting transactions to avoid reverts. - -## Payment Store - -```typescript -import { createX402r, createMemoryStore } from '@x402r/sdk' - -const client = createX402r({ - publicClient, - operatorAddress: '0x...', - paymentStore: createMemoryStore(), -}) -``` - -`createMemoryStore()` is an in-memory `PaymentStore` for development. Data is lost when the process exits. For production, implement the `PaymentStore` interface with your database. - -## Extend - -Add custom action groups or fill undefined slots with `.extend()`: - -```typescript -import { createX402r, queryActions } from '@x402r/sdk' - -const client = createX402r({ publicClient, operatorAddress: '0x...' }) - -// query group was undefined because no paymentIndexRecorderAddress was provided -const extended = client.extend( - queryActions('0xRecorderAddress...', { eventFromBlock: 100000n }) -) - -// extended.query is now defined -const payments = await extended.query.getPayerPayments(payerAddress) -``` - -Built-in properties cannot be overridden by extensions. Extensions can only fill slots that are `undefined` on the base client. - -## Next Steps - - - - Role narrowing details, type imports, and error types. - - - Get operator and condition addresses for your config. - - diff --git a/sdk/deploy-operator.mdx b/sdk/deploy-operator.mdx index 1b8a385..8e752b2 100644 --- a/sdk/deploy-operator.mdx +++ b/sdk/deploy-operator.mdx @@ -117,8 +117,8 @@ console.log('Operator:', result.operatorAddress) console.log('EscrowPeriod:', result.escrowPeriodAddress) console.log('RefundRequest:', result.refundRequestAddress) console.log('Freeze:', result.freezeAddress) -console.log('New deployments:', result.summary.newDeployments) -console.log('Existing (reused):', result.summary.existingContracts) +console.log('New deployments:', result.summary.newCount) +console.log('Existing (reused):', result.summary.existingCount) ``` ## Configuration Options @@ -137,19 +137,20 @@ The `deployMarketplaceOperator` function returns: ```typescript interface MarketplaceOperatorDeployment { - operatorAddress: Address; // The PaymentOperator - escrowPeriodAddress: Address; // EscrowPeriod recorder/condition - freezeAddress: Address; // Freeze condition - refundRequestAddress: Address; // RefundRequest recorder - refundRequestEvidenceAddress: Address; // Evidence storage - arbiterConditionAddress: Address; // StaticAddressCondition for arbiter - refundInEscrowConditionAddress: Address; // OR(Receiver, Arbiter) - feeCalculatorAddress: Address | null; // null if no fee - txHashes: Hash[]; // All deployment tx hashes + operatorAddress: Address + escrowPeriodAddress: Address + freezeAddress: Address | null + refundRequestAddress: Address + refundRequestEvidenceAddress: Address + refundInEscrowConditionAddress: Address // OR(Receiver, Arbiter) + feeCalculatorAddress: Address | null // null if no fee + operatorConfig: OperatorConfig + deployments: DeployResult[] summary: { - newDeployments: number; // Newly deployed contracts - existingContracts: number; // Reused existing contracts - }; + newCount: number + existingCount: number + txHashes: `0x${string}`[] + } } ``` diff --git a/sdk/installation.mdx b/sdk/installation.mdx deleted file mode 100644 index 1220b2e..0000000 --- a/sdk/installation.mdx +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: "Installation" -description: "Install the x402r SDK and set up viem clients" -icon: "download" ---- - - -```bash npm -npm install @x402r/sdk viem -``` -```bash pnpm -pnpm add @x402r/sdk viem -``` -```bash bun -bun add @x402r/sdk viem -``` - - -This installs the full SDK and [viem](https://viem.sh), the only peer dependency. - - - Low-level access to types, ABIs, and deploy utilities without the client factory: - - - ```bash npm - npm install @x402r/core viem - ``` - ```bash pnpm - pnpm add @x402r/core viem - ``` - ```bash bun - bun add @x402r/core viem - ``` - - - x402 server integration — marks payment options as refundable: - - - ```bash npm - npm install @x402r/helpers @x402r/evm - ``` - ```bash pnpm - pnpm add @x402r/helpers @x402r/evm - ``` - ```bash bun - bun add @x402r/helpers @x402r/evm - ``` - - - - `@x402r/evm` is a peer dependency of `@x402r/helpers`. It provides escrow scheme types used by the `refundable()` helper. - - - -## Set Up viem Clients - -The SDK operates through viem's `PublicClient` (reads) and `WalletClient` (writes). Create both for your target chain: - -```typescript -import { createPublicClient, createWalletClient, http } from 'viem' -import { baseSepolia } from 'viem/chains' -import { privateKeyToAccount } from 'viem/accounts' - -const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) - -const publicClient = createPublicClient({ - chain: baseSepolia, - transport: http(), -}) - -const walletClient = createWalletClient({ - account, - chain: baseSepolia, - transport: http(), -}) -``` - - -Never commit private keys. Use environment variables or a secrets manager. - - -For custom transports, batch config, or other client options, see the [viem client docs](https://viem.sh/docs/clients/public). - -## Get Chain Config - -```typescript -import { getChainConfig } from '@x402r/sdk' - -const config = getChainConfig(84532) // Base Sepolia -``` - -Returns protocol addresses, USDC, factory addresses, and condition singletons for the chain: - -```typescript -config.usdc // 0x036CbD53842c5426634e7929541eC2318f3dCF7e -config.authCaptureEscrow // escrow contract (same on every chain) -config.factories // 12 factory addresses (same on every chain) -config.conditions // 3 condition singletons (same on every chain) -``` - -USDC is the only address that differs between chains. Everything else is identical via CREATE3. - - -V2 uses numeric chain IDs (`84532`). The CAIP-2 format (`'eip155:84532'`) is only needed for `toNetworkId()` / `fromNetworkId()` interop. - - -## Next Steps - - - - Pick between `createX402r()` and role presets. - - - Type imports, role narrowing, and error types. - - diff --git a/sdk/merchant-quickstart.mdx b/sdk/merchant-quickstart.mdx index 916a7ae..f92c59d 100644 --- a/sdk/merchant-quickstart.mdx +++ b/sdk/merchant-quickstart.mdx @@ -65,7 +65,6 @@ import type { PaymentInfo } from '@x402r/sdk' // paymentInfo comes from the facilitator callback or your payment records const paymentInfo: PaymentInfo = { /* ... */ } -```typescript const amounts = await merchant.payment.getAmounts(paymentInfo) console.log('Collected:', amounts.hasCollectedPayment) // true console.log('Capturable:', amounts.capturableAmount) // 1000000n diff --git a/sdk/merchant/quickstart.mdx b/sdk/merchant/quickstart.mdx index 4929d3f..84aa102 100644 --- a/sdk/merchant/quickstart.mdx +++ b/sdk/merchant/quickstart.mdx @@ -18,7 +18,7 @@ npm install @x402r/merchant @x402r/helpers @x402r/core viem ## Setup -Create viem clients as described in [Installation](/sdk/installation), then: +Create viem clients as described in [Installation](/sdk/overview), then: ```typescript import { X402rMerchant } from '@x402r/merchant'; diff --git a/sdk/overview.mdx b/sdk/overview.mdx index 27af086..9cc91ea 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -8,7 +8,7 @@ x402 payments are instant and irreversible. x402r adds escrow holds, refund wind Three roles interact with the protocol: -- **Merchants** authorize payments into escrow and release funds after delivery +- **Merchants** receive payments into escrow and release funds after delivery - **Payers** can request refunds, freeze payments, and submit evidence during disputes - **Arbiters** review disputes and approve or deny refund requests @@ -86,5 +86,5 @@ All contracts are deployed to identical addresses on every chain via CREATE3. On | Optimism | `10` | `0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85` | | Celo | `42220` | `0xcebA9300f2b948710d2653dD7B07f33A8B32118C` | | Avalanche C-Chain | `43114` | `0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E` | -| Monad | `143` | `0x754704Bc059F8C67012fEd69BC8a327a5aafb603` | +| Monad | `143` | `0x754704Bc059F8C67012fEd69BC8A327a5aafb603` | | Linea | `59144` | `0x176211869cA2b568f2A7D4EE941E073a821EE1ff` | diff --git a/sdk/typescript.mdx b/sdk/typescript.mdx deleted file mode 100644 index 79669e6..0000000 --- a/sdk/typescript.mdx +++ /dev/null @@ -1,148 +0,0 @@ ---- -title: "TypeScript" -description: "Type imports, role narrowing tables, and error types" -icon: "code" ---- - -TypeScript 5.0+ with `strict: true` and `moduleResolution: "bundler"` or `"node16"`. - -## Type Imports - -Client types and action group interfaces from `@x402r/sdk`: - -```typescript -import type { - X402r, - X402rConfig, - PayerClient, - MerchantClient, - ArbiterClient, - PaymentActions, - RefundActions, - EscrowActions, - EvidenceActions, - FreezeActions, - QueryActions, - OperatorActions, - WatchActions, - PaymentStore, - ResolvedConfig, -} from '@x402r/sdk' -``` - -Domain types re-exported from `@x402r/core`: - -```typescript -import type { - PaymentInfo, - PaymentAmounts, - ConditionSlot, - OperatorSlots, - FeeAddresses, - FeeCalculationResult, - RefundRequestData, - EvidenceEntry, - X402rChainConfig, -} from '@x402r/sdk' // re-exported — no need to depend on @x402r/core directly -``` - -## Role Narrowing - -Each role preset restricts which methods appear in autocomplete via TypeScript `Pick` types. The runtime client is identical — narrowing is a DX convenience only, not a security boundary. - -### PayerClient - -| Group | Available Methods | -|-------|-------------------| -| `payment` | `getState`, `getAmounts` | -| `refund` | `request`, `cancel`, `get`, `getByKey`, `getStatus`, `has`, `getStoredPaymentInfo`, `getPayerRequests`, `getCancelCount`, `getCancelledAmount` | -| `freeze` | `freeze`, `isFrozen` | -| `evidence` | All | -| `escrow` | All | -| `query` | All | -| `operator` | `getConfig`, `getFeeAddresses` | -| `watch` | All | - -### MerchantClient - -| Group | Available Methods | -|-------|-------------------| -| `payment` | `authorize`, `charge`, `release`, `refundInEscrow`, `refundPostEscrow`, `approvePostEscrowRefund`, `getPostEscrowRefundAllowance`, `getState`, `getAmounts` | -| `refund` | `get`, `getByKey`, `getStatus`, `has`, `getStoredPaymentInfo`, `getReceiverRequests`, `getCancelCount`, `getCancelledAmount` | -| `freeze` | `isFrozen` | -| `evidence` | All | -| `escrow` | All | -| `query` | All | -| `operator` | All | -| `watch` | All | - -### ArbiterClient - -| Group | Available Methods | -|-------|-------------------| -| `payment` | `refundInEscrow`, `getState`, `getAmounts` | -| `refund` | `deny`, `refuse`, `get`, `getByKey`, `getStatus`, `has`, `getStoredPaymentInfo`, `getOperatorRequests`, `getCancelCount`, `getCancelledAmount` | -| `freeze` | `unfreeze`, `isFrozen` | -| `evidence` | All | -| `escrow` | All | -| `query` | All | -| `operator` | `getConfig`, `getFeeAddresses`, `getAccumulatedProtocolFees`, `distributeFees` | -| `watch` | All | - - -A payer *could* use `createX402r()` and call `payment.authorize()`, but the on-chain condition would revert the transaction. Type narrowing prevents mistakes; [conditions](/contracts/conditions/overview) enforce access. - - -## Conditional Action Groups - -`escrow`, `refund`, `evidence`, `freeze`, and `query` are typed as `T | undefined`. They are `undefined` when the corresponding address is not in the config: - -```typescript -const client = createX402r({ - publicClient, - operatorAddress: '0x...', - // no escrowPeriodAddress -}) - -client.escrow // EscrowActions | undefined - -// Use optional chaining -const inEscrow = await client.escrow?.isDuringEscrow(paymentInfo) -``` - -This reflects the protocol: these contracts are periphery. Not every operator deploys all of them. If you know the address exists, provide it in the config and the group will be defined. - -`payment`, `operator`, and `watch` are always defined. - -## Error Types - -Three error classes, all extending `X402rError`: - -| Error | When | -|-------|------| -| `ValidationError` | Missing `walletClient` on a role preset. `refundRequestEvidenceAddress` without `refundRequestAddress`. Chain cannot be determined from config. | -| `ConfigError` | `chainId` is not in the supported chains list. | -| `ContractCallError` | A contract read or write call fails on-chain (revert, out of gas, etc.). | - -```typescript -import { ValidationError, ConfigError, ContractCallError } from '@x402r/sdk' - -try { - const payer = createPayerClient({ publicClient, operatorAddress: '0x...' }) -} catch (e) { - if (e instanceof ValidationError) { - // "walletClient is required for createPayerClient" - } -} -``` - -## Next Steps - - - - Get operator and condition addresses for your client config. - - - On-chain architecture, conditions, and recorders. - - diff --git a/x402-integration/comparison.mdx b/x402-integration/comparison.mdx index d6b34c8..41da4ae 100644 --- a/x402-integration/comparison.mdx +++ b/x402-integration/comparison.mdx @@ -366,7 +366,7 @@ flowchart TD Understand operator implementations. - + Build your first payment flow. diff --git a/x402-integration/escrow-scheme.mdx b/x402-integration/escrow-scheme.mdx index d082a1a..0428249 100644 --- a/x402-integration/escrow-scheme.mdx +++ b/x402-integration/escrow-scheme.mdx @@ -405,7 +405,7 @@ See [Comparison](/x402-integration/comparison) for detailed trade-offs. Learn about escrow and operator contracts. - + Build your first escrow-based payment flow. diff --git a/x402-integration/overview.mdx b/x402-integration/overview.mdx index b97f466..3eb8d88 100644 --- a/x402-integration/overview.mdx +++ b/x402-integration/overview.mdx @@ -217,7 +217,7 @@ Smart contract that controls capture/void logic. Different operators enable diff Understand the escrow and operator contracts. - + Get started building with x402r. From a4a6757cd6299f9f042f10dbb588bf251b2290df Mon Sep 17 00:00:00 2001 From: vraspar Date: Mon, 30 Mar 2026 23:42:36 -0700 Subject: [PATCH 12/23] Add two arbiter models: dispute resolution and delivery protection - arbiter-quickstart: landing page explaining both models with comparison table, links to the two tutorials - arbiter-dispute: existing dispute flow (moved from arbiter-quickstart) - arbiter-delivery: new page for automated evaluation flow using deployDeliveryProtectionOperator() and forwardToArbiter() - overview: add "Two Operator Models" section - docs.json: add arbiter-dispute and arbiter-delivery to nav Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 4 +- sdk/arbiter-delivery.mdx | 157 ++++++++++++++++++++++++++++++++ sdk/arbiter-dispute.mdx | 171 +++++++++++++++++++++++++++++++++++ sdk/arbiter-quickstart.mdx | 178 +++++-------------------------------- sdk/overview.mdx | 8 +- 5 files changed, 361 insertions(+), 157 deletions(-) create mode 100644 sdk/arbiter-delivery.mdx create mode 100644 sdk/arbiter-dispute.mdx diff --git a/docs.json b/docs.json index d71aa00..2c3343c 100644 --- a/docs.json +++ b/docs.json @@ -92,7 +92,9 @@ "sdk/overview", "sdk/merchant-quickstart", "sdk/payer-quickstart", - "sdk/arbiter-quickstart" + "sdk/arbiter-quickstart", + "sdk/arbiter-dispute", + "sdk/arbiter-delivery" ] }, { diff --git a/sdk/arbiter-delivery.mdx b/sdk/arbiter-delivery.mdx new file mode 100644 index 0000000..f96d28b --- /dev/null +++ b/sdk/arbiter-delivery.mdx @@ -0,0 +1,157 @@ +--- +title: "Delivery Protection Arbiter" +description: "Automatically evaluate every transaction and release or let funds expire." +icon: "shield-check" +--- + +In this model, the arbiter is the only address that can release funds. The merchant sets up a `forwardToArbiter()` hook that sends the HTTP response body to your arbiter service after every payment. Your service evaluates the response (AI, schema validation, heuristics) and calls `release()` if it passes. If you do not release, funds auto-refund to the payer after escrow expires. + +### Prerequisites + +* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) +* Node.js 18+ and npm + + +There is a full [AI garbage detector example](https://github.com/BackTrackCo/arbiter-examples) that implements this pattern with heuristic + LLM evaluation. + + +### 1. Install Dependencies + + +```bash npm +npm install @x402r/sdk @x402r/core +``` +```bash pnpm +pnpm add @x402r/sdk @x402r/core +``` +```bash bun +bun add @x402r/sdk @x402r/core +``` + + +`@x402r/core` is needed for `deployDeliveryProtectionOperator()`. + +### 2. Deploy a Delivery Protection Operator + +```typescript +import { deployDeliveryProtectionOperator } from '@x402r/core' + +const deployment = await deployDeliveryProtectionOperator( + walletClient, + publicClient, + { + chainId: 84532, + arbiter: account.address, // your arbiter service wallet + feeRecipient: account.address, // receives protocol fees + escrowPeriodSeconds: 300n, // 5 minutes (short for automated flows) + }, +) + +console.log('Operator:', deployment.operatorAddress) +console.log('EscrowPeriod:', deployment.escrowPeriodAddress) +console.log('ArbiterCondition:', deployment.arbiterConditionAddress) +``` + +This is simpler than the marketplace preset. No RefundRequest, Evidence, or Freeze contracts. The operator's `releaseCondition` is set to `StaticAddressCondition(arbiter)`, so only your arbiter address can call `release()`. + +### 3. Merchant Setup: Forward to Arbiter + +The merchant configures their x402 resource server to forward response data to your arbiter after every payment settlement: + +```typescript +import { forwardToArbiter } from '@x402r/helpers' + +const resourceServer = new x402ResourceServer(facilitatorClient) + .register(networkId, new CommerceEvmScheme(serverConfig)) + .onAfterSettle( + forwardToArbiter('http://your-arbiter:3001', { + onError: (err) => console.error('Arbiter unreachable:', err), + }) + ) +``` + +`forwardToArbiter()` POSTs to `{arbiterUrl}/verify` with: + +```json +{ + "responseBody": "the HTTP response body as a string", + "transaction": "0xsettlement_tx_hash", + "paymentPayload": { "x402Version": 1, "scheme": "commerce", "payload": "..." } +} +``` + +Fire-and-forget. Does not block the response to the client. Only fires for `commerce` scheme settlements. + +### 4. Build the Arbiter Service + +Your arbiter service receives the POST, evaluates the response, and calls `release()` if it passes: + +```typescript +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { createX402r } from '@x402r/sdk' + +const account = privateKeyToAccount(process.env.ARBITER_PRIVATE_KEY as `0x${string}`) + +const arbiter = createX402r({ + publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), + walletClient: createWalletClient({ + account, + chain: baseSepolia, + transport: http(), + }), + operatorAddress: deployment.operatorAddress, + escrowPeriodAddress: deployment.escrowPeriodAddress, +}) + +// Express route handling the POST from forwardToArbiter() +app.post('/verify', async (req, res) => { + const { responseBody, transaction, paymentPayload } = req.body + + // Your evaluation logic here + const passed = await evaluate(responseBody) + + if (passed) { + // Extract paymentInfo from paymentPayload + const paymentInfo = extractPaymentInfo(paymentPayload) + const amounts = await arbiter.payment.getAmounts(paymentInfo) + + await arbiter.payment.release(paymentInfo, amounts.capturableAmount) + res.json({ verdict: 'PASS' }) + } else { + // Do nothing. Funds auto-refund after escrow expires. + res.json({ verdict: 'FAIL' }) + } +}) +``` + +The `evaluate()` function is where your logic lives. It could be: +- **Heuristic checks:** HTTP status code, response size, content-type validation +- **AI evaluation:** send response body to an LLM and ask "is this a valid response?" +- **Schema validation:** check if the response matches an expected JSON schema + +### 5. What Happens on Failure + +If the arbiter does not call `release()`, the payer can reclaim funds after the escrow period expires by calling `refundInEscrow()`. The escrow period acts as the verification window. + +```typescript +// Anyone can call this after escrow expires (gated by EscrowPeriod condition) +await payer.payment.refundInEscrow(paymentInfo, amount) +``` + +No dispute process needed. The timeout is the safety net. + +## Next Steps + + + + For human-reviewed disputes instead of automated evaluation. + + + Full deployment config and condition slot details. + + + Runnable examples for every SDK operation. + + diff --git a/sdk/arbiter-dispute.mdx b/sdk/arbiter-dispute.mdx new file mode 100644 index 0000000..46e83a1 --- /dev/null +++ b/sdk/arbiter-dispute.mdx @@ -0,0 +1,171 @@ +--- +title: "Dispute Resolution Arbiter" +description: "Review refund requests, approve or deny refunds, and distribute fees." +icon: "scale-balanced" +--- + +### Prerequisites + +* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) +* Node.js 18+ and npm +* A marketplace operator where your address is configured as the arbiter (see [Deploy an Operator](/sdk/deploy-operator)) + + +There are pre-configured [arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) in the SDK repo. + + +### 1. Install Dependencies + + +```bash npm +npm install @x402r/sdk +``` +```bash pnpm +pnpm add @x402r/sdk +``` +```bash bun +bun add @x402r/sdk +``` + + +### 2. Create an Arbiter Client + +```typescript +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { createArbiterClient } from '@x402r/sdk' + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) + +const arbiter = createArbiterClient({ + publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), + walletClient: createWalletClient({ + account, + chain: baseSepolia, + transport: http(), + }), + operatorAddress: '0x...', // from deploy result + refundRequestAddress: '0x...', // from deploy result + refundRequestEvidenceAddress: '0x...', // from deploy result + escrowPeriodAddress: '0x...', + freezeAddress: '0x...', +}) +``` + +### 3. Check for Pending Refund Requests + +```typescript +import type { PaymentInfo } from '@x402r/sdk' + +const paymentInfo: PaymentInfo = { /* ... */ } + +const hasRefund = await arbiter.refund?.has(paymentInfo) +if (hasRefund) { + const request = await arbiter.refund?.get(paymentInfo) + console.log('Amount:', request?.amount) + console.log('Status:', request?.status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Refused +} +``` + +To list all refund requests for your operator: + +```typescript +const requests = await arbiter.refund?.getOperatorRequests( + arbiter.config.operatorAddress, + 0n, // offset + 10n, // count +) +console.log('Pending requests:', requests) +``` + +### 4. Review Evidence + +Both payers and merchants can submit evidence as IPFS CIDs. Read all entries before making a decision: + +```typescript +const count = await arbiter.evidence?.count(paymentInfo) +console.log('Evidence entries:', count) + +const batch = await arbiter.evidence?.getBatch(paymentInfo, 0n, count!) + +for (const entry of batch!.entries) { + console.log('CID:', entry.cid) + console.log('Submitter:', entry.submitter) + console.log('Timestamp:', entry.timestamp) +} +``` + +### 5. Approve a Refund + +Call `payment.refundInEscrow()` to approve. The RefundRequest recorder auto-approves the pending request, so there is no separate `approve()` call: + +```typescript +const tx = await arbiter.payment.refundInEscrow(paymentInfo, request!.amount) +console.log('Refund approved:', tx) + +// Verify +const approved = await arbiter.refund?.get(paymentInfo) +console.log('Approved amount:', approved?.approvedAmount) +console.log('Status:', approved?.status) // 1 = Approved +``` + +### 6. Deny a Refund Request + +If the evidence does not support a refund: + +```typescript +const denyTx = await arbiter.refund?.deny(paymentInfo) +console.log('Refund denied:', denyTx) +``` + +Or decline to rule entirely: + +```typescript +const refuseTx = await arbiter.refund?.refuse(paymentInfo) +console.log('Declined to rule:', refuseTx) +``` + +### 7. Unfreeze a Payment + +If the payer froze the payment during the dispute, unfreeze it after resolution: + +```typescript +const frozen = await arbiter.freeze?.isFrozen(paymentInfo) +if (frozen) { + const tx = await arbiter.freeze?.unfreeze(paymentInfo) + console.log('Unfrozen:', tx) +} +``` + +### 8. Distribute Accumulated Fees + +Protocol fees accumulate on the operator when payments are released: + +```typescript +import { getChainConfig } from '@x402r/sdk' + +const config = getChainConfig(84532) + +const fees = await arbiter.operator.getAccumulatedProtocolFees(config.usdc) +console.log('Accumulated fees:', fees) + +if (fees > 0n) { + const tx = await arbiter.operator.distributeFees(config.usdc) + console.log('Fees distributed:', tx) +} +``` + +## Next Steps + + + + Automated evaluation for every transaction. + + + How your address gets configured as arbiter on an operator. + + + Full dispute resolution scenario end-to-end. + + diff --git a/sdk/arbiter-quickstart.mdx b/sdk/arbiter-quickstart.mdx index 8edb490..6c64daa 100644 --- a/sdk/arbiter-quickstart.mdx +++ b/sdk/arbiter-quickstart.mdx @@ -1,173 +1,41 @@ --- title: "Quickstart for Arbiters" -description: "Review disputes, approve or deny refunds, and distribute fees." +description: "Two arbiter models: dispute resolution and delivery protection." icon: "gavel" --- -### Prerequisites +x402r supports two arbiter models depending on your use case. -* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) -* Node.js 18+ and npm -* An operator where your address is configured as the arbiter (see [Deploy an Operator](/sdk/deploy-operator)) +### Marketplace (Dispute Resolution) - -There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts). - +The merchant releases funds after escrow. If the payer contests, they file a refund request. The arbiter reviews evidence and approves or denies the refund. -### 1. Install Dependencies +Uses `deployMarketplaceOperator()`. Includes RefundRequest, Evidence, and Freeze contracts. - -```bash npm -npm install @x402r/sdk -``` -```bash pnpm -pnpm add @x402r/sdk -``` -```bash bun -bun add @x402r/sdk -``` - +**When to use:** general-purpose commerce, human arbitration (e.g. Kleros), cases where most transactions are uncontested. -### 2. Create an Arbiter Client +### Delivery Protection -```typescript -import { createPublicClient, createWalletClient, http } from 'viem' -import { baseSepolia } from 'viem/chains' -import { privateKeyToAccount } from 'viem/accounts' -import { createArbiterClient } from '@x402r/sdk' +The arbiter evaluates every transaction automatically. Only the arbiter can release funds. If the arbiter does not release, funds auto-refund to the payer after escrow expires. -const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) +Uses `deployDeliveryProtectionOperator()`. No RefundRequest or Freeze needed. -const arbiter = createArbiterClient({ - publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), - walletClient: createWalletClient({ - account, - chain: baseSepolia, - transport: http(), - }), - operatorAddress: '0x...', // your operator address - refundRequestAddress: '0x...', // from deploy result - refundRequestEvidenceAddress: '0x...', // from deploy result - escrowPeriodAddress: '0x...', - freezeAddress: '0x...', -}) -``` +**When to use:** AI content verification (garbage detection), schema validation, any flow where every response needs automated quality checks. -`createArbiterClient` restricts TypeScript types to arbiter-relevant methods. `refund.deny()` and `freeze.unfreeze()` show up in autocomplete; `payment.authorize()` does not because that is a merchant action. +| | Marketplace | Delivery Protection | +|---|---|---| +| Who releases funds | Merchant (after escrow) | Arbiter only | +| Dispute process | Payer files refund request | No disputes needed | +| Arbiter involvement | Only on disputes | Every transaction | +| Contracts needed | Operator + EscrowPeriod + RefundRequest + Evidence + Freeze | Operator + EscrowPeriod + StaticAddressCondition | +| Speed | Arbiter acts on demand | Arbiter acts automatically | +| Deploy preset | `deployMarketplaceOperator()` | `deployDeliveryProtectionOperator()` | -### 3. Check for Pending Refund Requests - -```typescript -import type { PaymentInfo } from '@x402r/sdk' - -const paymentInfo: PaymentInfo = { /* ... */ } - -const hasRefund = await arbiter.refund?.has(paymentInfo) -if (hasRefund) { - const request = await arbiter.refund?.get(paymentInfo) - console.log('Amount:', request?.amount) - console.log('Status:', request?.status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Refused -} -``` - -To list all refund requests for your operator: - -```typescript -const requests = await arbiter.refund?.getOperatorRequests( - arbiter.config.operatorAddress, - 0n, // offset - 10n, // count -) -console.log('Pending requests:', requests) -``` - -### 4. Review Evidence - -Both payers and merchants can submit evidence as IPFS CIDs. Read all entries before making a decision: - -```typescript -const count = await arbiter.evidence?.count(paymentInfo) -console.log('Evidence entries:', count) - -const batch = await arbiter.evidence?.getBatch(paymentInfo, 0n, count!) - -for (const entry of batch!.entries) { - console.log('CID:', entry.cid) - console.log('Submitter:', entry.submitter) - console.log('Timestamp:', entry.timestamp) -} -``` - -### 5. Approve a Refund - -Call `payment.refundInEscrow()` to approve. The RefundRequest recorder auto-approves the pending request, so there is no separate `approve()` call: - -```typescript -const tx = await arbiter.payment.refundInEscrow(paymentInfo, request!.amount) -console.log('Refund approved:', tx) - -// Verify -const approved = await arbiter.refund?.get(paymentInfo) -console.log('Approved amount:', approved?.approvedAmount) -console.log('Status:', approved?.status) // 1 = Approved -``` - -### 6. Deny a Refund Request - -If the evidence does not support a refund: - -```typescript -const denyTx = await arbiter.refund?.deny(paymentInfo) -console.log('Refund denied:', denyTx) -``` - -Or decline to rule entirely: - -```typescript -const refuseTx = await arbiter.refund?.refuse(paymentInfo) -console.log('Declined to rule:', refuseTx) -``` - -### 7. Unfreeze a Payment - -If the payer froze the payment during the dispute, unfreeze it after resolution: - -```typescript -const frozen = await arbiter.freeze?.isFrozen(paymentInfo) -if (frozen) { - const tx = await arbiter.freeze?.unfreeze(paymentInfo) - console.log('Unfrozen:', tx) -} -``` - -### 8. Distribute Accumulated Fees - -Protocol fees accumulate on the operator when payments are released. Any role can distribute them: - -```typescript -import { getChainConfig } from '@x402r/sdk' - -const config = getChainConfig(84532) - -const fees = await arbiter.operator.getAccumulatedProtocolFees(config.usdc) -console.log('Accumulated fees:', fees) - -if (fees > 0n) { - const tx = await arbiter.operator.distributeFees(config.usdc) - console.log('Fees distributed:', tx) -} -``` - -## Next Steps - - - - How your address gets configured as arbiter on an operator. - - - Conditions and recorders that control on-chain access. + + + Review refund requests, evidence, approve or deny. - - Full dispute resolution scenario end-to-end. + + Evaluate every transaction, release or let expire. diff --git a/sdk/overview.mdx b/sdk/overview.mdx index 9cc91ea..209becb 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -10,7 +10,13 @@ Three roles interact with the protocol: - **Merchants** receive payments into escrow and release funds after delivery - **Payers** can request refunds, freeze payments, and submit evidence during disputes -- **Arbiters** review disputes and approve or deny refund requests +- **Arbiters** verify transactions or resolve disputes (two models below) + +### Two Operator Models + +**Marketplace** (`deployMarketplaceOperator`): The merchant releases funds after escrow. If the payer contests, they file a refund request and an arbiter resolves it. Best for general commerce where most transactions are uncontested. + +**Delivery Protection** (`deployDeliveryProtectionOperator`): The arbiter evaluates every transaction automatically and is the only address that can release funds. If the arbiter does not release, funds auto-refund after escrow. Best for AI content verification, schema validation, or any flow needing automated quality checks. ### Packages From 729e28a2bb71701b5d3faded2d2d2804c6c55272 Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 00:09:55 -0700 Subject: [PATCH 13/23] docs: use parseForwardedPayload + fromNetworkId in arbiter quickstart Replace the placeholder extractPaymentInfo() with the real SDK helpers: - parseForwardedPayload() for typed PaymentInfo extraction - fromNetworkId() for CAIP-2 chain ID parsing - createArbiterClient() instead of full createX402r() Closes x402r-sdk#100 Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/arbiter-delivery.mdx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/sdk/arbiter-delivery.mdx b/sdk/arbiter-delivery.mdx index f96d28b..6015094 100644 --- a/sdk/arbiter-delivery.mdx +++ b/sdk/arbiter-delivery.mdx @@ -84,17 +84,20 @@ Fire-and-forget. Does not block the response to the client. Only fires for `comm ### 4. Build the Arbiter Service -Your arbiter service receives the POST, evaluates the response, and calls `release()` if it passes: +Your arbiter service receives the POST, evaluates the response, and calls `release()` if it passes. + +Use `parseForwardedPayload()` from `@x402r/helpers` to extract typed `PaymentInfo` (with BigInt fields restored) and `fromNetworkId()` to parse the CAIP-2 network identifier into a chain ID: ```typescript import { createPublicClient, createWalletClient, http } from 'viem' import { baseSepolia } from 'viem/chains' import { privateKeyToAccount } from 'viem/accounts' -import { createX402r } from '@x402r/sdk' +import { createArbiterClient, fromNetworkId } from '@x402r/sdk' +import { parseForwardedPayload } from '@x402r/helpers' const account = privateKeyToAccount(process.env.ARBITER_PRIVATE_KEY as `0x${string}`) -const arbiter = createX402r({ +const arbiter = createArbiterClient({ publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), walletClient: createWalletClient({ account, @@ -107,16 +110,16 @@ const arbiter = createX402r({ // Express route handling the POST from forwardToArbiter() app.post('/verify', async (req, res) => { - const { responseBody, transaction, paymentPayload } = req.body + const { responseBody, paymentInfo, network, transaction } = + parseForwardedPayload(req.body) + + const chainId = fromNetworkId(network) // "eip155:84532" -> 84532 // Your evaluation logic here const passed = await evaluate(responseBody) if (passed) { - // Extract paymentInfo from paymentPayload - const paymentInfo = extractPaymentInfo(paymentPayload) const amounts = await arbiter.payment.getAmounts(paymentInfo) - await arbiter.payment.release(paymentInfo, amounts.capturableAmount) res.json({ verdict: 'PASS' }) } else { From 13b4ef3ae6e6796fefea222c8fbfd2e0a9158762 Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 00:23:48 -0700 Subject: [PATCH 14/23] Restructure SDK nav: Marketplace, Delivery Protection, Resources Nav changes: - "Getting Started" renamed to "Marketplace" (it is the marketplace flow) - New "Delivery Protection" section with overview, merchant, arbiter pages - New "Resources" section with create-client, deploy-operator, examples Page changes: - arbiter-quickstart: flattened back to dispute resolution content - delivery-protection: overview with comparison table - delivery-merchant: deploy operator + forwardToArbiter() setup - delivery-arbiter: build arbiter service + parseForwardedPayload() - create-client: broad reference for createX402r / role presets / config Review fixes: - RefundRequestStatus: 3=Cancelled, 4=Refused (was wrong) - CREATE2 -> CREATE3 in deploy-operator (2 places) - CommerceEvmScheme -> registerCommerceEvmScheme (server-side) - "Authorize payments" -> "Accept payments" on merchant card - "SDK Installation" -> "SDK Overview" on protocol card - Created x402r-sdk#102 for missing deployDeliveryProtectionOperator export Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 15 ++- sdk/arbiter-delivery.mdx | 160 ------------------------------ sdk/arbiter-dispute.mdx | 171 -------------------------------- sdk/arbiter-quickstart.mdx | 180 +++++++++++++++++++++++++++++----- sdk/create-client.mdx | 111 +++++++++++++++++++++ sdk/delivery-arbiter.mdx | 124 +++++++++++++++++++++++ sdk/delivery-merchant.mdx | 113 +++++++++++++++++++++ sdk/delivery-protection.mdx | 28 ++++++ sdk/deploy-operator.mdx | 6 +- sdk/payer-quickstart.mdx | 2 +- x402-integration/overview.mdx | 2 +- 11 files changed, 547 insertions(+), 365 deletions(-) delete mode 100644 sdk/arbiter-delivery.mdx delete mode 100644 sdk/arbiter-dispute.mdx create mode 100644 sdk/create-client.mdx create mode 100644 sdk/delivery-arbiter.mdx create mode 100644 sdk/delivery-merchant.mdx create mode 100644 sdk/delivery-protection.mdx diff --git a/docs.json b/docs.json index 2c3343c..a4aa444 100644 --- a/docs.json +++ b/docs.json @@ -87,19 +87,26 @@ "tab": "SDK", "groups": [ { - "group": "Getting Started", + "group": "Marketplace", "pages": [ "sdk/overview", "sdk/merchant-quickstart", "sdk/payer-quickstart", - "sdk/arbiter-quickstart", - "sdk/arbiter-dispute", - "sdk/arbiter-delivery" + "sdk/arbiter-quickstart" + ] + }, + { + "group": "Delivery Protection", + "pages": [ + "sdk/delivery-protection", + "sdk/delivery-merchant", + "sdk/delivery-arbiter" ] }, { "group": "Resources", "pages": [ + "sdk/create-client", "sdk/deploy-operator", "sdk/examples" ] diff --git a/sdk/arbiter-delivery.mdx b/sdk/arbiter-delivery.mdx deleted file mode 100644 index 6015094..0000000 --- a/sdk/arbiter-delivery.mdx +++ /dev/null @@ -1,160 +0,0 @@ ---- -title: "Delivery Protection Arbiter" -description: "Automatically evaluate every transaction and release or let funds expire." -icon: "shield-check" ---- - -In this model, the arbiter is the only address that can release funds. The merchant sets up a `forwardToArbiter()` hook that sends the HTTP response body to your arbiter service after every payment. Your service evaluates the response (AI, schema validation, heuristics) and calls `release()` if it passes. If you do not release, funds auto-refund to the payer after escrow expires. - -### Prerequisites - -* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) -* Node.js 18+ and npm - - -There is a full [AI garbage detector example](https://github.com/BackTrackCo/arbiter-examples) that implements this pattern with heuristic + LLM evaluation. - - -### 1. Install Dependencies - - -```bash npm -npm install @x402r/sdk @x402r/core -``` -```bash pnpm -pnpm add @x402r/sdk @x402r/core -``` -```bash bun -bun add @x402r/sdk @x402r/core -``` - - -`@x402r/core` is needed for `deployDeliveryProtectionOperator()`. - -### 2. Deploy a Delivery Protection Operator - -```typescript -import { deployDeliveryProtectionOperator } from '@x402r/core' - -const deployment = await deployDeliveryProtectionOperator( - walletClient, - publicClient, - { - chainId: 84532, - arbiter: account.address, // your arbiter service wallet - feeRecipient: account.address, // receives protocol fees - escrowPeriodSeconds: 300n, // 5 minutes (short for automated flows) - }, -) - -console.log('Operator:', deployment.operatorAddress) -console.log('EscrowPeriod:', deployment.escrowPeriodAddress) -console.log('ArbiterCondition:', deployment.arbiterConditionAddress) -``` - -This is simpler than the marketplace preset. No RefundRequest, Evidence, or Freeze contracts. The operator's `releaseCondition` is set to `StaticAddressCondition(arbiter)`, so only your arbiter address can call `release()`. - -### 3. Merchant Setup: Forward to Arbiter - -The merchant configures their x402 resource server to forward response data to your arbiter after every payment settlement: - -```typescript -import { forwardToArbiter } from '@x402r/helpers' - -const resourceServer = new x402ResourceServer(facilitatorClient) - .register(networkId, new CommerceEvmScheme(serverConfig)) - .onAfterSettle( - forwardToArbiter('http://your-arbiter:3001', { - onError: (err) => console.error('Arbiter unreachable:', err), - }) - ) -``` - -`forwardToArbiter()` POSTs to `{arbiterUrl}/verify` with: - -```json -{ - "responseBody": "the HTTP response body as a string", - "transaction": "0xsettlement_tx_hash", - "paymentPayload": { "x402Version": 1, "scheme": "commerce", "payload": "..." } -} -``` - -Fire-and-forget. Does not block the response to the client. Only fires for `commerce` scheme settlements. - -### 4. Build the Arbiter Service - -Your arbiter service receives the POST, evaluates the response, and calls `release()` if it passes. - -Use `parseForwardedPayload()` from `@x402r/helpers` to extract typed `PaymentInfo` (with BigInt fields restored) and `fromNetworkId()` to parse the CAIP-2 network identifier into a chain ID: - -```typescript -import { createPublicClient, createWalletClient, http } from 'viem' -import { baseSepolia } from 'viem/chains' -import { privateKeyToAccount } from 'viem/accounts' -import { createArbiterClient, fromNetworkId } from '@x402r/sdk' -import { parseForwardedPayload } from '@x402r/helpers' - -const account = privateKeyToAccount(process.env.ARBITER_PRIVATE_KEY as `0x${string}`) - -const arbiter = createArbiterClient({ - publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), - walletClient: createWalletClient({ - account, - chain: baseSepolia, - transport: http(), - }), - operatorAddress: deployment.operatorAddress, - escrowPeriodAddress: deployment.escrowPeriodAddress, -}) - -// Express route handling the POST from forwardToArbiter() -app.post('/verify', async (req, res) => { - const { responseBody, paymentInfo, network, transaction } = - parseForwardedPayload(req.body) - - const chainId = fromNetworkId(network) // "eip155:84532" -> 84532 - - // Your evaluation logic here - const passed = await evaluate(responseBody) - - if (passed) { - const amounts = await arbiter.payment.getAmounts(paymentInfo) - await arbiter.payment.release(paymentInfo, amounts.capturableAmount) - res.json({ verdict: 'PASS' }) - } else { - // Do nothing. Funds auto-refund after escrow expires. - res.json({ verdict: 'FAIL' }) - } -}) -``` - -The `evaluate()` function is where your logic lives. It could be: -- **Heuristic checks:** HTTP status code, response size, content-type validation -- **AI evaluation:** send response body to an LLM and ask "is this a valid response?" -- **Schema validation:** check if the response matches an expected JSON schema - -### 5. What Happens on Failure - -If the arbiter does not call `release()`, the payer can reclaim funds after the escrow period expires by calling `refundInEscrow()`. The escrow period acts as the verification window. - -```typescript -// Anyone can call this after escrow expires (gated by EscrowPeriod condition) -await payer.payment.refundInEscrow(paymentInfo, amount) -``` - -No dispute process needed. The timeout is the safety net. - -## Next Steps - - - - For human-reviewed disputes instead of automated evaluation. - - - Full deployment config and condition slot details. - - - Runnable examples for every SDK operation. - - diff --git a/sdk/arbiter-dispute.mdx b/sdk/arbiter-dispute.mdx deleted file mode 100644 index 46e83a1..0000000 --- a/sdk/arbiter-dispute.mdx +++ /dev/null @@ -1,171 +0,0 @@ ---- -title: "Dispute Resolution Arbiter" -description: "Review refund requests, approve or deny refunds, and distribute fees." -icon: "scale-balanced" ---- - -### Prerequisites - -* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) -* Node.js 18+ and npm -* A marketplace operator where your address is configured as the arbiter (see [Deploy an Operator](/sdk/deploy-operator)) - - -There are pre-configured [arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) in the SDK repo. - - -### 1. Install Dependencies - - -```bash npm -npm install @x402r/sdk -``` -```bash pnpm -pnpm add @x402r/sdk -``` -```bash bun -bun add @x402r/sdk -``` - - -### 2. Create an Arbiter Client - -```typescript -import { createPublicClient, createWalletClient, http } from 'viem' -import { baseSepolia } from 'viem/chains' -import { privateKeyToAccount } from 'viem/accounts' -import { createArbiterClient } from '@x402r/sdk' - -const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) - -const arbiter = createArbiterClient({ - publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), - walletClient: createWalletClient({ - account, - chain: baseSepolia, - transport: http(), - }), - operatorAddress: '0x...', // from deploy result - refundRequestAddress: '0x...', // from deploy result - refundRequestEvidenceAddress: '0x...', // from deploy result - escrowPeriodAddress: '0x...', - freezeAddress: '0x...', -}) -``` - -### 3. Check for Pending Refund Requests - -```typescript -import type { PaymentInfo } from '@x402r/sdk' - -const paymentInfo: PaymentInfo = { /* ... */ } - -const hasRefund = await arbiter.refund?.has(paymentInfo) -if (hasRefund) { - const request = await arbiter.refund?.get(paymentInfo) - console.log('Amount:', request?.amount) - console.log('Status:', request?.status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Refused -} -``` - -To list all refund requests for your operator: - -```typescript -const requests = await arbiter.refund?.getOperatorRequests( - arbiter.config.operatorAddress, - 0n, // offset - 10n, // count -) -console.log('Pending requests:', requests) -``` - -### 4. Review Evidence - -Both payers and merchants can submit evidence as IPFS CIDs. Read all entries before making a decision: - -```typescript -const count = await arbiter.evidence?.count(paymentInfo) -console.log('Evidence entries:', count) - -const batch = await arbiter.evidence?.getBatch(paymentInfo, 0n, count!) - -for (const entry of batch!.entries) { - console.log('CID:', entry.cid) - console.log('Submitter:', entry.submitter) - console.log('Timestamp:', entry.timestamp) -} -``` - -### 5. Approve a Refund - -Call `payment.refundInEscrow()` to approve. The RefundRequest recorder auto-approves the pending request, so there is no separate `approve()` call: - -```typescript -const tx = await arbiter.payment.refundInEscrow(paymentInfo, request!.amount) -console.log('Refund approved:', tx) - -// Verify -const approved = await arbiter.refund?.get(paymentInfo) -console.log('Approved amount:', approved?.approvedAmount) -console.log('Status:', approved?.status) // 1 = Approved -``` - -### 6. Deny a Refund Request - -If the evidence does not support a refund: - -```typescript -const denyTx = await arbiter.refund?.deny(paymentInfo) -console.log('Refund denied:', denyTx) -``` - -Or decline to rule entirely: - -```typescript -const refuseTx = await arbiter.refund?.refuse(paymentInfo) -console.log('Declined to rule:', refuseTx) -``` - -### 7. Unfreeze a Payment - -If the payer froze the payment during the dispute, unfreeze it after resolution: - -```typescript -const frozen = await arbiter.freeze?.isFrozen(paymentInfo) -if (frozen) { - const tx = await arbiter.freeze?.unfreeze(paymentInfo) - console.log('Unfrozen:', tx) -} -``` - -### 8. Distribute Accumulated Fees - -Protocol fees accumulate on the operator when payments are released: - -```typescript -import { getChainConfig } from '@x402r/sdk' - -const config = getChainConfig(84532) - -const fees = await arbiter.operator.getAccumulatedProtocolFees(config.usdc) -console.log('Accumulated fees:', fees) - -if (fees > 0n) { - const tx = await arbiter.operator.distributeFees(config.usdc) - console.log('Fees distributed:', tx) -} -``` - -## Next Steps - - - - Automated evaluation for every transaction. - - - How your address gets configured as arbiter on an operator. - - - Full dispute resolution scenario end-to-end. - - diff --git a/sdk/arbiter-quickstart.mdx b/sdk/arbiter-quickstart.mdx index 6c64daa..89ce585 100644 --- a/sdk/arbiter-quickstart.mdx +++ b/sdk/arbiter-quickstart.mdx @@ -1,41 +1,171 @@ --- -title: "Quickstart for Arbiters" -description: "Two arbiter models: dispute resolution and delivery protection." -icon: "gavel" +title: "Dispute Resolution Arbiter" +description: "Review refund requests, approve or deny refunds, and distribute fees." +icon: "scale-balanced" --- -x402r supports two arbiter models depending on your use case. +### Prerequisites -### Marketplace (Dispute Resolution) +* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) +* Node.js 18+ and npm +* A marketplace operator where your address is configured as the arbiter (see [Deploy an Operator](/sdk/deploy-operator)) -The merchant releases funds after escrow. If the payer contests, they file a refund request. The arbiter reviews evidence and approves or denies the refund. + +There are pre-configured [arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) in the SDK repo. + -Uses `deployMarketplaceOperator()`. Includes RefundRequest, Evidence, and Freeze contracts. +### 1. Install Dependencies -**When to use:** general-purpose commerce, human arbitration (e.g. Kleros), cases where most transactions are uncontested. + +```bash npm +npm install @x402r/sdk +``` +```bash pnpm +pnpm add @x402r/sdk +``` +```bash bun +bun add @x402r/sdk +``` + -### Delivery Protection +### 2. Create an Arbiter Client -The arbiter evaluates every transaction automatically. Only the arbiter can release funds. If the arbiter does not release, funds auto-refund to the payer after escrow expires. +```typescript +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { createArbiterClient } from '@x402r/sdk' -Uses `deployDeliveryProtectionOperator()`. No RefundRequest or Freeze needed. +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) -**When to use:** AI content verification (garbage detection), schema validation, any flow where every response needs automated quality checks. +const arbiter = createArbiterClient({ + publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), + walletClient: createWalletClient({ + account, + chain: baseSepolia, + transport: http(), + }), + operatorAddress: '0x...', // from deploy result + refundRequestAddress: '0x...', // from deploy result + refundRequestEvidenceAddress: '0x...', // from deploy result + escrowPeriodAddress: '0x...', + freezeAddress: '0x...', +}) +``` -| | Marketplace | Delivery Protection | -|---|---|---| -| Who releases funds | Merchant (after escrow) | Arbiter only | -| Dispute process | Payer files refund request | No disputes needed | -| Arbiter involvement | Only on disputes | Every transaction | -| Contracts needed | Operator + EscrowPeriod + RefundRequest + Evidence + Freeze | Operator + EscrowPeriod + StaticAddressCondition | -| Speed | Arbiter acts on demand | Arbiter acts automatically | -| Deploy preset | `deployMarketplaceOperator()` | `deployDeliveryProtectionOperator()` | +### 3. Check for Pending Refund Requests - - - Review refund requests, evidence, approve or deny. +```typescript +import type { PaymentInfo } from '@x402r/sdk' + +const paymentInfo: PaymentInfo = { /* ... */ } + +const hasRefund = await arbiter.refund?.has(paymentInfo) +if (hasRefund) { + const request = await arbiter.refund?.get(paymentInfo) + console.log('Amount:', request?.amount) + console.log('Status:', request?.status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Cancelled, 4 = Refused +} +``` + +To list all refund requests for your operator: + +```typescript +const requests = await arbiter.refund?.getOperatorRequests( + arbiter.config.operatorAddress, + 0n, // offset + 10n, // count +) +console.log('Pending requests:', requests) +``` + +### 4. Review Evidence + +Both payers and merchants can submit evidence as IPFS CIDs. Read all entries before making a decision: + +```typescript +const count = await arbiter.evidence?.count(paymentInfo) +console.log('Evidence entries:', count) + +const batch = await arbiter.evidence?.getBatch(paymentInfo, 0n, count!) + +for (const entry of batch!.entries) { + console.log('CID:', entry.cid) + console.log('Submitter:', entry.submitter) + console.log('Timestamp:', entry.timestamp) +} +``` + +### 5. Approve a Refund + +Call `payment.refundInEscrow()` to approve. The RefundRequest recorder auto-approves the pending request, so there is no separate `approve()` call: + +```typescript +const tx = await arbiter.payment.refundInEscrow(paymentInfo, request!.amount) +console.log('Refund approved:', tx) + +// Verify +const approved = await arbiter.refund?.get(paymentInfo) +console.log('Approved amount:', approved?.approvedAmount) +console.log('Status:', approved?.status) // 1 = Approved +``` + +### 6. Deny a Refund Request + +If the evidence does not support a refund: + +```typescript +const denyTx = await arbiter.refund?.deny(paymentInfo) +console.log('Refund denied:', denyTx) +``` + +Or decline to rule entirely: + +```typescript +const refuseTx = await arbiter.refund?.refuse(paymentInfo) +console.log('Declined to rule:', refuseTx) +``` + +### 7. Unfreeze a Payment + +If the payer froze the payment during the dispute, unfreeze it after resolution: + +```typescript +const frozen = await arbiter.freeze?.isFrozen(paymentInfo) +if (frozen) { + const tx = await arbiter.freeze?.unfreeze(paymentInfo) + console.log('Unfrozen:', tx) +} +``` + +### 8. Distribute Accumulated Fees + +Protocol fees accumulate on the operator when payments are released: + +```typescript +import { getChainConfig } from '@x402r/sdk' + +const config = getChainConfig(84532) + +const fees = await arbiter.operator.getAccumulatedProtocolFees(config.usdc) +console.log('Accumulated fees:', fees) + +if (fees > 0n) { + const tx = await arbiter.operator.distributeFees(config.usdc) + console.log('Fees distributed:', tx) +} +``` + +## Next Steps + + + + Automated evaluation for every transaction. + + + How your address gets configured as arbiter on an operator. - - Evaluate every transaction, release or let expire. + + Full dispute resolution scenario end-to-end. diff --git a/sdk/create-client.mdx b/sdk/create-client.mdx new file mode 100644 index 0000000..658c895 --- /dev/null +++ b/sdk/create-client.mdx @@ -0,0 +1,111 @@ +--- +title: "Create a Client" +description: "Client factory, role presets, and configuration reference." +icon: "plug" +--- + +### Full Client + +`createX402r()` returns a client with all action groups. No type restrictions. + +```typescript +import { createX402r } from '@x402r/sdk' + +const client = createX402r({ + publicClient, + walletClient, // optional for read-only + operatorAddress: '0x...', // from deploy result + escrowPeriodAddress: '0x...', // from deploy result + refundRequestAddress: '0x...', // from deploy result + refundRequestEvidenceAddress: '0x...', // from deploy result + freezeAddress: '0x...', // from deploy result +}) + +await client.payment.getAmounts(paymentInfo) +await client.refund?.request(paymentInfo, amount) +await client.escrow?.isDuringEscrow(paymentInfo) +``` + +### Role Presets + +Role presets call `createX402r()` internally and narrow the TypeScript types so autocomplete only shows relevant methods. All three require `walletClient`. + +```typescript +import { + createPayerClient, + createMerchantClient, + createArbiterClient, +} from '@x402r/sdk' + +const payer = createPayerClient({ publicClient, walletClient, operatorAddress: '0x...' }) +const merchant = createMerchantClient({ publicClient, walletClient, operatorAddress: '0x...' }) +const arbiter = createArbiterClient({ publicClient, walletClient, operatorAddress: '0x...' }) +``` + +Type narrowing is a DX convenience, not a security boundary. On-chain [conditions](/contracts/conditions/overview) enforce access control. + +### Config Reference + +| Field | Type | Required | Notes | +|-------|------|:--------:|-------| +| `publicClient` | `PublicClient` | Yes | viem public client for reads | +| `walletClient` | `WalletClient` | No | Required for writes. Role presets throw without it. | +| `operatorAddress` | `Address` | Yes | Your deployed PaymentOperator | +| `chainId` | `number` | No | Auto-detected from `publicClient.chain` | +| `escrowPeriodAddress` | `Address` | No | Activates `escrow` group | +| `refundRequestAddress` | `Address` | No | Activates `refund` group | +| `refundRequestEvidenceAddress` | `Address` | No | Activates `evidence` group (requires `refundRequestAddress`) | +| `freezeAddress` | `Address` | No | Activates `freeze` group | +| `paymentIndexRecorderAddress` | `Address` | No | Activates `query` group | +| `paymentStore` | `PaymentStore` | No | Pluggable storage for payment lookups | +| `eventFromBlock` | `bigint` | No | Starting block for event-based payment lookups | + +### Action Groups + +| Group | Methods | Requirement | +|-------|---------|-------------| +| `payment` | 9 | Always available | +| `operator` | 8 | Always available | +| `watch` | 4 | Always available | +| `escrow` | 3 | `escrowPeriodAddress` | +| `refund` | 15 | `refundRequestAddress` | +| `evidence` | 4 | `refundRequestEvidenceAddress` | +| `freeze` | 3 | `freezeAddress` | +| `query` | 3 | `paymentIndexRecorderAddress` | + +Groups without their required address are `undefined` on the client. Use optional chaining: + +```typescript +await client.escrow?.isDuringEscrow(paymentInfo) // undefined if no escrowPeriodAddress +``` + +### Extend + +Add custom action groups with `.extend()`: + +```typescript +import { createX402r, queryActions } from '@x402r/sdk' + +const client = createX402r({ publicClient, operatorAddress: '0x...' }) + +const extended = client.extend( + queryActions('0xRecorderAddress', { eventFromBlock: 100000n }) +) + +// extended.query is now defined +const payments = await extended.query.getPayerPayments(payerAddress) +``` + +## Next Steps + + + + Accept payments and release funds. + + + Get the addresses for your client config. + + + Conditions and recorders that control on-chain access. + + diff --git a/sdk/delivery-arbiter.mdx b/sdk/delivery-arbiter.mdx new file mode 100644 index 0000000..626689f --- /dev/null +++ b/sdk/delivery-arbiter.mdx @@ -0,0 +1,124 @@ +--- +title: "Arbiter Setup" +description: "Build a service that evaluates responses and releases funds." +icon: "shield-check" +--- + +### Prerequisites + +* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) +* Node.js 18+ and npm +* Operator and escrow addresses from the [Merchant Setup](/sdk/delivery-merchant) + + +There is a full [AI garbage detector example](https://github.com/BackTrackCo/arbiter-examples) that implements this pattern with heuristic + LLM evaluation. + + +### 1. Install Dependencies + + +```bash npm +npm install @x402r/sdk @x402r/helpers express +``` +```bash pnpm +pnpm add @x402r/sdk @x402r/helpers express +``` +```bash bun +bun add @x402r/sdk @x402r/helpers express +``` + + +### 2. Create the Arbiter Client + +```typescript +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { createArbiterClient, fromNetworkId } from '@x402r/sdk' + +const account = privateKeyToAccount(process.env.ARBITER_PRIVATE_KEY as `0x${string}`) + +const arbiter = createArbiterClient({ + publicClient: createPublicClient({ chain: baseSepolia, transport: http() }), + walletClient: createWalletClient({ + account, + chain: baseSepolia, + transport: http(), + }), + operatorAddress: process.env.OPERATOR_ADDRESS as `0x${string}`, + escrowPeriodAddress: process.env.ESCROW_PERIOD_ADDRESS as `0x${string}`, +}) +``` + +### 3. Handle the Verify Endpoint + +The merchant's `forwardToArbiter()` hook POSTs to `/verify`. Use `parseForwardedPayload()` to extract typed `PaymentInfo` with BigInt fields restored: + +```typescript +import { parseForwardedPayload } from '@x402r/helpers' +import express from 'express' + +const app = express() +app.use(express.json()) + +app.post('/verify', async (req, res) => { + const { responseBody, paymentInfo, network, transaction } = + parseForwardedPayload(req.body) + + const chainId = fromNetworkId(network) // "eip155:84532" -> 84532 + + // Your evaluation logic + const passed = await evaluate(responseBody) + + if (passed) { + const amounts = await arbiter.payment.getAmounts(paymentInfo) + await arbiter.payment.release(paymentInfo, amounts.capturableAmount) + res.json({ verdict: 'PASS' }) + } else { + // Do nothing. Funds auto-refund after escrow expires. + res.json({ verdict: 'FAIL' }) + } +}) + +app.listen(3001) +``` + +### 4. Implement Your Evaluation Logic + +The `evaluate()` function is where your logic lives. It could be: + +- **Heuristic checks:** HTTP status code, response size, content-type validation +- **AI evaluation:** send response body to an LLM and ask "is this a valid response?" +- **Schema validation:** check if the response matches an expected JSON schema + +```typescript +async function evaluate(responseBody: string): Promise { + // Example: reject empty or error responses + if (!responseBody || responseBody.length < 10) return false + if (responseBody.includes('"error"')) return false + + // Example: LLM evaluation + // const result = await llm.evaluate(responseBody) + // return result.verdict === 'PASS' + + return true +} +``` + +### 5. What Happens on Failure + +If the arbiter does not call `release()`, funds auto-refund to the payer after the escrow period expires. The escrow period acts as the verification window. No dispute process needed. + +## Next Steps + + + + Deploy the operator and configure forwardToArbiter(). + + + For human-reviewed disputes instead of automated evaluation. + + + Runnable examples for every SDK operation. + + diff --git a/sdk/delivery-merchant.mdx b/sdk/delivery-merchant.mdx new file mode 100644 index 0000000..161ffb6 --- /dev/null +++ b/sdk/delivery-merchant.mdx @@ -0,0 +1,113 @@ +--- +title: "Merchant Setup" +description: "Deploy a delivery protection operator and forward responses to the arbiter." +icon: "store" +--- + +### Prerequisites + +* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) +* Node.js 18+ and npm +* An arbiter service URL (see [Arbiter Setup](/sdk/delivery-arbiter)) + +### 1. Install Dependencies + + +```bash npm +npm install @x402r/sdk @x402r/core @x402r/helpers +``` +```bash pnpm +pnpm add @x402r/sdk @x402r/core @x402r/helpers +``` +```bash bun +bun add @x402r/sdk @x402r/core @x402r/helpers +``` + + +`@x402r/core` is needed for `deployDeliveryProtectionOperator()`. `@x402r/helpers` is needed for `forwardToArbiter()`. + +### 2. Deploy a Delivery Protection Operator + +```typescript +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { deployDeliveryProtectionOperator } from '@x402r/core' + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) + +const publicClient = createPublicClient({ + chain: baseSepolia, + transport: http(), +}) + +const walletClient = createWalletClient({ + account, + chain: baseSepolia, + transport: http(), +}) + +const deployment = await deployDeliveryProtectionOperator( + walletClient, + publicClient, + { + chainId: 84532, + arbiter: '0xArbiterServiceAddress', // the arbiter service wallet + feeRecipient: account.address, + escrowPeriodSeconds: 300n, // 5 minutes (short for automated flows) + }, +) + +console.log('Operator:', deployment.operatorAddress) +console.log('EscrowPeriod:', deployment.escrowPeriodAddress) +``` + +This is simpler than the marketplace preset. No RefundRequest, Evidence, or Freeze contracts. The operator's `releaseCondition` is `StaticAddressCondition(arbiter)`, so only the arbiter address can call `release()`. + + +`deployDeliveryProtectionOperator` export is tracked in [x402r-sdk#102](https://github.com/BackTrackCo/x402r-sdk/issues/102). Until resolved, import from `@x402r/core/deploy`. + + +### 3. Configure forwardToArbiter() + +Add the `forwardToArbiter()` hook to your x402 resource server. After every payment settlement, it POSTs the HTTP response body to your arbiter service: + +```typescript +import { forwardToArbiter } from '@x402r/helpers' + +const resourceServer = new x402ResourceServer(facilitatorConfig) +registerCommerceEvmScheme(resourceServer) + +resourceServer.onAfterSettle( + forwardToArbiter('http://your-arbiter:3001', { + onError: (err) => console.error('Arbiter unreachable:', err), + }) +) +``` + +The hook POSTs to `{arbiterUrl}/verify` with: + +```json +{ + "responseBody": "the HTTP response body as a string", + "transaction": "0xsettlement_tx_hash", + "paymentPayload": { "x402Version": 1, "scheme": "commerce", "payload": "..." } +} +``` + +Fire-and-forget. Does not block the response to the client. Only fires for `commerce` scheme settlements. + +### 4. Share Addresses with the Arbiter + +The arbiter service needs `operatorAddress` and `escrowPeriodAddress` from step 2 to create its SDK client. Share these via config, environment variables, or a shared registry. + +## Next Steps + + + + Build the service that evaluates responses and releases funds. + + + Full deployment config and condition slot details. + + diff --git a/sdk/delivery-protection.mdx b/sdk/delivery-protection.mdx new file mode 100644 index 0000000..4dcb851 --- /dev/null +++ b/sdk/delivery-protection.mdx @@ -0,0 +1,28 @@ +--- +title: "Delivery Protection" +description: "Automated quality verification for every transaction." +icon: "shield-check" +--- + +In the delivery protection model, the arbiter evaluates every transaction automatically. Only the arbiter can release funds. If the arbiter does not release, funds auto-refund to the payer after escrow expires. + +This is different from the [marketplace model](/sdk/overview) where the merchant releases funds and the arbiter only gets involved when a payer files a dispute. + +| | Marketplace | Delivery Protection | +|---|---|---| +| Who releases funds | Merchant (after escrow) | Arbiter only | +| Dispute process | Payer files refund request | No disputes needed | +| Arbiter involvement | Only on disputes | Every transaction | +| Contracts needed | Operator + EscrowPeriod + RefundRequest + Evidence + Freeze | Operator + EscrowPeriod + StaticAddressCondition | +| Deploy preset | `deployMarketplaceOperator()` | `deployDeliveryProtectionOperator()` | + +**When to use:** AI content verification (garbage detection), schema validation, or any flow where every response needs automated quality checks before funds are released. + + + + Deploy the operator and configure forwardToArbiter(). + + + Build the service that evaluates responses and releases funds. + + diff --git a/sdk/deploy-operator.mdx b/sdk/deploy-operator.mdx index 8e752b2..563ce4e 100644 --- a/sdk/deploy-operator.mdx +++ b/sdk/deploy-operator.mdx @@ -21,7 +21,7 @@ A complete marketplace operator deployment includes: 5. **StaticFeeCalculator** Optional operator fee (basis points) 6. **PaymentOperator** The main contract tying everything together -All contracts are deployed via factories using CREATE2, so identical configurations produce identical addresses across deployments. +All contracts are deployed via factories using CREATE3, so identical configurations produce identical addresses across deployments. ## Deploy Your Operator @@ -155,7 +155,7 @@ interface MarketplaceOperatorDeployment { ``` -Because all contracts use CREATE2, redeploying with the same parameters is idempotent it will detect existing contracts and skip them. The `summary` tells you what was new vs reused. +Because all contracts use CREATE3, redeploying with the same parameters is idempotent it will detect existing contracts and skip them. The `summary` tells you what was new vs reused. ## Preview Addresses (No Deploy) @@ -201,7 +201,7 @@ Deployment requires gas fees. Ensure your wallet has ETH on the target network. - Authorize payments, release funds from escrow. + Accept payments, release funds from escrow. Request refunds, freeze payments, submit evidence. diff --git a/sdk/payer-quickstart.mdx b/sdk/payer-quickstart.mdx index 50d99de..9ec45e8 100644 --- a/sdk/payer-quickstart.mdx +++ b/sdk/payer-quickstart.mdx @@ -88,7 +88,7 @@ if (hasExisting) { // Check refund status const status = await payer.refund?.getStatus(paymentInfo) -console.log('Status:', status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Refused +console.log('Status:', status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Cancelled, 4 = Refused ``` ### 5. Freeze a Payment (Optional) diff --git a/x402-integration/overview.mdx b/x402-integration/overview.mdx index 3eb8d88..07863cc 100644 --- a/x402-integration/overview.mdx +++ b/x402-integration/overview.mdx @@ -217,7 +217,7 @@ Smart contract that controls capture/void logic. Different operators enable diff Understand the escrow and operator contracts. - + Get started building with x402r. From 6590c9b97eeccd55f4c93e0fdd0aac277699d9a9 Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 01:19:27 -0700 Subject: [PATCH 15/23] Fix nav, titles, installs, and review issues (round 3) Nav restructure: - Overview is now top-level (not under Marketplace) - "Quickstart for X" removed everywhere. New titles: Merchant Guide, Payer Guide, Arbiter Guide - Files renamed: merchant.mdx, payer.mdx, arbiter.mdx Install fixes: - delivery-merchant: @x402r/core @x402r/helpers (dropped @x402r/sdk, merchant doesn't create SDK client on this page) - delivery-arbiter: @x402r/sdk @x402r/helpers (dropped express) - overview: @x402r/helpers only (dropped @x402r/evm) Review fixes (round 3): 1. fromNetworkId imported from @x402r/core (not exported from sdk) 2. "merchant action" reworded to "not part of the payer role" 3. Payload JSON shape fixed to show actual nested structure 4. Double spaces in deploy-operator replaced with punctuation Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 13 +++++++++---- sdk/{arbiter-quickstart.mdx => arbiter.mdx} | 2 +- sdk/create-client.mdx | 2 +- sdk/delivery-arbiter.mdx | 11 ++++++----- sdk/delivery-merchant.mdx | 17 ++++++++++++----- sdk/deploy-operator.mdx | 18 +++++++++--------- sdk/{merchant-quickstart.mdx => merchant.mdx} | 2 +- sdk/overview.mdx | 14 +++++++------- sdk/{payer-quickstart.mdx => payer.mdx} | 8 ++++---- 9 files changed, 50 insertions(+), 37 deletions(-) rename sdk/{arbiter-quickstart.mdx => arbiter.mdx} (99%) rename sdk/{merchant-quickstart.mdx => merchant.mdx} (99%) rename sdk/{payer-quickstart.mdx => payer.mdx} (94%) diff --git a/docs.json b/docs.json index a4aa444..d87944f 100644 --- a/docs.json +++ b/docs.json @@ -86,13 +86,18 @@ { "tab": "SDK", "groups": [ + { + "group": "", + "pages": [ + "sdk/overview" + ] + }, { "group": "Marketplace", "pages": [ - "sdk/overview", - "sdk/merchant-quickstart", - "sdk/payer-quickstart", - "sdk/arbiter-quickstart" + "sdk/merchant", + "sdk/payer", + "sdk/arbiter" ] }, { diff --git a/sdk/arbiter-quickstart.mdx b/sdk/arbiter.mdx similarity index 99% rename from sdk/arbiter-quickstart.mdx rename to sdk/arbiter.mdx index 89ce585..63db4a0 100644 --- a/sdk/arbiter-quickstart.mdx +++ b/sdk/arbiter.mdx @@ -1,5 +1,5 @@ --- -title: "Dispute Resolution Arbiter" +title: "Arbiter Guide" description: "Review refund requests, approve or deny refunds, and distribute fees." icon: "scale-balanced" --- diff --git a/sdk/create-client.mdx b/sdk/create-client.mdx index 658c895..320187b 100644 --- a/sdk/create-client.mdx +++ b/sdk/create-client.mdx @@ -99,7 +99,7 @@ const payments = await extended.query.getPayerPayments(payerAddress) ## Next Steps - + Accept payments and release funds. diff --git a/sdk/delivery-arbiter.mdx b/sdk/delivery-arbiter.mdx index 626689f..282240c 100644 --- a/sdk/delivery-arbiter.mdx +++ b/sdk/delivery-arbiter.mdx @@ -18,13 +18,13 @@ There is a full [AI garbage detector example](https://github.com/BackTrackCo/arb ```bash npm -npm install @x402r/sdk @x402r/helpers express +npm install @x402r/sdk @x402r/helpers ``` ```bash pnpm -pnpm add @x402r/sdk @x402r/helpers express +pnpm add @x402r/sdk @x402r/helpers ``` ```bash bun -bun add @x402r/sdk @x402r/helpers express +bun add @x402r/sdk @x402r/helpers ``` @@ -34,7 +34,8 @@ bun add @x402r/sdk @x402r/helpers express import { createPublicClient, createWalletClient, http } from 'viem' import { baseSepolia } from 'viem/chains' import { privateKeyToAccount } from 'viem/accounts' -import { createArbiterClient, fromNetworkId } from '@x402r/sdk' +import { createArbiterClient } from '@x402r/sdk' +import { fromNetworkId } from '@x402r/core' const account = privateKeyToAccount(process.env.ARBITER_PRIVATE_KEY as `0x${string}`) @@ -115,7 +116,7 @@ If the arbiter does not call `release()`, funds auto-refund to the payer after t Deploy the operator and configure forwardToArbiter(). - + For human-reviewed disputes instead of automated evaluation. diff --git a/sdk/delivery-merchant.mdx b/sdk/delivery-merchant.mdx index 161ffb6..9522a6e 100644 --- a/sdk/delivery-merchant.mdx +++ b/sdk/delivery-merchant.mdx @@ -14,17 +14,17 @@ icon: "store" ```bash npm -npm install @x402r/sdk @x402r/core @x402r/helpers +npm install @x402r/core @x402r/helpers ``` ```bash pnpm -pnpm add @x402r/sdk @x402r/core @x402r/helpers +pnpm add @x402r/core @x402r/helpers ``` ```bash bun -bun add @x402r/sdk @x402r/core @x402r/helpers +bun add @x402r/core @x402r/helpers ``` -`@x402r/core` is needed for `deployDeliveryProtectionOperator()`. `@x402r/helpers` is needed for `forwardToArbiter()`. +`@x402r/core` is needed for `deployDeliveryProtectionOperator()`. `@x402r/helpers` provides the `forwardToArbiter()` hook. ### 2. Deploy a Delivery Protection Operator @@ -91,10 +91,17 @@ The hook POSTs to `{arbiterUrl}/verify` with: { "responseBody": "the HTTP response body as a string", "transaction": "0xsettlement_tx_hash", - "paymentPayload": { "x402Version": 1, "scheme": "commerce", "payload": "..." } + "paymentPayload": { + "x402Version": 1, + "scheme": "commerce", + "accepted": { "network": "eip155:84532", ... }, + "payload": { "paymentInfo": { ... }, ... } + } } ``` +The arbiter uses `parseForwardedPayload()` from `@x402r/helpers` to extract `paymentInfo` and `network` from the nested structure. + Fire-and-forget. Does not block the response to the client. Only fires for `commerce` scheme settlements. ### 4. Share Addresses with the Arbiter diff --git a/sdk/deploy-operator.mdx b/sdk/deploy-operator.mdx index 563ce4e..5e7f140 100644 --- a/sdk/deploy-operator.mdx +++ b/sdk/deploy-operator.mdx @@ -14,12 +14,12 @@ The `@x402r/core` package includes deployment utilities that handle the full lif A complete marketplace operator deployment includes: -1. **EscrowPeriod** Records authorization time, enforces waiting period before release -2. **Freeze** Allows payer to freeze payment during escrow, receiver to unfreeze -3. **StaticAddressCondition** Restricts refund approval to the designated arbiter -4. **OrCondition** Allows either the receiver OR the arbiter to approve in-escrow refunds -5. **StaticFeeCalculator** Optional operator fee (basis points) -6. **PaymentOperator** The main contract tying everything together +1. **EscrowPeriod** - Records authorization time, enforces waiting period before release +2. **Freeze** - Allows payer to freeze payment during escrow, receiver to unfreeze +3. **StaticAddressCondition** - Restricts refund approval to the designated arbiter +4. **OrCondition** - Allows either the receiver OR the arbiter to approve in-escrow refunds +5. **StaticFeeCalculator** - Optional operator fee (basis points) +6. **PaymentOperator** - The main contract tying everything together All contracts are deployed via factories using CREATE3, so identical configurations produce identical addresses across deployments. @@ -155,7 +155,7 @@ interface MarketplaceOperatorDeployment { ``` -Because all contracts use CREATE3, redeploying with the same parameters is idempotent it will detect existing contracts and skip them. The `summary` tells you what was new vs reused. +Because all contracts use CREATE3, redeploying with the same parameters is idempotent. It will detect existing contracts and skip them. The `summary` tells you what was new vs reused. ## Preview Addresses (No Deploy) @@ -200,10 +200,10 @@ Deployment requires gas fees. Ensure your wallet has ETH on the target network. ## Next Steps - + Accept payments, release funds from escrow. - + Request refunds, freeze payments, submit evidence. diff --git a/sdk/merchant-quickstart.mdx b/sdk/merchant.mdx similarity index 99% rename from sdk/merchant-quickstart.mdx rename to sdk/merchant.mdx index f92c59d..56d3419 100644 --- a/sdk/merchant-quickstart.mdx +++ b/sdk/merchant.mdx @@ -1,5 +1,5 @@ --- -title: "Quickstart for Merchants" +title: "Merchant Guide" description: "Accept a payment into escrow, check state, and release funds." icon: "store" --- diff --git a/sdk/overview.mdx b/sdk/overview.mdx index 209becb..7732926 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -52,26 +52,26 @@ For x402 server integration: ```bash npm -npm install @x402r/helpers @x402r/evm +npm install @x402r/helpers ``` ```bash pnpm -pnpm add @x402r/helpers @x402r/evm +pnpm add @x402r/helpers ``` ```bash bun -bun add @x402r/helpers @x402r/evm +bun add @x402r/helpers ``` -### Quickstarts +### Guides - + Deploy an operator, accept a payment, release funds from escrow. - + Check payment state, request a refund, submit evidence. - + Review disputes, approve or deny refunds, distribute fees. diff --git a/sdk/payer-quickstart.mdx b/sdk/payer.mdx similarity index 94% rename from sdk/payer-quickstart.mdx rename to sdk/payer.mdx index 9ec45e8..3ad4f0f 100644 --- a/sdk/payer-quickstart.mdx +++ b/sdk/payer.mdx @@ -1,5 +1,5 @@ --- -title: "Quickstart for Payers" +title: "Payer Guide" description: "Check payment state, request a refund, freeze a payment, and submit evidence." icon: "user" --- @@ -8,7 +8,7 @@ icon: "user" * A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) * Node.js 18+ and npm -* An authorized payment on an x402r operator (see [Quickstart for Merchants](/sdk/merchant-quickstart)) +* An authorized payment on an x402r operator (see [Merchant Guide](/sdk/merchant)) There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [payer examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts). @@ -53,7 +53,7 @@ const payer = createPayerClient({ }) ``` -`createPayerClient` restricts TypeScript types to payer-relevant methods. `refund.request()` shows up in autocomplete; `payment.authorize()` does not because that is a merchant action. +`createPayerClient` restricts TypeScript types to payer-relevant methods. `refund.request()` shows up in autocomplete; `payment.authorize()` does not because it is not part of the payer role. ### 3. Check Payment State @@ -134,7 +134,7 @@ console.log('Refund cancelled:', cancelTx) ## Next Steps - + What happens after you submit a refund request. From db2624435b142264474d51df07ef7b9bf2fee2c5 Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 01:44:42 -0700 Subject: [PATCH 16/23] Fix technical issues, remove boilerplate, add warnings Factual fixes: - Add SKALE Base to chain table (chainId: 1187947933) - Remove outdated #102 note from delivery-merchant - Add deployDeliveryProtectionOperator section to deploy-operator - Fix double spaces in deploy-operator (em dash remnants) Remove repeated boilerplate: - Type narrowing explanation removed from merchant, payer, arbiter (function names are self-explanatory, create-client has reference) - Install CodeGroup trimmed to one-liner in marketplace role guides Add Mintlify warnings: - Merchant: release() reverts during escrow - Payer: freeze() blocks release, timing note for escrow window - Arbiter: refundInEscrow() auto-approves (no undo), deny vs refuse - Delivery merchant: forwardToArbiter is fire-and-forget - Delivery arbiter: service downtime means no evaluation Nav: deploy-operator top-level, Resources -> Reference, create-client title -> Client Configuration Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 6 +++--- sdk/arbiter.mdx | 20 ++++++++----------- sdk/create-client.mdx | 2 +- sdk/delivery-arbiter.mdx | 4 +++- sdk/delivery-merchant.mdx | 8 +++----- sdk/delivery-protection.mdx | 2 +- sdk/deploy-operator.mdx | 39 +++++++++++++++++++++++++++++++++++++ sdk/merchant.mdx | 20 +++++-------------- sdk/overview.mdx | 5 +++-- sdk/payer.mdx | 20 ++++++++----------- 10 files changed, 74 insertions(+), 52 deletions(-) diff --git a/docs.json b/docs.json index d87944f..aec87b6 100644 --- a/docs.json +++ b/docs.json @@ -89,7 +89,8 @@ { "group": "", "pages": [ - "sdk/overview" + "sdk/overview", + "sdk/deploy-operator" ] }, { @@ -109,10 +110,9 @@ ] }, { - "group": "Resources", + "group": "Reference", "pages": [ "sdk/create-client", - "sdk/deploy-operator", "sdk/examples" ] } diff --git a/sdk/arbiter.mdx b/sdk/arbiter.mdx index 63db4a0..48bf231 100644 --- a/sdk/arbiter.mdx +++ b/sdk/arbiter.mdx @@ -14,19 +14,11 @@ icon: "scale-balanced" There are pre-configured [arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) in the SDK repo. -### 1. Install Dependencies +### 1. Install `@x402r/sdk` - -```bash npm +```bash npm install @x402r/sdk ``` -```bash pnpm -pnpm add @x402r/sdk -``` -```bash bun -bun add @x402r/sdk -``` - ### 2. Create an Arbiter Client @@ -98,7 +90,9 @@ for (const entry of batch!.entries) { ### 5. Approve a Refund -Call `payment.refundInEscrow()` to approve. The RefundRequest recorder auto-approves the pending request, so there is no separate `approve()` call: + +`refundInEscrow()` auto-approves the pending RefundRequest. There is no undo. + ```typescript const tx = await arbiter.payment.refundInEscrow(paymentInfo, request!.amount) @@ -112,7 +106,9 @@ console.log('Status:', approved?.status) // 1 = Approved ### 6. Deny a Refund Request -If the evidence does not support a refund: + +`deny()` = you reviewed and rejected the claim. `refuse()` = you decline to rule (e.g. conflict of interest). + ```typescript const denyTx = await arbiter.refund?.deny(paymentInfo) diff --git a/sdk/create-client.mdx b/sdk/create-client.mdx index 320187b..c477f60 100644 --- a/sdk/create-client.mdx +++ b/sdk/create-client.mdx @@ -1,5 +1,5 @@ --- -title: "Create a Client" +title: "Client Configuration" description: "Client factory, role presets, and configuration reference." icon: "plug" --- diff --git a/sdk/delivery-arbiter.mdx b/sdk/delivery-arbiter.mdx index 282240c..66b3290 100644 --- a/sdk/delivery-arbiter.mdx +++ b/sdk/delivery-arbiter.mdx @@ -108,7 +108,9 @@ async function evaluate(responseBody: string): Promise { ### 5. What Happens on Failure -If the arbiter does not call `release()`, funds auto-refund to the payer after the escrow period expires. The escrow period acts as the verification window. No dispute process needed. + +If your service goes down, no payments get evaluated and funds stay in escrow until timeout. The escrow period protects payers, but add uptime monitoring and alerting. + ## Next Steps diff --git a/sdk/delivery-merchant.mdx b/sdk/delivery-merchant.mdx index 9522a6e..935505d 100644 --- a/sdk/delivery-merchant.mdx +++ b/sdk/delivery-merchant.mdx @@ -64,10 +64,6 @@ console.log('EscrowPeriod:', deployment.escrowPeriodAddress) This is simpler than the marketplace preset. No RefundRequest, Evidence, or Freeze contracts. The operator's `releaseCondition` is `StaticAddressCondition(arbiter)`, so only the arbiter address can call `release()`. - -`deployDeliveryProtectionOperator` export is tracked in [x402r-sdk#102](https://github.com/BackTrackCo/x402r-sdk/issues/102). Until resolved, import from `@x402r/core/deploy`. - - ### 3. Configure forwardToArbiter() Add the `forwardToArbiter()` hook to your x402 resource server. After every payment settlement, it POSTs the HTTP response body to your arbiter service: @@ -102,7 +98,9 @@ The hook POSTs to `{arbiterUrl}/verify` with: The arbiter uses `parseForwardedPayload()` from `@x402r/helpers` to extract `paymentInfo` and `network` from the nested structure. -Fire-and-forget. Does not block the response to the client. Only fires for `commerce` scheme settlements. + +`forwardToArbiter()` is fire-and-forget. If the arbiter service is unreachable, funds stay in escrow until timeout. Add monitoring for arbiter availability. + ### 4. Share Addresses with the Arbiter diff --git a/sdk/delivery-protection.mdx b/sdk/delivery-protection.mdx index 4dcb851..a466e2d 100644 --- a/sdk/delivery-protection.mdx +++ b/sdk/delivery-protection.mdx @@ -16,7 +16,7 @@ This is different from the [marketplace model](/sdk/overview) where the merchant | Contracts needed | Operator + EscrowPeriod + RefundRequest + Evidence + Freeze | Operator + EscrowPeriod + StaticAddressCondition | | Deploy preset | `deployMarketplaceOperator()` | `deployDeliveryProtectionOperator()` | -**When to use:** AI content verification (garbage detection), schema validation, or any flow where every response needs automated quality checks before funds are released. +Use this when every response needs automated quality checks: AI content verification, garbage detection, schema validation. diff --git a/sdk/deploy-operator.mdx b/sdk/deploy-operator.mdx index 5e7f140..fbaa26c 100644 --- a/sdk/deploy-operator.mdx +++ b/sdk/deploy-operator.mdx @@ -197,6 +197,45 @@ Deployment is supported on all [supported chains](/sdk/overview#supported-chains Deployment requires gas fees. Ensure your wallet has ETH on the target network. On Base Sepolia, you can get testnet ETH from [Base network faucets](https://docs.base.org/base-chain/tools/network-faucets). +## Delivery Protection Operator + +For automated quality verification (AI garbage detection, schema validation), use the simpler delivery protection preset. No RefundRequest, Evidence, or Freeze contracts. The arbiter is the only address that can release funds. + +```typescript +import { deployDeliveryProtectionOperator } from '@x402r/core' + +const deployment = await deployDeliveryProtectionOperator( + walletClient, + publicClient, + { + chainId: 84532, + arbiter: '0xArbiterServiceAddress', + feeRecipient: account.address, + escrowPeriodSeconds: 300n, // 5 minutes + }, +) + +console.log('Operator:', deployment.operatorAddress) +console.log('EscrowPeriod:', deployment.escrowPeriodAddress) +console.log('ArbiterCondition:', deployment.arbiterConditionAddress) +``` + +| Option | Type | Description | +|--------|------|-------------| +| `chainId` | `number` | Target chain | +| `arbiter` | `Address` | Only address that can call `release()` | +| `feeRecipient` | `Address` | Receives protocol fees | +| `escrowPeriodSeconds` | `bigint` | Verification window before auto-refund | + +### Delivery Protection Slot Configuration + +| Slot | Contract | Purpose | +|------|----------|---------| +| `RELEASE_CONDITION` | StaticAddressCondition(arbiter) | Only arbiter can release | +| `AUTHORIZE_RECORDER` | EscrowPeriod | Records authorization time | +| `REFUND_IN_ESCROW_CONDITION` | EscrowPeriod | Anyone can refund after escrow expires | +| `REFUND_POST_ESCROW_CONDITION` | Receiver | Receiver can refund post-escrow | + ## Next Steps diff --git a/sdk/merchant.mdx b/sdk/merchant.mdx index 56d3419..a3d03d2 100644 --- a/sdk/merchant.mdx +++ b/sdk/merchant.mdx @@ -14,19 +14,11 @@ icon: "store" There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [merchant examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/merchant) and full [scenario scripts](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios). -### 1. Install Dependencies +### 1. Install `@x402r/sdk` - -```bash npm +```bash npm install @x402r/sdk ``` -```bash pnpm -pnpm add @x402r/sdk -``` -```bash bun -bun add @x402r/sdk -``` - ### 2. Create a Merchant Client @@ -53,12 +45,8 @@ const merchant = createMerchantClient({ }) ``` -`createMerchantClient` restricts TypeScript types to merchant-relevant methods. `payment.release()` shows up in autocomplete; `refund.request()` does not because that is a payer action. - ### 3. Check Payment State -In the x402 flow, the payer signs an ERC-3009 authorization, the facilitator settles it on-chain, and funds land in escrow on your operator. Your first interaction as a merchant is checking what arrived: - ```typescript import type { PaymentInfo } from '@x402r/sdk' @@ -76,7 +64,9 @@ console.log('In escrow:', inEscrow) // true ### 4. Release Funds After Escrow -Once the escrow period expires, release funds to the receiver (merchant): + +`release()` reverts if called during escrow. Check `escrow.isDuringEscrow()` first. Pass a smaller amount to release partially. + ```typescript const releaseTx = await merchant.payment.release(paymentInfo, 1_000_000n) diff --git a/sdk/overview.mdx b/sdk/overview.mdx index 7732926..4b43e59 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -14,9 +14,9 @@ Three roles interact with the protocol: ### Two Operator Models -**Marketplace** (`deployMarketplaceOperator`): The merchant releases funds after escrow. If the payer contests, they file a refund request and an arbiter resolves it. Best for general commerce where most transactions are uncontested. +**Marketplace** (`deployMarketplaceOperator`): The merchant releases funds after escrow. If the payer contests, they file a refund request and an arbiter resolves it. Use this for general commerce where most transactions are uncontested. -**Delivery Protection** (`deployDeliveryProtectionOperator`): The arbiter evaluates every transaction automatically and is the only address that can release funds. If the arbiter does not release, funds auto-refund after escrow. Best for AI content verification, schema validation, or any flow needing automated quality checks. +**Delivery Protection** (`deployDeliveryProtectionOperator`): The arbiter evaluates every transaction automatically and is the only address that can release funds. If the arbiter does not release, funds auto-refund after escrow. Use this for AI content verification, schema validation, or automated quality checks. ### Packages @@ -94,3 +94,4 @@ All contracts are deployed to identical addresses on every chain via CREATE3. On | Avalanche C-Chain | `43114` | `0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E` | | Monad | `143` | `0x754704Bc059F8C67012fEd69BC8A327a5aafb603` | | Linea | `59144` | `0x176211869cA2b568f2A7D4EE941E073a821EE1ff` | +| SKALE Base | `1187947933` | `0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20` | diff --git a/sdk/payer.mdx b/sdk/payer.mdx index 3ad4f0f..61a8b7e 100644 --- a/sdk/payer.mdx +++ b/sdk/payer.mdx @@ -14,19 +14,11 @@ icon: "user" There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [payer examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts). -### 1. Install Dependencies +### 1. Install `@x402r/sdk` - -```bash npm +```bash npm install @x402r/sdk ``` -```bash pnpm -pnpm add @x402r/sdk -``` -```bash bun -bun add @x402r/sdk -``` - ### 2. Create a Payer Client @@ -53,7 +45,9 @@ const payer = createPayerClient({ }) ``` -`createPayerClient` restricts TypeScript types to payer-relevant methods. `refund.request()` shows up in autocomplete; `payment.authorize()` does not because it is not part of the payer role. + +All payer actions (refund, freeze, evidence) must happen during the escrow period. Once escrow expires, the merchant can release. + ### 3. Check Payment State @@ -93,7 +87,9 @@ console.log('Status:', status) // 0 = Pending, 1 = Approved, 2 = Denied, 3 = Can ### 5. Freeze a Payment (Optional) -Freezing blocks the merchant from releasing funds while the dispute is active: + +`freeze()` blocks the merchant from releasing until the arbiter unfreezes. Only use when you need to prevent a release during investigation. + ```typescript const frozen = await payer.freeze?.isFrozen(paymentInfo) From fb76aba451af3815973c89b25e5f16c0b6ac2ee2 Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 01:47:15 -0700 Subject: [PATCH 17/23] Fix SDK tab 404: name the top-level group Empty group name caused Mintlify to not resolve a default page for the SDK tab. Named it "Getting Started". Co-Authored-By: Claude Opus 4.6 (1M context) --- docs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs.json b/docs.json index aec87b6..d4c2b4a 100644 --- a/docs.json +++ b/docs.json @@ -87,7 +87,7 @@ "tab": "SDK", "groups": [ { - "group": "", + "group": "Getting Started", "pages": [ "sdk/overview", "sdk/deploy-operator" From 93c5772b20b900f272d791fc9ea455984ee1d6fb Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 01:52:04 -0700 Subject: [PATCH 18/23] Rename create-client title, declutter deploy-operator - create-client title: "Create x402r Client" (was "Client Configuration") - deploy-operator: collapse reference sections into Accordions (deployment result type, preview addresses, slot configs) Keeps the main deploy flow visible, hides detail until needed Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/create-client.mdx | 2 +- sdk/deploy-operator.mdx | 120 +++++++++++++++++++--------------------- 2 files changed, 59 insertions(+), 63 deletions(-) diff --git a/sdk/create-client.mdx b/sdk/create-client.mdx index c477f60..5df2a78 100644 --- a/sdk/create-client.mdx +++ b/sdk/create-client.mdx @@ -1,5 +1,5 @@ --- -title: "Client Configuration" +title: "Create x402r Client" description: "Client factory, role presets, and configuration reference." icon: "plug" --- diff --git a/sdk/deploy-operator.mdx b/sdk/deploy-operator.mdx index fbaa26c..3d31753 100644 --- a/sdk/deploy-operator.mdx +++ b/sdk/deploy-operator.mdx @@ -131,65 +131,61 @@ console.log('Existing (reused):', result.summary.existingCount) | `freezeDurationSeconds` | `bigint` | How long freezes last. Default: `0n` (permanent until unfrozen) | | `operatorFeeBps` | `bigint` | Fee in basis points. Default: `0n` (no fee). `100n` = 1% | -## Deployment Result - -The `deployMarketplaceOperator` function returns: - -```typescript -interface MarketplaceOperatorDeployment { - operatorAddress: Address - escrowPeriodAddress: Address - freezeAddress: Address | null - refundRequestAddress: Address - refundRequestEvidenceAddress: Address - refundInEscrowConditionAddress: Address // OR(Receiver, Arbiter) - feeCalculatorAddress: Address | null // null if no fee - operatorConfig: OperatorConfig - deployments: DeployResult[] - summary: { - newCount: number - existingCount: number - txHashes: `0x${string}`[] - } -} -``` - - -Because all contracts use CREATE3, redeploying with the same parameters is idempotent. It will detect existing contracts and skip them. The `summary` tells you what was new vs reused. - - -## Preview Addresses (No Deploy) - -You can preview what addresses will be created without actually deploying: + + + ```typescript + interface MarketplaceOperatorDeployment { + operatorAddress: Address + escrowPeriodAddress: Address + freezeAddress: Address | null + refundRequestAddress: Address + refundRequestEvidenceAddress: Address + refundInEscrowConditionAddress: Address // OR(Receiver, Arbiter) + feeCalculatorAddress: Address | null // null if no fee + operatorConfig: OperatorConfig + deployments: DeployResult[] + summary: { + newCount: number + existingCount: number + txHashes: `0x${string}`[] + } + } + ``` -```typescript -import { previewMarketplaceOperator } from '@x402r/core' + Redeploying with the same parameters is idempotent (CREATE3). It detects existing contracts and skips them. Check `summary` for what was new vs reused. + -const preview = await previewMarketplaceOperator(publicClient, { - chainId: 84532, - feeRecipient: '0xYourAddress...', - arbiter: '0xArbiterAddress...', - escrowPeriodSeconds: 604_800n, -}) + + Compute addresses without deploying: -console.log('Operator will be at:', preview.operatorAddress); -console.log('EscrowPeriod will be at:', preview.escrowPeriodAddress); -``` - -## Operator Slot Configuration + ```typescript + import { previewMarketplaceOperator } from '@x402r/core' -The deployed operator has the following slot configuration: + const preview = await previewMarketplaceOperator(publicClient, { + chainId: 84532, + feeRecipient: '0xYourAddress...', + arbiter: '0xArbiterAddress...', + escrowPeriodSeconds: 604_800n, + }) -| Slot | Contract | Purpose | -|------|----------|---------| -| `AUTHORIZE_CONDITION` | UsdcTvlLimit | Safety limit on authorization | -| `AUTHORIZE_RECORDER` | EscrowPeriod | Records authorization timestamp | -| `CHARGE_CONDITION` | (none) | No restrictions on charge | -| `RELEASE_CONDITION` | EscrowPeriod | Blocks release during escrow period | -| `REFUND_IN_ESCROW_CONDITION` | OR(Receiver, Arbiter) | Receiver or arbiter can approve | -| `REFUND_POST_ESCROW_CONDITION` | Receiver | Only receiver after escrow | -| `FEE_CALCULATOR` | StaticFeeCalculator | Fixed percentage fee | -| `FEE_RECIPIENT` | Your address | Receives fees | + console.log('Operator will be at:', preview.operatorAddress) + console.log('EscrowPeriod will be at:', preview.escrowPeriodAddress) + ``` + + + + | Slot | Contract | Purpose | + |------|----------|---------| + | `AUTHORIZE_CONDITION` | UsdcTvlLimit | Safety limit on authorization | + | `AUTHORIZE_RECORDER` | EscrowPeriod | Records authorization timestamp | + | `CHARGE_CONDITION` | (none) | No restrictions on charge | + | `RELEASE_CONDITION` | EscrowPeriod | Blocks release during escrow period | + | `REFUND_IN_ESCROW_CONDITION` | OR(Receiver, Arbiter) | Receiver or arbiter can approve | + | `REFUND_POST_ESCROW_CONDITION` | Receiver | Only receiver after escrow | + | `FEE_CALCULATOR` | StaticFeeCalculator | Fixed percentage fee | + | `FEE_RECIPIENT` | Your address | Receives fees | + + Deployment is supported on all [supported chains](/sdk/overview#supported-chains). Pass the numeric `chainId` in the options. @@ -227,14 +223,14 @@ console.log('ArbiterCondition:', deployment.arbiterConditionAddress) | `feeRecipient` | `Address` | Receives protocol fees | | `escrowPeriodSeconds` | `bigint` | Verification window before auto-refund | -### Delivery Protection Slot Configuration - -| Slot | Contract | Purpose | -|------|----------|---------| -| `RELEASE_CONDITION` | StaticAddressCondition(arbiter) | Only arbiter can release | -| `AUTHORIZE_RECORDER` | EscrowPeriod | Records authorization time | -| `REFUND_IN_ESCROW_CONDITION` | EscrowPeriod | Anyone can refund after escrow expires | -| `REFUND_POST_ESCROW_CONDITION` | Receiver | Receiver can refund post-escrow | + + | Slot | Contract | Purpose | + |------|----------|---------| + | `RELEASE_CONDITION` | StaticAddressCondition(arbiter) | Only arbiter can release | + | `AUTHORIZE_RECORDER` | EscrowPeriod | Records authorization time | + | `REFUND_IN_ESCROW_CONDITION` | EscrowPeriod | Anyone can refund after escrow expires | + | `REFUND_POST_ESCROW_CONDITION` | Receiver | Receiver can refund post-escrow | + ## Next Steps From 2e42a9d2b0571effff098ff74a77619ee2e33e66 Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 01:55:46 -0700 Subject: [PATCH 19/23] Remove fabricated deploy-operator CLI section The entire "Deploy Your Operator" Steps section (clone, .env.example, pnpm start, deploy-short-escrow.ts) referenced a deploy-operator example directory that does not exist in the SDK. Removed all fabricated content. The SDK programmatic deployment code remains. Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/deploy-operator.mdx | 82 +---------------------------------------- 1 file changed, 2 insertions(+), 80 deletions(-) diff --git a/sdk/deploy-operator.mdx b/sdk/deploy-operator.mdx index 3d31753..ca8f7a1 100644 --- a/sdk/deploy-operator.mdx +++ b/sdk/deploy-operator.mdx @@ -4,87 +4,9 @@ description: "Deploy a PaymentOperator with escrow, freeze, and dispute resoluti icon: "rocket" --- -The `@x402r/core` package includes deployment utilities that handle the full lifecycle of deploying a PaymentOperator and all its supporting contracts. +## Marketplace Operator - - Clone the deploy-operator example and deploy your own operator in minutes. - - -## Overview - -A complete marketplace operator deployment includes: - -1. **EscrowPeriod** - Records authorization time, enforces waiting period before release -2. **Freeze** - Allows payer to freeze payment during escrow, receiver to unfreeze -3. **StaticAddressCondition** - Restricts refund approval to the designated arbiter -4. **OrCondition** - Allows either the receiver OR the arbiter to approve in-escrow refunds -5. **StaticFeeCalculator** - Optional operator fee (basis points) -6. **PaymentOperator** - The main contract tying everything together - -All contracts are deployed via factories using CREATE3, so identical configurations produce identical addresses across deployments. - -## Deploy Your Operator - -**Prerequisites:** -- Node.js 20+, pnpm 9.15+ -- A private key with Base Sepolia ETH ([get testnet ETH](https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet)) - - - - ```bash - git clone https://github.com/BackTrackCo/x402r-sdk.git - cd x402r-sdk - pnpm install && pnpm build - ``` - - - Copy the example env file and set your private key: - ```bash - cd examples/deploy-operator - cp .env.example .env - ``` - - Edit `.env` with your values. Only `PRIVATE_KEY` is required everything else has sensible defaults: - - | Variable | Default | Description | - |----------|---------|-------------| - | `PRIVATE_KEY` | (required) | Deployer wallet private key | - | `ARBITER` | deployer address | Dispute resolver | - | `FEE_RECIPIENT` | deployer address | Receives operator fees | - | `ESCROW_PERIOD` | `604800` (7 days) | Escrow period in seconds | - | `FREEZE_DURATION` | `259200` (3 days) | Freeze duration in seconds | - | `FEE_BPS` | `100` (1%) | Operator fee in basis points | - | `NETWORK_ID` | `eip155:84532` | Chain identifier | - | `RPC_URL` | `https://sepolia.base.org` | RPC endpoint | - - - ```bash - pnpm start - ``` - - Or pass env vars inline from the SDK root: - ```bash - PRIVATE_KEY=0x... pnpm tsx examples/deploy-operator/index.ts - ``` - - - The script outputs all deployed contract addresses and BaseScan links. Use the `PaymentOperator` address in your payment payloads: - ```typescript - operator: "0xYourOperatorAddress..." - ``` - - - - -For quick E2E testing with short timers (5-minute escrow, 3-minute freeze), use the short-escrow variant instead: -```bash -PRIVATE_KEY=0x... pnpm tsx examples/deploy-operator/deploy-short-escrow.ts -``` - - -## Using the SDK Directly - -If you want to integrate deployment into your own code: +A marketplace operator deployment includes: EscrowPeriod, Freeze, StaticAddressCondition (arbiter), OrCondition (receiver OR arbiter for refunds), optional StaticFeeCalculator, and the PaymentOperator itself. All deployed via CREATE3 factories. ```typescript import { createPublicClient, createWalletClient, http } from 'viem'; From ce5e6c23eafb40bb3266e7aa47cd5c3f570c1dc4 Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 02:03:23 -0700 Subject: [PATCH 20/23] Remove duplicated deploy step from delivery-merchant Deploy is already on the deploy-operator page. Link there instead of repeating 30 lines of deploy code. Page now focuses on what the merchant actually does: configure forwardToArbiter(). Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/delivery-merchant.mdx | 66 ++++----------------------------------- 1 file changed, 6 insertions(+), 60 deletions(-) diff --git a/sdk/delivery-merchant.mdx b/sdk/delivery-merchant.mdx index 935505d..38f0294 100644 --- a/sdk/delivery-merchant.mdx +++ b/sdk/delivery-merchant.mdx @@ -1,70 +1,16 @@ --- title: "Merchant Setup" -description: "Deploy a delivery protection operator and forward responses to the arbiter." +description: "Configure forwardToArbiter() to send responses to the arbiter for evaluation." icon: "store" --- ### Prerequisites -* A wallet with ETH on Base Sepolia for gas ([faucet](https://www.alchemy.com/faucets/base-sepolia)) -* Node.js 18+ and npm +* A deployed delivery protection operator (see [Deploy an Operator](/sdk/deploy-operator#delivery-protection-operator)) * An arbiter service URL (see [Arbiter Setup](/sdk/delivery-arbiter)) +* `@x402r/helpers` installed (`npm install @x402r/helpers`) -### 1. Install Dependencies - - -```bash npm -npm install @x402r/core @x402r/helpers -``` -```bash pnpm -pnpm add @x402r/core @x402r/helpers -``` -```bash bun -bun add @x402r/core @x402r/helpers -``` - - -`@x402r/core` is needed for `deployDeliveryProtectionOperator()`. `@x402r/helpers` provides the `forwardToArbiter()` hook. - -### 2. Deploy a Delivery Protection Operator - -```typescript -import { createPublicClient, createWalletClient, http } from 'viem' -import { baseSepolia } from 'viem/chains' -import { privateKeyToAccount } from 'viem/accounts' -import { deployDeliveryProtectionOperator } from '@x402r/core' - -const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) - -const publicClient = createPublicClient({ - chain: baseSepolia, - transport: http(), -}) - -const walletClient = createWalletClient({ - account, - chain: baseSepolia, - transport: http(), -}) - -const deployment = await deployDeliveryProtectionOperator( - walletClient, - publicClient, - { - chainId: 84532, - arbiter: '0xArbiterServiceAddress', // the arbiter service wallet - feeRecipient: account.address, - escrowPeriodSeconds: 300n, // 5 minutes (short for automated flows) - }, -) - -console.log('Operator:', deployment.operatorAddress) -console.log('EscrowPeriod:', deployment.escrowPeriodAddress) -``` - -This is simpler than the marketplace preset. No RefundRequest, Evidence, or Freeze contracts. The operator's `releaseCondition` is `StaticAddressCondition(arbiter)`, so only the arbiter address can call `release()`. - -### 3. Configure forwardToArbiter() +### 1. Configure forwardToArbiter() Add the `forwardToArbiter()` hook to your x402 resource server. After every payment settlement, it POSTs the HTTP response body to your arbiter service: @@ -102,9 +48,9 @@ The arbiter uses `parseForwardedPayload()` from `@x402r/helpers` to extract `pay `forwardToArbiter()` is fire-and-forget. If the arbiter service is unreachable, funds stay in escrow until timeout. Add monitoring for arbiter availability. -### 4. Share Addresses with the Arbiter +### 2. Share Addresses with the Arbiter -The arbiter service needs `operatorAddress` and `escrowPeriodAddress` from step 2 to create its SDK client. Share these via config, environment variables, or a shared registry. +The arbiter service needs `operatorAddress` and `escrowPeriodAddress` from your [deployment](/sdk/deploy-operator#delivery-protection-operator) to create its SDK client. Share these via config, environment variables, or a shared registry. ## Next Steps From d8784702c97ddadd1ba8c5ce8d8ba6e522c683a5 Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 02:04:19 -0700 Subject: [PATCH 21/23] Restore proper install step for delivery-merchant Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/delivery-merchant.mdx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sdk/delivery-merchant.mdx b/sdk/delivery-merchant.mdx index 38f0294..7e7d645 100644 --- a/sdk/delivery-merchant.mdx +++ b/sdk/delivery-merchant.mdx @@ -8,9 +8,14 @@ icon: "store" * A deployed delivery protection operator (see [Deploy an Operator](/sdk/deploy-operator#delivery-protection-operator)) * An arbiter service URL (see [Arbiter Setup](/sdk/delivery-arbiter)) -* `@x402r/helpers` installed (`npm install @x402r/helpers`) -### 1. Configure forwardToArbiter() +### 1. Install Dependencies + +```bash +npm install @x402r/helpers +``` + +### 2. Configure forwardToArbiter() Add the `forwardToArbiter()` hook to your x402 resource server. After every payment settlement, it POSTs the HTTP response body to your arbiter service: @@ -48,7 +53,7 @@ The arbiter uses `parseForwardedPayload()` from `@x402r/helpers` to extract `pay `forwardToArbiter()` is fire-and-forget. If the arbiter service is unreachable, funds stay in escrow until timeout. Add monitoring for arbiter availability. -### 2. Share Addresses with the Arbiter +### 3. Share Addresses with the Arbiter The arbiter service needs `operatorAddress` and `escrowPeriodAddress` from your [deployment](/sdk/deploy-operator#delivery-protection-operator) to create its SDK client. Share these via config, environment variables, or a shared registry. From f3549e4694f517c35bb99af20d7e725774077b9e Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 02:05:13 -0700 Subject: [PATCH 22/23] Restore CodeGroup for install command in delivery-merchant Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/delivery-merchant.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sdk/delivery-merchant.mdx b/sdk/delivery-merchant.mdx index 7e7d645..28ab7f7 100644 --- a/sdk/delivery-merchant.mdx +++ b/sdk/delivery-merchant.mdx @@ -11,9 +11,17 @@ icon: "store" ### 1. Install Dependencies -```bash + +```bash npm npm install @x402r/helpers ``` +```bash pnpm +pnpm add @x402r/helpers +``` +```bash bun +bun add @x402r/helpers +``` + ### 2. Configure forwardToArbiter() From 2a314a6aa982c99bb1a5b6ed18c0c8f78b84127c Mon Sep 17 00:00:00 2001 From: vraspar Date: Tue, 31 Mar 2026 02:06:09 -0700 Subject: [PATCH 23/23] Restore CodeGroup for install commands in marketplace guides Merchant, payer, and arbiter had bare npm install without pnpm/bun tabs. Now consistent with all other pages. Co-Authored-By: Claude Opus 4.6 (1M context) --- sdk/arbiter.mdx | 12 ++++++++++-- sdk/merchant.mdx | 12 ++++++++++-- sdk/payer.mdx | 12 ++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/sdk/arbiter.mdx b/sdk/arbiter.mdx index 48bf231..cdc4d92 100644 --- a/sdk/arbiter.mdx +++ b/sdk/arbiter.mdx @@ -14,11 +14,19 @@ icon: "scale-balanced" There are pre-configured [arbiter examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/arbiter) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts) in the SDK repo. -### 1. Install `@x402r/sdk` +### 1. Install Dependencies -```bash + +```bash npm npm install @x402r/sdk ``` +```bash pnpm +pnpm add @x402r/sdk +``` +```bash bun +bun add @x402r/sdk +``` + ### 2. Create an Arbiter Client diff --git a/sdk/merchant.mdx b/sdk/merchant.mdx index a3d03d2..c1c9864 100644 --- a/sdk/merchant.mdx +++ b/sdk/merchant.mdx @@ -14,11 +14,19 @@ icon: "store" There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [merchant examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/merchant) and full [scenario scripts](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios). -### 1. Install `@x402r/sdk` +### 1. Install Dependencies -```bash + +```bash npm npm install @x402r/sdk ``` +```bash pnpm +pnpm add @x402r/sdk +``` +```bash bun +bun add @x402r/sdk +``` + ### 2. Create a Merchant Client diff --git a/sdk/payer.mdx b/sdk/payer.mdx index 61a8b7e..4289d64 100644 --- a/sdk/payer.mdx +++ b/sdk/payer.mdx @@ -14,11 +14,19 @@ icon: "user" There are pre-configured [examples in the x402r-sdk repo](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples), including [payer examples](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/payer) and a full [dispute resolution scenario](https://github.com/BackTrackCo/x402r-sdk/tree/main/examples/scenarios/dispute-resolution.ts). -### 1. Install `@x402r/sdk` +### 1. Install Dependencies -```bash + +```bash npm npm install @x402r/sdk ``` +```bash pnpm +pnpm add @x402r/sdk +``` +```bash bun +bun add @x402r/sdk +``` + ### 2. Create a Payer Client