kawasekit 0.5.0 → 0.7.0
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/dist/{chunk-6CNAYQOL.js → chunk-7YKRKV6D.js} +18 -4
- package/dist/chunk-7YKRKV6D.js.map +1 -0
- package/dist/{chunk-S2ZSX2VS.js → chunk-NY6VLXQB.js} +3 -3
- package/dist/{chunk-S2ZSX2VS.js.map → chunk-NY6VLXQB.js.map} +1 -1
- package/dist/chunk-UIDDRHLW.js +65 -0
- package/dist/chunk-UIDDRHLW.js.map +1 -0
- package/dist/chunk-Y2LOACWO.js +88 -0
- package/dist/chunk-Y2LOACWO.js.map +1 -0
- package/dist/cli/index.cjs +41 -9
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +5 -5
- package/dist/index.cjs +87 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -3
- package/dist/index.d.ts +78 -3
- package/dist/index.js +4 -4
- package/dist/policy/index.cjs +42 -10
- package/dist/policy/index.cjs.map +1 -1
- package/dist/policy/index.d.cts +41 -3
- package/dist/policy/index.d.ts +41 -3
- package/dist/policy/index.js +2 -2
- package/dist/signer/index.cjs.map +1 -1
- package/dist/signer/index.d.cts +1 -1
- package/dist/signer/index.d.ts +1 -1
- package/dist/signer/index.js +2 -2
- package/dist/{spending-policy-BD2Mpm-L.d.ts → spending-policy-BBgXDrVD.d.ts} +10 -3
- package/dist/{spending-policy-CGIaBpg-.d.cts → spending-policy-CwXcTFD_.d.cts} +10 -3
- package/package.json +1 -1
- package/dist/chunk-6CNAYQOL.js.map +0 -1
- package/dist/chunk-E47SIVFY.js +0 -44
- package/dist/chunk-E47SIVFY.js.map +0 -1
- package/dist/chunk-RJLDKDWL.js +0 -43
- package/dist/chunk-RJLDKDWL.js.map +0 -1
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { getAddress } from 'viem';
|
|
2
2
|
|
|
3
|
+
// src/policy/spending-policy.ts
|
|
4
|
+
function normalizeRecipientAllowlist(allowlist) {
|
|
5
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6
|
+
const normalized = [];
|
|
7
|
+
for (const entry of allowlist) {
|
|
8
|
+
const checksummed = getAddress(entry);
|
|
9
|
+
if (!seen.has(checksummed)) {
|
|
10
|
+
seen.add(checksummed);
|
|
11
|
+
normalized.push(checksummed);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
// src/policy/spending-policy.ts
|
|
4
18
|
var SpendingPolicyConfigError = class extends Error {
|
|
5
19
|
field;
|
|
@@ -102,7 +116,7 @@ function createSpendingPolicy(params) {
|
|
|
102
116
|
}
|
|
103
117
|
return { token, maxPerSign: l.maxPerSign };
|
|
104
118
|
});
|
|
105
|
-
const recipientAllowlist = params.recipientAllowlist === "any" ? "any" : params.recipientAllowlist
|
|
119
|
+
const recipientAllowlist = params.recipientAllowlist === "any" ? "any" : normalizeRecipientAllowlist(params.recipientAllowlist);
|
|
106
120
|
return {
|
|
107
121
|
version: "1",
|
|
108
122
|
session: { id: params.session.id, notAfter: params.session.notAfter },
|
|
@@ -120,6 +134,6 @@ function mergeSpendState(state, spend) {
|
|
|
120
134
|
return { spentPerToken };
|
|
121
135
|
}
|
|
122
136
|
|
|
123
|
-
export { SpendingPolicyConfigError, createSpendingPolicy, evaluateSpendingPolicy, mergeSpendState };
|
|
124
|
-
//# sourceMappingURL=chunk-
|
|
125
|
-
//# sourceMappingURL=chunk-
|
|
137
|
+
export { SpendingPolicyConfigError, createSpendingPolicy, evaluateSpendingPolicy, mergeSpendState, normalizeRecipientAllowlist };
|
|
138
|
+
//# sourceMappingURL=chunk-7YKRKV6D.js.map
|
|
139
|
+
//# sourceMappingURL=chunk-7YKRKV6D.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/policy/normalize-allowlist.ts","../src/policy/spending-policy.ts"],"names":["getAddress"],"mappings":"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","file":"chunk-7YKRKV6D.js","sourcesContent":["/**\n * Shared recipient-allowlist normalization for the two policy paths — the\n * off-chain {@link createSpendingPolicy} (`./spending-policy`) and the on-chain\n * {@link createJpycDailyLimitPolicies} (`./daily-limit`). Keeping one normalizer\n * means both agree on what \"the same allowlist\" is: a future change (e.g.\n * rejecting the zero address) lands in one place, not two that can drift.\n *\n * @packageDocumentation\n */\n\nimport type { Address } from \"viem\";\nimport { getAddress } from \"viem\";\n\n/**\n * Checksum-normalize ({@link getAddress}) and de-duplicate an address allowlist,\n * preserving first-seen order. `getAddress` rejects a malformed address.\n *\n * This does **not** decide what an empty list means — `[]` returns `[]`. Each\n * caller owns that policy: the off-chain `SpendingPolicy` treats `[]` as\n * deny-all, while the on-chain `createJpycDailyLimitPolicies` rejects it (an\n * on-chain allowlist cannot encode \"match nothing\").\n */\nexport function normalizeRecipientAllowlist(allowlist: readonly Address[]): Address[] {\n\tconst seen = new Set<Address>();\n\tconst normalized: Address[] = [];\n\tfor (const entry of allowlist) {\n\t\tconst checksummed = getAddress(entry);\n\t\tif (!seen.has(checksummed)) {\n\t\t\tseen.add(checksummed);\n\t\t\tnormalized.push(checksummed);\n\t\t}\n\t}\n\treturn normalized;\n}\n","/**\n * Spending policy — policy-as-data for the x402 / EIP-3009 PolicyGatedSigner\n * (M6). One declarative {@link SpendingPolicy} (session + expiry, per-token\n * `maxPerSign` + cumulative cap, recipient allowlist, `revoked`) and one pure,\n * deny-closed evaluator {@link evaluateSpendingPolicy}.\n *\n * The same specification is enforced SDK-side (the `local` adapter) and, for the\n * `mpc-2p` adapter, re-implemented backend-side in Rust (the `kawasekit-mpc-2p`\n * co-signer); a shared conformance corpus\n * (`__fixtures__/spending-policy.vectors.json`) keeps the two in lockstep.\n * The evaluator is **check-only** — it reads `SpendState` and never mutates it;\n * the cumulative-cap *commit* (folding a successful spend back in via\n * {@link mergeSpendState}) is the adapter's job, and atomic+authoritative\n * commit is a property of the `cryptographic` adapter only.\n *\n * This is the **x402-EOA** policy path. The smart-account / ZeroDev session-key\n * path is `createJpycDailyLimitPolicies` (`./daily-limit`) — a sibling, not a\n * replacement.\n *\n * @packageDocumentation\n */\n\nimport type { Address } from \"viem\";\nimport { getAddress } from \"viem\";\nimport type { PaymentIntent, PolicyRejection } from \"../signer/types\";\nimport { normalizeRecipientAllowlist } from \"./normalize-allowlist\";\n\n/** Per-token spend limits. A token absent from the policy's `perToken` is NOT allowed. */\nexport interface TokenLimit {\n\treadonly token: Address;\n\t/** Max value per single signature, token base units. Generalizes `maxAmountPerSign` (threat 1.14). */\n\treadonly maxPerSign: bigint;\n\t/** Optional total across the session. `undefined` = uncapped. MUST be `>= maxPerSign`. */\n\treadonly cumulativeCap?: bigint;\n}\n\n/**\n * Policy-as-data evaluated for every {@link PaymentIntent}. Deny-closed\n * throughout: a token not listed in `perToken` is rejected, and\n * `recipientAllowlist` is **required** — `\"any\"` (unrestricted) is a conscious,\n * greppable choice, never a silent default.\n */\nexport interface SpendingPolicy {\n\treadonly version: \"1\";\n\t/** Session id + expiry (unix seconds). An authorization may not outlive the session. */\n\treadonly session: { readonly id: string; readonly notAfter: bigint };\n\treadonly perToken: readonly TokenLimit[];\n\t/**\n\t * Recipient restriction (**required** — no silent allow-open default):\n\t * `\"any\"` = unrestricted, `[]` = deny-all, `[...]` = allowlist. Making\n\t * `\"any\"` explicit keeps the policy deny-closed like `perToken`.\n\t *\n\t * The on-chain {@link createJpycDailyLimitPolicies} takes the same\n\t * `Address[] | \"any\"` shape, with two on-chain-forced differences: it is\n\t * optional (omitted = `\"any\"`), and `[]` throws there (an on-chain allowlist\n\t * cannot encode deny-all).\n\t */\n\treadonly recipientAllowlist: readonly Address[] | \"any\";\n\treadonly revoked: boolean;\n}\n\n/**\n * Cross-call cumulative spend, per token. Injected into the evaluator (never a\n * module global). For the `local` adapter this is a single-process, caller-managed\n * **read-only view**; the authoritative ledger lives in the `cryptographic`\n * adapter's backend.\n */\nexport interface SpendState {\n\treadonly spentPerToken: readonly { readonly token: Address; readonly spent: bigint }[];\n}\n\n/** The outcome of {@link evaluateSpendingPolicy}. */\nexport type PolicyDecision =\n\t| { readonly ok: true }\n\t| { readonly ok: false; readonly rejection: PolicyRejection };\n\n/** Thrown by {@link createSpendingPolicy} on a malformed policy. */\nexport class SpendingPolicyConfigError extends Error {\n\treadonly field: string;\n\treadonly reason: string;\n\n\tconstructor(field: string, reason: string, options?: { cause?: unknown }) {\n\t\tsuper(`Invalid SpendingPolicy (${field}): ${reason}`, options);\n\t\tthis.name = \"SpendingPolicyConfigError\";\n\t\tthis.field = field;\n\t\tthis.reason = reason;\n\t}\n}\n\nfunction deny(reason: PolicyRejection[\"reason\"], detail: string): PolicyDecision {\n\treturn { ok: false, rejection: { reason, detail } };\n}\n\n/**\n * Evaluate a {@link SpendingPolicy} against a decoded {@link PaymentIntent}.\n *\n * Pure, deterministic, no I/O. Deny-closed; the first failing check wins. All\n * amount comparisons are `bigint`; all address equality uses `getAddress()` on\n * both sides. **Reads `state`, never mutates it** — see {@link mergeSpendState}.\n * `detail` strings never contain the nonce or any signature.\n *\n * @example\n * ```ts\n * const decision = evaluateSpendingPolicy(policy, intent, state, BigInt(Math.floor(Date.now() / 1000)));\n * if (!decision.ok) console.warn(decision.rejection.reason);\n * ```\n */\nexport function evaluateSpendingPolicy(\n\tpolicy: SpendingPolicy,\n\tintent: PaymentIntent,\n\tstate: SpendState,\n\tnowSeconds: bigint,\n): PolicyDecision {\n\tif (policy.revoked) {\n\t\treturn deny(\"revoked\", \"the spending policy has been revoked\");\n\t}\n\tif (nowSeconds > policy.session.notAfter) {\n\t\treturn deny(\"expired\", `session expired at ${policy.session.notAfter} (now ${nowSeconds})`);\n\t}\n\tif (intent.validBefore > policy.session.notAfter) {\n\t\treturn deny(\n\t\t\t\"expired\",\n\t\t\t`authorization validBefore ${intent.validBefore} outlives the session (notAfter ${policy.session.notAfter})`,\n\t\t);\n\t}\n\n\tconst intentToken = getAddress(intent.token);\n\tconst limit = policy.perToken.find((l) => getAddress(l.token) === intentToken);\n\tif (limit === undefined) {\n\t\treturn deny(\"token_not_allowed\", `token ${intentToken} is not in the policy`);\n\t}\n\n\tif (policy.recipientAllowlist !== \"any\") {\n\t\tconst to = getAddress(intent.to);\n\t\tconst allowed = policy.recipientAllowlist.some((a) => getAddress(a) === to);\n\t\tif (!allowed) {\n\t\t\treturn deny(\"recipient_not_allowed\", `recipient ${to} is not on the allowlist`);\n\t\t}\n\t}\n\n\tif (intent.value > limit.maxPerSign) {\n\t\treturn deny(\n\t\t\t\"amount_exceeds_per_sign\",\n\t\t\t`value ${intent.value} exceeds maxPerSign ${limit.maxPerSign}`,\n\t\t);\n\t}\n\n\tif (limit.cumulativeCap !== undefined) {\n\t\tconst spent = state.spentPerToken.find((s) => getAddress(s.token) === intentToken)?.spent ?? 0n;\n\t\tif (spent + intent.value > limit.cumulativeCap) {\n\t\t\treturn deny(\n\t\t\t\t\"amount_exceeds_cumulative\",\n\t\t\t\t`spent ${spent} + value ${intent.value} exceeds cumulativeCap ${limit.cumulativeCap}`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn { ok: true };\n}\n\n/** Parameters for {@link createSpendingPolicy}. */\nexport interface CreateSpendingPolicyParams {\n\treadonly session: { readonly id: string; readonly notAfter: bigint };\n\treadonly perToken: readonly TokenLimit[];\n\t/** Required: `\"any\"` (unrestricted), `[]` (deny-all), or an allowlist. No silent default. */\n\treadonly recipientAllowlist: readonly Address[] | \"any\";\n\t/** Defaults to `false`. */\n\treadonly revoked?: boolean;\n}\n\n/**\n * Validate + normalize a {@link SpendingPolicy}. Checksums all addresses\n * (`getAddress`), de-dupes the recipient allowlist (shared\n * {@link normalizeRecipientAllowlist}; `[]` stays deny-all), rejects an empty\n * `perToken` (deny-closed), a non-positive `maxPerSign`/`cumulativeCap`, a\n * `cumulativeCap < maxPerSign`, and duplicate tokens. Throws\n * {@link SpendingPolicyConfigError} on violation.\n *\n * @example\n * ```ts\n * const policy = createSpendingPolicy({\n * session: { id: conversationId, notAfter: BigInt(deadline) },\n * perToken: [{ token: JPYC, maxPerSign: 1_000n, cumulativeCap: 10_000n }],\n * recipientAllowlist: [merchant],\n * });\n * ```\n */\nexport function createSpendingPolicy(params: CreateSpendingPolicyParams): SpendingPolicy {\n\tif (typeof params.session.id !== \"string\" || params.session.id === \"\") {\n\t\tthrow new SpendingPolicyConfigError(\"session.id\", \"must be a non-empty string\");\n\t}\n\tif (params.session.notAfter <= 0n) {\n\t\tthrow new SpendingPolicyConfigError(\n\t\t\t\"session.notAfter\",\n\t\t\t`must be a positive unix-seconds bigint, got ${params.session.notAfter}`,\n\t\t);\n\t}\n\tif (params.perToken.length === 0) {\n\t\tthrow new SpendingPolicyConfigError(\n\t\t\t\"perToken\",\n\t\t\t\"must list at least one token (the policy is deny-closed)\",\n\t\t);\n\t}\n\n\tconst seen = new Set<string>();\n\tconst perToken: readonly TokenLimit[] = params.perToken.map((l) => {\n\t\tconst token = getAddress(l.token);\n\t\tif (seen.has(token)) {\n\t\t\tthrow new SpendingPolicyConfigError(\"perToken\", `duplicate token ${token}`);\n\t\t}\n\t\tseen.add(token);\n\t\tif (l.maxPerSign <= 0n) {\n\t\t\tthrow new SpendingPolicyConfigError(\n\t\t\t\t\"perToken.maxPerSign\",\n\t\t\t\t`must be positive, got ${l.maxPerSign} for ${token}`,\n\t\t\t);\n\t\t}\n\t\tif (l.cumulativeCap !== undefined) {\n\t\t\tif (l.cumulativeCap <= 0n) {\n\t\t\t\tthrow new SpendingPolicyConfigError(\n\t\t\t\t\t\"perToken.cumulativeCap\",\n\t\t\t\t\t`must be positive, got ${l.cumulativeCap} for ${token}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (l.cumulativeCap < l.maxPerSign) {\n\t\t\t\tthrow new SpendingPolicyConfigError(\n\t\t\t\t\t\"perToken.cumulativeCap\",\n\t\t\t\t\t`cumulativeCap (${l.cumulativeCap}) must be >= maxPerSign (${l.maxPerSign}) for ${token}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn { token, maxPerSign: l.maxPerSign, cumulativeCap: l.cumulativeCap };\n\t\t}\n\t\treturn { token, maxPerSign: l.maxPerSign };\n\t});\n\n\t// Checksum-normalize + de-dupe the allowlist (shared with the on-chain\n\t// daily-limit path). `[]` stays `[]` — deny-all is a valid off-chain policy.\n\tconst recipientAllowlist =\n\t\tparams.recipientAllowlist === \"any\"\n\t\t\t? \"any\"\n\t\t\t: normalizeRecipientAllowlist(params.recipientAllowlist);\n\n\treturn {\n\t\tversion: \"1\",\n\t\tsession: { id: params.session.id, notAfter: params.session.notAfter },\n\t\tperToken,\n\t\trecipientAllowlist,\n\t\trevoked: params.revoked ?? false,\n\t};\n}\n\n/**\n * Fold a successful spend back into a {@link SpendState} (pure; returns a new\n * state). The caller of the `local` adapter uses this to keep `cumulativeCap`\n * meaningful across calls — `local` does not own an authoritative ledger.\n */\nexport function mergeSpendState(\n\tstate: SpendState,\n\tspend: { readonly token: Address; readonly value: bigint },\n): SpendState {\n\tconst token = getAddress(spend.token);\n\tconst exists = state.spentPerToken.some((s) => getAddress(s.token) === token);\n\tconst spentPerToken = exists\n\t\t? state.spentPerToken.map((s) =>\n\t\t\t\tgetAddress(s.token) === token ? { token, spent: s.spent + spend.value } : s,\n\t\t\t)\n\t\t: [...state.spentPerToken, { token, spent: spend.value }];\n\treturn { spentPerToken };\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PolicyGatedSignerConfigError, resolveAssetParam, signTransferWithAuthorization, resolvedAssetToEip3009Domain, transferWithAuthorizationTypes, CoSignUnavailableError } from './chunk-VJUXTVSW.js';
|
|
2
|
-
import { evaluateSpendingPolicy } from './chunk-
|
|
2
|
+
import { evaluateSpendingPolicy } from './chunk-7YKRKV6D.js';
|
|
3
3
|
import { getAddress, hashTypedData, toHex, recoverAddress, serializeSignature } from 'viem';
|
|
4
4
|
|
|
5
5
|
function createLocalPolicyGatedSigner(params) {
|
|
@@ -394,5 +394,5 @@ function assembleSignature(sig) {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
export { MAX_FRAME_BYTES, WIRE_VERSION, canonicalRequestBytes, createLocalPolicyGatedSigner, createMpc2pPolicyGatedSigner, toWireIntent };
|
|
397
|
-
//# sourceMappingURL=chunk-
|
|
398
|
-
//# sourceMappingURL=chunk-
|
|
397
|
+
//# sourceMappingURL=chunk-NY6VLXQB.js.map
|
|
398
|
+
//# sourceMappingURL=chunk-NY6VLXQB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/signer/local.ts","../src/signer/mpc-2p-wire.ts","../src/signer/mpc-2p.ts"],"names":["getAddress"],"mappings":"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","file":"chunk-S2ZSX2VS.js","sourcesContent":["/**\n * The `local` PolicyGatedSigner adapter — `enforcement: \"advisory\"`.\n *\n * Wraps a viem {@link Account} + a {@link SpendingPolicy} + a pinned EIP-712\n * domain. `sign(intent)` evaluates the policy SDK-side and, on pass, produces a\n * real EIP-3009 authorization via {@link signTransferWithAuthorization}. It is\n * **advisory** because the wrapped key can still sign anything elsewhere — the\n * gate is only reached if the caller chooses to call *this* `sign()`. Use it for\n * dev, the A1 cross-language fallback, and any flow that is explicitly not\n * bounded/regulated; the type-gate (`requireNonBypassable`) keeps it out of\n * flows that require non-bypassable enforcement.\n *\n * @packageDocumentation\n */\n\nimport type { Account } from \"viem\";\nimport { getAddress } from \"viem\";\nimport type { SpendingPolicy, SpendState } from \"../policy/spending-policy\";\nimport { evaluateSpendingPolicy } from \"../policy/spending-policy\";\nimport type { X402AssetParam } from \"../tokens/asset-domain\";\nimport { resolveAssetParam, resolvedAssetToEip3009Domain } from \"../tokens/asset-domain\";\nimport { signTransferWithAuthorization } from \"../tokens/eip3009\";\nimport { PolicyGatedSignerConfigError } from \"./errors\";\nimport type { PaymentIntent, PolicyGatedSigner, SignerDescription, SignResult } from \"./types\";\n\n/** Parameters for {@link createLocalPolicyGatedSigner}. */\nexport interface CreateLocalPolicyGatedSignerParams {\n\t/** EOA / LocalAccount that signs the EIP-3009 authorization. */\n\treadonly account: Account;\n\t/** The spending policy this signer enforces (SDK-side, advisory). */\n\treadonly policy: SpendingPolicy;\n\t/** Asset binding — pins the EIP-712 domain `name`/`version`/`verifyingContract`. */\n\treadonly asset: X402AssetParam;\n\t/**\n\t * Required literal acknowledgement that this signer is **advisory** (a\n\t * key-holder can bypass its policy). Omitting it is a compile error (TS) and\n\t * a construction-time throw (JS) — so constructing an advisory signer is a\n\t * conscious, greppable act. For bounded/regulated flows use a cryptographic\n\t * adapter instead.\n\t */\n\treadonly acknowledgeAdvisory: true;\n\t/**\n\t * Optional cumulative-spend view (read-only) the adapter evaluates\n\t * `cumulativeCap` against. `local` does not own an authoritative ledger; the\n\t * caller folds a successful spend back in (e.g. via `mergeSpendState`) before\n\t * the next call. Default: empty.\n\t */\n\treadonly spendState?: () => SpendState | Promise<SpendState>;\n}\n\n/**\n * Construct a `local` (advisory) PolicyGatedSigner.\n *\n * @example\n * ```ts\n * const signer = createLocalPolicyGatedSigner({\n * account,\n * policy: createSpendingPolicy({ session: { id, notAfter }, perToken: [{ token: JPYC, maxPerSign: 1_000n }], recipientAllowlist: \"any\" }),\n * asset: { kind: \"known\", id: \"jpyc-v2\" },\n * acknowledgeAdvisory: true,\n * });\n * const result = await signer.sign(intent);\n * ```\n */\nexport function createLocalPolicyGatedSigner(\n\tparams: CreateLocalPolicyGatedSignerParams,\n): PolicyGatedSigner<\"advisory\"> {\n\tif (params.acknowledgeAdvisory !== true) {\n\t\tthrow new PolicyGatedSignerConfigError(\n\t\t\t\"acknowledgeAdvisory\",\n\t\t\t\"a local signer is advisory (a key-holder can bypass its policy); pass `acknowledgeAdvisory: true` to construct one consciously, or use a cryptographic adapter for bounded/regulated flows\",\n\t\t);\n\t}\n\n\tconst { account, policy, spendState } = params;\n\tconst pinned = resolveAssetParam(params.asset);\n\tconst from = getAddress(account.address);\n\n\treturn {\n\t\tenforcement: \"advisory\",\n\t\tfrom,\n\t\tasync sign(intent: PaymentIntent): Promise<SignResult> {\n\t\t\tif (getAddress(intent.from) !== from) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\trejection: {\n\t\t\t\t\t\treason: \"from_mismatch\",\n\t\t\t\t\t\tdetail: `intent.from ${getAddress(intent.from)} does not equal signer.from ${from}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (getAddress(intent.token) !== pinned.verifyingContract) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\trejection: {\n\t\t\t\t\t\treason: \"token_not_allowed\",\n\t\t\t\t\t\tdetail: `intent.token ${getAddress(intent.token)} does not equal the signer's pinned verifyingContract ${pinned.verifyingContract}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst state: SpendState = (await spendState?.()) ?? { spentPerToken: [] };\n\t\t\tconst nowSeconds = BigInt(Math.floor(Date.now() / 1000));\n\t\t\tconst decision = evaluateSpendingPolicy(policy, intent, state, nowSeconds);\n\t\t\tif (!decision.ok) {\n\t\t\t\treturn { ok: false, rejection: decision.rejection };\n\t\t\t}\n\n\t\t\tconst signed = await signTransferWithAuthorization(\n\t\t\t\taccount,\n\t\t\t\tresolvedAssetToEip3009Domain(pinned, intent.chainId),\n\t\t\t\t{\n\t\t\t\t\tfrom,\n\t\t\t\t\tto: intent.to,\n\t\t\t\t\tvalue: intent.value,\n\t\t\t\t\tvalidAfter: intent.validAfter,\n\t\t\t\t\tvalidBefore: intent.validBefore,\n\t\t\t\t\tnonce: intent.nonce,\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { ok: true, signature: signed.signature, intent };\n\t\t},\n\t\tdescribe(): SignerDescription {\n\t\t\treturn {\n\t\t\t\tenforcement: \"advisory\",\n\t\t\t\tfrom,\n\t\t\t\tpolicyId: policy.session.id,\n\t\t\t\tnotAfter: policy.session.notAfter,\n\t\t\t\trevoked: policy.revoked,\n\t\t\t};\n\t\t},\n\t};\n}\n","/**\n * The `mpc-2p` cross-process **wire** (v2) — the versioned `CoSignFrame` control\n * envelope + the A3 canonical-request encoding, as TypeScript source-of-truth.\n *\n * This is the public, TS-interpreted half of the two-layer wire (RFC m6-3a §4.2):\n * a small, versioned envelope carries the decoded {@link PaymentIntent} (never a\n * digest — A4) + a per-ceremony id + ssid + a freshness element, plus the opaque\n * DKLs round payloads (hex, produced/consumed only by the WASM crypto agent). The\n * shapes here mirror the deployed `kawasekit-mpc-2p` Rust serde **byte-for-byte**\n * (snake_case `wire_version`, `session_id`, `ceremony_id`, `ssid`, `freshness_ts`,\n * `freshness_nonce`, `auth_tag`; the string-encoded {@link WireIntent}), so a frame\n * the SDK builds round-trips through the backend's `serde_json`.\n *\n * `canonicalRequestBytes` is the A3 analog of the EIP-712 digest corpus: it\n * reproduces the backend's `auth::canonical_request_bytes` exactly, so the injected\n * authenticator's HMAC equals what the co-signer verifies (RFC §4.6:\n * `HMAC_k(wireVersion ‖ ceremonyId ‖ ssid ‖ canonical(intent) ‖ freshness)`).\n *\n * @packageDocumentation\n */\n\nimport type { Hex } from \"viem\";\nimport type { PaymentIntent } from \"./types\";\n\n/** The wire protocol version. A frame whose `wire_version` differs is rejected. */\nexport const WIRE_VERSION = 2 as const;\n\n/**\n * The inbound round-payload bound, in bytes — mirrors the backend's\n * `crypto-core/src/transport.rs` `MAX_FRAME_BYTES` (8 MiB) so the agent side\n * enforces the same pre-decode cap the backend does (RFC m6-3a §4.2/M3). The\n * adapter refuses an over-bound `round` payload **before** hex-decoding it into\n * the WASM boundary; the private transport should additionally cap raw\n * websocket messages (e.g. the `ws` client's `maxPayload`).\n */\nexport const MAX_FRAME_BYTES = 8 * 1024 * 1024;\n\n/**\n * The string-encoded EIP-3009 intent as it crosses the wire — lowercase `0x`\n * addresses, **decimal** integer strings, `0x` hex nonce. Decoupled from any\n * library's native serde so a stdlib client reproduces it trivially; matches the\n * Rust `WireIntent` field names + encoding exactly.\n */\nexport interface WireIntent {\n\treadonly token: string;\n\treadonly chain_id: number;\n\treadonly from: string;\n\treadonly to: string;\n\treadonly value: string;\n\treadonly valid_after: string;\n\treadonly valid_before: string;\n\treadonly nonce: string;\n}\n\n/** Encode a decoded {@link PaymentIntent} for the wire. */\nexport function toWireIntent(intent: PaymentIntent): WireIntent {\n\treturn {\n\t\ttoken: intent.token.toLowerCase(),\n\t\tchain_id: intent.chainId,\n\t\tfrom: intent.from.toLowerCase(),\n\t\tto: intent.to.toLowerCase(),\n\t\tvalue: intent.value.toString(),\n\t\tvalid_after: intent.validAfter.toString(),\n\t\tvalid_before: intent.validBefore.toString(),\n\t\tnonce: intent.nonce.toLowerCase(),\n\t};\n}\n\n/**\n * The A3 request envelope the adapter generates per `sign()` and authenticates: a\n * per-ceremony id + ssid (bound into the A3 tag) + the intent + a freshness element\n * (a timestamp + a per-request nonce distinct from the EIP-3009 nonce).\n */\nexport interface CoSignRequestEnvelope {\n\treadonly ceremonyId: string;\n\treadonly ssid: string;\n\treadonly intent: PaymentIntent;\n\t/** Unix seconds; the backend checks it against a clock-skew window. */\n\treadonly freshnessTs: number;\n\t/** `0x` hex, distinct from the EIP-3009 nonce — the anti-replay key. */\n\treadonly freshnessNonce: Hex;\n}\n\n/**\n * A versioned control frame: the only cross-language content of the wire. The\n * DKLs round bytes ride opaque inside `payload`; everything else is the small,\n * pinned envelope. Internally tagged by `kind` (matches the Rust serde).\n */\nexport type CoSignFrame = { readonly wire_version: typeof WIRE_VERSION } & (\n\t| {\n\t\t\treadonly kind: \"request\";\n\t\t\treadonly session_id: string;\n\t\t\treadonly ceremony_id: string;\n\t\t\treadonly ssid: string;\n\t\t\treadonly intent: WireIntent;\n\t\t\treadonly freshness_ts: number;\n\t\t\treadonly freshness_nonce: Hex;\n\t\t\t/** A3 authenticator over {@link canonicalRequestBytes}, `0x`-hex. */\n\t\t\treadonly auth_tag: Hex;\n\t }\n\t| { readonly kind: \"round\"; readonly payload: Hex }\n\t| { readonly kind: \"result\"; readonly r: Hex; readonly s: Hex; readonly v: number }\n\t| { readonly kind: \"rejection\"; readonly reason: string; readonly detail: string }\n\t| { readonly kind: \"error\"; readonly message: string }\n);\n\n/**\n * The A3 canonical encoding of a request — a byte-exact mirror of the backend's\n * `auth::canonical_request_bytes` (domain-tag `kawasekit-mpc-2p/cosign-request/v3`,\n * one `key=value\\n` line per field: `wireVersion ‖ ceremonyId ‖ ssid ‖\n * canonical(intent) ‖ freshness`; lowercase `0x` addresses + freshness nonce,\n * decimal integers, trailing newline). The injected authenticator HMACs these\n * bytes; because the encoding is shared, the tag equals the co-signer's.\n */\nexport function canonicalRequestBytes(env: CoSignRequestEnvelope): Uint8Array {\n\tconst i = env.intent;\n\tconst lines = [\n\t\t\"kawasekit-mpc-2p/cosign-request/v3\",\n\t\t\"wireVersion=2\",\n\t\t`ceremonyId=${env.ceremonyId}`,\n\t\t`ssid=${env.ssid}`,\n\t\t`token=${i.token.toLowerCase()}`,\n\t\t`chainId=${i.chainId}`,\n\t\t`from=${i.from.toLowerCase()}`,\n\t\t`to=${i.to.toLowerCase()}`,\n\t\t`value=${i.value}`,\n\t\t`validAfter=${i.validAfter}`,\n\t\t`validBefore=${i.validBefore}`,\n\t\t`nonce=${i.nonce.toLowerCase()}`,\n\t\t`freshnessTs=${env.freshnessTs}`,\n\t\t`freshnessNonce=${env.freshnessNonce.toLowerCase()}`,\n\t];\n\treturn new TextEncoder().encode(lines.map((l) => `${l}\\n`).join(\"\"));\n}\n","/**\n * The `mpc-2p` PolicyGatedSigner adapter — `enforcement: \"cryptographic\"`.\n *\n * A 2-of-2 MPC co-signer: the agent holds ONE DKLs share, the owner backend holds\n * the other + the authoritative policy gate. No valid signature exists without a\n * **policy-passing co-sign**, so — unlike `local` — a single key-holder cannot\n * bypass the policy. That is what earns the non-bypassable `\"cryptographic\"` label\n * and lets the M6-0 type-gate (`requireNonBypassable`) reject an advisory signer at\n * compile time in bounded flows.\n *\n * **Open-core / thin adapter.** This module is pure orchestration over injected\n * interfaces — it ships in the public SDK with **no crypto, no socket, no key**:\n *\n * - {@link Mpc2pCoSignAgent} — the WASM DKLs share (the private package's compiled\n * `crypto-core`); drives the agent's half of the ceremony round-by-round.\n * - {@link CoSignTransport} — the authenticated, encrypted (wss/mTLS) frame channel\n * to the owner backend (the private package's transport).\n * - {@link CoSignRequestAuthenticator} — the A3 HMAC (the pre-shared key never\n * enters this module).\n *\n * The adapter owns the protocol: it pins the EIP-712 domain, **re-derives the\n * digest from the intent (A4)** via the shared `transferWithAuthorizationTypes`\n * source-of-truth, computes the A3 canonical bytes, frames the versioned\n * {@link CoSignFrame} envelope, pumps the ceremony, and maps the terminal frame to\n * a {@link SignResult}. Crucially it has **no local-signing path**: any\n * transport/availability failure throws {@link CoSignUnavailableError}, never an\n * `{ ok: true }` and never a {@link PolicyRejection} (the no-silent-fallback\n * guarantee, RFC m6-3a constraint 3 / W8).\n *\n * **Wire hardening (RFC §4.7/§4.8, Track C):** a bounded **transient-only retry**\n * replays the byte-identical intent (nonce included — idempotency-by-nonce is the\n * safety net) under a fresh A3 envelope and accepts the backend's **roundless\n * idempotent-replay result** via an ecrecover/low-S self-check; the **ceremony\n * deadline always fires before `validBefore`** (minus a clock-skew budget, W11),\n * and inbound round payloads are **bounded** before the WASM boundary (M3). Tuning\n * via {@link Mpc2pWireOptions}.\n *\n * @packageDocumentation\n */\n\nimport type { Address, Hex } from \"viem\";\nimport { getAddress, hashTypedData, recoverAddress, serializeSignature, toHex } from \"viem\";\nimport type { X402AssetParam } from \"../tokens/asset-domain\";\nimport { resolveAssetParam, resolvedAssetToEip3009Domain } from \"../tokens/asset-domain\";\nimport { transferWithAuthorizationTypes } from \"../tokens/eip3009\";\nimport { CoSignUnavailableError, PolicyGatedSignerConfigError } from \"./errors\";\nimport type { CoSignFrame, CoSignRequestEnvelope } from \"./mpc-2p-wire\";\nimport { canonicalRequestBytes, MAX_FRAME_BYTES, toWireIntent, WIRE_VERSION } from \"./mpc-2p-wire\";\nimport type {\n\tPaymentIntent,\n\tPolicyGatedSigner,\n\tPolicyRejection,\n\tSignerDescription,\n\tSignResult,\n} from \"./types\";\n\n/** The agent's half of the 2-of-2 ceremony (the injected WASM DKLs share). */\nexport interface Mpc2pCoSignAgent {\n\t/** The group 2-of-2 EOA this share controls — must equal the signer's `from`. */\n\tgroupEoa(): Address;\n\t/** Begin signing the 32-byte EIP-712 `digest`; returns the first outbound round (hex). */\n\tstart(digest: Hex): Hex;\n\t/** Feed one inbound round (hex); returns the next outbound round, or the final signature. */\n\tstep(incoming: Hex): Mpc2pStepOutcome;\n}\n\n/** The result of one {@link Mpc2pCoSignAgent.step}. */\nexport type Mpc2pStepOutcome =\n\t| { readonly outbound: Hex }\n\t| { readonly signature: { readonly r: Hex; readonly s: Hex; readonly v: number } };\n\n/** One authenticated, encrypted ceremony connection to the owner backend. */\nexport interface CoSignConnection {\n\t/** Send one control frame. */\n\tsend(frame: CoSignFrame): Promise<void>;\n\t/** Receive the next control frame (resolves per round). */\n\trecv(): Promise<CoSignFrame>;\n\t/** Release the connection (best-effort; errors here are ignored). */\n\tclose(): void | Promise<void>;\n}\n\n/** Opens a wss/mTLS {@link CoSignConnection} for a single co-sign ceremony. */\nexport interface CoSignTransport {\n\tconnect(): Promise<CoSignConnection>;\n}\n\n/** The A3 request authenticator — HMAC (or signature) over the canonical request bytes. */\nexport interface CoSignRequestAuthenticator {\n\t/** Authenticator tag over {@link canonicalRequestBytes}; the key stays inside. */\n\ttag(canonicalRequest: Uint8Array): Hex | Promise<Hex>;\n}\n\n/**\n * Wire liveness / retry tuning (RFC m6-3a §4.7 + §4.8). Every field is optional\n * with a safe default. The invariants these enforce:\n *\n * - **Retry (4b/H2):** only the **transient transport class** is retried (connect\n * failed / connection dropped), replaying the **byte-identical intent** — nonce\n * included, so the backend's idempotency-by-nonce is the safety net — under a\n * fresh A3 envelope (the freshness guard rejects a replayed envelope by design).\n * A delivered rejection, a ban/identifiable-abort, a protocol anomaly, or a\n * timeout is never retried.\n * - **Liveness (4c/W11/M1+M3):** the ceremony deadline always fires **before**\n * `intent.validBefore − clockSkewBudgetSecs` (a co-signature must never be born\n * expired), and a `sign()` whose remaining window is under\n * `minWindowSecs + clockSkewBudgetSecs` is refused up front.\n * - **Inbound bound (4c/M3):** a `round` payload over `maxFrameBytes` is refused\n * before it reaches the WASM boundary (mirrors the backend's bound).\n */\nexport interface Mpc2pWireOptions {\n\t/** Max ceremony attempts for transient transport failures (default 2 = one retry). */\n\treadonly maxAttempts?: number;\n\t/** Overall per-`sign()` budget in ms, across ALL attempts (default 30_000). */\n\treadonly ceremonyTimeoutMs?: number;\n\t/** Minimum remaining validity window required at `sign()` start, in seconds (default 30). */\n\treadonly minWindowSecs?: number;\n\t/** Max tolerated agent-host clock skew, in seconds (default 30). */\n\treadonly clockSkewBudgetSecs?: number;\n\t/** Inbound round-payload bound in bytes (default {@link MAX_FRAME_BYTES} = 8 MiB). */\n\treadonly maxFrameBytes?: number;\n}\n\n/** Parameters for {@link createMpc2pPolicyGatedSigner}. */\nexport interface Mpc2pSignerParams {\n\t/** The group 2-of-2 EOA; every `intent.from` must equal this (asserted vs the agent). */\n\treadonly from: Address;\n\t/** Asset binding — pins the EIP-712 domain `name`/`version`/`verifyingContract` (A4). */\n\treadonly asset: X402AssetParam;\n\t/** The bound policy session, for `describe()` (the backend holds the authoritative policy). */\n\treadonly session: { readonly id: string; readonly notAfter: bigint };\n\t/** The injected WASM DKLs share (private package). */\n\treadonly agent: Mpc2pCoSignAgent;\n\t/** The injected wss/mTLS frame channel (private package). */\n\treadonly transport: CoSignTransport;\n\t/** The injected A3 authenticator (the pre-shared key never enters the SDK). */\n\treadonly authenticator: CoSignRequestAuthenticator;\n\t/** Wire liveness / retry tuning (optional; safe defaults — {@link Mpc2pWireOptions}). */\n\treadonly wire?: Mpc2pWireOptions;\n}\n\n/**\n * The backend `rejection` reasons that are genuine **policy denials** (the owner\n * decided \"no\" — audit-meaningful → `{ ok: false, rejection }`). Any other reason\n * on a rejection frame is treated as \"the owner did not cleanly decide\" and throws\n * {@link CoSignUnavailableError} (no-silent-fallback; never a misclassified denial).\n */\nconst POLICY_REASONS: ReadonlySet<PolicyRejection[\"reason\"]> = new Set([\n\t\"revoked\",\n\t\"expired\",\n\t\"token_not_allowed\",\n\t\"recipient_not_allowed\",\n\t\"amount_exceeds_per_sign\",\n\t\"amount_exceeds_cumulative\",\n\t\"intent_digest_mismatch\",\n\t\"unauthenticated\",\n\t\"from_mismatch\",\n\t\"nonce_reuse_conflict\",\n]);\n\nfunction rejection(reason: PolicyRejection[\"reason\"], detail: string): SignResult {\n\treturn { ok: false, rejection: { reason, detail } };\n}\n\n/**\n * Construct an `mpc-2p` (cryptographic) PolicyGatedSigner over injected agent +\n * transport + authenticator.\n *\n * @example\n * ```ts\n * // The agent, transport and authenticator come from the private `kawasekit-mpc-2p`\n * // package (the WASM share, the wss/mTLS client, the HMAC key) — never bundled here.\n * const signer = createMpc2pPolicyGatedSigner({\n * from: groupEoa,\n * asset: { kind: \"known\", id: \"jpyc-v2\" },\n * session: { id: \"sess-1\", notAfter: 2_000_000_000n },\n * agent, transport, authenticator,\n * });\n * requireNonBypassable(signer); // ✓ \"cryptographic\" — passes the type-gate\n * const result = await signer.sign(intent);\n * ```\n */\nexport function createMpc2pPolicyGatedSigner(\n\tparams: Mpc2pSignerParams,\n): PolicyGatedSigner<\"cryptographic\"> {\n\tconst { agent, transport, authenticator, session } = params;\n\tconst pinned = resolveAssetParam(params.asset);\n\tconst from = getAddress(params.from);\n\tconst wire = params.wire ?? {};\n\tconst maxAttempts = Math.max(1, wire.maxAttempts ?? 2);\n\tconst ceremonyTimeoutMs = wire.ceremonyTimeoutMs ?? 30_000;\n\tconst minWindowSecs = wire.minWindowSecs ?? 30;\n\tconst clockSkewBudgetSecs = wire.clockSkewBudgetSecs ?? 30;\n\tconst maxFrameBytes = wire.maxFrameBytes ?? MAX_FRAME_BYTES;\n\n\t// The injected share MUST control the declared group EOA, or every signature\n\t// would recover to the wrong address.\n\tconst agentEoa = getAddress(agent.groupEoa());\n\tif (agentEoa !== from) {\n\t\tthrow new PolicyGatedSignerConfigError(\n\t\t\t\"from\",\n\t\t\t`the agent share controls ${agentEoa} but params.from is ${from}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tenforcement: \"cryptographic\",\n\t\tfrom,\n\t\tasync sign(intent: PaymentIntent): Promise<SignResult> {\n\t\t\t// Adapter-local pre-checks (cheap; no wire needed). Same shape as `local`.\n\t\t\tif (getAddress(intent.from) !== from) {\n\t\t\t\treturn rejection(\n\t\t\t\t\t\"from_mismatch\",\n\t\t\t\t\t`intent.from ${getAddress(intent.from)} does not equal signer.from ${from}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (getAddress(intent.token) !== pinned.verifyingContract) {\n\t\t\t\treturn rejection(\n\t\t\t\t\t\"token_not_allowed\",\n\t\t\t\t\t`intent.token ${getAddress(intent.token)} does not equal the signer's pinned verifyingContract ${pinned.verifyingContract}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// A4: re-derive the EIP-712 digest from the intent + the pinned domain (the\n\t\t\t// SDK's exported types are the cross-language source of truth). The agent and\n\t\t\t// the backend each sign THIS digest; the SoT keeps them byte-identical.\n\t\t\tconst digest = hashTypedData({\n\t\t\t\tdomain: resolvedAssetToEip3009Domain(pinned, intent.chainId),\n\t\t\t\ttypes: transferWithAuthorizationTypes,\n\t\t\t\tprimaryType: \"TransferWithAuthorization\",\n\t\t\t\tmessage: {\n\t\t\t\t\tfrom,\n\t\t\t\t\tto: intent.to,\n\t\t\t\t\tvalue: intent.value,\n\t\t\t\t\tvalidAfter: intent.validAfter,\n\t\t\t\t\tvalidBefore: intent.validBefore,\n\t\t\t\t\tnonce: intent.nonce,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// 4c (W11/M1+M3) — the ceremony-liveness minimum-window invariant: the rounds +\n\t\t\t// the bounded retry budget must all complete BEFORE validBefore, and validity is\n\t\t\t// judged against chain time, so the agent host's clock-skew budget is part of the\n\t\t\t// bar. A window already too small risks a born-expired signature (wasted\n\t\t\t// facilitator gas + retry/cap pressure); the owner has not decided anything, so\n\t\t\t// this throws (never a rejection).\n\t\t\tconst validBeforeSecs = Number(intent.validBefore);\n\t\t\tconst nowSecs = Math.floor(Date.now() / 1000);\n\t\t\tif (validBeforeSecs - nowSecs < minWindowSecs + clockSkewBudgetSecs) {\n\t\t\t\tthrow new CoSignUnavailableError(\n\t\t\t\t\t`intent.validBefore (${validBeforeSecs}) leaves under the minimum ceremony window (${minWindowSecs}s + ${clockSkewBudgetSecs}s clock-skew budget) — refusing a co-sign that risks being born expired (W11)`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// One deadline for the whole sign() — every attempt included — capped so the\n\t\t\t// ceremony timeout always fires before validBefore (minus the clock-skew budget).\n\t\t\tconst deadlineMs = Math.min(\n\t\t\t\tDate.now() + ceremonyTimeoutMs,\n\t\t\t\t(validBeforeSecs - clockSkewBudgetSecs) * 1000,\n\t\t\t);\n\n\t\t\t// 4b (RFC §4.7/H2) — bounded transient-only retry. Every attempt re-presents the\n\t\t\t// *byte-identical* `intent` (nonce included — the backend's idempotency-by-nonce\n\t\t\t// + atomic SpendState are the safety net) under a FRESH A3 envelope: the\n\t\t\t// ceremonyId/ssid/freshness are per-attempt BY DESIGN (the backend's freshness\n\t\t\t// guard rejects a replayed envelope, while idempotency keys on the unchanged\n\t\t\t// EIP-3009 nonce). Only the transient transport class retries — never a\n\t\t\t// delivered rejection, never a ban/identifiable-abort, never a timeout.\n\t\t\tlet lastTransient: CoSignUnavailableError | undefined;\n\t\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt += 1) {\n\t\t\t\tif (Date.now() >= deadlineMs) break;\n\t\t\t\t// A3 (v2): a fresh per-ceremony id + ssid + freshness (a timestamp + a\n\t\t\t\t// per-request nonce, distinct from the EIP-3009 nonce), authenticated over the\n\t\t\t\t// canonical bytes (the shared SoT; the HMAC key lives inside the injected\n\t\t\t\t// authenticator). Web Crypto is isomorphic (Node 19+ / browsers).\n\t\t\t\tconst env: CoSignRequestEnvelope = {\n\t\t\t\t\tceremonyId: globalThis.crypto.randomUUID(),\n\t\t\t\t\tssid: globalThis.crypto.randomUUID(),\n\t\t\t\t\tintent,\n\t\t\t\t\tfreshnessTs: Math.floor(Date.now() / 1000),\n\t\t\t\t\tfreshnessNonce: toHex(globalThis.crypto.getRandomValues(new Uint8Array(16))),\n\t\t\t\t};\n\t\t\t\tconst authTag = await authenticator.tag(canonicalRequestBytes(env));\n\t\t\t\ttry {\n\t\t\t\t\tconst conn = await openConnection(transport);\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn await runCeremony({\n\t\t\t\t\t\t\tconn,\n\t\t\t\t\t\t\tagent,\n\t\t\t\t\t\t\tsessionId: session.id,\n\t\t\t\t\t\t\tenv,\n\t\t\t\t\t\t\tdigest,\n\t\t\t\t\t\t\tauthTag,\n\t\t\t\t\t\t\tfrom,\n\t\t\t\t\t\t\tdeadlineMs,\n\t\t\t\t\t\t\tmaxFrameBytes,\n\t\t\t\t\t\t});\n\t\t\t\t\t} finally {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait conn.close();\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// best-effort cleanup; a close error must not mask the result/throw.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof CoSignUnavailableError && error.transient && attempt < maxAttempts) {\n\t\t\t\t\t\tlastTransient = error;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow (\n\t\t\t\tlastTransient ??\n\t\t\t\tnew CoSignUnavailableError(\n\t\t\t\t\t\"co-sign ceremony budget exhausted before validBefore (W11) — no attempt could start\",\n\t\t\t\t)\n\t\t\t);\n\t\t},\n\t\tdescribe(): SignerDescription {\n\t\t\treturn {\n\t\t\t\tenforcement: \"cryptographic\",\n\t\t\t\tfrom,\n\t\t\t\tpolicyId: session.id,\n\t\t\t\tnotAfter: session.notAfter,\n\t\t\t\t// The backend owns authoritative revocation (a revoked session → a\n\t\t\t\t// rejection at sign time); this metadata field is best-effort.\n\t\t\t\trevoked: false,\n\t\t\t};\n\t\t},\n\t};\n}\n\nasync function openConnection(transport: CoSignTransport): Promise<CoSignConnection> {\n\ttry {\n\t\treturn await transport.connect();\n\t} catch (cause) {\n\t\t// Connect failure = the transient transport class (the one retried, §4.7).\n\t\tthrow new CoSignUnavailableError(\"co-signer connection failed\", { cause, transient: true });\n\t}\n}\n\n/** Everything one ceremony attempt needs (one connection, one A3 envelope). */\ninterface CeremonyContext {\n\treadonly conn: CoSignConnection;\n\treadonly agent: Mpc2pCoSignAgent;\n\treadonly sessionId: string;\n\treadonly env: CoSignRequestEnvelope;\n\treadonly digest: Hex;\n\treadonly authTag: Hex;\n\t/** The group EOA every result must recover to (the §4.4 self-check). */\n\treadonly from: Address;\n\t/** Absolute epoch-ms the ceremony must finish by (fires before validBefore — W11). */\n\treadonly deadlineMs: number;\n\t/** Inbound round-payload bound in bytes (mirrors the backend's MAX_FRAME_BYTES). */\n\treadonly maxFrameBytes: number;\n}\n\n/**\n * Drive one full ceremony attempt over `ctx.conn`: send the authenticated request +\n * the agent's first round, then pump rounds until a terminal frame. Returns a\n * {@link SignResult} (success or a policy denial) or throws\n * {@link CoSignUnavailableError}. There is no path that returns `{ ok: true }`\n * without a backend `result` frame that passes the ecrecover/low-S self-check.\n */\nasync function runCeremony(ctx: CeremonyContext): Promise<SignResult> {\n\tconst { conn, agent, env, digest, from } = ctx;\n\tconst send = async (frame: CoSignFrame): Promise<void> => {\n\t\ttry {\n\t\t\tawait withDeadline(conn.send(frame), ctx.deadlineMs);\n\t\t} catch (cause) {\n\t\t\tif (cause instanceof CoSignUnavailableError) throw cause; // the deadline (never retried)\n\t\t\tthrow new CoSignUnavailableError(\"co-sign connection dropped while sending\", {\n\t\t\t\tcause,\n\t\t\t\ttransient: true,\n\t\t\t});\n\t\t}\n\t};\n\tconst recv = async (): Promise<CoSignFrame> => {\n\t\tlet frame: CoSignFrame;\n\t\ttry {\n\t\t\tframe = await withDeadline(conn.recv(), ctx.deadlineMs);\n\t\t} catch (cause) {\n\t\t\tif (cause instanceof CoSignUnavailableError) throw cause; // the deadline (never retried)\n\t\t\tthrow new CoSignUnavailableError(\"co-sign connection dropped mid-ceremony\", {\n\t\t\t\tcause,\n\t\t\t\ttransient: true,\n\t\t\t});\n\t\t}\n\t\tif (frame.wire_version !== WIRE_VERSION) {\n\t\t\tthrow new CoSignUnavailableError(\n\t\t\t\t`co-signer spoke wire_version ${frame.wire_version}, expected ${WIRE_VERSION}`,\n\t\t\t);\n\t\t}\n\t\treturn frame;\n\t};\n\n\tawait send({\n\t\twire_version: WIRE_VERSION,\n\t\tkind: \"request\",\n\t\tsession_id: ctx.sessionId,\n\t\tceremony_id: env.ceremonyId,\n\t\tssid: env.ssid,\n\t\tintent: toWireIntent(env.intent),\n\t\tfreshness_ts: env.freshnessTs,\n\t\tfreshness_nonce: env.freshnessNonce,\n\t\tauth_tag: ctx.authTag,\n\t});\n\n\tlet first: Hex;\n\ttry {\n\t\tfirst = agent.start(digest);\n\t} catch (cause) {\n\t\t// A protocol-level failure, not a transport loss — restart-not-resume classifies\n\t\t// only the transient transport class as retry-safe (§4.7), so this never retries.\n\t\tthrow new CoSignUnavailableError(\"the agent failed to start the ceremony\", { cause });\n\t}\n\tawait send({ wire_version: WIRE_VERSION, kind: \"round\", payload: first });\n\n\tlet agentSig: { r: Hex; s: Hex; v: number } | null = null;\n\tfor (;;) {\n\t\tconst frame = await recv();\n\t\tswitch (frame.kind) {\n\t\t\tcase \"round\": {\n\t\t\t\t// 4c (M3) — the agent-side inbound bound, mirroring the backend's\n\t\t\t\t// MAX_FRAME_BYTES: an over-bound payload is refused BEFORE hex-decode and\n\t\t\t\t// before it crosses the WASM boundary.\n\t\t\t\tconst payloadBytes = (frame.payload.length - 2) / 2;\n\t\t\t\tif (payloadBytes > ctx.maxFrameBytes) {\n\t\t\t\t\tthrow new CoSignUnavailableError(\n\t\t\t\t\t\t`co-signer sent a ${payloadBytes}-byte round frame (bound ${ctx.maxFrameBytes}) — refused pre-decode`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tlet step: Mpc2pStepOutcome;\n\t\t\t\ttry {\n\t\t\t\t\tstep = agent.step(frame.payload);\n\t\t\t\t} catch (cause) {\n\t\t\t\t\t// Protocol abort (possibly a cheating peer) — never retried (§4.7).\n\t\t\t\t\tthrow new CoSignUnavailableError(\"the agent rejected a round frame\", { cause });\n\t\t\t\t}\n\t\t\t\tif (\"outbound\" in step) {\n\t\t\t\t\tawait send({ wire_version: WIRE_VERSION, kind: \"round\", payload: step.outbound });\n\t\t\t\t} else {\n\t\t\t\t\tagentSig = step.signature;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"result\": {\n\t\t\t\tconst signature = assembleSignature({ r: frame.r, s: frame.s, v: frame.v });\n\t\t\t\tif (agentSig === null) {\n\t\t\t\t\t// A ROUNDLESS result = the backend's idempotent replay (§4.7): a retry hit\n\t\t\t\t\t// a nonce whose ceremony already committed, and the CACHED signature came\n\t\t\t\t\t// back with zero rounds. There is no agent-derived signature to cross-check,\n\t\t\t\t\t// so the self-check is recovery: low-S + ecrecover(digest) == from (§4.4).\n\t\t\t\t\tawait verifyRecovers(digest, signature, frame.s, from, \"idempotent-replay\");\n\t\t\t\t\treturn { ok: true, signature, intent: env.intent };\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\tframe.r.toLowerCase() !== agentSig.r.toLowerCase() ||\n\t\t\t\t\tframe.s.toLowerCase() !== agentSig.s.toLowerCase() ||\n\t\t\t\t\tframe.v !== agentSig.v\n\t\t\t\t) {\n\t\t\t\t\tthrow new CoSignUnavailableError(\"the backend and agent derived different signatures\");\n\t\t\t\t}\n\t\t\t\t// The §4.4 ecrecover/low-S self-check: agent/backend agreement alone does not\n\t\t\t\t// prove the signature recovers to the group EOA over OUR digest — recovery does.\n\t\t\t\tawait verifyRecovers(digest, signature, frame.s, from, \"co-signed\");\n\t\t\t\treturn { ok: true, signature, intent: env.intent };\n\t\t\t}\n\t\t\tcase \"rejection\": {\n\t\t\t\t// A genuine policy denial → typed rejection. A non-policy \"rejection\"\n\t\t\t\t// (transient/internal/ban) is \"the owner did not decide\" → no-fallback throw,\n\t\t\t\t// and it is NOT the transient transport class (never retried — a ban or an\n\t\t\t\t// in-flight duplicate must surface, not silently re-spin).\n\t\t\t\tif (POLICY_REASONS.has(frame.reason as PolicyRejection[\"reason\"])) {\n\t\t\t\t\t// reason membership verified against POLICY_REASONS above.\n\t\t\t\t\treturn rejection(frame.reason as PolicyRejection[\"reason\"], frame.detail);\n\t\t\t\t}\n\t\t\t\tthrow new CoSignUnavailableError(\n\t\t\t\t\t`co-signer returned a non-policy rejection (${frame.reason}): ${frame.detail}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcase \"error\": {\n\t\t\t\tthrow new CoSignUnavailableError(`co-signer error: ${frame.message}`);\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\t// Unknown frame kind — never a silent success.\n\t\t\t\tthrow new CoSignUnavailableError(\"co-signer sent an unexpected frame\");\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Race `promise` against the ceremony deadline (W11: the timeout must fire before\n * `validBefore`). A deadline strike throws a NON-transient\n * {@link CoSignUnavailableError} — the retry budget is spent, never re-spun.\n */\nfunction withDeadline<T>(promise: Promise<T>, deadlineMs: number): Promise<T> {\n\tconst remaining = deadlineMs - Date.now();\n\tif (remaining <= 0) {\n\t\treturn Promise.reject(\n\t\t\tnew CoSignUnavailableError(\n\t\t\t\t\"co-sign ceremony timed out (the deadline fires before validBefore — W11)\",\n\t\t\t),\n\t\t);\n\t}\n\treturn new Promise<T>((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\treject(\n\t\t\t\tnew CoSignUnavailableError(\n\t\t\t\t\t\"co-sign ceremony timed out (the deadline fires before validBefore — W11)\",\n\t\t\t\t),\n\t\t\t);\n\t\t}, remaining);\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve(value);\n\t\t\t},\n\t\t\t(cause) => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\treject(cause);\n\t\t\t},\n\t\t);\n\t});\n}\n\n/** secp256k1 n/2 — the EIP-2 low-S bound. */\nconst SECP256K1_HALF_N = 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0n;\n\n/**\n * The RFC §4.4 result self-check: the signature is low-S (EIP-2) and recovers to\n * the group EOA over the adapter's OWN re-derived digest — the exact check the\n * token contract's `ecrecover` will repeat on-chain.\n */\nasync function verifyRecovers(\n\tdigest: Hex,\n\tsignature: Hex,\n\ts: Hex,\n\tfrom: Address,\n\twhat: string,\n): Promise<void> {\n\tif (BigInt(s) > SECP256K1_HALF_N) {\n\t\tthrow new CoSignUnavailableError(`the ${what} signature is not low-S (EIP-2)`);\n\t}\n\tlet recovered: Address;\n\ttry {\n\t\trecovered = await recoverAddress({ hash: digest, signature });\n\t} catch (cause) {\n\t\tthrow new CoSignUnavailableError(`the ${what} signature failed to recover`, { cause });\n\t}\n\tif (getAddress(recovered) !== from) {\n\t\tthrow new CoSignUnavailableError(\n\t\t\t`the ${what} signature recovers to ${getAddress(recovered)}, not the group EOA ${from}`,\n\t\t);\n\t}\n}\n\n/** Assemble a 65-byte EIP-3009 signature from the agent's `{ r, s, v }` (v = recovery_id + 27). */\nfunction assembleSignature(sig: { r: Hex; s: Hex; v: number }): Hex {\n\tconst yParity = sig.v - 27;\n\tif (yParity !== 0 && yParity !== 1) {\n\t\tthrow new CoSignUnavailableError(`malformed recovery id v=${sig.v} (expected 27 or 28)`);\n\t}\n\treturn serializeSignature({ r: sig.r, s: sig.s, yParity });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/signer/local.ts","../src/signer/mpc-2p-wire.ts","../src/signer/mpc-2p.ts"],"names":["getAddress"],"mappings":"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","file":"chunk-NY6VLXQB.js","sourcesContent":["/**\n * The `local` PolicyGatedSigner adapter — `enforcement: \"advisory\"`.\n *\n * Wraps a viem {@link Account} + a {@link SpendingPolicy} + a pinned EIP-712\n * domain. `sign(intent)` evaluates the policy SDK-side and, on pass, produces a\n * real EIP-3009 authorization via {@link signTransferWithAuthorization}. It is\n * **advisory** because the wrapped key can still sign anything elsewhere — the\n * gate is only reached if the caller chooses to call *this* `sign()`. Use it for\n * dev, the A1 cross-language fallback, and any flow that is explicitly not\n * bounded/regulated; the type-gate (`requireNonBypassable`) keeps it out of\n * flows that require non-bypassable enforcement.\n *\n * @packageDocumentation\n */\n\nimport type { Account } from \"viem\";\nimport { getAddress } from \"viem\";\nimport type { SpendingPolicy, SpendState } from \"../policy/spending-policy\";\nimport { evaluateSpendingPolicy } from \"../policy/spending-policy\";\nimport type { X402AssetParam } from \"../tokens/asset-domain\";\nimport { resolveAssetParam, resolvedAssetToEip3009Domain } from \"../tokens/asset-domain\";\nimport { signTransferWithAuthorization } from \"../tokens/eip3009\";\nimport { PolicyGatedSignerConfigError } from \"./errors\";\nimport type { PaymentIntent, PolicyGatedSigner, SignerDescription, SignResult } from \"./types\";\n\n/** Parameters for {@link createLocalPolicyGatedSigner}. */\nexport interface CreateLocalPolicyGatedSignerParams {\n\t/** EOA / LocalAccount that signs the EIP-3009 authorization. */\n\treadonly account: Account;\n\t/** The spending policy this signer enforces (SDK-side, advisory). */\n\treadonly policy: SpendingPolicy;\n\t/** Asset binding — pins the EIP-712 domain `name`/`version`/`verifyingContract`. */\n\treadonly asset: X402AssetParam;\n\t/**\n\t * Required literal acknowledgement that this signer is **advisory** (a\n\t * key-holder can bypass its policy). Omitting it is a compile error (TS) and\n\t * a construction-time throw (JS) — so constructing an advisory signer is a\n\t * conscious, greppable act. For bounded/regulated flows use a cryptographic\n\t * adapter instead.\n\t */\n\treadonly acknowledgeAdvisory: true;\n\t/**\n\t * Optional cumulative-spend view (read-only) the adapter evaluates\n\t * `cumulativeCap` against. `local` does not own an authoritative ledger; the\n\t * caller folds a successful spend back in (e.g. via `mergeSpendState`) before\n\t * the next call. Default: empty.\n\t */\n\treadonly spendState?: () => SpendState | Promise<SpendState>;\n}\n\n/**\n * Construct a `local` (advisory) PolicyGatedSigner.\n *\n * @example\n * ```ts\n * const signer = createLocalPolicyGatedSigner({\n * account,\n * policy: createSpendingPolicy({ session: { id, notAfter }, perToken: [{ token: JPYC, maxPerSign: 1_000n }], recipientAllowlist: \"any\" }),\n * asset: { kind: \"known\", id: \"jpyc-v2\" },\n * acknowledgeAdvisory: true,\n * });\n * const result = await signer.sign(intent);\n * ```\n */\nexport function createLocalPolicyGatedSigner(\n\tparams: CreateLocalPolicyGatedSignerParams,\n): PolicyGatedSigner<\"advisory\"> {\n\tif (params.acknowledgeAdvisory !== true) {\n\t\tthrow new PolicyGatedSignerConfigError(\n\t\t\t\"acknowledgeAdvisory\",\n\t\t\t\"a local signer is advisory (a key-holder can bypass its policy); pass `acknowledgeAdvisory: true` to construct one consciously, or use a cryptographic adapter for bounded/regulated flows\",\n\t\t);\n\t}\n\n\tconst { account, policy, spendState } = params;\n\tconst pinned = resolveAssetParam(params.asset);\n\tconst from = getAddress(account.address);\n\n\treturn {\n\t\tenforcement: \"advisory\",\n\t\tfrom,\n\t\tasync sign(intent: PaymentIntent): Promise<SignResult> {\n\t\t\tif (getAddress(intent.from) !== from) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\trejection: {\n\t\t\t\t\t\treason: \"from_mismatch\",\n\t\t\t\t\t\tdetail: `intent.from ${getAddress(intent.from)} does not equal signer.from ${from}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (getAddress(intent.token) !== pinned.verifyingContract) {\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\trejection: {\n\t\t\t\t\t\treason: \"token_not_allowed\",\n\t\t\t\t\t\tdetail: `intent.token ${getAddress(intent.token)} does not equal the signer's pinned verifyingContract ${pinned.verifyingContract}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst state: SpendState = (await spendState?.()) ?? { spentPerToken: [] };\n\t\t\tconst nowSeconds = BigInt(Math.floor(Date.now() / 1000));\n\t\t\tconst decision = evaluateSpendingPolicy(policy, intent, state, nowSeconds);\n\t\t\tif (!decision.ok) {\n\t\t\t\treturn { ok: false, rejection: decision.rejection };\n\t\t\t}\n\n\t\t\tconst signed = await signTransferWithAuthorization(\n\t\t\t\taccount,\n\t\t\t\tresolvedAssetToEip3009Domain(pinned, intent.chainId),\n\t\t\t\t{\n\t\t\t\t\tfrom,\n\t\t\t\t\tto: intent.to,\n\t\t\t\t\tvalue: intent.value,\n\t\t\t\t\tvalidAfter: intent.validAfter,\n\t\t\t\t\tvalidBefore: intent.validBefore,\n\t\t\t\t\tnonce: intent.nonce,\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { ok: true, signature: signed.signature, intent };\n\t\t},\n\t\tdescribe(): SignerDescription {\n\t\t\treturn {\n\t\t\t\tenforcement: \"advisory\",\n\t\t\t\tfrom,\n\t\t\t\tpolicyId: policy.session.id,\n\t\t\t\tnotAfter: policy.session.notAfter,\n\t\t\t\trevoked: policy.revoked,\n\t\t\t};\n\t\t},\n\t};\n}\n","/**\n * The `mpc-2p` cross-process **wire** (v2) — the versioned `CoSignFrame` control\n * envelope + the A3 canonical-request encoding, as TypeScript source-of-truth.\n *\n * This is the public, TS-interpreted half of the two-layer wire (RFC m6-3a §4.2):\n * a small, versioned envelope carries the decoded {@link PaymentIntent} (never a\n * digest — A4) + a per-ceremony id + ssid + a freshness element, plus the opaque\n * DKLs round payloads (hex, produced/consumed only by the WASM crypto agent). The\n * shapes here mirror the deployed `kawasekit-mpc-2p` Rust serde **byte-for-byte**\n * (snake_case `wire_version`, `session_id`, `ceremony_id`, `ssid`, `freshness_ts`,\n * `freshness_nonce`, `auth_tag`; the string-encoded {@link WireIntent}), so a frame\n * the SDK builds round-trips through the backend's `serde_json`.\n *\n * `canonicalRequestBytes` is the A3 analog of the EIP-712 digest corpus: it\n * reproduces the backend's `auth::canonical_request_bytes` exactly, so the injected\n * authenticator's HMAC equals what the co-signer verifies (RFC §4.6:\n * `HMAC_k(wireVersion ‖ ceremonyId ‖ ssid ‖ canonical(intent) ‖ freshness)`).\n *\n * @packageDocumentation\n */\n\nimport type { Hex } from \"viem\";\nimport type { PaymentIntent } from \"./types\";\n\n/** The wire protocol version. A frame whose `wire_version` differs is rejected. */\nexport const WIRE_VERSION = 2 as const;\n\n/**\n * The inbound round-payload bound, in bytes — mirrors the backend's\n * `crypto-core/src/transport.rs` `MAX_FRAME_BYTES` (8 MiB) so the agent side\n * enforces the same pre-decode cap the backend does (RFC m6-3a §4.2/M3). The\n * adapter refuses an over-bound `round` payload **before** hex-decoding it into\n * the WASM boundary; the private transport should additionally cap raw\n * websocket messages (e.g. the `ws` client's `maxPayload`).\n */\nexport const MAX_FRAME_BYTES = 8 * 1024 * 1024;\n\n/**\n * The string-encoded EIP-3009 intent as it crosses the wire — lowercase `0x`\n * addresses, **decimal** integer strings, `0x` hex nonce. Decoupled from any\n * library's native serde so a stdlib client reproduces it trivially; matches the\n * Rust `WireIntent` field names + encoding exactly.\n */\nexport interface WireIntent {\n\treadonly token: string;\n\treadonly chain_id: number;\n\treadonly from: string;\n\treadonly to: string;\n\treadonly value: string;\n\treadonly valid_after: string;\n\treadonly valid_before: string;\n\treadonly nonce: string;\n}\n\n/** Encode a decoded {@link PaymentIntent} for the wire. */\nexport function toWireIntent(intent: PaymentIntent): WireIntent {\n\treturn {\n\t\ttoken: intent.token.toLowerCase(),\n\t\tchain_id: intent.chainId,\n\t\tfrom: intent.from.toLowerCase(),\n\t\tto: intent.to.toLowerCase(),\n\t\tvalue: intent.value.toString(),\n\t\tvalid_after: intent.validAfter.toString(),\n\t\tvalid_before: intent.validBefore.toString(),\n\t\tnonce: intent.nonce.toLowerCase(),\n\t};\n}\n\n/**\n * The A3 request envelope the adapter generates per `sign()` and authenticates: a\n * per-ceremony id + ssid (bound into the A3 tag) + the intent + a freshness element\n * (a timestamp + a per-request nonce distinct from the EIP-3009 nonce).\n */\nexport interface CoSignRequestEnvelope {\n\treadonly ceremonyId: string;\n\treadonly ssid: string;\n\treadonly intent: PaymentIntent;\n\t/** Unix seconds; the backend checks it against a clock-skew window. */\n\treadonly freshnessTs: number;\n\t/** `0x` hex, distinct from the EIP-3009 nonce — the anti-replay key. */\n\treadonly freshnessNonce: Hex;\n}\n\n/**\n * A versioned control frame: the only cross-language content of the wire. The\n * DKLs round bytes ride opaque inside `payload`; everything else is the small,\n * pinned envelope. Internally tagged by `kind` (matches the Rust serde).\n */\nexport type CoSignFrame = { readonly wire_version: typeof WIRE_VERSION } & (\n\t| {\n\t\t\treadonly kind: \"request\";\n\t\t\treadonly session_id: string;\n\t\t\treadonly ceremony_id: string;\n\t\t\treadonly ssid: string;\n\t\t\treadonly intent: WireIntent;\n\t\t\treadonly freshness_ts: number;\n\t\t\treadonly freshness_nonce: Hex;\n\t\t\t/** A3 authenticator over {@link canonicalRequestBytes}, `0x`-hex. */\n\t\t\treadonly auth_tag: Hex;\n\t }\n\t| { readonly kind: \"round\"; readonly payload: Hex }\n\t| { readonly kind: \"result\"; readonly r: Hex; readonly s: Hex; readonly v: number }\n\t| { readonly kind: \"rejection\"; readonly reason: string; readonly detail: string }\n\t| { readonly kind: \"error\"; readonly message: string }\n);\n\n/**\n * The A3 canonical encoding of a request — a byte-exact mirror of the backend's\n * `auth::canonical_request_bytes` (domain-tag `kawasekit-mpc-2p/cosign-request/v3`,\n * one `key=value\\n` line per field: `wireVersion ‖ ceremonyId ‖ ssid ‖\n * canonical(intent) ‖ freshness`; lowercase `0x` addresses + freshness nonce,\n * decimal integers, trailing newline). The injected authenticator HMACs these\n * bytes; because the encoding is shared, the tag equals the co-signer's.\n */\nexport function canonicalRequestBytes(env: CoSignRequestEnvelope): Uint8Array {\n\tconst i = env.intent;\n\tconst lines = [\n\t\t\"kawasekit-mpc-2p/cosign-request/v3\",\n\t\t\"wireVersion=2\",\n\t\t`ceremonyId=${env.ceremonyId}`,\n\t\t`ssid=${env.ssid}`,\n\t\t`token=${i.token.toLowerCase()}`,\n\t\t`chainId=${i.chainId}`,\n\t\t`from=${i.from.toLowerCase()}`,\n\t\t`to=${i.to.toLowerCase()}`,\n\t\t`value=${i.value}`,\n\t\t`validAfter=${i.validAfter}`,\n\t\t`validBefore=${i.validBefore}`,\n\t\t`nonce=${i.nonce.toLowerCase()}`,\n\t\t`freshnessTs=${env.freshnessTs}`,\n\t\t`freshnessNonce=${env.freshnessNonce.toLowerCase()}`,\n\t];\n\treturn new TextEncoder().encode(lines.map((l) => `${l}\\n`).join(\"\"));\n}\n","/**\n * The `mpc-2p` PolicyGatedSigner adapter — `enforcement: \"cryptographic\"`.\n *\n * A 2-of-2 MPC co-signer: the agent holds ONE DKLs share, the owner backend holds\n * the other + the authoritative policy gate. No valid signature exists without a\n * **policy-passing co-sign**, so — unlike `local` — a single key-holder cannot\n * bypass the policy. That is what earns the non-bypassable `\"cryptographic\"` label\n * and lets the M6-0 type-gate (`requireNonBypassable`) reject an advisory signer at\n * compile time in bounded flows.\n *\n * **Open-core / thin adapter.** This module is pure orchestration over injected\n * interfaces — it ships in the public SDK with **no crypto, no socket, no key**:\n *\n * - {@link Mpc2pCoSignAgent} — the WASM DKLs share (the private package's compiled\n * `crypto-core`); drives the agent's half of the ceremony round-by-round.\n * - {@link CoSignTransport} — the authenticated, encrypted (wss/mTLS) frame channel\n * to the owner backend (the private package's transport).\n * - {@link CoSignRequestAuthenticator} — the A3 HMAC (the pre-shared key never\n * enters this module).\n *\n * The adapter owns the protocol: it pins the EIP-712 domain, **re-derives the\n * digest from the intent (A4)** via the shared `transferWithAuthorizationTypes`\n * source-of-truth, computes the A3 canonical bytes, frames the versioned\n * {@link CoSignFrame} envelope, pumps the ceremony, and maps the terminal frame to\n * a {@link SignResult}. Crucially it has **no local-signing path**: any\n * transport/availability failure throws {@link CoSignUnavailableError}, never an\n * `{ ok: true }` and never a {@link PolicyRejection} (the no-silent-fallback\n * guarantee, RFC m6-3a constraint 3 / W8).\n *\n * **Wire hardening (RFC §4.7/§4.8, Track C):** a bounded **transient-only retry**\n * replays the byte-identical intent (nonce included — idempotency-by-nonce is the\n * safety net) under a fresh A3 envelope and accepts the backend's **roundless\n * idempotent-replay result** via an ecrecover/low-S self-check; the **ceremony\n * deadline always fires before `validBefore`** (minus a clock-skew budget, W11),\n * and inbound round payloads are **bounded** before the WASM boundary (M3). Tuning\n * via {@link Mpc2pWireOptions}.\n *\n * @packageDocumentation\n */\n\nimport type { Address, Hex } from \"viem\";\nimport { getAddress, hashTypedData, recoverAddress, serializeSignature, toHex } from \"viem\";\nimport type { X402AssetParam } from \"../tokens/asset-domain\";\nimport { resolveAssetParam, resolvedAssetToEip3009Domain } from \"../tokens/asset-domain\";\nimport { transferWithAuthorizationTypes } from \"../tokens/eip3009\";\nimport { CoSignUnavailableError, PolicyGatedSignerConfigError } from \"./errors\";\nimport type { CoSignFrame, CoSignRequestEnvelope } from \"./mpc-2p-wire\";\nimport { canonicalRequestBytes, MAX_FRAME_BYTES, toWireIntent, WIRE_VERSION } from \"./mpc-2p-wire\";\nimport type {\n\tPaymentIntent,\n\tPolicyGatedSigner,\n\tPolicyRejection,\n\tSignerDescription,\n\tSignResult,\n} from \"./types\";\n\n/** The agent's half of the 2-of-2 ceremony (the injected WASM DKLs share). */\nexport interface Mpc2pCoSignAgent {\n\t/** The group 2-of-2 EOA this share controls — must equal the signer's `from`. */\n\tgroupEoa(): Address;\n\t/** Begin signing the 32-byte EIP-712 `digest`; returns the first outbound round (hex). */\n\tstart(digest: Hex): Hex;\n\t/** Feed one inbound round (hex); returns the next outbound round, or the final signature. */\n\tstep(incoming: Hex): Mpc2pStepOutcome;\n}\n\n/** The result of one {@link Mpc2pCoSignAgent.step}. */\nexport type Mpc2pStepOutcome =\n\t| { readonly outbound: Hex }\n\t| { readonly signature: { readonly r: Hex; readonly s: Hex; readonly v: number } };\n\n/** One authenticated, encrypted ceremony connection to the owner backend. */\nexport interface CoSignConnection {\n\t/** Send one control frame. */\n\tsend(frame: CoSignFrame): Promise<void>;\n\t/** Receive the next control frame (resolves per round). */\n\trecv(): Promise<CoSignFrame>;\n\t/** Release the connection (best-effort; errors here are ignored). */\n\tclose(): void | Promise<void>;\n}\n\n/** Opens a wss/mTLS {@link CoSignConnection} for a single co-sign ceremony. */\nexport interface CoSignTransport {\n\tconnect(): Promise<CoSignConnection>;\n}\n\n/** The A3 request authenticator — HMAC (or signature) over the canonical request bytes. */\nexport interface CoSignRequestAuthenticator {\n\t/** Authenticator tag over {@link canonicalRequestBytes}; the key stays inside. */\n\ttag(canonicalRequest: Uint8Array): Hex | Promise<Hex>;\n}\n\n/**\n * Wire liveness / retry tuning (RFC m6-3a §4.7 + §4.8). Every field is optional\n * with a safe default. The invariants these enforce:\n *\n * - **Retry (4b/H2):** only the **transient transport class** is retried (connect\n * failed / connection dropped), replaying the **byte-identical intent** — nonce\n * included, so the backend's idempotency-by-nonce is the safety net — under a\n * fresh A3 envelope (the freshness guard rejects a replayed envelope by design).\n * A delivered rejection, a ban/identifiable-abort, a protocol anomaly, or a\n * timeout is never retried.\n * - **Liveness (4c/W11/M1+M3):** the ceremony deadline always fires **before**\n * `intent.validBefore − clockSkewBudgetSecs` (a co-signature must never be born\n * expired), and a `sign()` whose remaining window is under\n * `minWindowSecs + clockSkewBudgetSecs` is refused up front.\n * - **Inbound bound (4c/M3):** a `round` payload over `maxFrameBytes` is refused\n * before it reaches the WASM boundary (mirrors the backend's bound).\n */\nexport interface Mpc2pWireOptions {\n\t/** Max ceremony attempts for transient transport failures (default 2 = one retry). */\n\treadonly maxAttempts?: number;\n\t/** Overall per-`sign()` budget in ms, across ALL attempts (default 30_000). */\n\treadonly ceremonyTimeoutMs?: number;\n\t/** Minimum remaining validity window required at `sign()` start, in seconds (default 30). */\n\treadonly minWindowSecs?: number;\n\t/** Max tolerated agent-host clock skew, in seconds (default 30). */\n\treadonly clockSkewBudgetSecs?: number;\n\t/** Inbound round-payload bound in bytes (default {@link MAX_FRAME_BYTES} = 8 MiB). */\n\treadonly maxFrameBytes?: number;\n}\n\n/** Parameters for {@link createMpc2pPolicyGatedSigner}. */\nexport interface Mpc2pSignerParams {\n\t/** The group 2-of-2 EOA; every `intent.from` must equal this (asserted vs the agent). */\n\treadonly from: Address;\n\t/** Asset binding — pins the EIP-712 domain `name`/`version`/`verifyingContract` (A4). */\n\treadonly asset: X402AssetParam;\n\t/** The bound policy session, for `describe()` (the backend holds the authoritative policy). */\n\treadonly session: { readonly id: string; readonly notAfter: bigint };\n\t/** The injected WASM DKLs share (private package). */\n\treadonly agent: Mpc2pCoSignAgent;\n\t/** The injected wss/mTLS frame channel (private package). */\n\treadonly transport: CoSignTransport;\n\t/** The injected A3 authenticator (the pre-shared key never enters the SDK). */\n\treadonly authenticator: CoSignRequestAuthenticator;\n\t/** Wire liveness / retry tuning (optional; safe defaults — {@link Mpc2pWireOptions}). */\n\treadonly wire?: Mpc2pWireOptions;\n}\n\n/**\n * The backend `rejection` reasons that are genuine **policy denials** (the owner\n * decided \"no\" — audit-meaningful → `{ ok: false, rejection }`). Any other reason\n * on a rejection frame is treated as \"the owner did not cleanly decide\" and throws\n * {@link CoSignUnavailableError} (no-silent-fallback; never a misclassified denial).\n */\nconst POLICY_REASONS: ReadonlySet<PolicyRejection[\"reason\"]> = new Set([\n\t\"revoked\",\n\t\"expired\",\n\t\"token_not_allowed\",\n\t\"recipient_not_allowed\",\n\t\"amount_exceeds_per_sign\",\n\t\"amount_exceeds_cumulative\",\n\t\"intent_digest_mismatch\",\n\t\"unauthenticated\",\n\t\"from_mismatch\",\n\t\"nonce_reuse_conflict\",\n]);\n\nfunction rejection(reason: PolicyRejection[\"reason\"], detail: string): SignResult {\n\treturn { ok: false, rejection: { reason, detail } };\n}\n\n/**\n * Construct an `mpc-2p` (cryptographic) PolicyGatedSigner over injected agent +\n * transport + authenticator.\n *\n * @example\n * ```ts\n * // The agent, transport and authenticator come from the private `kawasekit-mpc-2p`\n * // package (the WASM share, the wss/mTLS client, the HMAC key) — never bundled here.\n * const signer = createMpc2pPolicyGatedSigner({\n * from: groupEoa,\n * asset: { kind: \"known\", id: \"jpyc-v2\" },\n * session: { id: \"sess-1\", notAfter: 2_000_000_000n },\n * agent, transport, authenticator,\n * });\n * requireNonBypassable(signer); // ✓ \"cryptographic\" — passes the type-gate\n * const result = await signer.sign(intent);\n * ```\n */\nexport function createMpc2pPolicyGatedSigner(\n\tparams: Mpc2pSignerParams,\n): PolicyGatedSigner<\"cryptographic\"> {\n\tconst { agent, transport, authenticator, session } = params;\n\tconst pinned = resolveAssetParam(params.asset);\n\tconst from = getAddress(params.from);\n\tconst wire = params.wire ?? {};\n\tconst maxAttempts = Math.max(1, wire.maxAttempts ?? 2);\n\tconst ceremonyTimeoutMs = wire.ceremonyTimeoutMs ?? 30_000;\n\tconst minWindowSecs = wire.minWindowSecs ?? 30;\n\tconst clockSkewBudgetSecs = wire.clockSkewBudgetSecs ?? 30;\n\tconst maxFrameBytes = wire.maxFrameBytes ?? MAX_FRAME_BYTES;\n\n\t// The injected share MUST control the declared group EOA, or every signature\n\t// would recover to the wrong address.\n\tconst agentEoa = getAddress(agent.groupEoa());\n\tif (agentEoa !== from) {\n\t\tthrow new PolicyGatedSignerConfigError(\n\t\t\t\"from\",\n\t\t\t`the agent share controls ${agentEoa} but params.from is ${from}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tenforcement: \"cryptographic\",\n\t\tfrom,\n\t\tasync sign(intent: PaymentIntent): Promise<SignResult> {\n\t\t\t// Adapter-local pre-checks (cheap; no wire needed). Same shape as `local`.\n\t\t\tif (getAddress(intent.from) !== from) {\n\t\t\t\treturn rejection(\n\t\t\t\t\t\"from_mismatch\",\n\t\t\t\t\t`intent.from ${getAddress(intent.from)} does not equal signer.from ${from}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (getAddress(intent.token) !== pinned.verifyingContract) {\n\t\t\t\treturn rejection(\n\t\t\t\t\t\"token_not_allowed\",\n\t\t\t\t\t`intent.token ${getAddress(intent.token)} does not equal the signer's pinned verifyingContract ${pinned.verifyingContract}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// A4: re-derive the EIP-712 digest from the intent + the pinned domain (the\n\t\t\t// SDK's exported types are the cross-language source of truth). The agent and\n\t\t\t// the backend each sign THIS digest; the SoT keeps them byte-identical.\n\t\t\tconst digest = hashTypedData({\n\t\t\t\tdomain: resolvedAssetToEip3009Domain(pinned, intent.chainId),\n\t\t\t\ttypes: transferWithAuthorizationTypes,\n\t\t\t\tprimaryType: \"TransferWithAuthorization\",\n\t\t\t\tmessage: {\n\t\t\t\t\tfrom,\n\t\t\t\t\tto: intent.to,\n\t\t\t\t\tvalue: intent.value,\n\t\t\t\t\tvalidAfter: intent.validAfter,\n\t\t\t\t\tvalidBefore: intent.validBefore,\n\t\t\t\t\tnonce: intent.nonce,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// 4c (W11/M1+M3) — the ceremony-liveness minimum-window invariant: the rounds +\n\t\t\t// the bounded retry budget must all complete BEFORE validBefore, and validity is\n\t\t\t// judged against chain time, so the agent host's clock-skew budget is part of the\n\t\t\t// bar. A window already too small risks a born-expired signature (wasted\n\t\t\t// facilitator gas + retry/cap pressure); the owner has not decided anything, so\n\t\t\t// this throws (never a rejection).\n\t\t\tconst validBeforeSecs = Number(intent.validBefore);\n\t\t\tconst nowSecs = Math.floor(Date.now() / 1000);\n\t\t\tif (validBeforeSecs - nowSecs < minWindowSecs + clockSkewBudgetSecs) {\n\t\t\t\tthrow new CoSignUnavailableError(\n\t\t\t\t\t`intent.validBefore (${validBeforeSecs}) leaves under the minimum ceremony window (${minWindowSecs}s + ${clockSkewBudgetSecs}s clock-skew budget) — refusing a co-sign that risks being born expired (W11)`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// One deadline for the whole sign() — every attempt included — capped so the\n\t\t\t// ceremony timeout always fires before validBefore (minus the clock-skew budget).\n\t\t\tconst deadlineMs = Math.min(\n\t\t\t\tDate.now() + ceremonyTimeoutMs,\n\t\t\t\t(validBeforeSecs - clockSkewBudgetSecs) * 1000,\n\t\t\t);\n\n\t\t\t// 4b (RFC §4.7/H2) — bounded transient-only retry. Every attempt re-presents the\n\t\t\t// *byte-identical* `intent` (nonce included — the backend's idempotency-by-nonce\n\t\t\t// + atomic SpendState are the safety net) under a FRESH A3 envelope: the\n\t\t\t// ceremonyId/ssid/freshness are per-attempt BY DESIGN (the backend's freshness\n\t\t\t// guard rejects a replayed envelope, while idempotency keys on the unchanged\n\t\t\t// EIP-3009 nonce). Only the transient transport class retries — never a\n\t\t\t// delivered rejection, never a ban/identifiable-abort, never a timeout.\n\t\t\tlet lastTransient: CoSignUnavailableError | undefined;\n\t\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt += 1) {\n\t\t\t\tif (Date.now() >= deadlineMs) break;\n\t\t\t\t// A3 (v2): a fresh per-ceremony id + ssid + freshness (a timestamp + a\n\t\t\t\t// per-request nonce, distinct from the EIP-3009 nonce), authenticated over the\n\t\t\t\t// canonical bytes (the shared SoT; the HMAC key lives inside the injected\n\t\t\t\t// authenticator). Web Crypto is isomorphic (Node 19+ / browsers).\n\t\t\t\tconst env: CoSignRequestEnvelope = {\n\t\t\t\t\tceremonyId: globalThis.crypto.randomUUID(),\n\t\t\t\t\tssid: globalThis.crypto.randomUUID(),\n\t\t\t\t\tintent,\n\t\t\t\t\tfreshnessTs: Math.floor(Date.now() / 1000),\n\t\t\t\t\tfreshnessNonce: toHex(globalThis.crypto.getRandomValues(new Uint8Array(16))),\n\t\t\t\t};\n\t\t\t\tconst authTag = await authenticator.tag(canonicalRequestBytes(env));\n\t\t\t\ttry {\n\t\t\t\t\tconst conn = await openConnection(transport);\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn await runCeremony({\n\t\t\t\t\t\t\tconn,\n\t\t\t\t\t\t\tagent,\n\t\t\t\t\t\t\tsessionId: session.id,\n\t\t\t\t\t\t\tenv,\n\t\t\t\t\t\t\tdigest,\n\t\t\t\t\t\t\tauthTag,\n\t\t\t\t\t\t\tfrom,\n\t\t\t\t\t\t\tdeadlineMs,\n\t\t\t\t\t\t\tmaxFrameBytes,\n\t\t\t\t\t\t});\n\t\t\t\t\t} finally {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait conn.close();\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// best-effort cleanup; a close error must not mask the result/throw.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof CoSignUnavailableError && error.transient && attempt < maxAttempts) {\n\t\t\t\t\t\tlastTransient = error;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow (\n\t\t\t\tlastTransient ??\n\t\t\t\tnew CoSignUnavailableError(\n\t\t\t\t\t\"co-sign ceremony budget exhausted before validBefore (W11) — no attempt could start\",\n\t\t\t\t)\n\t\t\t);\n\t\t},\n\t\tdescribe(): SignerDescription {\n\t\t\treturn {\n\t\t\t\tenforcement: \"cryptographic\",\n\t\t\t\tfrom,\n\t\t\t\tpolicyId: session.id,\n\t\t\t\tnotAfter: session.notAfter,\n\t\t\t\t// The backend owns authoritative revocation (a revoked session → a\n\t\t\t\t// rejection at sign time); this metadata field is best-effort.\n\t\t\t\trevoked: false,\n\t\t\t};\n\t\t},\n\t};\n}\n\nasync function openConnection(transport: CoSignTransport): Promise<CoSignConnection> {\n\ttry {\n\t\treturn await transport.connect();\n\t} catch (cause) {\n\t\t// Connect failure = the transient transport class (the one retried, §4.7).\n\t\tthrow new CoSignUnavailableError(\"co-signer connection failed\", { cause, transient: true });\n\t}\n}\n\n/** Everything one ceremony attempt needs (one connection, one A3 envelope). */\ninterface CeremonyContext {\n\treadonly conn: CoSignConnection;\n\treadonly agent: Mpc2pCoSignAgent;\n\treadonly sessionId: string;\n\treadonly env: CoSignRequestEnvelope;\n\treadonly digest: Hex;\n\treadonly authTag: Hex;\n\t/** The group EOA every result must recover to (the §4.4 self-check). */\n\treadonly from: Address;\n\t/** Absolute epoch-ms the ceremony must finish by (fires before validBefore — W11). */\n\treadonly deadlineMs: number;\n\t/** Inbound round-payload bound in bytes (mirrors the backend's MAX_FRAME_BYTES). */\n\treadonly maxFrameBytes: number;\n}\n\n/**\n * Drive one full ceremony attempt over `ctx.conn`: send the authenticated request +\n * the agent's first round, then pump rounds until a terminal frame. Returns a\n * {@link SignResult} (success or a policy denial) or throws\n * {@link CoSignUnavailableError}. There is no path that returns `{ ok: true }`\n * without a backend `result` frame that passes the ecrecover/low-S self-check.\n */\nasync function runCeremony(ctx: CeremonyContext): Promise<SignResult> {\n\tconst { conn, agent, env, digest, from } = ctx;\n\tconst send = async (frame: CoSignFrame): Promise<void> => {\n\t\ttry {\n\t\t\tawait withDeadline(conn.send(frame), ctx.deadlineMs);\n\t\t} catch (cause) {\n\t\t\tif (cause instanceof CoSignUnavailableError) throw cause; // the deadline (never retried)\n\t\t\tthrow new CoSignUnavailableError(\"co-sign connection dropped while sending\", {\n\t\t\t\tcause,\n\t\t\t\ttransient: true,\n\t\t\t});\n\t\t}\n\t};\n\tconst recv = async (): Promise<CoSignFrame> => {\n\t\tlet frame: CoSignFrame;\n\t\ttry {\n\t\t\tframe = await withDeadline(conn.recv(), ctx.deadlineMs);\n\t\t} catch (cause) {\n\t\t\tif (cause instanceof CoSignUnavailableError) throw cause; // the deadline (never retried)\n\t\t\tthrow new CoSignUnavailableError(\"co-sign connection dropped mid-ceremony\", {\n\t\t\t\tcause,\n\t\t\t\ttransient: true,\n\t\t\t});\n\t\t}\n\t\tif (frame.wire_version !== WIRE_VERSION) {\n\t\t\tthrow new CoSignUnavailableError(\n\t\t\t\t`co-signer spoke wire_version ${frame.wire_version}, expected ${WIRE_VERSION}`,\n\t\t\t);\n\t\t}\n\t\treturn frame;\n\t};\n\n\tawait send({\n\t\twire_version: WIRE_VERSION,\n\t\tkind: \"request\",\n\t\tsession_id: ctx.sessionId,\n\t\tceremony_id: env.ceremonyId,\n\t\tssid: env.ssid,\n\t\tintent: toWireIntent(env.intent),\n\t\tfreshness_ts: env.freshnessTs,\n\t\tfreshness_nonce: env.freshnessNonce,\n\t\tauth_tag: ctx.authTag,\n\t});\n\n\tlet first: Hex;\n\ttry {\n\t\tfirst = agent.start(digest);\n\t} catch (cause) {\n\t\t// A protocol-level failure, not a transport loss — restart-not-resume classifies\n\t\t// only the transient transport class as retry-safe (§4.7), so this never retries.\n\t\tthrow new CoSignUnavailableError(\"the agent failed to start the ceremony\", { cause });\n\t}\n\tawait send({ wire_version: WIRE_VERSION, kind: \"round\", payload: first });\n\n\tlet agentSig: { r: Hex; s: Hex; v: number } | null = null;\n\tfor (;;) {\n\t\tconst frame = await recv();\n\t\tswitch (frame.kind) {\n\t\t\tcase \"round\": {\n\t\t\t\t// 4c (M3) — the agent-side inbound bound, mirroring the backend's\n\t\t\t\t// MAX_FRAME_BYTES: an over-bound payload is refused BEFORE hex-decode and\n\t\t\t\t// before it crosses the WASM boundary.\n\t\t\t\tconst payloadBytes = (frame.payload.length - 2) / 2;\n\t\t\t\tif (payloadBytes > ctx.maxFrameBytes) {\n\t\t\t\t\tthrow new CoSignUnavailableError(\n\t\t\t\t\t\t`co-signer sent a ${payloadBytes}-byte round frame (bound ${ctx.maxFrameBytes}) — refused pre-decode`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tlet step: Mpc2pStepOutcome;\n\t\t\t\ttry {\n\t\t\t\t\tstep = agent.step(frame.payload);\n\t\t\t\t} catch (cause) {\n\t\t\t\t\t// Protocol abort (possibly a cheating peer) — never retried (§4.7).\n\t\t\t\t\tthrow new CoSignUnavailableError(\"the agent rejected a round frame\", { cause });\n\t\t\t\t}\n\t\t\t\tif (\"outbound\" in step) {\n\t\t\t\t\tawait send({ wire_version: WIRE_VERSION, kind: \"round\", payload: step.outbound });\n\t\t\t\t} else {\n\t\t\t\t\tagentSig = step.signature;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"result\": {\n\t\t\t\tconst signature = assembleSignature({ r: frame.r, s: frame.s, v: frame.v });\n\t\t\t\tif (agentSig === null) {\n\t\t\t\t\t// A ROUNDLESS result = the backend's idempotent replay (§4.7): a retry hit\n\t\t\t\t\t// a nonce whose ceremony already committed, and the CACHED signature came\n\t\t\t\t\t// back with zero rounds. There is no agent-derived signature to cross-check,\n\t\t\t\t\t// so the self-check is recovery: low-S + ecrecover(digest) == from (§4.4).\n\t\t\t\t\tawait verifyRecovers(digest, signature, frame.s, from, \"idempotent-replay\");\n\t\t\t\t\treturn { ok: true, signature, intent: env.intent };\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\tframe.r.toLowerCase() !== agentSig.r.toLowerCase() ||\n\t\t\t\t\tframe.s.toLowerCase() !== agentSig.s.toLowerCase() ||\n\t\t\t\t\tframe.v !== agentSig.v\n\t\t\t\t) {\n\t\t\t\t\tthrow new CoSignUnavailableError(\"the backend and agent derived different signatures\");\n\t\t\t\t}\n\t\t\t\t// The §4.4 ecrecover/low-S self-check: agent/backend agreement alone does not\n\t\t\t\t// prove the signature recovers to the group EOA over OUR digest — recovery does.\n\t\t\t\tawait verifyRecovers(digest, signature, frame.s, from, \"co-signed\");\n\t\t\t\treturn { ok: true, signature, intent: env.intent };\n\t\t\t}\n\t\t\tcase \"rejection\": {\n\t\t\t\t// A genuine policy denial → typed rejection. A non-policy \"rejection\"\n\t\t\t\t// (transient/internal/ban) is \"the owner did not decide\" → no-fallback throw,\n\t\t\t\t// and it is NOT the transient transport class (never retried — a ban or an\n\t\t\t\t// in-flight duplicate must surface, not silently re-spin).\n\t\t\t\tif (POLICY_REASONS.has(frame.reason as PolicyRejection[\"reason\"])) {\n\t\t\t\t\t// reason membership verified against POLICY_REASONS above.\n\t\t\t\t\treturn rejection(frame.reason as PolicyRejection[\"reason\"], frame.detail);\n\t\t\t\t}\n\t\t\t\tthrow new CoSignUnavailableError(\n\t\t\t\t\t`co-signer returned a non-policy rejection (${frame.reason}): ${frame.detail}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcase \"error\": {\n\t\t\t\tthrow new CoSignUnavailableError(`co-signer error: ${frame.message}`);\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\t// Unknown frame kind — never a silent success.\n\t\t\t\tthrow new CoSignUnavailableError(\"co-signer sent an unexpected frame\");\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Race `promise` against the ceremony deadline (W11: the timeout must fire before\n * `validBefore`). A deadline strike throws a NON-transient\n * {@link CoSignUnavailableError} — the retry budget is spent, never re-spun.\n */\nfunction withDeadline<T>(promise: Promise<T>, deadlineMs: number): Promise<T> {\n\tconst remaining = deadlineMs - Date.now();\n\tif (remaining <= 0) {\n\t\treturn Promise.reject(\n\t\t\tnew CoSignUnavailableError(\n\t\t\t\t\"co-sign ceremony timed out (the deadline fires before validBefore — W11)\",\n\t\t\t),\n\t\t);\n\t}\n\treturn new Promise<T>((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\treject(\n\t\t\t\tnew CoSignUnavailableError(\n\t\t\t\t\t\"co-sign ceremony timed out (the deadline fires before validBefore — W11)\",\n\t\t\t\t),\n\t\t\t);\n\t\t}, remaining);\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve(value);\n\t\t\t},\n\t\t\t(cause) => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\treject(cause);\n\t\t\t},\n\t\t);\n\t});\n}\n\n/** secp256k1 n/2 — the EIP-2 low-S bound. */\nconst SECP256K1_HALF_N = 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0n;\n\n/**\n * The RFC §4.4 result self-check: the signature is low-S (EIP-2) and recovers to\n * the group EOA over the adapter's OWN re-derived digest — the exact check the\n * token contract's `ecrecover` will repeat on-chain.\n */\nasync function verifyRecovers(\n\tdigest: Hex,\n\tsignature: Hex,\n\ts: Hex,\n\tfrom: Address,\n\twhat: string,\n): Promise<void> {\n\tif (BigInt(s) > SECP256K1_HALF_N) {\n\t\tthrow new CoSignUnavailableError(`the ${what} signature is not low-S (EIP-2)`);\n\t}\n\tlet recovered: Address;\n\ttry {\n\t\trecovered = await recoverAddress({ hash: digest, signature });\n\t} catch (cause) {\n\t\tthrow new CoSignUnavailableError(`the ${what} signature failed to recover`, { cause });\n\t}\n\tif (getAddress(recovered) !== from) {\n\t\tthrow new CoSignUnavailableError(\n\t\t\t`the ${what} signature recovers to ${getAddress(recovered)}, not the group EOA ${from}`,\n\t\t);\n\t}\n}\n\n/** Assemble a 65-byte EIP-3009 signature from the agent's `{ r, s, v }` (v = recovery_id + 27). */\nfunction assembleSignature(sig: { r: Hex; s: Hex; v: number }): Hex {\n\tconst yParity = sig.v - 27;\n\tif (yParity !== 0 && yParity !== 1) {\n\t\tthrow new CoSignUnavailableError(`malformed recovery id v=${sig.v} (expected 27 or 28)`);\n\t}\n\treturn serializeSignature({ r: sig.r, s: sig.s, yParity });\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { normalizeRecipientAllowlist } from './chunk-7YKRKV6D.js';
|
|
2
|
+
import { jpycAbi } from './chunk-KT7XDT2T.js';
|
|
3
|
+
import { CallPolicyVersion, toCallPolicy, ParamCondition, toRateLimitPolicy } from '@zerodev/permissions/policies';
|
|
4
|
+
|
|
5
|
+
function buildJpycTransferCallPolicy(params) {
|
|
6
|
+
if (params.maxPerTransfer <= 0n) {
|
|
7
|
+
throw new Error(`maxPerTransfer must be positive, got ${params.maxPerTransfer}.`);
|
|
8
|
+
}
|
|
9
|
+
const recipients = params.recipientAllowlist === void 0 || params.recipientAllowlist === "any" ? null : normalizeRecipientAllowlist(params.recipientAllowlist);
|
|
10
|
+
if (recipients !== null && recipients.length === 0) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
'recipientAllowlist must not be empty \u2014 omit it or pass "any" to allow any recipient.'
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
if (recipients !== null && params.callPolicyVersion === CallPolicyVersion.V0_0_1) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"recipientAllowlist requires callPolicyVersion V0_0_2 or later (the ONE_OF condition is unsupported on V0_0_1)."
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return toCallPolicy({
|
|
21
|
+
policyVersion: params.callPolicyVersion ?? CallPolicyVersion.V0_0_4,
|
|
22
|
+
permissions: [
|
|
23
|
+
{
|
|
24
|
+
target: params.jpycAddress,
|
|
25
|
+
abi: jpycAbi,
|
|
26
|
+
functionName: "transfer",
|
|
27
|
+
args: [
|
|
28
|
+
// Recipient (`to`): unrestricted (`null`) unless an allowlist resolved
|
|
29
|
+
// above, in which case it is constrained via the ONE_OF condition.
|
|
30
|
+
recipients === null ? null : { condition: ParamCondition.ONE_OF, value: recipients },
|
|
31
|
+
// value: must be ≤ maxPerTransfer.
|
|
32
|
+
{
|
|
33
|
+
condition: ParamCondition.LESS_THAN_OR_EQUAL,
|
|
34
|
+
value: params.maxPerTransfer
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/policy/daily-limit.ts
|
|
43
|
+
var ONE_DAY_SECONDS = 86400;
|
|
44
|
+
function createJpycDailyLimitPolicies(params) {
|
|
45
|
+
if (!Number.isInteger(params.maxTransfersPerDay) || params.maxTransfersPerDay < 1) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`createJpycDailyLimitPolicies: maxTransfersPerDay must be a positive integer, got ${params.maxTransfersPerDay}.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const callPolicy = buildJpycTransferCallPolicy({
|
|
51
|
+
jpycAddress: params.jpycAddress,
|
|
52
|
+
maxPerTransfer: params.maxPerTransfer,
|
|
53
|
+
recipientAllowlist: params.recipientAllowlist,
|
|
54
|
+
callPolicyVersion: params.callPolicyVersion
|
|
55
|
+
});
|
|
56
|
+
const rateLimitPolicy = toRateLimitPolicy({
|
|
57
|
+
interval: ONE_DAY_SECONDS,
|
|
58
|
+
count: params.maxTransfersPerDay
|
|
59
|
+
});
|
|
60
|
+
return [callPolicy, rateLimitPolicy];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { ONE_DAY_SECONDS, buildJpycTransferCallPolicy, createJpycDailyLimitPolicies };
|
|
64
|
+
//# sourceMappingURL=chunk-UIDDRHLW.js.map
|
|
65
|
+
//# sourceMappingURL=chunk-UIDDRHLW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/policy/jpyc-call-policy.ts","../src/policy/daily-limit.ts"],"names":[],"mappings":";;;;AA0CO,SAAS,4BAA4B,MAAA,EAA8C;AACzF,EAAA,IAAI,MAAA,CAAO,kBAAkB,EAAA,EAAI;AAChC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,qCAAA,EAAwC,MAAA,CAAO,cAAc,CAAA,CAAA,CAAG,CAAA;AAAA,EACjF;AAKA,EAAA,MAAM,UAAA,GACL,MAAA,CAAO,kBAAA,KAAuB,MAAA,IAAa,MAAA,CAAO,uBAAuB,KAAA,GACtE,IAAA,GACA,2BAAA,CAA4B,MAAA,CAAO,kBAAkB,CAAA;AACzD,EAAA,IAAI,UAAA,KAAe,IAAA,IAAQ,UAAA,CAAW,MAAA,KAAW,CAAA,EAAG;AACnD,IAAA,MAAM,IAAI,KAAA;AAAA,MACT;AAAA,KACD;AAAA,EACD;AAIA,EAAA,IAAI,UAAA,KAAe,IAAA,IAAQ,MAAA,CAAO,iBAAA,KAAsB,kBAAkB,MAAA,EAAQ;AACjF,IAAA,MAAM,IAAI,KAAA;AAAA,MACT;AAAA,KACD;AAAA,EACD;AAEA,EAAA,OAAO,YAAA,CAAa;AAAA,IACnB,aAAA,EAAe,MAAA,CAAO,iBAAA,IAAqB,iBAAA,CAAkB,MAAA;AAAA,IAC7D,WAAA,EAAa;AAAA,MACZ;AAAA,QACC,QAAQ,MAAA,CAAO,WAAA;AAAA,QACf,GAAA,EAAK,OAAA;AAAA,QACL,YAAA,EAAc,UAAA;AAAA,QACd,IAAA,EAAM;AAAA;AAAA;AAAA,UAGL,UAAA,KAAe,OAAO,IAAA,GAAO,EAAE,WAAW,cAAA,CAAe,MAAA,EAAQ,OAAO,UAAA,EAAW;AAAA;AAAA,UAEnF;AAAA,YACC,WAAW,cAAA,CAAe,kBAAA;AAAA,YAC1B,OAAO,MAAA,CAAO;AAAA;AACf;AACD;AACD;AACD,GACA,CAAA;AACF;;;AC7DO,IAAM,eAAA,GAAkB;AA2ExB,SAAS,6BACf,MAAA,EAC4B;AAC5B,EAAA,IAAI,CAAC,OAAO,SAAA,CAAU,MAAA,CAAO,kBAAkB,CAAA,IAAK,MAAA,CAAO,qBAAqB,CAAA,EAAG;AAClF,IAAA,MAAM,IAAI,KAAA;AAAA,MACT,CAAA,iFAAA,EAAoF,OAAO,kBAAkB,CAAA,CAAA;AAAA,KAC9G;AAAA,EACD;AAIA,EAAA,MAAM,aAAa,2BAAA,CAA4B;AAAA,IAC9C,aAAa,MAAA,CAAO,WAAA;AAAA,IACpB,gBAAgB,MAAA,CAAO,cAAA;AAAA,IACvB,oBAAoB,MAAA,CAAO,kBAAA;AAAA,IAC3B,mBAAmB,MAAA,CAAO;AAAA,GAC1B,CAAA;AAED,EAAA,MAAM,kBAAkB,iBAAA,CAAkB;AAAA,IACzC,QAAA,EAAU,eAAA;AAAA,IACV,OAAO,MAAA,CAAO;AAAA,GACd,CAAA;AAED,EAAA,OAAO,CAAC,YAAY,eAAe,CAAA;AACpC","file":"chunk-UIDDRHLW.js","sourcesContent":["/**\n * Shared builder for the JPYC `transfer` callPolicy — the on-chain rule that\n * locks a ZeroDev session key to `JPYC.transfer(to, value)` with\n * `value ≤ maxPerTransfer` and an optional recipient allowlist (condition\n * `ONE_OF`). Used by BOTH {@link createJpycDailyLimitPolicies} (`./daily-limit`)\n * and {@link createBuyListPolicies} (`./buy-list`) so the recipient/amount\n * constraint is built identically; each caller composes its own rate-limit /\n * timestamp policy on top.\n *\n * @packageDocumentation\n */\n\nimport type { Policy } from \"@zerodev/permissions\";\nimport { CallPolicyVersion, ParamCondition, toCallPolicy } from \"@zerodev/permissions/policies\";\nimport type { Address } from \"viem\";\nimport { jpycAbi } from \"../tokens/jpyc\";\nimport { normalizeRecipientAllowlist } from \"./normalize-allowlist\";\n\n/** Parameters for {@link buildJpycTransferCallPolicy}. */\nexport interface JpycTransferCallPolicyParams {\n\t/** JPYC contract address on the target chain. */\n\treadonly jpycAddress: Address;\n\t/** Maximum JPYC (raw units) per single `transfer`. Must be positive. */\n\treadonly maxPerTransfer: bigint;\n\t/**\n\t * Recipient restriction. An address list constrains `to` to those recipients\n\t * (`ONE_OF`); `\"any\"` (or `undefined`) leaves it unrestricted. Entries are\n\t * checksum-normalized + de-duplicated ({@link normalizeRecipientAllowlist}).\n\t * An **empty** array throws (an on-chain allowlist cannot encode \"match\n\t * nothing\"). An address list requires `callPolicyVersion` ≥ `V0_0_2`.\n\t */\n\treadonly recipientAllowlist?: readonly Address[] | \"any\" | undefined;\n\t/** ZeroDev callPolicy on-chain version. Defaults to V0_0_4. */\n\treadonly callPolicyVersion?: CallPolicyVersion | undefined;\n}\n\n/**\n * Build the single ZeroDev callPolicy for a JPYC `transfer`, enforcing the\n * per-transfer amount cap and (optionally) the recipient allowlist. Throws on a\n * non-positive `maxPerTransfer`, an empty allowlist, or an allowlist combined\n * with `callPolicyVersion` `V0_0_1` (which lacks `ONE_OF`).\n */\nexport function buildJpycTransferCallPolicy(params: JpycTransferCallPolicyParams): Policy {\n\tif (params.maxPerTransfer <= 0n) {\n\t\tthrow new Error(`maxPerTransfer must be positive, got ${params.maxPerTransfer}.`);\n\t}\n\t// Resolve the recipient constraint up-front. Omitted or \"any\" => unrestricted\n\t// (a `null` arg); an address list => checksum-normalized + de-duped (shared\n\t// with the off-chain SpendingPolicy). Unlike off-chain, an empty list is a\n\t// caller bug here — an on-chain allowlist cannot encode \"deny all\".\n\tconst recipients =\n\t\tparams.recipientAllowlist === undefined || params.recipientAllowlist === \"any\"\n\t\t\t? null\n\t\t\t: normalizeRecipientAllowlist(params.recipientAllowlist);\n\tif (recipients !== null && recipients.length === 0) {\n\t\tthrow new Error(\n\t\t\t'recipientAllowlist must not be empty — omit it or pass \"any\" to allow any recipient.',\n\t\t);\n\t}\n\t// The ONE_OF condition (used to encode an address list) is only supported by\n\t// the V0_0_2+ CallPolicy contract; catch the incompatible combination here\n\t// with a typed message rather than a bare error from deep inside toCallPolicy.\n\tif (recipients !== null && params.callPolicyVersion === CallPolicyVersion.V0_0_1) {\n\t\tthrow new Error(\n\t\t\t\"recipientAllowlist requires callPolicyVersion V0_0_2 or later (the ONE_OF condition is unsupported on V0_0_1).\",\n\t\t);\n\t}\n\n\treturn toCallPolicy({\n\t\tpolicyVersion: params.callPolicyVersion ?? CallPolicyVersion.V0_0_4,\n\t\tpermissions: [\n\t\t\t{\n\t\t\t\ttarget: params.jpycAddress,\n\t\t\t\tabi: jpycAbi,\n\t\t\t\tfunctionName: \"transfer\",\n\t\t\t\targs: [\n\t\t\t\t\t// Recipient (`to`): unrestricted (`null`) unless an allowlist resolved\n\t\t\t\t\t// above, in which case it is constrained via the ONE_OF condition.\n\t\t\t\t\trecipients === null ? null : { condition: ParamCondition.ONE_OF, value: recipients },\n\t\t\t\t\t// value: must be ≤ maxPerTransfer.\n\t\t\t\t\t{\n\t\t\t\t\t\tcondition: ParamCondition.LESS_THAN_OR_EQUAL,\n\t\t\t\t\t\tvalue: params.maxPerTransfer,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n}\n","/**\n * Daily-limit spending policy for JPYC, built on ZeroDev's Permission System.\n *\n * Composes two ZeroDev policies:\n * 1. **callPolicy** — locks the session key to `JPYC.transfer(to, value)`\n * with `value ≤ maxPerTransfer`, and — when {@link\n * CreateJpycDailyLimitPoliciesParams.recipientAllowlist} is provided —\n * restricts `to` to the allowlisted recipients (otherwise any recipient is\n * allowed). This is the on-chain enforcement behind the \"pay only registered\n * merchants\" model: a buy-list resolves to its merchants' addresses, which\n * are baked into a disposable session key here.\n * 2. **rateLimitPolicy** — caps userOp count to `maxTransfersPerDay` in any\n * 24-hour rolling window.\n *\n * Effective daily cap = `maxPerTransfer × maxTransfersPerDay`. This is not a\n * cumulative-amount tracker (ZeroDev doesn't ship one), but the agent cannot\n * exceed either dimension, so the spirit of \"daily limit\" holds.\n *\n * @packageDocumentation\n */\n\nimport type { Policy } from \"@zerodev/permissions\";\nimport { type CallPolicyVersion, toRateLimitPolicy } from \"@zerodev/permissions/policies\";\nimport type { Address } from \"viem\";\nimport { buildJpycTransferCallPolicy } from \"./jpyc-call-policy\";\n\n/** One day in seconds — the period for {@link createJpycDailyLimitPolicies}. */\nexport const ONE_DAY_SECONDS = 86_400;\n\n/** Parameters for {@link createJpycDailyLimitPolicies}. */\nexport interface CreateJpycDailyLimitPoliciesParams {\n\t/** JPYC contract address on the target chain. */\n\treadonly jpycAddress: Address;\n\t/** Maximum JPYC (in raw units) the session key may move in one transfer. */\n\treadonly maxPerTransfer: bigint;\n\t/** Maximum number of transfer userOps the session key may submit per day. */\n\treadonly maxTransfersPerDay: number;\n\t/**\n\t * ZeroDev callPolicy on-chain version. Defaults to V0_0_4 (latest at the\n\t * time of writing). Bump only after auditing the new version's semantics.\n\t */\n\treadonly callPolicyVersion?: CallPolicyVersion;\n\t/**\n\t * Recipient restriction. An address list restricts the session key to\n\t * `transfer` JPYC only to those recipients (enforced on-chain via the\n\t * callPolicy `to` argument, condition `ONE_OF`); every other recipient is\n\t * rejected before any funds move. `\"any\"` (or omitting the field) leaves the\n\t * recipient unrestricted.\n\t *\n\t * The type mirrors the off-chain {@link SpendingPolicy.recipientAllowlist}\n\t * (`Address[] | \"any\"`) so a buy-list's resolved allowlist can feed both the\n\t * off-chain and on-chain policy paths unchanged. Two **deliberate** differences\n\t * from that sibling, forced by on-chain semantics:\n\t * - it is **optional** here (omitted = `\"any\"`) for backward compatibility —\n\t * the pre-allowlist policy left the recipient unrestricted;\n\t * - an **empty** array `[]` **throws** (rather than meaning deny-all as it does\n\t * off-chain) — an on-chain allowlist cannot encode \"match nothing\"; omit it\n\t * or pass `\"any\"` for unrestricted, or list ≥1 recipient.\n\t *\n\t * Entries are checksum-normalized and de-duplicated (shared with the off-chain\n\t * path via {@link normalizeRecipientAllowlist}). An address list requires\n\t * `callPolicyVersion` ≥ `V0_0_2` (the `ONE_OF` condition is unsupported on\n\t * `V0_0_1`); the default `V0_0_4` is fine.\n\t */\n\treadonly recipientAllowlist?: readonly Address[] | \"any\";\n}\n\n/**\n * Builds the ZeroDev policy bundle that enforces a JPYC daily spend limit.\n *\n * Plug the returned policies into `toPermissionValidator({ policies, … })`\n * — see {@link createAgentSmartAccount} for the common wiring.\n *\n * @example\n * ```ts\n * import { parseUnits } from \"viem\";\n * import {\n * createJpycDailyLimitPolicies,\n * getJpycAddress,\n * JPYC_DECIMALS,\n * polygonAmoy,\n * } from \"kawasekit\";\n *\n * const policies = createJpycDailyLimitPolicies({\n * jpycAddress: getJpycAddress(polygonAmoy.id),\n * maxPerTransfer: parseUnits(\"100\", JPYC_DECIMALS), // 100 JPYC / tx\n * maxTransfersPerDay: 10, // 10 tx / day\n * // effective daily cap = 1000 JPYC\n * });\n * ```\n *\n * @example Restrict the session key to a buy-list's merchants only\n * ```ts\n * const policies = createJpycDailyLimitPolicies({\n * jpycAddress: getJpycAddress(polygonAmoy.id),\n * maxPerTransfer: parseUnits(\"100\", JPYC_DECIMALS),\n * maxTransfersPerDay: 5,\n * // on-chain: any transfer to a non-allowlisted address reverts before funds move\n * recipientAllowlist: [merchantA, merchantB],\n * });\n * ```\n */\nexport function createJpycDailyLimitPolicies(\n\tparams: CreateJpycDailyLimitPoliciesParams,\n): readonly [Policy, Policy] {\n\tif (!Number.isInteger(params.maxTransfersPerDay) || params.maxTransfersPerDay < 1) {\n\t\tthrow new Error(\n\t\t\t`createJpycDailyLimitPolicies: maxTransfersPerDay must be a positive integer, got ${params.maxTransfersPerDay}.`,\n\t\t);\n\t}\n\n\t// callPolicy (amount cap + optional recipient allowlist) is the shared builder;\n\t// this function adds the daily-window rate limit on top.\n\tconst callPolicy = buildJpycTransferCallPolicy({\n\t\tjpycAddress: params.jpycAddress,\n\t\tmaxPerTransfer: params.maxPerTransfer,\n\t\trecipientAllowlist: params.recipientAllowlist,\n\t\tcallPolicyVersion: params.callPolicyVersion,\n\t});\n\n\tconst rateLimitPolicy = toRateLimitPolicy({\n\t\tinterval: ONE_DAY_SECONDS,\n\t\tcount: params.maxTransfersPerDay,\n\t});\n\n\treturn [callPolicy, rateLimitPolicy] as const;\n}\n"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { buildJpycTransferCallPolicy } from './chunk-UIDDRHLW.js';
|
|
2
|
+
import { getJpycAddress, jpycAbi } from './chunk-KT7XDT2T.js';
|
|
3
|
+
import { isSupportedChainId } from './chunk-SOTYGX67.js';
|
|
4
|
+
import { isAddress, encodeFunctionData } from 'viem';
|
|
5
|
+
import { toRateLimitPolicy, toTimestampPolicy } from '@zerodev/permissions/policies';
|
|
6
|
+
|
|
7
|
+
var TransferJpycInputError = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(`transferJpyc: ${message}`);
|
|
10
|
+
this.name = "TransferJpycInputError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
async function transferJpyc(kernelClient, params) {
|
|
14
|
+
if (!isAddress(params.to, { strict: false })) {
|
|
15
|
+
throw new TransferJpycInputError(`\`to\` is not a valid address: ${params.to}`);
|
|
16
|
+
}
|
|
17
|
+
if (params.amount <= 0n) {
|
|
18
|
+
throw new TransferJpycInputError(`\`amount\` must be positive, got ${params.amount}.`);
|
|
19
|
+
}
|
|
20
|
+
const chainId = kernelClient.chain.id;
|
|
21
|
+
if (!isSupportedChainId(chainId)) {
|
|
22
|
+
throw new TransferJpycInputError(`Chain ID ${chainId} is not a kawasekit-supported chain.`);
|
|
23
|
+
}
|
|
24
|
+
const jpycAddress = getJpycAddress(chainId);
|
|
25
|
+
const data = encodeFunctionData({
|
|
26
|
+
abi: jpycAbi,
|
|
27
|
+
functionName: "transfer",
|
|
28
|
+
args: [params.to, params.amount]
|
|
29
|
+
});
|
|
30
|
+
const callData = await kernelClient.account.encodeCalls([{ to: jpycAddress, value: 0n, data }]);
|
|
31
|
+
const userOpHash = await kernelClient.sendUserOperation({ callData });
|
|
32
|
+
if (params.waitForReceipt === false) {
|
|
33
|
+
return { userOpHash, transactionHash: null, success: null };
|
|
34
|
+
}
|
|
35
|
+
const receipt = await kernelClient.waitForUserOperationReceipt({ hash: userOpHash });
|
|
36
|
+
return {
|
|
37
|
+
userOpHash,
|
|
38
|
+
transactionHash: receipt.receipt.transactionHash,
|
|
39
|
+
success: receipt.success
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function createBuyListPolicies(params) {
|
|
43
|
+
if (params.merchants.length === 0) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"createBuyListPolicies: merchants must not be empty \u2014 a buy-list must target at least one merchant."
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (!Number.isInteger(params.maxTransfers) || params.maxTransfers < 1) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`createBuyListPolicies: maxTransfers must be a positive integer, got ${params.maxTransfers}.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (!Number.isInteger(params.validUntil) || params.validUntil <= 0) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`createBuyListPolicies: validUntil must be a positive unix-seconds integer, got ${params.validUntil}.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const validAfter = params.validAfter ?? 0;
|
|
59
|
+
if (!Number.isInteger(validAfter) || validAfter < 0) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`createBuyListPolicies: validAfter must be a non-negative unix-seconds integer, got ${params.validAfter}.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (validAfter >= params.validUntil) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`createBuyListPolicies: validAfter (${validAfter}) must be before validUntil (${params.validUntil}).`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const callPolicy = buildJpycTransferCallPolicy({
|
|
70
|
+
jpycAddress: params.jpycAddress,
|
|
71
|
+
maxPerTransfer: params.maxPerTransfer,
|
|
72
|
+
recipientAllowlist: params.merchants,
|
|
73
|
+
callPolicyVersion: params.callPolicyVersion
|
|
74
|
+
});
|
|
75
|
+
const rateLimitPolicy = toRateLimitPolicy({
|
|
76
|
+
interval: params.validUntil - validAfter,
|
|
77
|
+
count: params.maxTransfers,
|
|
78
|
+
startAt: validAfter
|
|
79
|
+
});
|
|
80
|
+
const timestampPolicy = toTimestampPolicy(
|
|
81
|
+
params.validAfter === void 0 ? { validUntil: params.validUntil } : { validAfter: params.validAfter, validUntil: params.validUntil }
|
|
82
|
+
);
|
|
83
|
+
return [callPolicy, rateLimitPolicy, timestampPolicy];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { TransferJpycInputError, createBuyListPolicies, transferJpyc };
|
|
87
|
+
//# sourceMappingURL=chunk-Y2LOACWO.js.map
|
|
88
|
+
//# sourceMappingURL=chunk-Y2LOACWO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/transfer-jpyc.ts","../src/policy/buy-list.ts"],"names":[],"mappings":"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","file":"chunk-Y2LOACWO.js","sourcesContent":["/**\n * High-level helper: transfer JPYC from a Kernel smart account via a sponsored\n * UserOp.\n *\n * This is the canonical \"agent payment\" path for M2. The flow:\n * 1. Resolve the JPYC contract address for `kernelClient.chain`.\n * 2. Encode `JPYC.transfer(to, amount)` calldata.\n * 3. Wrap it as a Kernel call and submit via `sendUserOperation`.\n * 4. Wait for the bundler receipt and return both hashes.\n *\n * EIP-3009 `transferWithAuthorization` cannot be used here: JPYC's signature\n * verification is pure `ecrecover`, so a smart account cannot be `from`. See\n * `src/tokens/eip3009.ts` for the EOA-payer path.\n *\n * @packageDocumentation\n */\n\nimport type { KernelAccountClient } from \"@zerodev/sdk\";\nimport type { Address, Chain, Hex, Transport } from \"viem\";\nimport { encodeFunctionData, isAddress } from \"viem\";\nimport type { SmartAccount } from \"viem/account-abstraction\";\nimport { isSupportedChainId, type SupportedChainId } from \"../chains\";\nimport { getJpycAddress, jpycAbi } from \"../tokens/jpyc\";\n\n/** A {@link KernelAccountClient} that is fully configured (chain + account). */\nexport type ConfiguredKernelClient = KernelAccountClient<Transport, Chain, SmartAccount>;\n\n/** Parameters for {@link transferJpyc}. */\nexport interface TransferJpycParams {\n\t/** Recipient address. */\n\treadonly to: Address;\n\t/**\n\t * Raw token amount in the token's smallest unit (JPYC has 18 decimals).\n\t *\n\t * @example\n\t * ```ts\n\t * import { parseUnits } from \"viem\";\n\t * import { JPYC_DECIMALS } from \"kawasekit\";\n\t *\n\t * parseUnits(\"100\", JPYC_DECIMALS); // 100 JPYC\n\t * ```\n\t */\n\treadonly amount: bigint;\n\t/**\n\t * Optional. Defaults to waiting for the bundler receipt before returning.\n\t * Set to `false` to return after the UserOp is submitted but before it\n\t * lands on chain — useful when the caller wants to do its own polling.\n\t */\n\treadonly waitForReceipt?: boolean;\n}\n\n/** Result of a {@link transferJpyc} call. */\nexport interface TransferJpycResult {\n\treadonly userOpHash: Hex;\n\t/** `null` when `waitForReceipt: false` was requested. */\n\treadonly transactionHash: Hex | null;\n\t/** `true` if the bundler receipt reported success; `null` if not awaited. */\n\treadonly success: boolean | null;\n}\n\n/** Thrown when {@link transferJpyc} is called with invalid arguments. */\nexport class TransferJpycInputError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(`transferJpyc: ${message}`);\n\t\tthis.name = \"TransferJpycInputError\";\n\t}\n}\n\n/**\n * Transfer JPYC from the Kernel smart account to `to` via a sponsored UserOp.\n *\n * @example\n * ```ts\n * import { parseUnits } from \"viem\";\n * import { JPYC_DECIMALS, transferJpyc } from \"kawasekit\";\n *\n * const { userOpHash, transactionHash } = await transferJpyc(kernelClient, {\n * to: \"0xBeef0000000000000000000000000000DEADBEEF\",\n * amount: parseUnits(\"100\", JPYC_DECIMALS),\n * });\n * ```\n */\nexport async function transferJpyc(\n\tkernelClient: ConfiguredKernelClient,\n\tparams: TransferJpycParams,\n): Promise<TransferJpycResult> {\n\tif (!isAddress(params.to, { strict: false })) {\n\t\tthrow new TransferJpycInputError(`\\`to\\` is not a valid address: ${params.to}`);\n\t}\n\tif (params.amount <= 0n) {\n\t\tthrow new TransferJpycInputError(`\\`amount\\` must be positive, got ${params.amount}.`);\n\t}\n\n\tconst chainId = kernelClient.chain.id;\n\tif (!isSupportedChainId(chainId)) {\n\t\tthrow new TransferJpycInputError(`Chain ID ${chainId} is not a kawasekit-supported chain.`);\n\t}\n\tconst jpycAddress = getJpycAddress(chainId satisfies SupportedChainId);\n\n\tconst data = encodeFunctionData({\n\t\tabi: jpycAbi,\n\t\tfunctionName: \"transfer\",\n\t\targs: [params.to, params.amount],\n\t});\n\n\tconst callData = await kernelClient.account.encodeCalls([{ to: jpycAddress, value: 0n, data }]);\n\n\tconst userOpHash = await kernelClient.sendUserOperation({ callData });\n\n\tif (params.waitForReceipt === false) {\n\t\treturn { userOpHash, transactionHash: null, success: null };\n\t}\n\n\tconst receipt = await kernelClient.waitForUserOperationReceipt({ hash: userOpHash });\n\treturn {\n\t\tuserOpHash,\n\t\ttransactionHash: receipt.receipt.transactionHash,\n\t\tsuccess: receipt.success,\n\t};\n}\n","/**\n * Buy-list → ZeroDev policy bundle for a **disposable, scoped session key**\n * (the Agent Commerce Hub authorization flow).\n *\n * A user's resolved buy-list (its merchants + a per-transfer cap + a total\n * transfer count + a schedule window) is baked into a single-use session key by\n * composing three on-chain policies:\n * 1. **callPolicy** — `JPYC.transfer(to, value)` with `value ≤ maxPerTransfer`\n * and `to ∈ merchants` (the allowlist; shared with\n * {@link createJpycDailyLimitPolicies} via {@link buildJpycTransferCallPolicy}).\n * 2. **rateLimitPolicy** — a **total** cap of `maxTransfers` over the whole\n * schedule window (the rate window is set to span `[validAfter, validUntil]`,\n * so `count` does NOT reset within the session — it is a session total, not a\n * per-day limit).\n * 3. **timestampPolicy** — the session key is only valid within\n * `[validAfter, validUntil]`.\n *\n * Cumulative budget (\"spend ≤ ¥X total\") is NOT a policy field — it is the\n * amount the user funds the account with (funding is the user's responsibility,\n * out of the SDK's scope). These policies bound *who* (allowlist), *how much per\n * transfer* (cap), *how many* (count), and *when* (window).\n *\n * @packageDocumentation\n */\n\nimport type { Policy } from \"@zerodev/permissions\";\nimport {\n\ttype CallPolicyVersion,\n\ttoRateLimitPolicy,\n\ttoTimestampPolicy,\n} from \"@zerodev/permissions/policies\";\nimport type { Address } from \"viem\";\nimport { buildJpycTransferCallPolicy } from \"./jpyc-call-policy\";\n\n/** Parameters for {@link createBuyListPolicies}. */\nexport interface CreateBuyListPoliciesParams {\n\t/** JPYC contract address on the target chain. */\n\treadonly jpycAddress: Address;\n\t/**\n\t * The buy-list's resolved merchant recipient addresses — the allowlist the\n\t * session key may pay. **Required and non-empty** (a buy-list always targets\n\t * specific merchants); checksum-normalized + de-duplicated.\n\t */\n\treadonly merchants: readonly Address[];\n\t/** Maximum JPYC (raw units) per single transfer. Must be positive. */\n\treadonly maxPerTransfer: bigint;\n\t/**\n\t * Maximum number of transfers over the WHOLE window — a session total, not a\n\t * per-day limit. Must be a positive integer.\n\t */\n\treadonly maxTransfers: number;\n\t/** Schedule-window end (unix seconds); the key is invalid after this. Must be a positive integer. */\n\treadonly validUntil: number;\n\t/**\n\t * Optional schedule-window start (unix seconds); the key is invalid before\n\t * this. Defaults to 0 (valid immediately). Must be `< validUntil`.\n\t */\n\treadonly validAfter?: number;\n\t/** ZeroDev callPolicy on-chain version. Defaults to V0_0_4. */\n\treadonly callPolicyVersion?: CallPolicyVersion;\n}\n\n/**\n * Build the ZeroDev policy bundle for a buy-list-scoped, single-use session key.\n *\n * Plug the returned policies into `toPermissionValidator({ policies, … })` and\n * issue the session key via {@link issueSessionKey}.\n *\n * @example\n * ```ts\n * import { parseUnits } from \"viem\";\n * import { createBuyListPolicies, getJpycAddress, JPYC_DECIMALS, polygonAmoy } from \"kawasekit\";\n *\n * const policies = createBuyListPolicies({\n * jpycAddress: getJpycAddress(polygonAmoy.id),\n * merchants: [merchantA, merchantB], // pay ONLY these (allowlist)\n * maxPerTransfer: parseUnits(\"500\", JPYC_DECIMALS),\n * maxTransfers: 3, // at most 3 transfers, total\n * validUntil: Math.floor(Date.now() / 1000) + 3 * 86_400, // valid 3 days\n * });\n * // user funds the account with their budget; the policies bound who/how-much/how-many/when.\n * ```\n */\nexport function createBuyListPolicies(\n\tparams: CreateBuyListPoliciesParams,\n): readonly [Policy, Policy, Policy] {\n\tif (params.merchants.length === 0) {\n\t\tthrow new Error(\n\t\t\t\"createBuyListPolicies: merchants must not be empty — a buy-list must target at least one merchant.\",\n\t\t);\n\t}\n\tif (!Number.isInteger(params.maxTransfers) || params.maxTransfers < 1) {\n\t\tthrow new Error(\n\t\t\t`createBuyListPolicies: maxTransfers must be a positive integer, got ${params.maxTransfers}.`,\n\t\t);\n\t}\n\tif (!Number.isInteger(params.validUntil) || params.validUntil <= 0) {\n\t\tthrow new Error(\n\t\t\t`createBuyListPolicies: validUntil must be a positive unix-seconds integer, got ${params.validUntil}.`,\n\t\t);\n\t}\n\tconst validAfter = params.validAfter ?? 0;\n\tif (!Number.isInteger(validAfter) || validAfter < 0) {\n\t\tthrow new Error(\n\t\t\t`createBuyListPolicies: validAfter must be a non-negative unix-seconds integer, got ${params.validAfter}.`,\n\t\t);\n\t}\n\tif (validAfter >= params.validUntil) {\n\t\tthrow new Error(\n\t\t\t`createBuyListPolicies: validAfter (${validAfter}) must be before validUntil (${params.validUntil}).`,\n\t\t);\n\t}\n\n\t// callPolicy: amount cap + merchant allowlist (shared with daily-limit).\n\tconst callPolicy = buildJpycTransferCallPolicy({\n\t\tjpycAddress: params.jpycAddress,\n\t\tmaxPerTransfer: params.maxPerTransfer,\n\t\trecipientAllowlist: params.merchants,\n\t\tcallPolicyVersion: params.callPolicyVersion,\n\t});\n\n\t// rateLimitPolicy: a TOTAL cap of maxTransfers over the window. Setting the\n\t// rate window to span exactly [validAfter, validUntil] (interval = the window\n\t// length, startAt = validAfter) keeps the whole session in one rate bucket, so\n\t// `count` is a session total — NOT a per-day limit that could reset and let\n\t// more than maxTransfers through over a multi-day window.\n\tconst rateLimitPolicy = toRateLimitPolicy({\n\t\tinterval: params.validUntil - validAfter,\n\t\tcount: params.maxTransfers,\n\t\tstartAt: validAfter,\n\t});\n\n\t// timestampPolicy: the key is only valid within the schedule window. Omit\n\t// validAfter when not given (exactOptionalPropertyTypes: don't pass undefined).\n\tconst timestampPolicy = toTimestampPolicy(\n\t\tparams.validAfter === undefined\n\t\t\t? { validUntil: params.validUntil }\n\t\t\t: { validAfter: params.validAfter, validUntil: params.validUntil },\n\t);\n\n\treturn [callPolicy, rateLimitPolicy, timestampPolicy] as const;\n}\n"]}
|