@4mica/x402 1.2.4 → 2.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -1
- package/README.md +67 -82
- package/dist/client/scheme.d.ts +40 -4
- package/dist/client/scheme.js +96 -38
- package/dist/domain.d.ts +12 -0
- package/dist/domain.js +30 -0
- package/dist/index.d.ts +2 -2
- package/dist/server/express/adapter.d.ts +2 -2
- package/dist/server/express/index.d.ts +13 -46
- package/dist/server/express/index.js +32 -71
- package/dist/server/facilitator.d.ts +2 -24
- package/dist/server/facilitator.js +0 -36
- package/dist/server/index.d.ts +2 -4
- package/dist/server/index.js +1 -2
- package/dist/server/scheme.d.ts +54 -10
- package/dist/server/scheme.js +118 -56
- package/dist/types.d.ts +36 -12
- package/package.json +33 -32
- package/.eslintrc.cjs +0 -29
- package/.prettierignore +0 -3
- package/.prettierrc +0 -6
- package/demo/.env.example +0 -8
- package/demo/README.md +0 -125
- package/demo/package.json +0 -26
- package/demo/src/client.ts +0 -54
- package/demo/src/deposit.ts +0 -37
- package/demo/src/server.ts +0 -81
- package/demo/tsconfig.json +0 -8
- package/demo/yarn.lock +0 -925
- package/eslint.config.mjs +0 -22
- package/src/client/index.ts +0 -1
- package/src/client/scheme.ts +0 -111
- package/src/index.ts +0 -9
- package/src/server/express/adapter.ts +0 -100
- package/src/server/express/index.ts +0 -499
- package/src/server/facilitator.ts +0 -206
- package/src/server/index.ts +0 -10
- package/src/server/scheme.ts +0 -229
- package/src/types.ts +0 -24
- package/tests/client-scheme.test.ts +0 -99
- package/tests/facilitator.test.ts +0 -174
- package/tsconfig.build.json +0 -5
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @4mica/x402
|
|
2
|
+
|
|
3
|
+
## 2.0.0-alpha.2
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 70d83f9: First release on `@4mica/sdk` 2.0 and the tab-free protocol.
|
|
8
|
+
|
|
9
|
+
- The tab handshake is gone: clients sign their claim straight from the payment
|
|
10
|
+
requirements with a random 32-byte `reqId`. `FourMicaFacilitatorClient.openTab`,
|
|
11
|
+
the `OpenTab*` types, the Express middlewares' `tabConfig` parameter and the
|
|
12
|
+
server scheme's `advertisedTabEndpoint` are removed. The flat ERC-8004 policy
|
|
13
|
+
types are replaced by `FourMicaRequirementsExtra` with a nested `validation`.
|
|
14
|
+
- Server: a `Money` price (`"$0.10"`) resolves to the stablecoin 4mica core lists
|
|
15
|
+
for the network (`GET /core/tokens`) instead of a hardcoded USDC table, so the
|
|
16
|
+
advertised `asset` is always one core accepts. `new FourMicaEvmScheme({
|
|
17
|
+
coreUrls?, stablecoinSymbol? })` points a network at a self-hosted core or
|
|
18
|
+
picks another listed token. The EIP-3009 `name` / `version` hints in `extra`
|
|
19
|
+
are gone: a 4mica-credit payer signs against core's guarantee domain.
|
|
20
|
+
- Client: `FourMicaEvmScheme.create(account, { coreUrls?, networks? })`
|
|
21
|
+
overrides a network's core and limits the up-front connections. The scheme
|
|
22
|
+
implements `findDefaultAsset` from core's token list, so `@x402/fetch` spend
|
|
23
|
+
controls accept the advertised asset and apply their USD cap to it.
|
|
24
|
+
- Express: the middlewares no longer overwrite a scheme server the caller
|
|
25
|
+
registered for a hosted network, so a self-hosted core on a hosted chain id
|
|
26
|
+
works.
|
|
27
|
+
- Supported networks: Base (`eip155:8453`) and Base Sepolia (`eip155:84532`)
|
|
28
|
+
for x402 v1 and v2; Ethereum Sepolia for v1 only.
|
|
29
|
+
|
|
30
|
+
### Minor Changes
|
|
31
|
+
|
|
32
|
+
- 9136814: The server scheme advertises core's EIP-712 domain on every requirement, as
|
|
33
|
+
`extra.name`, `extra.version` and `extra.verifyingContract` (`eip712_name`,
|
|
34
|
+
`eip712_version` and `contract_address` from `GET /core/public-params`, fetched
|
|
35
|
+
once per network). The client signs from those fields when they are present
|
|
36
|
+
instead of connecting to core, taking the chain id from `network`, so a payer
|
|
37
|
+
needs no core URL for a seller that advertises them. Requirements without them
|
|
38
|
+
are signed as before.
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- Updated dependencies [34eada6]
|
|
43
|
+
- Updated dependencies [8998b02]
|
|
44
|
+
- Updated dependencies [6b0974a]
|
|
45
|
+
- @4mica/sdk@2.0.0-alpha.0
|
|
46
|
+
|
|
47
|
+
## 2.0.0-alpha.1
|
|
48
|
+
|
|
49
|
+
- Breaking: migrate to `@4mica/sdk` 2.0 and the tab-free protocol. The tab
|
|
50
|
+
handshake is gone — clients sign their claim straight from the payment
|
|
51
|
+
requirements with a random 32-byte `reqId`.
|
|
52
|
+
- Breaking: `FourMicaEvmScheme` (server) takes no `advertisedTabEndpoint`;
|
|
53
|
+
`enhancePaymentRequirements` no longer injects `extra.tabEndpoint`.
|
|
54
|
+
- Breaking: the Express middlewares lose their `tabConfig` parameter and the
|
|
55
|
+
tab-open route interception.
|
|
56
|
+
- Breaking: `FourMicaFacilitatorClient.openTab`, `OpenTabRequest`,
|
|
57
|
+
`OpenTabResponse`, and `OpenTabError` are removed (`POST /tabs` no longer
|
|
58
|
+
exists).
|
|
59
|
+
- Breaking: `FourMicaV2RequirementsExtra` / `FourMicaPaymentRequirementsV2`
|
|
60
|
+
(the flat ERC-8004 validation policy) are replaced by
|
|
61
|
+
`FourMicaRequirementsExtra` with a nested `validation`
|
|
62
|
+
(`{ validator, subject, deadline?, params? }`).
|
|
2
63
|
|
|
3
64
|
## 0.3.0
|
|
65
|
+
|
|
4
66
|
- Breaking: include `reqId` in `PaymentGuaranteeRequestClaims` and signing payloads (EIP-712/EIP-191).
|
|
5
67
|
- Breaking: X402 envelopes now emit `req_id` and `TabResponse` exposes `nextReqId` for claim building.
|
|
6
68
|
- Fix: `listRecipientTabs` query parameter uses `settlement_status` to match core API.
|
package/README.md
CHANGED
|
@@ -25,15 +25,11 @@ app.use(
|
|
|
25
25
|
accepts: {
|
|
26
26
|
scheme: "4mica-credit",
|
|
27
27
|
price: "$0.10",
|
|
28
|
-
network: "eip155:
|
|
28
|
+
network: "eip155:84532", // Base Sepolia
|
|
29
29
|
payTo: "0xYourAddress",
|
|
30
30
|
},
|
|
31
31
|
description: "Access to premium content",
|
|
32
32
|
},
|
|
33
|
-
},
|
|
34
|
-
// Payment tab config
|
|
35
|
-
{
|
|
36
|
-
advertisedEndpoint: "https://api.example.com/tabs/open",
|
|
37
33
|
}
|
|
38
34
|
)
|
|
39
35
|
);
|
|
@@ -69,7 +65,7 @@ const scheme = await FourMicaEvmScheme.create(account);
|
|
|
69
65
|
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
|
|
70
66
|
schemes: [
|
|
71
67
|
{
|
|
72
|
-
network: "eip155:
|
|
68
|
+
network: "eip155:84532", // Base Sepolia
|
|
73
69
|
client: scheme,
|
|
74
70
|
},
|
|
75
71
|
],
|
|
@@ -81,6 +77,11 @@ const data = await response.json();
|
|
|
81
77
|
console.log(data);
|
|
82
78
|
```
|
|
83
79
|
|
|
80
|
+
`@x402/fetch` only pays in assets the scheme client recognises. `FourMicaEvmScheme` recognises
|
|
81
|
+
whatever 4mica core lists for the network (`GET /core/tokens`), so the server's advertised USDC
|
|
82
|
+
passes the client's spend controls and the default `$1` per-payment cap applies to it. Pass
|
|
83
|
+
`spendControls` to change the cap or allow other assets.
|
|
84
|
+
|
|
84
85
|
### Using with Axios
|
|
85
86
|
|
|
86
87
|
Use with `@x402/axios` for Axios-based applications:
|
|
@@ -101,7 +102,7 @@ const scheme = await FourMicaEvmScheme.create(account);
|
|
|
101
102
|
const api = wrapAxiosWithPaymentFromConfig(axios.create(), {
|
|
102
103
|
schemes: [
|
|
103
104
|
{
|
|
104
|
-
network: "eip155:
|
|
105
|
+
network: "eip155:84532", // Base Sepolia
|
|
105
106
|
client: scheme,
|
|
106
107
|
},
|
|
107
108
|
],
|
|
@@ -115,7 +116,7 @@ console.log(data);
|
|
|
115
116
|
|
|
116
117
|
## Server Configuration
|
|
117
118
|
|
|
118
|
-
### `paymentMiddlewareFromConfig(routes,
|
|
119
|
+
### `paymentMiddlewareFromConfig(routes, ...options)`
|
|
119
120
|
|
|
120
121
|
The recommended middleware factory for most use cases. Automatically configures the 4mica facilitator and registers the `FourMicaEvmScheme` for all supported networks.
|
|
121
122
|
|
|
@@ -129,7 +130,7 @@ The recommended middleware factory for most use cases. Automatically configures
|
|
|
129
130
|
accepts: {
|
|
130
131
|
scheme: "4mica-credit",
|
|
131
132
|
price: "$0.10",
|
|
132
|
-
network: "eip155:
|
|
133
|
+
network: "eip155:84532", // or "eip155:8453" for Base mainnet
|
|
133
134
|
payTo: "0xRecipientAddress",
|
|
134
135
|
},
|
|
135
136
|
description: "What the user is paying for",
|
|
@@ -137,45 +138,40 @@ The recommended middleware factory for most use cases. Automatically configures
|
|
|
137
138
|
}
|
|
138
139
|
```
|
|
139
140
|
|
|
140
|
-
2. **`
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// This is injected into paymentRequirements.extra and clients use it to open tabs
|
|
146
|
-
advertisedEndpoint: "https://api.example.com/tabs/open",
|
|
147
|
-
|
|
148
|
-
// Lifetime of payment tabs in seconds
|
|
149
|
-
ttlSeconds: 3600, // optional, defaults to facilitator's default
|
|
150
|
-
}
|
|
151
|
-
```
|
|
141
|
+
2. **`facilitatorClients`** (optional): Additional facilitator client(s) for other payment schemes
|
|
142
|
+
3. **`schemes`** (optional): Additional scheme registrations for other networks/schemes
|
|
143
|
+
4. **`paywallConfig`** (optional): Configuration for the built-in paywall UI
|
|
144
|
+
5. **`paywall`** (optional): Custom paywall provider
|
|
145
|
+
6. **`syncFacilitatorOnStart`** (optional): Whether to sync with facilitator on startup (defaults to true)
|
|
152
146
|
|
|
153
|
-
|
|
154
|
-
4. **`schemes`** (optional): Additional scheme registrations for other networks/schemes
|
|
155
|
-
5. **`paywallConfig`** (optional): Configuration for the built-in paywall UI
|
|
156
|
-
6. **`paywall`** (optional): Custom paywall provider
|
|
157
|
-
7. **`syncFacilitatorOnStart`** (optional): Whether to sync with facilitator on startup (defaults to true)
|
|
147
|
+
#### Validation-gated payments
|
|
158
148
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
149
|
+
To gate a payment on an external validator, advertise a nested `validation`
|
|
150
|
+
object in `paymentRequirements.extra`. The payer signs the same requirement
|
|
151
|
+
into their claims, and the guarantee only becomes payable once the validator
|
|
152
|
+
approves it:
|
|
162
153
|
|
|
163
154
|
```typescript
|
|
164
155
|
const extra = {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
tabEndpoint: 'https://api.example.com/tabs/open',
|
|
156
|
+
validation: {
|
|
157
|
+
validator: 'validator-id', // must be on core's allowlist
|
|
158
|
+
subject: '0x…32-byte hash…', // what the validator must approve
|
|
159
|
+
deadline: 1700000600, // optional; core tightens it to the cycle cutoff
|
|
160
|
+
params: '0x…', // optional validator-specific policy bytes
|
|
161
|
+
},
|
|
172
162
|
}
|
|
173
163
|
```
|
|
174
164
|
|
|
175
165
|
#### Supported Networks
|
|
176
166
|
|
|
177
|
-
- `eip155:
|
|
178
|
-
- `eip155:
|
|
167
|
+
- `eip155:8453` - Base
|
|
168
|
+
- `eip155:84532` - Base Sepolia
|
|
169
|
+
- `eip155:11155111` - Ethereum Sepolia (x402 v1 only)
|
|
170
|
+
|
|
171
|
+
A `price` such as `"$0.10"` resolves to the stablecoin 4mica core lists for the network
|
|
172
|
+
(`GET /core/tokens`), so the advertised `asset` is always one core accepts. Pass
|
|
173
|
+
`{ coreUrls: { "eip155:84532": "http://localhost:3000/" } }` to `new FourMicaEvmScheme(...)` and
|
|
174
|
+
register it through the `schemes` parameter to point a network at a self-hosted core.
|
|
179
175
|
|
|
180
176
|
### Advanced Server Usage
|
|
181
177
|
|
|
@@ -208,17 +204,7 @@ const resourceServer = new x402ResourceServer([
|
|
|
208
204
|
])
|
|
209
205
|
.register("eip155:8453", new ExactEvmScheme()); // Add Base mainnet with exact scheme
|
|
210
206
|
|
|
211
|
-
app.use(
|
|
212
|
-
paymentMiddleware(
|
|
213
|
-
routes,
|
|
214
|
-
resourceServer,
|
|
215
|
-
{
|
|
216
|
-
advertisedEndpoint: "https://api.example.com/tabs/open",
|
|
217
|
-
ttlSeconds: 3600,
|
|
218
|
-
},
|
|
219
|
-
paywallConfig
|
|
220
|
-
)
|
|
221
|
-
);
|
|
207
|
+
app.use(paymentMiddleware(routes, resourceServer, paywallConfig));
|
|
222
208
|
```
|
|
223
209
|
|
|
224
210
|
#### Using `paymentMiddlewareFromHTTPServer` with HTTP Hooks
|
|
@@ -242,16 +228,7 @@ const httpServer = new x402HTTPResourceServer(resourceServer, routes)
|
|
|
242
228
|
console.log("Protected request:", context.path);
|
|
243
229
|
});
|
|
244
230
|
|
|
245
|
-
app.use(
|
|
246
|
-
paymentMiddlewareFromHTTPServer(
|
|
247
|
-
httpServer,
|
|
248
|
-
{
|
|
249
|
-
advertisedEndpoint: "https://api.example.com/tabs/open",
|
|
250
|
-
ttlSeconds: 3600,
|
|
251
|
-
},
|
|
252
|
-
paywallConfig
|
|
253
|
-
)
|
|
254
|
-
);
|
|
231
|
+
app.use(paymentMiddlewareFromHTTPServer(httpServer, paywallConfig));
|
|
255
232
|
```
|
|
256
233
|
|
|
257
234
|
## Client Configuration
|
|
@@ -262,23 +239,37 @@ app.use(
|
|
|
262
239
|
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
|
|
263
240
|
import { FourMicaEvmScheme } from "@4mica/x402/client";
|
|
264
241
|
|
|
265
|
-
const
|
|
266
|
-
const
|
|
242
|
+
const baseSepoliaScheme = await FourMicaEvmScheme.create(baseSepoliaAccount);
|
|
243
|
+
const baseScheme = await FourMicaEvmScheme.create(baseAccount);
|
|
267
244
|
|
|
268
245
|
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
|
|
269
246
|
schemes: [
|
|
270
247
|
{
|
|
271
|
-
network: "eip155:
|
|
272
|
-
client:
|
|
248
|
+
network: "eip155:84532", // Base Sepolia
|
|
249
|
+
client: baseSepoliaScheme,
|
|
273
250
|
},
|
|
274
251
|
{
|
|
275
|
-
network: "eip155:
|
|
276
|
-
client:
|
|
252
|
+
network: "eip155:8453", // Base mainnet
|
|
253
|
+
client: baseScheme,
|
|
277
254
|
},
|
|
278
255
|
],
|
|
279
256
|
});
|
|
280
257
|
```
|
|
281
258
|
|
|
259
|
+
### Paying on a self-hosted core
|
|
260
|
+
|
|
261
|
+
`FourMicaEvmScheme.create` connects to every hosted network up front. Pass `coreUrls` to
|
|
262
|
+
override a network's core API URL and `networks` to limit the connections to the ones you use;
|
|
263
|
+
a resource server can also steer payers with `extra.rpcUrl` in its requirements, which wins over
|
|
264
|
+
both.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const scheme = await FourMicaEvmScheme.create(account, {
|
|
268
|
+
coreUrls: { "eip155:84532": "http://localhost:3000/" },
|
|
269
|
+
networks: ["eip155:84532"],
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
282
273
|
### Using Builder Pattern
|
|
283
274
|
|
|
284
275
|
```typescript
|
|
@@ -288,7 +279,7 @@ import { FourMicaEvmScheme } from "@4mica/x402/client";
|
|
|
288
279
|
const scheme = await FourMicaEvmScheme.create(account);
|
|
289
280
|
|
|
290
281
|
const client = new x402Client()
|
|
291
|
-
.register("eip155:
|
|
282
|
+
.register("eip155:84532", scheme);
|
|
292
283
|
|
|
293
284
|
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
|
|
294
285
|
```
|
|
@@ -311,7 +302,7 @@ app.use(
|
|
|
311
302
|
accepts: {
|
|
312
303
|
scheme: "4mica-credit",
|
|
313
304
|
price: "$0.05",
|
|
314
|
-
network: "eip155:
|
|
305
|
+
network: "eip155:84532",
|
|
315
306
|
payTo: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
|
|
316
307
|
},
|
|
317
308
|
description: "API data access",
|
|
@@ -320,15 +311,11 @@ app.use(
|
|
|
320
311
|
accepts: {
|
|
321
312
|
scheme: "4mica-credit",
|
|
322
313
|
price: "$0.20",
|
|
323
|
-
network: "eip155:
|
|
314
|
+
network: "eip155:84532",
|
|
324
315
|
payTo: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
|
|
325
316
|
},
|
|
326
317
|
description: "Computation service",
|
|
327
318
|
},
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
advertisedEndpoint: "https://api.example.com/tabs/open",
|
|
331
|
-
ttlSeconds: 7200, // 2 hours
|
|
332
319
|
}
|
|
333
320
|
)
|
|
334
321
|
);
|
|
@@ -358,7 +345,7 @@ async function main() {
|
|
|
358
345
|
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
|
|
359
346
|
schemes: [
|
|
360
347
|
{
|
|
361
|
-
network: "eip155:
|
|
348
|
+
network: "eip155:84532",
|
|
362
349
|
client: scheme,
|
|
363
350
|
},
|
|
364
351
|
],
|
|
@@ -386,18 +373,16 @@ main();
|
|
|
386
373
|
|
|
387
374
|
1. **Server Setup**: The middleware automatically registers the `FourMicaEvmScheme` and injects the 4mica facilitator client, so you don't need to configure them manually.
|
|
388
375
|
|
|
389
|
-
2. **
|
|
390
|
-
- The `advertisedEndpoint` is injected into `paymentRequirements.extra.tabEndpoint`
|
|
391
|
-
- Clients use this endpoint to open payment tabs
|
|
392
|
-
- The middleware automatically processes tab opening requests when they arrive at the advertised endpoint
|
|
393
|
-
|
|
394
|
-
3. **Payment Flow**:
|
|
376
|
+
2. **Payment Flow** (tab-free):
|
|
395
377
|
- Client makes initial request to protected resource
|
|
396
378
|
- Server responds with `402 Payment Required` including payment requirements
|
|
397
|
-
- Client
|
|
398
|
-
|
|
379
|
+
- Client signs a payment guarantee straight from those requirements using
|
|
380
|
+
the 4mica SDK — a random 32-byte `reqId` is minted locally; there is no
|
|
381
|
+
server round-trip before signing
|
|
399
382
|
- Client retries the request with the payment payload
|
|
400
|
-
- Server verifies and settles the payment via the 4mica facilitator
|
|
383
|
+
- Server verifies and settles the payment via the 4mica facilitator, which
|
|
384
|
+
issues the BLS guarantee certificate and binds it to the open settlement
|
|
385
|
+
cycle
|
|
401
386
|
- Server returns the protected resource
|
|
402
387
|
|
|
403
388
|
## License
|
package/dist/client/scheme.d.ts
CHANGED
|
@@ -1,11 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Account } from 'viem/accounts';
|
|
1
|
+
import type { Network, PaymentPayload, PaymentRequirements, SchemeNetworkClient } from '@x402/core/types';
|
|
2
|
+
import type { Account } from 'viem/accounts';
|
|
3
|
+
export interface FourMicaEvmSchemeClientOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Core API URL per network, overriding the hosted deployments in `@4mica/sdk`'s
|
|
6
|
+
* `NETWORKS`; set an entry to pay on a self-hosted core.
|
|
7
|
+
*/
|
|
8
|
+
coreUrls?: Partial<Record<Network, string>>;
|
|
9
|
+
/**
|
|
10
|
+
* Networks to connect to up front. Defaults to every hosted network plus the
|
|
11
|
+
* keys of `coreUrls`. Any other network connects lazily on its first payment.
|
|
12
|
+
*/
|
|
13
|
+
networks?: Network[];
|
|
14
|
+
}
|
|
15
|
+
/** What `findDefaultAsset` reports for an asset core lists: `@x402/core`'s `DefaultAsset`. */
|
|
16
|
+
export interface FourMicaDefaultAsset {
|
|
17
|
+
asset: string;
|
|
18
|
+
decimals: number;
|
|
19
|
+
symbol: string;
|
|
20
|
+
}
|
|
3
21
|
export declare class FourMicaEvmScheme implements SchemeNetworkClient {
|
|
4
22
|
private readonly signer;
|
|
5
|
-
private readonly
|
|
23
|
+
private readonly coreUrls;
|
|
6
24
|
readonly scheme = "4mica-credit";
|
|
25
|
+
private readonly x402Flows;
|
|
26
|
+
private readonly tokensByRpcUrl;
|
|
27
|
+
private readonly domainFlows;
|
|
7
28
|
private constructor();
|
|
8
29
|
private static createX402Flow;
|
|
9
|
-
static
|
|
30
|
+
/** Core's token list. Private static so tests can stub the network call. */
|
|
31
|
+
private static loadSupportedTokens;
|
|
32
|
+
static create(signer: Account, options?: FourMicaEvmSchemeClientOptions): Promise<FourMicaEvmScheme>;
|
|
33
|
+
findDefaultAsset(asset: string, network: Network): FourMicaDefaultAsset | undefined;
|
|
10
34
|
createPaymentPayload(x402Version: number, paymentRequirements: PaymentRequirements): Promise<Pick<PaymentPayload, 'x402Version' | 'payload'>>;
|
|
35
|
+
private coreUrl;
|
|
36
|
+
/**
|
|
37
|
+
* The flow that signs `paymentRequirements`. A requirement that carries
|
|
38
|
+
* core's EIP-712 domain in `extra` is signed locally, with no call to core.
|
|
39
|
+
* Otherwise the payer connects to core: the one a resource server names in
|
|
40
|
+
* `extra.rpcUrl`, then the payer's own overrides, then the hosted deployment.
|
|
41
|
+
*/
|
|
42
|
+
private flowForRequirements;
|
|
43
|
+
/** The flow for a core, connected and its token list loaded on first use. */
|
|
44
|
+
private flowFor;
|
|
45
|
+
/** A flow that signs under the domain the resource server advertised. */
|
|
46
|
+
private flowForDomain;
|
|
11
47
|
}
|
package/dist/client/scheme.js
CHANGED
|
@@ -1,47 +1,55 @@
|
|
|
1
|
-
import { Client, ConfigBuilder, X402Flow, } from '@4mica/sdk';
|
|
1
|
+
import { Client, ConfigBuilder, CorePublicParameters, PaymentSigner, RpcProxy, resolveNetworkRpcUrl, PaymentRequirementsV2 as SdkPaymentRequirementsV2, X402Flow, X402PaymentRequired, X402ResourceInfo, } from '@4mica/sdk';
|
|
2
|
+
import { chainIdOf, readDomainExtra } from '../domain.js';
|
|
2
3
|
import { SUPPORTED_NETWORKS } from '../server/scheme.js';
|
|
3
|
-
const NETWORK_RPC_URLS = {
|
|
4
|
-
'eip155:11155111': 'https://ethereum.sepolia.api.4mica.xyz',
|
|
5
|
-
'eip155:84532': 'https://base.sepolia.api.4mica.xyz',
|
|
6
|
-
'eip155:80002': 'https://api.4mica.xyz',
|
|
7
|
-
};
|
|
8
4
|
export class FourMicaEvmScheme {
|
|
9
|
-
constructor(signer,
|
|
10
|
-
// rpcUrl -> x402Flow
|
|
11
|
-
x402Flows) {
|
|
5
|
+
constructor(signer, coreUrls) {
|
|
12
6
|
this.signer = signer;
|
|
13
|
-
this.
|
|
7
|
+
this.coreUrls = coreUrls;
|
|
14
8
|
this.scheme = '4mica-credit';
|
|
9
|
+
// rpcUrl -> x402Flow
|
|
10
|
+
this.x402Flows = new Map();
|
|
11
|
+
// rpcUrl -> the tokens that core accepts guarantees against
|
|
12
|
+
this.tokensByRpcUrl = new Map();
|
|
13
|
+
// chainId|name|version|verifyingContract -> a flow that signs without core
|
|
14
|
+
this.domainFlows = new Map();
|
|
15
15
|
}
|
|
16
16
|
static async createX402Flow(signer, rpcUrl) {
|
|
17
17
|
const cfg = new ConfigBuilder().rpcUrl(rpcUrl).signer(signer).build();
|
|
18
|
-
const client = await Client.
|
|
18
|
+
const client = await Client.connect(cfg);
|
|
19
19
|
return X402Flow.fromClient(client);
|
|
20
20
|
}
|
|
21
|
-
static
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
/** Core's token list. Private static so tests can stub the network call. */
|
|
22
|
+
static loadSupportedTokens(rpcUrl) {
|
|
23
|
+
return new RpcProxy(rpcUrl).getSupportedTokens();
|
|
24
|
+
}
|
|
25
|
+
static async create(signer, options = {}) {
|
|
26
|
+
const coreUrls = options.coreUrls ?? {};
|
|
27
|
+
const networks = options.networks ?? [
|
|
28
|
+
...SUPPORTED_NETWORKS,
|
|
29
|
+
...Object.keys(coreUrls),
|
|
30
|
+
];
|
|
31
|
+
const scheme = new FourMicaEvmScheme(signer, coreUrls);
|
|
32
|
+
for (const network of new Set(networks)) {
|
|
33
|
+
const rpcUrl = scheme.coreUrl(network);
|
|
34
|
+
if (rpcUrl)
|
|
35
|
+
await scheme.flowFor(rpcUrl);
|
|
28
36
|
}
|
|
29
|
-
return
|
|
37
|
+
return scheme;
|
|
38
|
+
}
|
|
39
|
+
findDefaultAsset(asset, network) {
|
|
40
|
+
const rpcUrl = this.coreUrl(network);
|
|
41
|
+
const tokens = rpcUrl ? this.tokensByRpcUrl.get(rpcUrl) : undefined;
|
|
42
|
+
const token = tokens?.find((entry) => entry.address.toLowerCase() === asset.toLowerCase());
|
|
43
|
+
if (!token || token.decimals === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
return { asset: token.address, decimals: token.decimals, symbol: token.symbol };
|
|
30
46
|
}
|
|
31
47
|
async createPaymentPayload(x402Version, paymentRequirements) {
|
|
32
48
|
const network = paymentRequirements.network;
|
|
33
49
|
if (!network) {
|
|
34
50
|
throw new Error('Network is required in PaymentRequirements');
|
|
35
51
|
}
|
|
36
|
-
const
|
|
37
|
-
if (!rpcUrl) {
|
|
38
|
-
throw new Error(`No RPC URL configured for network ${network}`);
|
|
39
|
-
}
|
|
40
|
-
let x402Flow = this.x402Flows.get(rpcUrl);
|
|
41
|
-
if (!x402Flow) {
|
|
42
|
-
x402Flow = await FourMicaEvmScheme.createX402Flow(this.signer, rpcUrl);
|
|
43
|
-
this.x402Flows.set(rpcUrl, x402Flow);
|
|
44
|
-
}
|
|
52
|
+
const x402Flow = await this.flowForRequirements(paymentRequirements, network);
|
|
45
53
|
if (x402Version === 1) {
|
|
46
54
|
const signed = await x402Flow.signPayment(paymentRequirements, this.signer.address);
|
|
47
55
|
return {
|
|
@@ -57,17 +65,17 @@ export class FourMicaEvmScheme {
|
|
|
57
65
|
paymentRequirements.extra.resource !== null
|
|
58
66
|
? paymentRequirements.extra.resource
|
|
59
67
|
: {};
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
description: String(resourcePayload.description ?? ''),
|
|
63
|
-
mimeType: String(resourcePayload.mimeType ?? ''),
|
|
64
|
-
};
|
|
65
|
-
const paymentRequired = {
|
|
68
|
+
const accepted = SdkPaymentRequirementsV2.fromRaw(paymentRequirements);
|
|
69
|
+
const paymentRequired = new X402PaymentRequired({
|
|
66
70
|
x402Version: 2,
|
|
67
|
-
resource
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
resource: new X402ResourceInfo({
|
|
72
|
+
url: String(resourcePayload.url ?? ''),
|
|
73
|
+
description: String(resourcePayload.description ?? ''),
|
|
74
|
+
mimeType: String(resourcePayload.mimeType ?? ''),
|
|
75
|
+
}),
|
|
76
|
+
accepts: [accepted],
|
|
77
|
+
});
|
|
78
|
+
const signed = await x402Flow.signPaymentV2(paymentRequired, accepted, this.signer.address);
|
|
71
79
|
return {
|
|
72
80
|
x402Version: 2,
|
|
73
81
|
payload: signed.payload,
|
|
@@ -75,4 +83,54 @@ export class FourMicaEvmScheme {
|
|
|
75
83
|
}
|
|
76
84
|
throw new Error(`Unsupported x402Version: ${x402Version}`);
|
|
77
85
|
}
|
|
86
|
+
coreUrl(network) {
|
|
87
|
+
return this.coreUrls[network] ?? resolveNetworkRpcUrl(network);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The flow that signs `paymentRequirements`. A requirement that carries
|
|
91
|
+
* core's EIP-712 domain in `extra` is signed locally, with no call to core.
|
|
92
|
+
* Otherwise the payer connects to core: the one a resource server names in
|
|
93
|
+
* `extra.rpcUrl`, then the payer's own overrides, then the hosted deployment.
|
|
94
|
+
*/
|
|
95
|
+
async flowForRequirements(paymentRequirements, network) {
|
|
96
|
+
const domain = readDomainExtra(paymentRequirements.extra);
|
|
97
|
+
if (domain)
|
|
98
|
+
return this.flowForDomain(domain, network);
|
|
99
|
+
const rpcUrl = paymentRequirements.extra?.rpcUrl ?? this.coreUrl(network);
|
|
100
|
+
if (!rpcUrl) {
|
|
101
|
+
throw new Error(`No core API URL known for network ${network}`);
|
|
102
|
+
}
|
|
103
|
+
return this.flowFor(rpcUrl);
|
|
104
|
+
}
|
|
105
|
+
/** The flow for a core, connected and its token list loaded on first use. */
|
|
106
|
+
async flowFor(rpcUrl) {
|
|
107
|
+
let x402Flow = this.x402Flows.get(rpcUrl);
|
|
108
|
+
if (!x402Flow) {
|
|
109
|
+
x402Flow = await FourMicaEvmScheme.createX402Flow(this.signer, rpcUrl);
|
|
110
|
+
this.x402Flows.set(rpcUrl, x402Flow);
|
|
111
|
+
const { tokens } = await FourMicaEvmScheme.loadSupportedTokens(rpcUrl);
|
|
112
|
+
this.tokensByRpcUrl.set(rpcUrl, tokens);
|
|
113
|
+
}
|
|
114
|
+
return x402Flow;
|
|
115
|
+
}
|
|
116
|
+
/** A flow that signs under the domain the resource server advertised. */
|
|
117
|
+
flowForDomain(domain, network) {
|
|
118
|
+
const chainId = chainIdOf(network);
|
|
119
|
+
if (chainId === undefined) {
|
|
120
|
+
throw new Error(`Cannot derive an EIP-712 chain id from network ${network}`);
|
|
121
|
+
}
|
|
122
|
+
const key = [chainId, domain.name, domain.version, domain.verifyingContract.toLowerCase()].join('|');
|
|
123
|
+
let flow = this.domainFlows.get(key);
|
|
124
|
+
if (!flow) {
|
|
125
|
+
// Only the four domain fields matter for signing; the operator's BLS key
|
|
126
|
+
// is for verifying certificates, which this path never does.
|
|
127
|
+
const params = new CorePublicParameters(new Uint8Array(0), domain.verifyingContract, domain.name, domain.version, chainId);
|
|
128
|
+
const signer = new PaymentSigner(this.signer);
|
|
129
|
+
flow = new X402Flow({
|
|
130
|
+
signPayment: (claims, scheme) => signer.signRequest(params, claims, scheme),
|
|
131
|
+
});
|
|
132
|
+
this.domainFlows.set(key, flow);
|
|
133
|
+
}
|
|
134
|
+
return flow;
|
|
135
|
+
}
|
|
78
136
|
}
|
package/dist/domain.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Network } from '@x402/core/types';
|
|
2
|
+
import type { FourMicaDomainExtra } from './types.js';
|
|
3
|
+
/** The `extra` keys that carry core's EIP-712 domain. */
|
|
4
|
+
export declare const DOMAIN_EXTRA_KEYS: readonly ["name", "version", "verifyingContract"];
|
|
5
|
+
/**
|
|
6
|
+
* The EIP-712 domain a 4mica-credit requirement carries in `extra`, or
|
|
7
|
+
* `undefined` when it carries none. A partial set is a misconfigured seller:
|
|
8
|
+
* it throws rather than silently signing under a domain fetched from elsewhere.
|
|
9
|
+
*/
|
|
10
|
+
export declare function readDomainExtra(extra: Record<string, unknown> | undefined): FourMicaDomainExtra | undefined;
|
|
11
|
+
/** The chain id of an `eip155:*` CAIP-2 network; `undefined` for any other namespace. */
|
|
12
|
+
export declare function chainIdOf(network: Network | string): number | undefined;
|
package/dist/domain.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The `extra` keys that carry core's EIP-712 domain. */
|
|
2
|
+
export const DOMAIN_EXTRA_KEYS = ['name', 'version', 'verifyingContract'];
|
|
3
|
+
/**
|
|
4
|
+
* The EIP-712 domain a 4mica-credit requirement carries in `extra`, or
|
|
5
|
+
* `undefined` when it carries none. A partial set is a misconfigured seller:
|
|
6
|
+
* it throws rather than silently signing under a domain fetched from elsewhere.
|
|
7
|
+
*/
|
|
8
|
+
export function readDomainExtra(extra) {
|
|
9
|
+
if (!extra)
|
|
10
|
+
return undefined;
|
|
11
|
+
const present = DOMAIN_EXTRA_KEYS.filter((key) => typeof extra[key] === 'string' && extra[key].length > 0);
|
|
12
|
+
if (present.length === 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (present.length < DOMAIN_EXTRA_KEYS.length) {
|
|
15
|
+
const missing = DOMAIN_EXTRA_KEYS.filter((key) => !present.includes(key)).join(', ');
|
|
16
|
+
throw new Error(`4mica-credit requirements carry a partial EIP-712 domain in extra (missing ${missing})`);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
name: extra.name,
|
|
20
|
+
version: extra.version,
|
|
21
|
+
verifyingContract: extra.verifyingContract,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** The chain id of an `eip155:*` CAIP-2 network; `undefined` for any other namespace. */
|
|
25
|
+
export function chainIdOf(network) {
|
|
26
|
+
const [namespace, reference] = network.split(':');
|
|
27
|
+
if (namespace !== 'eip155' || !reference || !/^\d+$/.test(reference))
|
|
28
|
+
return undefined;
|
|
29
|
+
return Number(reference);
|
|
30
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export type {
|
|
1
|
+
export type { Network, PaymentPayload, PaymentRequired, PaymentRequirements, SchemeNetworkServer, } from '@x402/core/types';
|
|
2
|
+
export type { FourMicaPaymentRequirements, FourMicaRequirementsExtra, FourMicaValidationExtra, } from './types.js';
|