@4mica/x402 1.2.5 → 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.
Files changed (44) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/README.md +67 -82
  3. package/dist/client/scheme.d.ts +40 -4
  4. package/dist/client/scheme.js +96 -38
  5. package/dist/domain.d.ts +12 -0
  6. package/dist/domain.js +30 -0
  7. package/dist/index.d.ts +2 -2
  8. package/dist/server/express/adapter.d.ts +2 -2
  9. package/dist/server/express/index.d.ts +13 -46
  10. package/dist/server/express/index.js +32 -71
  11. package/dist/server/facilitator.d.ts +2 -24
  12. package/dist/server/facilitator.js +0 -36
  13. package/dist/server/index.d.ts +2 -4
  14. package/dist/server/index.js +1 -2
  15. package/dist/server/scheme.d.ts +54 -10
  16. package/dist/server/scheme.js +117 -55
  17. package/dist/types.d.ts +36 -12
  18. package/package.json +33 -32
  19. package/.eslintrc.cjs +0 -29
  20. package/.prettierignore +0 -3
  21. package/.prettierrc +0 -6
  22. package/demo/.env.example +0 -8
  23. package/demo/README.md +0 -125
  24. package/demo/package.json +0 -26
  25. package/demo/src/client.ts +0 -54
  26. package/demo/src/deposit.ts +0 -37
  27. package/demo/src/server.ts +0 -81
  28. package/demo/tsconfig.json +0 -8
  29. package/demo/yarn.lock +0 -925
  30. package/eslint.config.mjs +0 -22
  31. package/src/client/index.ts +0 -1
  32. package/src/client/scheme.ts +0 -111
  33. package/src/index.ts +0 -9
  34. package/src/server/express/adapter.ts +0 -100
  35. package/src/server/express/index.ts +0 -499
  36. package/src/server/facilitator.ts +0 -206
  37. package/src/server/index.ts +0 -10
  38. package/src/server/scheme.ts +0 -229
  39. package/src/types.ts +0 -24
  40. package/tests/client-scheme.test.ts +0 -99
  41. package/tests/facilitator.test.ts +0 -174
  42. package/tsconfig.build.json +0 -5
  43. package/tsconfig.json +0 -17
  44. package/vitest.config.ts +0 -12
package/CHANGELOG.md CHANGED
@@ -1,6 +1,68 @@
1
- # Changelog
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:11155111", // Ethereum Sepolia
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:11155111", // Ethereum Sepolia
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:11155111", // Ethereum Sepolia
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, tabConfig, ...options)`
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:11155111", // or "eip155:80002" for Polygon Amoy
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. **`tabConfig`** (required): Payment tab configuration
141
-
142
- ```typescript
143
- {
144
- // Full URL endpoint for opening payment tabs
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
- 3. **`facilitatorClients`** (optional): Additional facilitator client(s) for other payment schemes
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
- #### V2 Requirements Extra
160
-
161
- When using x402 V2, `paymentRequirements.extra` must include the validation policy fields expected by the 4mica SDKs and facilitator:
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
- validationRegistryAddress: '0x3333333333333333333333333333333333333333',
166
- validatorAddress: '0x4444444444444444444444444444444444444444',
167
- validatorAgentId: '7',
168
- minValidationScore: 80,
169
- jobHash: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
170
- requiredValidationTag: 'hard-finality', // optional
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:11155111` - Ethereum Sepolia
178
- - `eip155:80002` - Polygon Amoy
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 sepoliaScheme = await FourMicaEvmScheme.create(sepoliaAccount);
266
- const amoyScheme = await FourMicaEvmScheme.create(amoyAccount);
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:11155111", // Ethereum Sepolia
272
- client: sepoliaScheme,
248
+ network: "eip155:84532", // Base Sepolia
249
+ client: baseSepoliaScheme,
273
250
  },
274
251
  {
275
- network: "eip155:80002", // Polygon Amoy
276
- client: amoyScheme,
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:11155111", scheme);
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:11155111",
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:11155111",
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:11155111",
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. **Tab Management**: When a client requests a protected resource, the middleware handles the tab lifecycle:
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 requests a tab from the advertised endpoint
398
- - Client signs a payment guarantee using the 4mica SDK
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
@@ -1,11 +1,47 @@
1
- import { SchemeNetworkClient, PaymentRequirements, PaymentPayload } from '@x402/core/types';
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 x402Flows;
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 create(signer: Account): Promise<FourMicaEvmScheme>;
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
  }
@@ -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:8453': 'https://base.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.x402Flows = x402Flows;
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.new(cfg);
18
+ const client = await Client.connect(cfg);
19
19
  return X402Flow.fromClient(client);
20
20
  }
21
- static async create(signer) {
22
- const x402Flows = new Map();
23
- for (const network of SUPPORTED_NETWORKS) {
24
- const rpcUrl = NETWORK_RPC_URLS[network];
25
- if (!rpcUrl)
26
- continue;
27
- x402Flows.set(rpcUrl, await FourMicaEvmScheme.createX402Flow(signer, rpcUrl));
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 new FourMicaEvmScheme(signer, x402Flows);
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 rpcUrl = paymentRequirements.extra?.rpcUrl ?? NETWORK_RPC_URLS[network];
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 resource = {
61
- url: String(resourcePayload.url ?? ''),
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
- accepts: [paymentRequirements],
69
- };
70
- const signed = await x402Flow.signPaymentV2(paymentRequired, paymentRequirements, this.signer.address);
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
  }
@@ -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 { PaymentRequired, PaymentRequirements, PaymentPayload, Network, SchemeNetworkServer, } from '@x402/core/types';
2
- export type { FourMicaPaymentRequirementsV2, FourMicaV2RequirementsExtra } from './types.js';
1
+ export type { Network, PaymentPayload, PaymentRequired, PaymentRequirements, SchemeNetworkServer, } from '@x402/core/types';
2
+ export type { FourMicaPaymentRequirements, FourMicaRequirementsExtra, FourMicaValidationExtra, } from './types.js';
@@ -1,5 +1,5 @@
1
- import { HTTPAdapter } from '@x402/core/server';
2
- import { Request } from 'express';
1
+ import type { HTTPAdapter } from '@x402/core/server';
2
+ import type { Request } from 'express';
3
3
  /**
4
4
  * Express adapter implementation
5
5
  */