kawasekit 0.1.0-alpha.1 → 0.1.0-beta.3

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 (61) hide show
  1. package/README.md +86 -20
  2. package/dist/{chunk-2QYCWRYR.js → chunk-2V3W4B64.js} +123 -30
  3. package/dist/chunk-2V3W4B64.js.map +1 -0
  4. package/dist/{chunk-7DWIT6D4.js → chunk-6G345P2I.js} +3 -3
  5. package/dist/{chunk-7DWIT6D4.js.map → chunk-6G345P2I.js.map} +1 -1
  6. package/dist/{chunk-LNXYCHRY.js → chunk-CD6SQBZN.js} +222 -29
  7. package/dist/chunk-CD6SQBZN.js.map +1 -0
  8. package/dist/{chunk-V5PUKFPL.js → chunk-KWCPYGFE.js} +3 -3
  9. package/dist/{chunk-V5PUKFPL.js.map → chunk-KWCPYGFE.js.map} +1 -1
  10. package/dist/chunk-WMFCI6KC.js +333 -0
  11. package/dist/chunk-WMFCI6KC.js.map +1 -0
  12. package/dist/cli/index.cjs +4 -4
  13. package/dist/cli/index.cjs.map +1 -1
  14. package/dist/cli/index.js +8 -7
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/idempotency/index.cjs +353 -0
  17. package/dist/idempotency/index.cjs.map +1 -0
  18. package/dist/idempotency/index.d.cts +203 -0
  19. package/dist/idempotency/index.d.ts +203 -0
  20. package/dist/idempotency/index.js +5 -0
  21. package/dist/idempotency/index.js.map +1 -0
  22. package/dist/{index-BT-LhXV1.d.ts → index-CykLOgYD.d.ts} +13 -5
  23. package/dist/{index-CzX09uRS.d.cts → index-DzveM0RN.d.cts} +13 -5
  24. package/dist/index-NdNKNnZP.d.cts +808 -0
  25. package/dist/index-kqH78Yms.d.ts +808 -0
  26. package/dist/index.cjs +667 -41
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +41 -5
  29. package/dist/index.d.ts +41 -5
  30. package/dist/index.js +5 -4
  31. package/dist/observability/index.d.cts +203 -2
  32. package/dist/observability/index.d.ts +203 -2
  33. package/dist/observability/otlp/index.d.cts +2 -1
  34. package/dist/observability/otlp/index.d.ts +2 -1
  35. package/dist/observability/prometheus/index.d.cts +2 -1
  36. package/dist/observability/prometheus/index.d.ts +2 -1
  37. package/dist/{server-t1qVFVnJ.d.ts → server-BMeg_hNB.d.cts} +30 -2
  38. package/dist/{server-C3XkkTIP.d.cts → server-CSpATNiH.d.ts} +30 -2
  39. package/dist/session/index.cjs +1 -1
  40. package/dist/session/index.cjs.map +1 -1
  41. package/dist/session/index.d.cts +1 -1
  42. package/dist/session/index.d.ts +1 -1
  43. package/dist/session/index.js +1 -1
  44. package/dist/store-BY16tCbe.d.cts +166 -0
  45. package/dist/store-Bd-91QL0.d.ts +166 -0
  46. package/dist/{index-Bf78wMqn.d.cts → types-A_WwFpcv.d.cts} +1 -200
  47. package/dist/{index-WQ_Hq4_Z.d.ts → types-DwFfT4E7.d.ts} +1 -200
  48. package/dist/x402/hono/index.cjs +335 -4
  49. package/dist/x402/hono/index.cjs.map +1 -1
  50. package/dist/x402/hono/index.d.cts +4 -2
  51. package/dist/x402/hono/index.d.ts +4 -2
  52. package/dist/x402/hono/index.js +2 -1
  53. package/dist/x402/hono/index.js.map +1 -1
  54. package/dist/x402/index.cjs +524 -84
  55. package/dist/x402/index.cjs.map +1 -1
  56. package/dist/x402/index.d.cts +7 -632
  57. package/dist/x402/index.d.ts +7 -632
  58. package/dist/x402/index.js +3 -2
  59. package/package.json +12 -1
  60. package/dist/chunk-2QYCWRYR.js.map +0 -1
  61. package/dist/chunk-LNXYCHRY.js.map +0 -1
package/README.md CHANGED
@@ -5,7 +5,11 @@
5
5
  [![npm version](https://img.shields.io/npm/v/kawasekit.svg)](https://www.npmjs.com/package/kawasekit)
6
6
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
7
 
8
- 🚧 **Status**: Pre-alpha (M3 milestoneexternally-callable kawasekit). Built in public. Not yet ready for production use.
8
+ 🚧 **Status**: M4 complete — `kawasekit@0.1.0-beta.2` is published on npm (SLSA provenance, `beta` dist-tag) and mainnet-capable, with payment flows verified on Polygon mainnet. **Not yet GA.** Production use is currently constrained to **small per-call values**: the reasoning-step idempotency gap (see [`docs/THREAT_MODEL.md` §6.1](./docs/THREAT_MODEL.md#61-reasoning-step-idempotency-gap)) is not yet closed, so duplicate-payment scenarios are the integrator's responsibility. GA (`0.1.0` on the `latest` tag) is gated on closing the fund-correctness gaps the §6.1 idempotency layer (or GA explicitly scoped to small per-call values) plus a `maxAmountPerSign` ceiling — and a clean beta soak. Review happens **continuously in the open**: issues and counter-examples are welcome on GitHub and via [SECURITY.md](./SECURITY.md) (the §6.1 gap itself came from public feedback). A formal third-party audit is a goal on the road to `1.0`, not a `0.1.0` GA blocker. Built in public.
9
+
10
+ ```bash
11
+ pnpm add kawasekit@beta # 0.1.0-beta.2 — pre-GA, mainnet-capable
12
+ ```
9
13
 
10
14
  ## Vision
11
15
 
@@ -19,9 +23,9 @@ Built around modern account abstraction (ERC-4337 / Kernel v3.1) and Japan's fir
19
23
 
20
24
  - [x] **M1**: Smart account skeleton on Polygon Amoy
21
25
  - [x] **M2**: JPYC transfer via UserOp + EIP-3009 signing helpers + Daily Limit spending policy
22
- - [x] **M3**: x402 v2 server/client/facilitator + session-key lifecycle + Mastra/Hono integration example (this release)
23
- - [ ] **M4**: Mainnet support + observability + npm v0.1 release + CLI + docs site
24
- - [ ] **M5**: Community building + first real integrations
26
+ - [x] **M3**: x402 v2 server/client/facilitator + session-key lifecycle + Mastra/Hono integration example
27
+ - [x] **M4**: Polygon mainnet support + observability (Prometheus / OTLP) + CLI + docs site + npm `0.1.0-alpha`/`0.1.0-beta` release
28
+ - [ ] **M5**: Reasoning-step idempotency layer (§6.1) + `0.1.0` GA promote + Kaia support — the technical prerequisites for first real integrations
25
29
  - [ ] **M6**: Managed service alpha + Rust policy engine
26
30
 
27
31
  ## Quick Start
@@ -64,6 +68,28 @@ pays 0.001 JPYC, and the summary prints Polygonscan tx URLs for every
64
68
  settlement. See the [example README](./examples/agent-x402-jpyc/README.md)
65
69
  for the full walkthrough.
66
70
 
71
+ ### CLI (M4-4)
72
+
73
+ Installing the package exposes a `kawasekit` binary (`npx kawasekit <cmd>`, or
74
+ `pnpm exec kawasekit` in a workspace):
75
+
76
+ ```bash
77
+ pnpm add kawasekit@beta
78
+
79
+ npx kawasekit init # scaffold .env + required vars
80
+ npx kawasekit account create --chain polygonAmoy # deploy a Kernel smart account
81
+ npx kawasekit policy create --chain polygonAmoy # build a Daily Limit policy
82
+ npx kawasekit transfer --chain polygonAmoy # send JPYC via a sponsored UserOp
83
+ npx kawasekit session-key issue --chain polygonAmoy # issue a session key + envelope
84
+ npx kawasekit session-key restore --chain polygonAmoy
85
+ npx kawasekit session-key revoke --chain polygonAmoy
86
+ npx kawasekit session-key rotate --chain polygonAmoy
87
+ ```
88
+
89
+ Every on-chain command takes `--chain "polygon" | "polygonAmoy"`. Mainnet
90
+ (`--chain polygon`) additionally requires `KAWASEKIT_ALLOW_MAINNET=1` in the
91
+ environment as a safety guard against accidental real-funds broadcasts.
92
+
67
93
  ### Programmatic use (M2)
68
94
 
69
95
  ```typescript
@@ -147,7 +173,9 @@ const app = new Hono();
147
173
  app.use(
148
174
  "/weather/*",
149
175
  x402Middleware({
150
- facilitator: createSelfFacilitator({ walletClient, publicClient }),
176
+ // `network` is required (M4-1): it is cross-checked against
177
+ // walletClient.chain.isTestnet and throws on mismatch.
178
+ facilitator: createSelfFacilitator({ network: "testnet", walletClient, publicClient }),
151
179
  requirementsFor: () => [
152
180
  buildPaymentRequirements({
153
181
  chainId: polygonAmoy.id,
@@ -164,16 +192,45 @@ app.get("/weather/:city", (c) => c.json({ city: c.req.param("city"), weather: "s
164
192
  Client (any `fetch` becomes x402-aware):
165
193
 
166
194
  ```typescript
167
- import { createX402PaymentSigner, wrapFetch } from "kawasekit";
195
+ import { createX402PaymentSigner, JPYC_DECIMALS, wrapFetch } from "kawasekit";
196
+ import { parseUnits } from "viem";
168
197
  import { privateKeyToAccount } from "viem/accounts";
169
198
 
170
- const signer = createX402PaymentSigner({ account: privateKeyToAccount("0x...") });
171
- const fetch402 = wrapFetch({ signer });
199
+ const signer = createX402PaymentSigner({
200
+ network: "testnet",
201
+ account: privateKeyToAccount("0x..."),
202
+ // Pin to the JPYC v2 EIP-712 domain at construction. The wire-format
203
+ // extra.name / extra.version are ignored — Threat 1.4 mitigation.
204
+ asset: { kind: "known", id: "jpyc-v2" },
205
+ });
206
+
207
+ // onPayment is *required* at the type level — kawasekit refuses to default
208
+ // to "always pay" silently. The callback is your budget guard.
209
+ let spent = 0n;
210
+ const MAX_SPEND = parseUnits("100", JPYC_DECIMALS); // 100 JPYC (JPYC has 18 decimals)
211
+ const fetch402 = wrapFetch({
212
+ signer,
213
+ onPayment: (req) => {
214
+ const next = spent + BigInt(req.amount);
215
+ if (next > MAX_SPEND) return false; // budget exhausted → 402 returned
216
+ spent = next;
217
+ return true;
218
+ },
219
+ });
172
220
 
173
221
  const res = await fetch402("https://api.example.com/weather/Tokyo");
174
- // → 402 → signed retry → 200 with JPYC settled on-chain
222
+ // → 402 → onPayment guard → signed retry → 200 with JPYC settled on-chain
175
223
  ```
176
224
 
225
+ > **⚠️ Call-level idempotency only.** kawasekit guarantees that a single
226
+ > `fetch402(...)` call settles **at most once** (EIP-3009 nonce + viem
227
+ > `nonceManager`). It does **not** prevent your agent from invoking
228
+ > `fetch402(...)` twice for the same reasoning step — retries, regeneration,
229
+ > pause-resume, and multi-agent fan-out can each cause duplicate charges.
230
+ > **Step-level idempotency is your responsibility**: track an
231
+ > `Idempotency-Key` per reasoning step at the agent framework layer.
232
+ > See [`docs/THREAT_MODEL.md` §6.1](./docs/THREAT_MODEL.md#61-reasoning-step-idempotency-gap) for the threat boundary.
233
+
177
234
  ### Session-key lifecycle (M3-2)
178
235
 
179
236
  ```typescript
@@ -206,21 +263,30 @@ const restored = await restoreSessionAccount({
206
263
 
207
264
  ## Supported Chains
208
265
 
209
- | Chain | Status | JPYC |
266
+ JPYC availability and kawasekit support are **two separate axes** — JPYC being
267
+ live on a chain does **not** mean kawasekit has a config or has been tested
268
+ there. Today kawasekit ships a chain config only for **Polygon + Polygon Amoy**
269
+ (`src/chains/`); `getJpycAddress` / `SupportedChainId` accept only those two.
270
+
271
+ | Chain | JPYC availability | kawasekit support |
210
272
  |---|---|---|
211
- | Polygon | M2 | ✅ Live (`0xE7C3...c29`) |
212
- | Polygon Amoy (testnet) | M2 (primary dev target) | ✅ Live (`0xE7C3...c29`, mint-controlled) |
213
- | Kaia | M3+ | 🚧 In development |
214
- | Avalanche | M4+ | ✅ Live (`0xE7C3...c29`) |
215
- | Ethereum | M4+ | ✅ Live (`0xE7C3...c29`) |
273
+ | Polygon (mainnet) | ✅ Live (`0xE7C3c29`) | ✅ M4 — config shipped, verified with live mainnet txs |
274
+ | Polygon Amoy (testnet) | Live (`0xE7C3…c29`) | ✅ primary testnet target |
275
+ | Kaia | Live (`0xE7C3…c29`, same address)¹ | 🚧 planned M5 (x402 EOA-payer path first) |
276
+ | Avalanche | ✅ Live (`0xE7C3c29`) | ⬜ not yet — no chain config |
277
+ | Ethereum | ✅ Live (`0xE7C3c29`) | ⬜ not yet — no chain config |
278
+
279
+ ¹ JPYC officially launched on Kaia in 2026-05 (Kaia DLT Foundation; Unifi began
280
+ JPYC support 2026-05-22), same contract address as the other chains. kawasekit
281
+ has no Kaia chain config yet — support is scheduled for M5.
216
282
 
217
283
  ## Why Japan-first
218
284
 
219
285
  The Japanese stablecoin ecosystem in 2026 is uniquely positioned:
220
286
 
221
287
  - **JPYC** is a fully regulated yen-pegged stablecoin under the revised Payment Services Act
222
- - Multi-chain by design (same address on Ethereum, Polygon, Avalanche)
223
- - Kaia integration coming via LINE NEXT's Unifi
288
+ - Multi-chain by design (same address on Ethereum, Polygon, Avalanche, and Kaia)
289
+ - Now live on Kaia (2026-05), with LINE NEXT's Unifi supporting JPYC since 2026-05-22
224
290
  - Japanese AI startup ecosystem actively seeking modern payment rails
225
291
 
226
292
  kawasekit aims to be the developer-facing layer that connects this stablecoin infrastructure to the global AI agent ecosystem.
@@ -237,11 +303,11 @@ pnpm install
237
303
  pnpm dev
238
304
  ```
239
305
 
240
- The site will deploy to GitHub Pages on every push to `main` once Pages is enabled in the repository settings — see `.github/workflows/docs.yml`. The planned canonical URL is `kawasekit.k0yote.dev`.
306
+ The site is live at **[kawasekit.k0yote.dev](https://kawasekit.k0yote.dev)**, auto-deployed from `main` via `.github/workflows/docs.yml`.
241
307
 
242
308
  ## Contributing
243
309
 
244
- This is currently a solo project, but contributions will be welcomed once we hit M3. See [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) for guidelines.
310
+ This is currently a solo project, but with M1–M4 shipped and `0.1.0-beta` on npm, contributions are now welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) for guidelines.
245
311
 
246
312
  For now, feedback, issues, and discussions are the most valuable contributions.
247
313
 
@@ -259,4 +325,4 @@ This license includes an explicit patent grant, which is important for working i
259
325
 
260
326
  ---
261
327
 
262
- Follow development progress: [@k0yote](https://github.com/k0yote) · Project home: kawasekit.k0yote.dev (coming soon)
328
+ Follow development progress: [@k0yote](https://github.com/k0yote) · Project home: [kawasekit.k0yote.dev](https://kawasekit.k0yote.dev)
@@ -1,7 +1,8 @@
1
- import { X402InvalidPayloadError, X402_VERSION, chainIdToX402Network, X402_HEADER_PAYMENT_SIGNATURE, encodePaymentSignatureHeader, X402_HEADER_PAYMENT_RESPONSE, decodePaymentResponseHeader, x402NetworkToChainId, JPYC_V2_ADDRESS, JPYC_EIP712_DOMAIN_HINT, X402_HEADER_PAYMENT_REQUIRED, decodePaymentRequiredHeader, jpycAbi } from './chunk-LNXYCHRY.js';
1
+ import { JPYC_EIP712_DOMAIN_HINT, JPYC_V2_ADDRESS, X402InvalidPayloadError, X402_HEADER_PAYMENT_SIGNATURE, encodePaymentSignatureHeader, X402_HEADER_IDEMPOTENCY_KEY, X402_HEADER_PAYMENT_RESPONSE, decodePaymentResponseHeader, X402InvalidConfigError, X402_HEADER_PAYMENT_REQUIRED, decodePaymentRequiredHeader, jpycAbi } from './chunk-CD6SQBZN.js';
2
+ import { X402_VERSION, chainIdToX402Network, x402NetworkToChainId } from './chunk-WMFCI6KC.js';
2
3
  import { getChain, isSupportedChainId } from './chunk-SA7LMQFG.js';
3
4
  import { invokeHookSafely } from './chunk-LEHWRDVS.js';
4
- import { parseSignature, getAddress, recoverTypedDataAddress, isAddress } from 'viem';
5
+ import { getAddress, keccak256, stringToHex, parseSignature, isAddress, recoverTypedDataAddress } from 'viem';
5
6
 
6
7
  var transferWithAuthorizationTypes = {
7
8
  TransferWithAuthorization: [
@@ -34,6 +35,20 @@ function generateAuthorizationNonce() {
34
35
  crypto.getRandomValues(bytes);
35
36
  return `0x${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
36
37
  }
38
+ var EIP3009_NONCE_DOMAIN_TAG = "kawasekit/eip3009-nonce/1";
39
+ function deriveAuthorizationNonce(input, scope) {
40
+ if (input.idempotencyKey === "") {
41
+ throw new Error("deriveAuthorizationNonce: idempotencyKey must be a non-empty string");
42
+ }
43
+ const preimage = JSON.stringify([
44
+ EIP3009_NONCE_DOMAIN_TAG,
45
+ input.idempotencyKey,
46
+ getAddress(scope.from),
47
+ getAddress(scope.verifyingContract),
48
+ scope.chainId
49
+ ]);
50
+ return keccak256(stringToHex(preimage));
51
+ }
37
52
  function authorizationDeadlineFromNow(seconds, nowSec) {
38
53
  const now = nowSec ?? BigInt(Math.floor(Date.now() / 1e3));
39
54
  return now + BigInt(seconds);
@@ -98,6 +113,20 @@ function splitAuthorization(signature, domain, message) {
98
113
  message
99
114
  };
100
115
  }
116
+ var KNOWN_ASSETS = [
117
+ {
118
+ id: "jpyc-v2",
119
+ name: JPYC_EIP712_DOMAIN_HINT.name,
120
+ version: JPYC_EIP712_DOMAIN_HINT.version,
121
+ verifyingContract: getAddress(JPYC_V2_ADDRESS)
122
+ }
123
+ ];
124
+ function getKnownAssetDomain(id) {
125
+ return KNOWN_ASSETS.find((entry) => entry.id === id);
126
+ }
127
+ function listKnownAssetIds() {
128
+ return KNOWN_ASSETS.map((entry) => entry.id);
129
+ }
101
130
  var X402_DEFAULT_AUTHORIZATION_LIFETIME_SECONDS = 300;
102
131
  var UINT256_MAX = (1n << 256n) - 1n;
103
132
  var UINT256_DECIMAL = /^(0|[1-9][0-9]*)$/;
@@ -126,20 +155,51 @@ function assertAddress(value, field) {
126
155
  }
127
156
  return value;
128
157
  }
129
- function resolveDomain(requirements, override) {
130
- if (override) {
131
- return override;
132
- }
133
- const extra = requirements.extra;
134
- if (typeof extra.name === "string" && typeof extra.version === "string") {
135
- return { name: extra.name, version: extra.version };
158
+ function resolveAssetParam(asset) {
159
+ if (asset.kind === "known") {
160
+ const entry = getKnownAssetDomain(asset.id);
161
+ if (entry === void 0) {
162
+ throw new X402InvalidConfigError(
163
+ "asset.id",
164
+ `unknown asset id ${JSON.stringify(asset.id)}. Supported: ${listKnownAssetIds().map((id) => JSON.stringify(id)).join(", ")}.`
165
+ );
166
+ }
167
+ return {
168
+ name: entry.name,
169
+ version: entry.version,
170
+ verifyingContract: entry.verifyingContract
171
+ };
136
172
  }
137
- if (getAddress(requirements.asset) === getAddress(JPYC_V2_ADDRESS)) {
138
- return JPYC_EIP712_DOMAIN_HINT;
173
+ if (asset.kind === "unsafeOverride") {
174
+ const { domain } = asset;
175
+ if (typeof domain.name !== "string" || domain.name === "") {
176
+ throw new X402InvalidConfigError(
177
+ "asset.domain.name",
178
+ "`unsafeOverride.domain.name` must be a non-empty string"
179
+ );
180
+ }
181
+ if (typeof domain.version !== "string" || domain.version === "") {
182
+ throw new X402InvalidConfigError(
183
+ "asset.domain.version",
184
+ "`unsafeOverride.domain.version` must be a non-empty string"
185
+ );
186
+ }
187
+ if (!isAddress(domain.verifyingContract, { strict: false })) {
188
+ throw new X402InvalidConfigError(
189
+ "asset.domain.verifyingContract",
190
+ `\`unsafeOverride.domain.verifyingContract\` must be a valid address, got ${JSON.stringify(domain.verifyingContract)}`
191
+ );
192
+ }
193
+ return {
194
+ name: domain.name,
195
+ version: domain.version,
196
+ verifyingContract: getAddress(domain.verifyingContract)
197
+ };
139
198
  }
140
- throw new X402InvalidPayloadError(
141
- "PaymentRequirements",
142
- "`extra.name` and `extra.version` are required for exact-EVM signing on a non-JPYC asset"
199
+ const exhaustive = asset;
200
+ throw new X402InvalidConfigError(
201
+ "asset.kind",
202
+ `unsupported kind ${JSON.stringify(exhaustive.kind)}. Expected "known" or "unsafeOverride".`
143
203
  );
144
204
  }
145
205
  function validateRequirements(requirements) {
@@ -179,7 +239,14 @@ function createX402PaymentSigner(params) {
179
239
  `\`defaultLifetimeSeconds\` must be positive, got ${defaultLifetimeSeconds}`
180
240
  );
181
241
  }
182
- const domainOverride = params.domainOverride;
242
+ const maxAmountPerSign = params.maxAmountPerSign;
243
+ if (maxAmountPerSign !== void 0 && maxAmountPerSign <= 0n) {
244
+ throw new X402InvalidPayloadError(
245
+ "X402PaymentSignerConfig",
246
+ `\`maxAmountPerSign\` must be a positive bigint, got ${maxAmountPerSign}`
247
+ );
248
+ }
249
+ const pinnedDomain = resolveAssetParam(params.asset);
183
250
  return {
184
251
  address: account.address,
185
252
  async sign(signParams) {
@@ -198,7 +265,18 @@ function createX402PaymentSigner(params) {
198
265
  `signer was configured for network="testnet" but requirements.network="${paymentRequirements.network}" (chainId ${chainId}) is a mainnet \u2014 refusing to sign payment for real funds`
199
266
  );
200
267
  }
201
- const domain = resolveDomain(paymentRequirements, domainOverride);
268
+ if (getAddress(asset) !== pinnedDomain.verifyingContract) {
269
+ throw new X402InvalidPayloadError(
270
+ "PaymentRequirements",
271
+ `requirements.asset (${getAddress(asset)}) does not match the signer's pinned verifyingContract (${pinnedDomain.verifyingContract}) \u2014 refusing to sign for an asset the signer was not configured to handle`
272
+ );
273
+ }
274
+ if (maxAmountPerSign !== void 0 && value > maxAmountPerSign) {
275
+ throw new X402InvalidPayloadError(
276
+ "PaymentRequirements",
277
+ `requirements.amount (${value}) exceeds the signer's \`maxAmountPerSign\` ceiling (${maxAmountPerSign}) \u2014 refusing to sign a payment above the configured per-signature limit (threat 1.14)`
278
+ );
279
+ }
202
280
  const lifetime = Math.min(defaultLifetimeSeconds, paymentRequirements.maxTimeoutSeconds);
203
281
  const validAfter = signParams.validAfter ?? 0n;
204
282
  const validBefore = signParams.validBefore ?? authorizationDeadlineFromNow(lifetime);
@@ -208,10 +286,22 @@ function createX402PaymentSigner(params) {
208
286
  `\`validBefore\` (${validBefore}) must be greater than \`validAfter\` (${validAfter})`
209
287
  );
210
288
  }
211
- const nonce = generateAuthorizationNonce();
289
+ const nonce = signParams.idempotencyKey !== void 0 ? deriveAuthorizationNonce(
290
+ { idempotencyKey: signParams.idempotencyKey },
291
+ {
292
+ from: account.address,
293
+ verifyingContract: pinnedDomain.verifyingContract,
294
+ chainId
295
+ }
296
+ ) : generateAuthorizationNonce();
212
297
  const signed = await signTransferWithAuthorization(
213
298
  account,
214
- { name: domain.name, version: domain.version, chainId, verifyingContract: asset },
299
+ {
300
+ name: pinnedDomain.name,
301
+ version: pinnedDomain.version,
302
+ chainId,
303
+ verifyingContract: pinnedDomain.verifyingContract
304
+ },
215
305
  {
216
306
  from: account.address,
217
307
  to: payTo,
@@ -302,7 +392,7 @@ var TRANSFER_AUTHORIZATION_TYPES = {
302
392
  { name: "nonce", type: "bytes32" }
303
393
  ]
304
394
  };
305
- function resolveDomain2(requirements) {
395
+ function resolveDomain(requirements) {
306
396
  const extra = requirements.extra;
307
397
  if (typeof extra.name === "string" && typeof extra.version === "string") {
308
398
  return { name: extra.name, version: extra.version };
@@ -509,7 +599,7 @@ function createSelfFacilitator(params) {
509
599
  }
510
600
  let recovered;
511
601
  try {
512
- const domain = resolveDomain2(req.paymentRequirements);
602
+ const domain = resolveDomain(req.paymentRequirements);
513
603
  recovered = await recoverTypedDataAddress({
514
604
  domain: {
515
605
  name: domain.name,
@@ -811,19 +901,22 @@ function wrapFetch(params) {
811
901
  emitFailure("no_acceptable_requirement", 402);
812
902
  return initialResponse;
813
903
  }
814
- if (onPayment) {
815
- const proceed = await onPayment(chosen, paymentRequired);
816
- if (proceed === false) {
817
- emitFailure("onPayment_declined", 402);
818
- return initialResponse;
819
- }
904
+ const proceed = await onPayment(chosen, paymentRequired);
905
+ if (proceed === false) {
906
+ emitFailure("onPayment_declined", 402);
907
+ return initialResponse;
820
908
  }
909
+ const idempotencyKey = params.idempotencyKeyFor?.(input, chosen, paymentRequired);
821
910
  const paymentPayload = await params.signer.sign({
822
911
  paymentRequirements: chosen,
823
- ...paymentRequired.resource ? { resource: paymentRequired.resource } : {}
912
+ ...paymentRequired.resource ? { resource: paymentRequired.resource } : {},
913
+ ...idempotencyKey !== void 0 ? { idempotencyKey } : {}
824
914
  });
825
915
  const retryHeaders = new Headers(init?.headers);
826
916
  retryHeaders.set(X402_HEADER_PAYMENT_SIGNATURE, encodePaymentSignatureHeader(paymentPayload));
917
+ if (idempotencyKey !== void 0) {
918
+ retryHeaders.set(X402_HEADER_IDEMPOTENCY_KEY, idempotencyKey);
919
+ }
827
920
  const retryResponse = await baseFetch(input, { ...init, headers: retryHeaders });
828
921
  if (retryResponse.status >= 200 && retryResponse.status < 300) {
829
922
  const settlementHeader = retryResponse.headers.get(X402_HEADER_PAYMENT_RESPONSE);
@@ -859,6 +952,6 @@ function wrapFetch(params) {
859
952
  };
860
953
  }
861
954
 
862
- export { X402_DEFAULT_AUTHORIZATION_LIFETIME_SECONDS, X402_FACILITATOR_ERROR_CODES, authorizationDeadlineFromNow, createCoinbaseFacilitator, createHttpFacilitator, createSelfFacilitator, createX402PaymentSigner, generateAuthorizationNonce, signCancelAuthorization, signReceiveWithAuthorization, signTransferWithAuthorization, wrapFetch };
863
- //# sourceMappingURL=chunk-2QYCWRYR.js.map
864
- //# sourceMappingURL=chunk-2QYCWRYR.js.map
955
+ export { X402_DEFAULT_AUTHORIZATION_LIFETIME_SECONDS, X402_FACILITATOR_ERROR_CODES, authorizationDeadlineFromNow, createCoinbaseFacilitator, createHttpFacilitator, createSelfFacilitator, createX402PaymentSigner, deriveAuthorizationNonce, generateAuthorizationNonce, getKnownAssetDomain, listKnownAssetIds, signCancelAuthorization, signReceiveWithAuthorization, signTransferWithAuthorization, wrapFetch };
956
+ //# sourceMappingURL=chunk-2V3W4B64.js.map
957
+ //# sourceMappingURL=chunk-2V3W4B64.js.map