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/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/docs.json b/docs.json index 3ae8129..d4c2b4a 100644 --- a/docs.json +++ b/docs.json @@ -90,53 +90,30 @@ "group": "Getting Started", "pages": [ "sdk/overview", - "sdk/installation", - "sdk/concepts", - "sdk/deploy-operator", - "sdk/examples", - "sdk/limitations" + "sdk/deploy-operator" ] }, { - "group": "Merchant", + "group": "Marketplace", "pages": [ - "sdk/merchant/getting-started", - "sdk/helpers/refundable", - "sdk/merchant/quickstart", - "sdk/merchant/refund-handling" + "sdk/merchant", + "sdk/payer", + "sdk/arbiter" ] }, { - "group": "Client", + "group": "Delivery Protection", "pages": [ - "sdk/client/quickstart", - "sdk/client/payment-queries", - "sdk/client/escrow-management", - "sdk/client/refund-operations" + "sdk/delivery-protection", + "sdk/delivery-merchant", + "sdk/delivery-arbiter" ] }, { - "group": "Arbiter", + "group": "Reference", "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/create-client", + "sdk/examples" ] } ] 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/arbiter.mdx b/sdk/arbiter.mdx new file mode 100644 index 0000000..cdc4d92 --- /dev/null +++ b/sdk/arbiter.mdx @@ -0,0 +1,175 @@ +--- +title: "Arbiter Guide" +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 = 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 + + +`refundInEscrow()` auto-approves the pending RefundRequest. There is no undo. + + +```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 + + +`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) +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/create-client.mdx b/sdk/create-client.mdx new file mode 100644 index 0000000..5df2a78 --- /dev/null +++ b/sdk/create-client.mdx @@ -0,0 +1,111 @@ +--- +title: "Create x402r 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..66b3290 --- /dev/null +++ b/sdk/delivery-arbiter.mdx @@ -0,0 +1,127 @@ +--- +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 +``` +```bash pnpm +pnpm add @x402r/sdk @x402r/helpers +``` +```bash bun +bun add @x402r/sdk @x402r/helpers +``` + + +### 2. Create the Arbiter Client + +```typescript +import { createPublicClient, createWalletClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { createArbiterClient } from '@x402r/sdk' +import { fromNetworkId } from '@x402r/core' + +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 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 + + + + 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..28ab7f7 --- /dev/null +++ b/sdk/delivery-merchant.mdx @@ -0,0 +1,77 @@ +--- +title: "Merchant Setup" +description: "Configure forwardToArbiter() to send responses to the arbiter for evaluation." +icon: "store" +--- + +### Prerequisites + +* 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)) + +### 1. Install Dependencies + + +```bash npm +npm install @x402r/helpers +``` +```bash pnpm +pnpm add @x402r/helpers +``` +```bash bun +bun add @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: + +```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", + "accepted": { "network": "eip155:84532", ... }, + "payload": { "paymentInfo": { ... }, ... } + } +} +``` + +The arbiter uses `parseForwardedPayload()` from `@x402r/helpers` to extract `paymentInfo` and `network` from the nested structure. + + +`forwardToArbiter()` is fire-and-forget. If the arbiter service is unreachable, funds stay in escrow until timeout. Add monitoring for arbiter availability. + + +### 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. + +## 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..a466e2d --- /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()` | + +Use this when every response needs automated quality checks: AI content verification, garbage detection, schema validation. + + + + 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 a97990b..ca8f7a1 100644 --- a/sdk/deploy-operator.mdx +++ b/sdk/deploy-operator.mdx @@ -4,124 +4,43 @@ 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 CREATE2, 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` | — | Deployer wallet (required) | - | `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'; 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.newCount) +console.log('Existing (reused):', result.summary.existingCount) ``` ## Configuration Options @@ -134,101 +53,120 @@ console.log('Existing (reused):', result.summary.existingContracts); | `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 + + + ```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}`[] + } + } + ``` -The `deployMarketplaceOperator` function returns: + Redeploying with the same parameters is idempotent (CREATE3). It detects existing contracts and skips them. Check `summary` for what was new vs reused. + -```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 - summary: { - newDeployments: number; // Newly deployed contracts - existingContracts: number; // Reused existing contracts - }; -} -``` + + Compute addresses without deploying: - -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. - + ```typescript + 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) + ``` + + + + | 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. + + +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). + -## Preview Addresses (No Deploy) +## Delivery Protection Operator -You can preview what addresses will be created without actually deploying: +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 { previewMarketplaceOperator } from '@x402r/core/deploy'; +import { deployDeliveryProtectionOperator } from '@x402r/core' -const preview = await previewMarketplaceOperator( +const deployment = await deployDeliveryProtectionOperator( + walletClient, publicClient, - 'eip155:84532', { - feeRecipient: '0xYourAddress...', - arbiter: '0xArbiterAddress...', - escrowPeriodSeconds: 604800n, - } -); - -console.log('Operator will be at:', preview.operatorAddress); -console.log('EscrowPeriod will be at:', preview.escrowPeriodAddress); + 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) ``` -## Operator Slot Configuration - -The deployed operator has the following slot configuration: - -| 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 | - -## 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 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). - +| 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 | + + + | 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 - - Clone and run the deploy-operator example. + + Accept 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) | diff --git a/sdk/installation.mdx b/sdk/installation.mdx deleted file mode 100644 index 7485080..0000000 --- a/sdk/installation.mdx +++ /dev/null @@ -1,70 +0,0 @@ ---- -title: "Installation" -description: "Install and configure the X402r SDK packages" -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: - -```typescript -import { getNetworkConfig } from '@x402r/core'; - -const config = getNetworkConfig('eip155:84532'); // Base Sepolia - -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 -``` - - -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'`. - - - -Never commit private keys to version control. Use environment variables or a secrets manager. - - -## Next Steps - - - - Learn about payment states, escrow, and the refund lifecycle. - - - Working examples for merchants, clients, and arbiters. - - diff --git a/sdk/merchant.mdx b/sdk/merchant.mdx new file mode 100644 index 0000000..c1c9864 --- /dev/null +++ b/sdk/merchant.mdx @@ -0,0 +1,121 @@ +--- +title: "Merchant Guide" +description: "Accept a payment into escrow, check state, and release funds." +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 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). + + +### 1. Install Dependencies + + +```bash npm +npm install @x402r/sdk +``` +```bash pnpm +pnpm add @x402r/sdk +``` +```bash bun +bun add @x402r/sdk +``` + + +### 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 merchant = createMerchantClient({ + 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 +}) +``` + +### 3. Check Payment State + +```typescript +import type { PaymentInfo } from '@x402r/sdk' + +// paymentInfo comes from the facilitator callback or your payment records +const paymentInfo: PaymentInfo = { /* ... */ } + +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 +``` + +### 4. Release Funds After Escrow + + +`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) +console.log('Released:', releaseTx) + +// Verify +const after = await merchant.payment.getAmounts(paymentInfo) +console.log('Capturable after release:', after.capturableAmount) // 0n +``` + +### 5. Handle Refund Requests (Optional) + +If a payer disputes, check for pending refund requests: + +```typescript +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) +} +``` + +## Next Steps + + + + Full deployment config, slot details, and preview addresses. + + + How escrow, capture, and void work under the hood. + + + Full scenario scripts to copy from. + + 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 c0e9ceb..4b43e59 100644 --- a/sdk/overview.mdx +++ b/sdk/overview.mdx @@ -1,49 +1,97 @@ --- -title: "SDK Overview" -description: "TypeScript SDK for the X402r refundable payments protocol (experimental)" +title: "Overview" +description: "TypeScript SDK for adding escrow, refunds, and dispute resolution to x402 payments" 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 payments are instant and irreversible. x402r adds escrow holds, refund windows, and dispute resolution on top. -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. +Three roles interact with the protocol: -## Packages +- **Merchants** receive payments into escrow and release funds after delivery +- **Payers** can request refunds, freeze payments, and submit evidence during disputes +- **Arbiters** verify transactions or resolve disputes (two models below) -The SDK is organized into packages designed for specific roles in the payment ecosystem: +### 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. 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. Use this for AI content verification, schema validation, or automated quality checks. + +### Packages + + +```bash npm +npm install @x402r/sdk +``` +```bash pnpm +pnpm add @x402r/sdk +``` +```bash bun +bun add @x402r/sdk +``` + + +`@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: + + +```bash npm +npm install @x402r/core +``` +```bash pnpm +pnpm add @x402r/core +``` +```bash bun +bun add @x402r/core +``` + + +For x402 server integration: + + +```bash npm +npm install @x402r/helpers +``` +```bash pnpm +pnpm add @x402r/helpers +``` +```bash bun +bun add @x402r/helpers +``` + + +### Guides - - Shared types, ABIs, network config, deploy utilities, and condition builders. - - - SDK for payers to request refunds, freeze payments, and manage escrow. + + Deploy an operator, accept a payment, release funds from escrow. - - SDK for merchants to release payments, charge, and handle refunds. + + Check payment state, request a refund, submit evidence. - - SDK for arbiters to resolve disputes and manage refund decisions. - - - Framework-agnostic helper to mark x402 payment options as refundable with escrow configuration. + + Review disputes, approve or deny refunds, distribute fees. -## 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 | +### Supported Chains + +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` | +| 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` | +| SKALE Base | `1187947933` | `0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20` | diff --git a/sdk/payer.mdx b/sdk/payer.mdx new file mode 100644 index 0000000..4289d64 --- /dev/null +++ b/sdk/payer.mdx @@ -0,0 +1,150 @@ +--- +title: "Payer Guide" +description: "Check payment state, request a refund, freeze a payment, and submit evidence." +icon: "user" +--- + +### 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 [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). + + +### 1. Install Dependencies + + +```bash npm +npm install @x402r/sdk +``` +```bash pnpm +pnpm add @x402r/sdk +``` +```bash bun +bun add @x402r/sdk +``` + + +### 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...', +}) +``` + + +All payer actions (refund, freeze, evidence) must happen during the escrow period. Once escrow expires, the merchant can release. + + +### 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 = Cancelled, 4 = Refused +``` + +### 5. Freeze a Payment (Optional) + + +`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) +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) +``` + +## Next Steps + + + + What happens after you submit a refund request. + + + Escrow timing, capture, and the payment lifecycle. + + + Full dispute resolution scenario end-to-end. + + 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..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.