Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
18bf6d3
Rewrite V2 SDK Getting Started pages with editorial voice
vraspar Mar 28, 2026
d08da77
Remove V1 SDK pages from nav
vraspar Mar 29, 2026
f743b45
Rewrite SDK docs as step-by-step quickstart tutorials
vraspar Mar 29, 2026
4cf2ed2
Remove em dashes, add CodeGroup to all install commands
vraspar Mar 29, 2026
ed13e13
Add arbiter quickstart, examples page, update deploy-operator
vraspar Mar 29, 2026
87eef31
Clean up page layout: icons, cards, remove cruft
vraspar Mar 29, 2026
62eda3d
Use Mintlify Note component instead of bold Note pattern
vraspar Mar 29, 2026
0f33b70
Fix Next Steps links to point where readers actually go next
vraspar Mar 29, 2026
fecbb68
Remove viem from install commands
vraspar Mar 29, 2026
8a2b99f
Remove deploy and authorize steps from merchant quickstart
vraspar Mar 29, 2026
05840cb
Address PR review: fix broken fence, wrong types, orphaned pages
vraspar Mar 29, 2026
a4a6757
Add two arbiter models: dispute resolution and delivery protection
vraspar Mar 31, 2026
729e28a
docs: use parseForwardedPayload + fromNetworkId in arbiter quickstart
vraspar Mar 31, 2026
13b4ef3
Restructure SDK nav: Marketplace, Delivery Protection, Resources
vraspar Mar 31, 2026
6590c9b
Fix nav, titles, installs, and review issues (round 3)
vraspar Mar 31, 2026
db26244
Fix technical issues, remove boilerplate, add warnings
vraspar Mar 31, 2026
fb76aba
Fix SDK tab 404: name the top-level group
vraspar Mar 31, 2026
93c5772
Rename create-client title, declutter deploy-operator
vraspar Mar 31, 2026
2e42a9d
Remove fabricated deploy-operator CLI section
vraspar Mar 31, 2026
ce5e6c2
Remove duplicated deploy step from delivery-merchant
vraspar Mar 31, 2026
d878470
Restore proper install step for delivery-merchant
vraspar Mar 31, 2026
f3549e4
Restore CodeGroup for install command in delivery-merchant
vraspar Mar 31, 2026
2a314a6
Restore CodeGroup for install commands in marketplace guides
vraspar Mar 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
2 changes: 1 addition & 1 deletion contracts/factories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ const deployments = {
<Card title="Deploy an Operator" icon="rocket" href="/sdk/deploy-operator">
Use the SDK's `deployMarketplaceOperator()` for simplified deployment.
</Card>
<Card title="SDK Installation" icon="download" href="/sdk/installation">
<Card title="SDK Overview" icon="download" href="/sdk/overview">
Install the SDK packages.
</Card>
</CardGroup>
2 changes: 1 addition & 1 deletion contracts/periphery/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ All periphery contracts use **unified CREATE3 addresses** — the same address o
| AlwaysTrueCondition | `0xb295df7E7f786fd84D614AB26b1f2e86026C3483` |

<Note>
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.
</Note>

## Next Steps
Expand Down
47 changes: 12 additions & 35 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
]
Expand Down
2 changes: 1 addition & 1 deletion roadmap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ All contracts use **unified CREATE3 addresses** — same address on every suppor
| Condition singletons (Payer, Receiver, AlwaysTrue) | Deployed |

<Note>
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.
</Note>

## Get Involved
Expand Down
175 changes: 175 additions & 0 deletions sdk/arbiter.mdx
Original file line number Diff line number Diff line change
@@ -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))

<Note>
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.
</Note>

### 1. Install Dependencies

<CodeGroup>
```bash npm
npm install @x402r/sdk
```
```bash pnpm
pnpm add @x402r/sdk
```
```bash bun
bun add @x402r/sdk
```
</CodeGroup>

### 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

<Warning>
`refundInEscrow()` auto-approves the pending RefundRequest. There is no undo.
</Warning>

```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

<Note>
`deny()` = you reviewed and rejected the claim. `refuse()` = you decline to rule (e.g. conflict of interest).
</Note>

```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

<CardGroup cols={3}>
<Card title="Delivery Protection" icon="shield-check" href="/sdk/delivery-protection">
Automated evaluation for every transaction.
</Card>
<Card title="Deploy Operator" icon="rocket" href="/sdk/deploy-operator">
How your address gets configured as arbiter on an operator.
</Card>
<Card title="Examples" icon="code" href="/sdk/examples">
Full dispute resolution scenario end-to-end.
</Card>
</CardGroup>
111 changes: 111 additions & 0 deletions sdk/create-client.mdx
Original file line number Diff line number Diff line change
@@ -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

<CardGroup cols={3}>
<Card title="Merchant Guide" icon="store" href="/sdk/merchant">
Accept payments and release funds.
</Card>
<Card title="Deploy Operator" icon="rocket" href="/sdk/deploy-operator">
Get the addresses for your client config.
</Card>
<Card title="Smart Contracts" icon="file-contract" href="/contracts/overview">
Conditions and recorders that control on-chain access.
</Card>
</CardGroup>
Loading
Loading