Skip to content

Commit 46be3ed

Browse files
feat: toju.network/x402@0.1.0 (#212)
* chore: x402 payment flow on base sepolia (#208) * chore: x402 payment flow on base sepolia this is a proof of concept for the work on integrating x402 into our architecture, so we have autonomous clients (AI Agents) pay for only what they store on IPFS. it proves that the full x402 cycle works: agent signs eip-3009 off-chain, coinbase facilitator verifies + settles usdc on base sepolia, server returns 200. the spike includes a minimal express server with `@x402/express` middleware and a client using `@x402/fetch` + `@x402/evm`. after running into a bug where the payment kept breaking, i found out that `ExactEvmScheme` expects `{ address, signTypedData }` directly on the signer — not a viem `WalletClient` (which puts address on account.address, not the root object). ```ts Paying from: 0x68a05F9AcF9180Cd2dA8e4bB833a704445FE07F8 --- GET /free --- Status: 200 { message: "This one's free." } --- GET /paid (x402) --- Status: 200 { message: "Payment verified. You're in.", timestamp: '2026-03-20T13:01:13.460Z' } Payment-Response header: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4Yjc4NmUyNzBhZjVjNDdhMDA5YTJjYzM3MGJkMTI4NDkyMzg4MTNmNjhiM2JhNjBkODExYjVlZTllZTA0OGRiYyIsIm5ldHdvcmsiOiJlaXAxNTU6ODQ1MzIiLCJwYXllciI6IjB4NjhhMDVGOUFjRjkxODBDZDJkQThlNGJCODMzYTcwNDQ0NUZFMDdGOSJ9 ``` * fmt * feat: add upload endpoint for agents integrating x402 (#209) * feat: add upload endpoint for agents integrating x402 i've included the agent upload route protected with the x402 middleware applicable to that route alione. dynamic pricing from size + duration is implemented with the same approach we use for our other packages. i'm also opting to store `depositAmount` for the cost in micro-usdc to maintain the same flow for atomic units of SOL in lamports. * typos * feat: add @toju.network/x402 (#210) * feat: add @toju.network/x402 this is the client-side counterpart to the agent upload endpoint we added in the previous PR. it wraps the x402 fetch client so agents can store files on storacha (IPFS) autonomously, paying in usdc on base with no wallet popups or human intervention. the client handles the full 402 → sign → retry flow internally. agents only need a private key and a file. i've also included an `estimateStorageCost` method so they can check the cost before committing to a store. * chore: fixed an issue where biome was unable to format changeset markdow files due to the lint-staged config. * chore: add x402 docs and update offering (#211) * chore: release @toju.network/x402@0.1.0
1 parent a6e0ed1 commit 46be3ed

25 files changed

Lines changed: 1389 additions & 77 deletions

docs/mint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"sdk/deposit",
5656
"sdk/sol-price",
5757
"sdk/upload-history",
58-
"sdk/renewal"
58+
"sdk/renewal",
59+
"sdk/x402"
5960
]
6061
}
6162
],

docs/sdk/x402.mdx

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
---
2+
title: 'Agent Payments (x402)'
3+
description: 'Store files autonomously and pay with USDC on Base — no human intervention required'
4+
---
5+
6+
## Overview
7+
8+
`@toju.network/x402` implements the [x402 protocol](https://x402.org) for autonomous storage payments. An agent instantiates `AgentClient` with a funded Base wallet, calls `store()`, and the SDK handles the full payment negotiation automatically — no wallet prompts, no manual signing steps.
9+
10+
The flow under the hood:
11+
12+
<Steps>
13+
<Step title="Send request">
14+
Agent sends `POST /upload/agent` with the file and storage parameters
15+
</Step>
16+
<Step title="Receive 402">
17+
Server responds with `402 Payment Required` and a price quote in USDC
18+
</Step>
19+
<Step title="Sign off-chain">
20+
SDK signs an EIP-3009 authorization (no on-chain transaction yet)
21+
</Step>
22+
<Step title="Retry with payment">
23+
SDK retries the request with the signed `X-PAYMENT` header attached
24+
</Step>
25+
<Step title="Facilitator settles">
26+
Coinbase's public facilitator verifies and settles the USDC transfer on Base
27+
</Step>
28+
<Step title="File stored">
29+
Server uploads to IPFS via Storacha and returns the CID
30+
</Step>
31+
</Steps>
32+
33+
## Install
34+
35+
```shell
36+
pnpm add @toju.network/x402
37+
```
38+
39+
## Quick start
40+
41+
```ts
42+
import { createAgentClient } from '@toju.network/x402'
43+
44+
const client = createAgentClient({
45+
privateKey: process.env.AGENT_PRIVATE_KEY as `0x${string}`,
46+
environment: 'mainnet',
47+
})
48+
49+
const file = new File([fileBuffer], 'report.pdf', { type: 'application/pdf' })
50+
const result = await client.store(file, { durationDays: 30 })
51+
52+
console.log(result.cid) // bafy...
53+
console.log(result.expiresAt) // 2025-06-01T00:00:00.000Z
54+
```
55+
56+
Your agent's wallet needs USDC on Base to pay. At our rate of 3×10⁻¹² USD/byte/day, storing 1 MB for 30 days costs about **$0.0001**.
57+
58+
## createAgentClient
59+
60+
```ts
61+
import { createAgentClient } from '@toju.network/x402'
62+
63+
const client = createAgentClient(options)
64+
```
65+
66+
### Options
67+
68+
<ParamField path="privateKey" type="`0x${string}`" required>
69+
EVM private key for the agent's wallet. Must hold USDC on Base to pay for storage.
70+
</ParamField>
71+
72+
<ParamField path="environment" type="'mainnet'" required>
73+
Target environment. Use `'mainnet'` for Base Mainnet with real USDC.
74+
</ParamField>
75+
76+
## estimateStorageCost
77+
78+
Check the cost before uploading.
79+
80+
```ts
81+
const estimate = await client.estimateStorageCost(
82+
1_000_000, // sizeInBytes
83+
30 // durationDays
84+
)
85+
86+
console.log(estimate.usdc) // '0.000090' (USDC, 6 decimal places)
87+
console.log(estimate.usd) // '0.00'
88+
```
89+
90+
### Parameters
91+
92+
<ParamField path="sizeInBytes" type="number" required>
93+
Raw byte count of the file
94+
</ParamField>
95+
96+
<ParamField path="durationDays" type="number" required>
97+
How long to keep the file on IPFS
98+
</ParamField>
99+
100+
### Response
101+
102+
<ResponseField name="usdc" type="string">
103+
Cost in USDC, formatted to 6 decimal places (e.g. `'0.000090'`)
104+
</ResponseField>
105+
106+
<ResponseField name="usd" type="string">
107+
Approximate USD cost, formatted to 2 decimal places
108+
</ResponseField>
109+
110+
## store
111+
112+
Upload a file and pay autonomously via x402.
113+
114+
```ts
115+
const result = await client.store(file, { durationDays: 30 })
116+
```
117+
118+
### Parameters
119+
120+
<ParamField path="file" type="File" required>
121+
The file to upload. Use the standard Web API `File` object — works in Node.js 20+ and all modern runtimes.
122+
</ParamField>
123+
124+
<ParamField path="options.durationDays" type="number" required>
125+
Storage duration in days
126+
</ParamField>
127+
128+
### Response
129+
130+
<ResponseField name="cid" type="string">
131+
IPFS content identifier for the uploaded file (e.g. `bafybei...`)
132+
</ResponseField>
133+
134+
<ResponseField name="expiresAt" type="string">
135+
ISO 8601 date string when the file will be removed from IPFS
136+
</ResponseField>
137+
138+
<ResponseField name="fileName" type="string">
139+
Original file name
140+
</ResponseField>
141+
142+
<ResponseField name="fileSize" type="number">
143+
File size in bytes
144+
</ResponseField>
145+
146+
## USDC on Base
147+
148+
USDC uses 6 decimal places on all EVM chains (Circle standard). The SDK and server handle conversion automatically.
149+
150+
| Network | USDC Contract |
151+
|---|---|
152+
| Base Mainnet | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
153+
154+
<Info>
155+
Your agent wallet needs enough USDC to cover the storage cost, plus a small amount of ETH on Base for gas. Gas fees on Base are typically under $0.001 per transaction.
156+
</Info>
157+
158+
## Framework integrations
159+
160+
`AgentClient` works with any framework that can hold an EVM private key:
161+
162+
```ts
163+
// LangChain tool
164+
import { DynamicTool } from 'langchain/tools'
165+
166+
const storeTool = new DynamicTool({
167+
name: 'store_file',
168+
description: 'Store a file on IPFS and return its CID',
169+
func: async (filePath: string) => {
170+
const buffer = await fs.readFile(filePath)
171+
const file = new File([buffer], path.basename(filePath))
172+
const result = await client.store(file, { durationDays: 30 })
173+
return result.cid
174+
},
175+
})
176+
```
177+
178+
```ts
179+
// CrewAI-style (TypeScript)
180+
const storeAction = async (input: { filePath: string; days: number }) => {
181+
const buffer = await fs.readFile(input.filePath)
182+
const file = new File([buffer], path.basename(input.filePath))
183+
return client.store(file, { durationDays: input.days })
184+
}
185+
```
186+
187+
## Related
188+
189+
<CardGroup cols={2}>
190+
<Card title="Pricing" icon="coins" href="/pricing">
191+
Storage rate and cost calculator
192+
</Card>
193+
<Card title="Upload (SOL)" icon="arrow-up" href="/sdk/deposit">
194+
Human wallet uploads with Solana
195+
</Card>
196+
<Card title="CID Computation" icon="hashtag" href="/concepts/cid-computation">
197+
How content identifiers work
198+
</Card>
199+
<Card title="Storage Payments" icon="credit-card" href="/concepts/storage-payments">
200+
Payment mechanics
201+
</Card>
202+
</CardGroup>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"release": "changeset publish"
1818
},
1919
"lint-staged": {
20-
"*.{ts,tsx,js,jsx,json,css,scss,md}": "biome format --write"
20+
"*.{ts,tsx,js,jsx,json,css,scss}": "biome format --write"
2121
},
2222
"devDependencies": {
2323
"@biomejs/biome": "^2.3.14",

packages/x402/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist

packages/x402/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# @toju.network/x402
2+
3+
## 0.1.0
4+
5+
### Minor Changes
6+
7+
- df76d21: initial release of `@toju.network/x402` — agent-friendly client for autonomous storage payments via the x402 protocol.
8+
9+
`AgentClient` wraps the x402 fetch flow so agents can store files on ipfs paying with USDC on Base with no human intervention. includes `store(file, { durationDays })` and `estimateStorageCost(sizeInBytes, durationDays)`. supports both `mainnet` and `sepolia` environments.

packages/x402/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
## @toju.network/x402
2+
3+
Agent-friendly client for autonomous storage payments via the [x402 protocol](https://x402.org). Store files on IPFS and pay with USDC on Base — no human intervention required.
4+
5+
**Features:**
6+
7+
- Automatic x402 payment negotiation (no manual auth flow)
8+
- Estimate storage costs in USDC before uploading
9+
- Single-file uploads with configurable storage duration
10+
- Supports both `mainnet` and `sepolia` environments
11+
- Works with any EVM-compatible private key (LangChain, CrewAI, AutoGen, custom agents)
12+
13+
**Looking for human wallet payments?** See [@toju.network/sol](https://www.npmjs.com/package/@toju.network/sol) (Solana) or [@toju.network/fil](https://www.npmjs.com/package/@toju.network/fil) (Filecoin).
14+
15+
## Install
16+
17+
```shell
18+
pnpm add @toju.network/x402
19+
```
20+
21+
## Quick start
22+
23+
```ts
24+
import { createAgentClient } from '@toju.network/x402'
25+
26+
const client = createAgentClient({
27+
privateKey: process.env.AGENT_PRIVATE_KEY as `0x${string}`,
28+
environment: 'mainnet',
29+
})
30+
31+
const file = new File([Buffer.from('hello world')], 'hello.txt', { type: 'text/plain' })
32+
const result = await client.store(file, { durationDays: 30 })
33+
34+
console.log('CID:', result.cid)
35+
console.log('Expires:', result.expiresAt)
36+
```
37+
38+
The client handles the full x402 flow automatically: sends the request, receives the 402 Payment Required response, signs the EIP-3009 off-chain authorization, and retries with the payment header attached. No polling, no manual signing.
39+
40+
## Environments
41+
42+
```ts
43+
const client = createAgentClient({ privateKey, environment: 'sepolia' }) // Base Sepolia (testnet)
44+
const client = createAgentClient({ privateKey, environment: 'mainnet' }) // Base Mainnet
45+
```
46+
47+
## Estimate cost
48+
49+
Before uploading, check how much a given file and duration will cost:
50+
51+
```ts
52+
const estimate = await client.estimateStorageCost(1_000_000, 30) // 1MB for 30 days
53+
54+
console.log(`Cost: ${estimate.usdc} USDC`)
55+
console.log(`Approx: $${estimate.usd}`)
56+
```
57+
58+
`sizeInBytes` is the raw byte count of the file. `durationDays` is how long to keep it on IPFS.
59+
60+
## Store a file
61+
62+
```ts
63+
const file = new File([fileBuffer], 'report.pdf', { type: 'application/pdf' })
64+
65+
const result = await client.store(file, { durationDays: 7 })
66+
67+
console.log(result.cid) // bafy...
68+
console.log(result.expiresAt) // ISO date string
69+
console.log(result.fileName) // 'report.pdf'
70+
console.log(result.fileSize) // bytes
71+
```
72+
73+
Your agent's wallet needs USDC on Base to pay. Get test USDC on Base Sepolia from [Circle's faucet](https://faucet.circle.com/).
74+
75+
## USDC contract addresses
76+
77+
| Network | Contract Address |
78+
|---|---|
79+
| Base Mainnet | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
80+
| Base Sepolia | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` |
81+
82+
USDC uses 6 decimal places on all EVM chains.
83+
84+
## Links
85+
86+
- [Documentation](https://docs.toju.network)
87+
- [Pricing](https://docs.toju.network/pricing)
88+
- [x402 Protocol](https://x402.org)
89+
- [GitHub](https://github.com/seetadev/Storacha-Solana-SDK)
90+
91+
## Contributing
92+
93+
See the [Contributing guide](../../CONTRIBUTING.md).

packages/x402/package.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"name": "@toju.network/x402",
3+
"version": "0.1.0",
4+
"description": "Agent-friendly client for storing files on IPFS via toju, paying autonomously with USDC on Base using the x402 protocol.",
5+
"license": "Apache-2.0",
6+
"type": "module",
7+
"module": "./dist/index.js",
8+
"main": "./dist/index.cjs",
9+
"types": "./dist/index.d.ts",
10+
"exports": {
11+
".": {
12+
"types": "./dist/index.d.ts",
13+
"import": "./dist/index.js",
14+
"require": "./dist/index.cjs"
15+
}
16+
},
17+
"files": [
18+
"dist"
19+
],
20+
"publishConfig": {
21+
"access": "public"
22+
},
23+
"repository": {
24+
"type": "git",
25+
"url": "https://github.com/seetadev/Storacha-Solana-SDK",
26+
"directory": "packages/x402"
27+
},
28+
"keywords": [
29+
"x402",
30+
"usdc",
31+
"base",
32+
"storacha",
33+
"ipfs",
34+
"agent",
35+
"ai-agent",
36+
"decentralized-storage",
37+
"eip-3009",
38+
"autonomous-payments"
39+
],
40+
"scripts": {
41+
"build": "tsup src/index.ts --format esm,cjs --minify --dts",
42+
"dev": "pnpm build --watch"
43+
},
44+
"author": "toju.network",
45+
"packageManager": "pnpm@10.11.0",
46+
"dependencies": {
47+
"@x402/core": "^2.7.0",
48+
"@x402/evm": "^2.7.0",
49+
"@x402/fetch": "^2.7.0",
50+
"viem": "~2.45.1"
51+
},
52+
"devDependencies": {
53+
"tsup": "^8.5.0",
54+
"typescript": "^5.8.3"
55+
}
56+
}

0 commit comments

Comments
 (0)