kawasekit 0.1.0-beta.3 → 0.1.0-beta.5
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/README.md +78 -26
- package/dist/asset-domain-CpJuDkI2.d.cts +102 -0
- package/dist/asset-domain-CpJuDkI2.d.ts +102 -0
- package/dist/chunk-DYTONQW2.js +76 -0
- package/dist/chunk-DYTONQW2.js.map +1 -0
- package/dist/{chunk-2V3W4B64.js → chunk-E2EG72U2.js} +112 -180
- package/dist/chunk-E2EG72U2.js.map +1 -0
- package/dist/chunk-E47SIVFY.js +44 -0
- package/dist/chunk-E47SIVFY.js.map +1 -0
- package/dist/chunk-KT7XDT2T.js +209 -0
- package/dist/chunk-KT7XDT2T.js.map +1 -0
- package/dist/chunk-MTMJNYOD.js +125 -0
- package/dist/chunk-MTMJNYOD.js.map +1 -0
- package/dist/{chunk-KWCPYGFE.js → chunk-N3CVLISJ.js} +3 -3
- package/dist/{chunk-KWCPYGFE.js.map → chunk-N3CVLISJ.js.map} +1 -1
- package/dist/{chunk-CD6SQBZN.js → chunk-PVUKX6IF.js} +7 -229
- package/dist/chunk-PVUKX6IF.js.map +1 -0
- package/dist/chunk-QHUCU5YX.js +179 -0
- package/dist/chunk-QHUCU5YX.js.map +1 -0
- package/dist/chunk-SOTYGX67.js +95 -0
- package/dist/chunk-SOTYGX67.js.map +1 -0
- package/dist/chunk-TTX3RBIZ.js +159 -0
- package/dist/chunk-TTX3RBIZ.js.map +1 -0
- package/dist/chunk-UQ7WJY6O.js +43 -0
- package/dist/chunk-UQ7WJY6O.js.map +1 -0
- package/dist/chunk-VPRR3TNA.js +204 -0
- package/dist/chunk-VPRR3TNA.js.map +1 -0
- package/dist/chunk-WMVJNPX2.js +41 -0
- package/dist/chunk-WMVJNPX2.js.map +1 -0
- package/dist/cli/index.cjs +66 -9
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +14 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/idempotency/index.cjs +52 -3
- package/dist/idempotency/index.cjs.map +1 -1
- package/dist/idempotency/index.d.cts +3 -3
- package/dist/idempotency/index.d.ts +3 -3
- package/dist/idempotency/index.js +3 -2
- package/dist/{chunk-WMFCI6KC.js → idempotency/redis/index.cjs} +136 -130
- package/dist/idempotency/redis/index.cjs.map +1 -0
- package/dist/idempotency/redis/index.d.cts +83 -0
- package/dist/idempotency/redis/index.d.ts +83 -0
- package/dist/idempotency/redis/index.js +105 -0
- package/dist/idempotency/redis/index.js.map +1 -0
- package/dist/index-2fEOL83n.d.cts +472 -0
- package/dist/index-2fEOL83n.d.ts +472 -0
- package/dist/{index-kqH78Yms.d.ts → index-BaAOB0xd.d.ts} +158 -72
- package/dist/{index-CykLOgYD.d.ts → index-CSpNGigO.d.ts} +1 -1
- package/dist/{index-DzveM0RN.d.cts → index-DFChv_fT.d.cts} +1 -1
- package/dist/{index-NdNKNnZP.d.cts → index-Z6AL1MR_.d.cts} +158 -72
- package/dist/index.cjs +497 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +366 -101
- package/dist/index.d.ts +366 -101
- package/dist/index.js +13 -6
- package/dist/observability/index.d.cts +2 -2
- package/dist/observability/index.d.ts +2 -2
- package/dist/observability/otlp/index.d.cts +2 -2
- package/dist/observability/otlp/index.d.ts +2 -2
- package/dist/observability/prometheus/index.d.cts +2 -2
- package/dist/observability/prometheus/index.d.ts +2 -2
- package/dist/policy/index.cjs +419 -0
- package/dist/policy/index.cjs.map +1 -0
- package/dist/policy/index.d.cts +66 -0
- package/dist/policy/index.d.ts +66 -0
- package/dist/policy/index.js +7 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/{server-CSpATNiH.d.ts → server-D_rZc-cW.d.ts} +2 -2
- package/dist/{server-BMeg_hNB.d.cts → server-ov8YstNS.d.cts} +2 -2
- package/dist/session/index.cjs +52 -3
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +2 -2
- package/dist/session/index.d.ts +2 -2
- package/dist/session/index.js +2 -2
- package/dist/signer/index.cjs +360 -0
- package/dist/signer/index.cjs.map +1 -0
- package/dist/signer/index.d.cts +131 -0
- package/dist/signer/index.d.ts +131 -0
- package/dist/signer/index.js +9 -0
- package/dist/signer/index.js.map +1 -0
- package/dist/spending-policy-BnNp_y5d.d.cts +129 -0
- package/dist/spending-policy-DiZBTBeN.d.ts +129 -0
- package/dist/{store-BY16tCbe.d.cts → store-DPOLS6yp.d.cts} +1 -1
- package/dist/{store-Bd-91QL0.d.ts → store-DmdoV3Ii.d.ts} +1 -1
- package/dist/{types-DwFfT4E7.d.ts → types-BR9UcvJO.d.ts} +1 -1
- package/dist/types-IEl-iOIx.d.cts +148 -0
- package/dist/types-IEl-iOIx.d.ts +148 -0
- package/dist/{types-A_WwFpcv.d.cts → types-TpS_8ztt.d.cts} +1 -1
- package/dist/x402/hono/index.cjs.map +1 -1
- package/dist/x402/hono/index.d.cts +4 -4
- package/dist/x402/hono/index.d.ts +4 -4
- package/dist/x402/hono/index.js +6 -3
- package/dist/x402/hono/index.js.map +1 -1
- package/dist/x402/index.cjs +274 -80
- package/dist/x402/index.cjs.map +1 -1
- package/dist/x402/index.d.cts +7 -5
- package/dist/x402/index.d.ts +7 -5
- package/dist/x402/index.js +8 -4
- package/package.json +32 -1
- package/dist/chunk-2V3W4B64.js.map +0 -1
- package/dist/chunk-6G345P2I.js +0 -81
- package/dist/chunk-6G345P2I.js.map +0 -1
- package/dist/chunk-CD6SQBZN.js.map +0 -1
- package/dist/chunk-SA7LMQFG.js +0 -44
- package/dist/chunk-SA7LMQFG.js.map +0 -1
- package/dist/chunk-WMFCI6KC.js.map +0 -1
- package/dist/index-jF4BGOIb.d.cts +0 -173
- package/dist/index-jF4BGOIb.d.ts +0 -173
package/README.md
CHANGED
|
@@ -5,10 +5,15 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/kawasekit)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
|
|
8
|
-
🚧 **Status**:
|
|
8
|
+
🚧 **Status**: M5 backbone complete — `kawasekit@0.1.0-beta.3` is published on npm (SLSA provenance, `beta` dist-tag) and mainnet-capable, with payment flows verified on Polygon mainnet. **Not yet GA**, but **both `0.1.0` fund-correctness gates are now closed**:
|
|
9
|
+
|
|
10
|
+
- **Reasoning-step idempotency** ([`docs/THREAT_MODEL.md` §6.1](./docs/THREAT_MODEL.md), now closed) — a default-on server dedup layer prevents duplicate payments for identical re-sends, and an opt-in client-derived EIP-3009 nonce makes the token contract reject re-signed same-intent duplicates on-chain. See the [idempotency note](#x402-paywall-m3-1).
|
|
11
|
+
- **`maxAmountPerSign`** (threat 1.14) — the signer can now pin a per-signature value ceiling, the way it already pins the asset.
|
|
12
|
+
|
|
13
|
+
This removes the earlier blanket "small per-call values only" caveat: preventing duplicate payment is now a matter of correct integration (wiring a reasoning-step key for the regenerate / fan-out case), not a missing SDK capability. GA (`0.1.0` on the `latest` tag) now waits on a **clean one-week beta soak**. Review happens **continuously in the open**: issues and counter-examples are welcome on GitHub and via [SECURITY.md](./SECURITY.md) (the §6.1 gap itself came from public feedback). A formal third-party audit is a goal on the road to `1.0`, not a `0.1.0` GA blocker. Built in public.
|
|
9
14
|
|
|
10
15
|
```bash
|
|
11
|
-
pnpm add kawasekit@beta # 0.1.0-beta.
|
|
16
|
+
pnpm add kawasekit@beta # 0.1.0-beta.3 — pre-GA, mainnet-capable
|
|
12
17
|
```
|
|
13
18
|
|
|
14
19
|
## Vision
|
|
@@ -25,7 +30,7 @@ Built around modern account abstraction (ERC-4337 / Kernel v3.1) and Japan's fir
|
|
|
25
30
|
- [x] **M2**: JPYC transfer via UserOp + EIP-3009 signing helpers + Daily Limit spending policy
|
|
26
31
|
- [x] **M3**: x402 v2 server/client/facilitator + session-key lifecycle + Mastra/Hono integration example
|
|
27
32
|
- [x] **M4**: Polygon mainnet support + observability (Prometheus / OTLP) + CLI + docs site + npm `0.1.0-alpha`/`0.1.0-beta` release
|
|
28
|
-
- [ ] **M5
|
|
33
|
+
- [ ] **M5** *(backbone done)*: reasoning-step idempotency layer (§6.1 ✅) + `maxAmountPerSign` ceiling (✅) → **both `0.1.0` fund-correctness gates closed**. Remaining: `0.1.0` GA promote (after a clean beta soak) and Kaia support (fast-follow). The README roadmap's framing — "Community building + first real integrations" — is the *outcome*; closing these gaps is the technical *prerequisite* for it.
|
|
29
34
|
- [ ] **M6**: Managed service alpha + Rust policy engine
|
|
30
35
|
|
|
31
36
|
## Quick Start
|
|
@@ -173,8 +178,10 @@ const app = new Hono();
|
|
|
173
178
|
app.use(
|
|
174
179
|
"/weather/*",
|
|
175
180
|
x402Middleware({
|
|
176
|
-
// `network` is required (M4-1):
|
|
177
|
-
// walletClient.
|
|
181
|
+
// `network` is required (M4-1): cross-checked against walletClient.chain.isTestnet,
|
|
182
|
+
// throws on mismatch. walletClient.account MUST be built with viem's `nonceManager`
|
|
183
|
+
// — createSelfFacilitator throws at construction otherwise (threat 2.2). On mainnet
|
|
184
|
+
// it waits for 4 confirmations by default (1 on testnet); override with `confirmations`.
|
|
178
185
|
facilitator: createSelfFacilitator({ network: "testnet", walletClient, publicClient }),
|
|
179
186
|
requirementsFor: () => [
|
|
180
187
|
buildPaymentRequirements({
|
|
@@ -189,10 +196,17 @@ app.use(
|
|
|
189
196
|
app.get("/weather/:city", (c) => c.json({ city: c.req.param("city"), weather: "sunny" }));
|
|
190
197
|
```
|
|
191
198
|
|
|
199
|
+
The server **deduplicates identical re-sent paid requests by default** (M5-1): an
|
|
200
|
+
in-memory store replays the cached response instead of settling twice, and closes
|
|
201
|
+
the verify→settle race. It is single-process — for multi-replica deployments pass a
|
|
202
|
+
shared store (or rely on the client-derived nonce below); disable with
|
|
203
|
+
`idempotency: { store: "none" }`. See the [idempotency note](#x402-paywall-m3-1) below.
|
|
204
|
+
|
|
192
205
|
Client (any `fetch` becomes x402-aware):
|
|
193
206
|
|
|
194
207
|
```typescript
|
|
195
208
|
import { createX402PaymentSigner, JPYC_DECIMALS, wrapFetch } from "kawasekit";
|
|
209
|
+
import { createIdempotencyKeyBuilder } from "kawasekit/idempotency";
|
|
196
210
|
import { parseUnits } from "viem";
|
|
197
211
|
import { privateKeyToAccount } from "viem/accounts";
|
|
198
212
|
|
|
@@ -202,8 +216,16 @@ const signer = createX402PaymentSigner({
|
|
|
202
216
|
// Pin to the JPYC v2 EIP-712 domain at construction. The wire-format
|
|
203
217
|
// extra.name / extra.version are ignored — Threat 1.4 mitigation.
|
|
204
218
|
asset: { kind: "known", id: "jpyc-v2" },
|
|
219
|
+
// Per-signature value ceiling (M5-2, threat 1.14): refuse to sign any
|
|
220
|
+
// requirement above this. Pins the *amount* the way `asset` pins the token.
|
|
221
|
+
maxAmountPerSign: parseUnits("1", JPYC_DECIMALS), // ≤ 1 JPYC per call
|
|
205
222
|
});
|
|
206
223
|
|
|
224
|
+
// One key builder per agent run; call .next(intent) at each tool-execution
|
|
225
|
+
// boundary and reuse the returned key for retries of that step.
|
|
226
|
+
const keys = createIdempotencyKeyBuilder({ conversationId: "conv-42" });
|
|
227
|
+
let stepKey = keys.next("fetch_weather:Tokyo");
|
|
228
|
+
|
|
207
229
|
// onPayment is *required* at the type level — kawasekit refuses to default
|
|
208
230
|
// to "always pay" silently. The callback is your budget guard.
|
|
209
231
|
let spent = 0n;
|
|
@@ -216,20 +238,36 @@ const fetch402 = wrapFetch({
|
|
|
216
238
|
spent = next;
|
|
217
239
|
return true;
|
|
218
240
|
},
|
|
241
|
+
// Reasoning-step idempotency (M5-1): the key is sent as an `Idempotency-Key`
|
|
242
|
+
// header AND derives a deterministic EIP-3009 nonce, so a regenerate /
|
|
243
|
+
// multi-agent re-sign of the SAME intent reuses the nonce and the token
|
|
244
|
+
// contract rejects the duplicate on-chain. Omit for today's random-nonce path.
|
|
245
|
+
idempotencyKeyFor: () => stepKey,
|
|
219
246
|
});
|
|
220
247
|
|
|
221
248
|
const res = await fetch402("https://api.example.com/weather/Tokyo");
|
|
222
249
|
// → 402 → onPayment guard → signed retry → 200 with JPYC settled on-chain
|
|
223
250
|
```
|
|
224
251
|
|
|
225
|
-
>
|
|
226
|
-
>
|
|
227
|
-
>
|
|
228
|
-
>
|
|
229
|
-
>
|
|
230
|
-
>
|
|
231
|
-
>
|
|
232
|
-
>
|
|
252
|
+
> **Reasoning-step idempotency (M5-1).** kawasekit now prevents one agent
|
|
253
|
+
> reasoning step from paying twice, across two layers:
|
|
254
|
+
>
|
|
255
|
+
> - **Identical re-send — handled by default.** The server dedup layer (shown in
|
|
256
|
+
> the server example above) replays the cached response for a re-sent /
|
|
257
|
+
> network-duplicate request and closes the verify→settle race — no
|
|
258
|
+
> configuration needed (threat 1.8c, ✅). It is single-process; use a shared
|
|
259
|
+
> store for multiple replicas, or rely on the derived nonce below.
|
|
260
|
+
> - **Re-signed same intent — wire the key.** A "Regenerate" click or multi-agent
|
|
261
|
+
> fan-out signs a *fresh* authorization for the same intent. Pass a
|
|
262
|
+
> reasoning-step key (`idempotencyKeyFor` + `createIdempotencyKeyBuilder`, shown
|
|
263
|
+
> in the client example) so the EIP-3009 nonce is derived deterministically and
|
|
264
|
+
> the JPYC contract rejects the duplicate **on-chain**, across uncoordinated
|
|
265
|
+
> replicas. The SDK can't do this for you — it never sees the LLM intent, only
|
|
266
|
+
> your harness does — so this half is **operator responsibility** (threat 1.8b,
|
|
267
|
+
> parallel to the asset pin). Omit the key to fall back to random-nonce behaviour.
|
|
268
|
+
>
|
|
269
|
+
> See [`docs/THREAT_MODEL.md` §6.1](./docs/THREAT_MODEL.md) (now closed) and the
|
|
270
|
+
> [design RFC](./docs/rfc/m5-1-reasoning-step-idempotency.md) for the full model.
|
|
233
271
|
|
|
234
272
|
### Session-key lifecycle (M3-2)
|
|
235
273
|
|
|
@@ -263,22 +301,36 @@ const restored = await restoreSessionAccount({
|
|
|
263
301
|
|
|
264
302
|
## Supported Chains
|
|
265
303
|
|
|
266
|
-
JPYC availability and kawasekit support are **two separate axes
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
(`
|
|
270
|
-
|
|
271
|
-
|
|
304
|
+
JPYC availability and kawasekit support are **two separate axes**. As of M5-3,
|
|
305
|
+
kawasekit ships chain configs for **Polygon, Kaia, Avalanche, and Ethereum**
|
|
306
|
+
(+ their testnets) in `src/chains/`, each carrying a per-chain finality default
|
|
307
|
+
(`defaultConfirmations`). JPYC is live at the same address on **all eight
|
|
308
|
+
chains** (Kaia / Kairos / Avalanche / Fuji / Sepolia confirmed by a read-only
|
|
309
|
+
on-chain `name()`/`symbol()` check; Polygon / Amoy / Ethereum established). Two
|
|
310
|
+
honest caveats: the **x402 EOA-payer path** works on every chain, but the
|
|
311
|
+
**smart-account path** (session keys, sponsored UserOps) is verified only on
|
|
312
|
+
Polygon — Kaia's runs via Pimlico in a later phase. Real x402 settlement is
|
|
313
|
+
**verified on Kaia Kairos** — a JPYC `transferWithAuthorization` settled through
|
|
314
|
+
the self-facilitator ([tx `0xe0a0…79c0`](https://kairos.kaiascan.io/tx/0xe0a0bfc75a447ff86c3502d49ff4e45cdf0396a1edd7eb5ed132dcb0130379c0),
|
|
315
|
+
`pnpm m5:kairos-x402-self-settle`); the other new chains are liveness-verified
|
|
316
|
+
but settlement is not yet exercised there.
|
|
317
|
+
|
|
318
|
+
| Chain (id) | JPYC (`0xE7C3…c29`) | kawasekit support |
|
|
272
319
|
|---|---|---|
|
|
273
|
-
| Polygon (
|
|
274
|
-
| Polygon Amoy (
|
|
275
|
-
| Kaia | ✅ Live
|
|
276
|
-
|
|
|
277
|
-
|
|
|
320
|
+
| Polygon (137) | ✅ Live | ✅ config + x402 + smart-account; verified with live mainnet txs |
|
|
321
|
+
| Polygon Amoy (80002) | ✅ Live | ✅ primary testnet target |
|
|
322
|
+
| Kaia (8217) | ✅ Live, same address¹ | ✅ M5-3 config — x402 EOA path; smart-account via Pimlico (later) |
|
|
323
|
+
| Kaia Kairos (1001) | ✅ Live (on-chain verified) | ✅ M5-3 — x402 EOA path, **settlement verified on-chain** (tx `0xe0a0…79c0`) |
|
|
324
|
+
| Avalanche (43114) | ✅ Live | ✅ M5-3 config — x402 EOA path; smart-account untested |
|
|
325
|
+
| Avalanche Fuji (43113) | ✅ Live (on-chain verified) | ✅ M5-3 config — x402 EOA path |
|
|
326
|
+
| Ethereum (1) | ✅ Live | ✅ M5-3 config — x402 EOA path; smart-account untested; deep confirmations (32) |
|
|
327
|
+
| Sepolia (11155111) | ✅ Live (on-chain verified) | ✅ M5-3 config — x402 EOA path |
|
|
278
328
|
|
|
279
329
|
¹ JPYC officially launched on Kaia in 2026-05 (Kaia DLT Foundation; Unifi began
|
|
280
|
-
JPYC support 2026-05-22), same contract address as the other chains.
|
|
281
|
-
|
|
330
|
+
JPYC support 2026-05-22), at the same contract address as the other chains. Kaia
|
|
331
|
+
runs IBFT consensus with immediate finality, so its `defaultConfirmations` is `1`
|
|
332
|
+
(not Polygon's `4`). See the
|
|
333
|
+
[finality-tuning recipe](./docs/recipes/facilitator-finality-tuning.md).
|
|
282
334
|
|
|
283
335
|
## Why Japan-first
|
|
284
336
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Address } from 'viem';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Known-asset registry for {@link createX402PaymentSigner}'s
|
|
5
|
+
* `asset: { kind: "known", id }` discriminated-union branch.
|
|
6
|
+
*
|
|
7
|
+
* kawasekit only ships pinned EIP-712 domain definitions for assets it has
|
|
8
|
+
* verified empirically against the deployed contracts. Adding a new entry
|
|
9
|
+
* here requires citing the source-file + line reference for the contract
|
|
10
|
+
* that owns the `name` / `version` (so the next reviewer can spot-check the
|
|
11
|
+
* claim, the same discipline `docs/THREAT_MODEL.md` §0 demands of any ✅
|
|
12
|
+
* verdict that delegates to an out-of-scope component).
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Known asset identifiers. New entries must update this union AND the table. */
|
|
18
|
+
type KnownAssetId = "jpyc-v2";
|
|
19
|
+
/** Fully-pinned EIP-712 domain for a known asset. */
|
|
20
|
+
interface KnownAssetDomain {
|
|
21
|
+
readonly id: KnownAssetId;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly version: string;
|
|
24
|
+
readonly verifyingContract: Address;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Look up a known asset's pinned EIP-712 domain by id.
|
|
28
|
+
*
|
|
29
|
+
* @returns The domain, or `undefined` if the id is not in the registry.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { getKnownAssetDomain } from "kawasekit";
|
|
34
|
+
*
|
|
35
|
+
* const jpyc = getKnownAssetDomain("jpyc-v2");
|
|
36
|
+
* if (jpyc === undefined) throw new Error("unreachable");
|
|
37
|
+
* console.log(jpyc.verifyingContract); // 0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function getKnownAssetDomain(id: KnownAssetId): KnownAssetDomain | undefined;
|
|
41
|
+
/** List every known asset id (for diagnostics / error messages). */
|
|
42
|
+
declare function listKnownAssetIds(): readonly KnownAssetId[];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* EIP-712 asset-domain resolution for x402 / EIP-3009 signing.
|
|
46
|
+
*
|
|
47
|
+
* Construction-time pinning of the EIP-712 domain (`name` / `version` /
|
|
48
|
+
* `verifyingContract`) a signer will use. The integrator declares an
|
|
49
|
+
* {@link X402AssetParam} — either a kawasekit-maintained `known` asset or a
|
|
50
|
+
* loud `unsafeOverride` — and {@link resolveAssetParam} resolves it to a pinned
|
|
51
|
+
* {@link ResolvedAsset}. The signer then trusts only this pinned domain and
|
|
52
|
+
* refuses to sign for a mismatched advertised asset (Threat 1.4: misadvertised
|
|
53
|
+
* EIP-712 domain).
|
|
54
|
+
*
|
|
55
|
+
* Token-domain concern, reused by both the x402 signer (`src/x402/client.ts`)
|
|
56
|
+
* and the M6 PolicyGatedSigner (`src/signer/`).
|
|
57
|
+
*
|
|
58
|
+
* @packageDocumentation
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/** EIP-712 token domain `name` / `version` pair. */
|
|
62
|
+
interface X402TokenDomain {
|
|
63
|
+
readonly name: string;
|
|
64
|
+
readonly version: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Asset binding for {@link createX402PaymentSigner} and the M6 PolicyGatedSigner.
|
|
68
|
+
* Required, discriminated.
|
|
69
|
+
*
|
|
70
|
+
* **Default-on whitelist**: integrators MUST declare which asset they intend
|
|
71
|
+
* to sign for. The `known` branch references a kawasekit-maintained
|
|
72
|
+
* whitelist (see `src/tokens/known-assets.ts`); the `unsafeOverride` branch
|
|
73
|
+
* is the deliberate escape hatch for any other asset and is named loudly so
|
|
74
|
+
* it survives a code review. Either way, the signer pins the EIP-712 domain
|
|
75
|
+
* at construction time and refuses to sign if `paymentRequirements.asset`
|
|
76
|
+
* disagrees with the pinned `verifyingContract`.
|
|
77
|
+
*
|
|
78
|
+
* Closes Threat 1.4 (misadvertised EIP-712 domain): the server's advertised
|
|
79
|
+
* `extra.name` / `extra.version` and `asset` are all ignored for signing
|
|
80
|
+
* purposes — the signer trusts only what the integrator declared here.
|
|
81
|
+
*/
|
|
82
|
+
type X402AssetParam = {
|
|
83
|
+
/** Use a kawasekit-maintained pinned EIP-712 domain. */
|
|
84
|
+
readonly kind: "known";
|
|
85
|
+
/** The asset id to pin. See {@link KnownAssetId} for the registry. */
|
|
86
|
+
readonly id: KnownAssetId;
|
|
87
|
+
} | {
|
|
88
|
+
/**
|
|
89
|
+
* Use a caller-supplied EIP-712 domain for an asset NOT on the
|
|
90
|
+
* kawasekit whitelist. The name is deliberately loud — pick this
|
|
91
|
+
* branch only when you have separately audited the contract and its
|
|
92
|
+
* `eip712Domain()` output.
|
|
93
|
+
*/
|
|
94
|
+
readonly kind: "unsafeOverride";
|
|
95
|
+
readonly domain: {
|
|
96
|
+
readonly name: string;
|
|
97
|
+
readonly version: string;
|
|
98
|
+
readonly verifyingContract: Address;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export { type KnownAssetDomain as K, type X402AssetParam as X, type KnownAssetId as a, type X402TokenDomain as b, getKnownAssetDomain as g, listKnownAssetIds as l };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Address } from 'viem';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Known-asset registry for {@link createX402PaymentSigner}'s
|
|
5
|
+
* `asset: { kind: "known", id }` discriminated-union branch.
|
|
6
|
+
*
|
|
7
|
+
* kawasekit only ships pinned EIP-712 domain definitions for assets it has
|
|
8
|
+
* verified empirically against the deployed contracts. Adding a new entry
|
|
9
|
+
* here requires citing the source-file + line reference for the contract
|
|
10
|
+
* that owns the `name` / `version` (so the next reviewer can spot-check the
|
|
11
|
+
* claim, the same discipline `docs/THREAT_MODEL.md` §0 demands of any ✅
|
|
12
|
+
* verdict that delegates to an out-of-scope component).
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Known asset identifiers. New entries must update this union AND the table. */
|
|
18
|
+
type KnownAssetId = "jpyc-v2";
|
|
19
|
+
/** Fully-pinned EIP-712 domain for a known asset. */
|
|
20
|
+
interface KnownAssetDomain {
|
|
21
|
+
readonly id: KnownAssetId;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly version: string;
|
|
24
|
+
readonly verifyingContract: Address;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Look up a known asset's pinned EIP-712 domain by id.
|
|
28
|
+
*
|
|
29
|
+
* @returns The domain, or `undefined` if the id is not in the registry.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { getKnownAssetDomain } from "kawasekit";
|
|
34
|
+
*
|
|
35
|
+
* const jpyc = getKnownAssetDomain("jpyc-v2");
|
|
36
|
+
* if (jpyc === undefined) throw new Error("unreachable");
|
|
37
|
+
* console.log(jpyc.verifyingContract); // 0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function getKnownAssetDomain(id: KnownAssetId): KnownAssetDomain | undefined;
|
|
41
|
+
/** List every known asset id (for diagnostics / error messages). */
|
|
42
|
+
declare function listKnownAssetIds(): readonly KnownAssetId[];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* EIP-712 asset-domain resolution for x402 / EIP-3009 signing.
|
|
46
|
+
*
|
|
47
|
+
* Construction-time pinning of the EIP-712 domain (`name` / `version` /
|
|
48
|
+
* `verifyingContract`) a signer will use. The integrator declares an
|
|
49
|
+
* {@link X402AssetParam} — either a kawasekit-maintained `known` asset or a
|
|
50
|
+
* loud `unsafeOverride` — and {@link resolveAssetParam} resolves it to a pinned
|
|
51
|
+
* {@link ResolvedAsset}. The signer then trusts only this pinned domain and
|
|
52
|
+
* refuses to sign for a mismatched advertised asset (Threat 1.4: misadvertised
|
|
53
|
+
* EIP-712 domain).
|
|
54
|
+
*
|
|
55
|
+
* Token-domain concern, reused by both the x402 signer (`src/x402/client.ts`)
|
|
56
|
+
* and the M6 PolicyGatedSigner (`src/signer/`).
|
|
57
|
+
*
|
|
58
|
+
* @packageDocumentation
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/** EIP-712 token domain `name` / `version` pair. */
|
|
62
|
+
interface X402TokenDomain {
|
|
63
|
+
readonly name: string;
|
|
64
|
+
readonly version: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Asset binding for {@link createX402PaymentSigner} and the M6 PolicyGatedSigner.
|
|
68
|
+
* Required, discriminated.
|
|
69
|
+
*
|
|
70
|
+
* **Default-on whitelist**: integrators MUST declare which asset they intend
|
|
71
|
+
* to sign for. The `known` branch references a kawasekit-maintained
|
|
72
|
+
* whitelist (see `src/tokens/known-assets.ts`); the `unsafeOverride` branch
|
|
73
|
+
* is the deliberate escape hatch for any other asset and is named loudly so
|
|
74
|
+
* it survives a code review. Either way, the signer pins the EIP-712 domain
|
|
75
|
+
* at construction time and refuses to sign if `paymentRequirements.asset`
|
|
76
|
+
* disagrees with the pinned `verifyingContract`.
|
|
77
|
+
*
|
|
78
|
+
* Closes Threat 1.4 (misadvertised EIP-712 domain): the server's advertised
|
|
79
|
+
* `extra.name` / `extra.version` and `asset` are all ignored for signing
|
|
80
|
+
* purposes — the signer trusts only what the integrator declared here.
|
|
81
|
+
*/
|
|
82
|
+
type X402AssetParam = {
|
|
83
|
+
/** Use a kawasekit-maintained pinned EIP-712 domain. */
|
|
84
|
+
readonly kind: "known";
|
|
85
|
+
/** The asset id to pin. See {@link KnownAssetId} for the registry. */
|
|
86
|
+
readonly id: KnownAssetId;
|
|
87
|
+
} | {
|
|
88
|
+
/**
|
|
89
|
+
* Use a caller-supplied EIP-712 domain for an asset NOT on the
|
|
90
|
+
* kawasekit whitelist. The name is deliberately loud — pick this
|
|
91
|
+
* branch only when you have separately audited the contract and its
|
|
92
|
+
* `eip712Domain()` output.
|
|
93
|
+
*/
|
|
94
|
+
readonly kind: "unsafeOverride";
|
|
95
|
+
readonly domain: {
|
|
96
|
+
readonly name: string;
|
|
97
|
+
readonly version: string;
|
|
98
|
+
readonly verifyingContract: Address;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export { type KnownAssetDomain as K, type X402AssetParam as X, type KnownAssetId as a, type X402TokenDomain as b, getKnownAssetDomain as g, listKnownAssetIds as l };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { PolicyGatedSignerConfigError, resolveAssetParam, signTransferWithAuthorization } from './chunk-VPRR3TNA.js';
|
|
2
|
+
import { evaluateSpendingPolicy } from './chunk-MTMJNYOD.js';
|
|
3
|
+
import { getAddress } from 'viem';
|
|
4
|
+
|
|
5
|
+
function createLocalPolicyGatedSigner(params) {
|
|
6
|
+
if (params.acknowledgeAdvisory !== true) {
|
|
7
|
+
throw new PolicyGatedSignerConfigError(
|
|
8
|
+
"acknowledgeAdvisory",
|
|
9
|
+
"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"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
const { account, policy, spendState } = params;
|
|
13
|
+
const pinned = resolveAssetParam(params.asset);
|
|
14
|
+
const from = getAddress(account.address);
|
|
15
|
+
return {
|
|
16
|
+
enforcement: "advisory",
|
|
17
|
+
from,
|
|
18
|
+
async sign(intent) {
|
|
19
|
+
if (getAddress(intent.from) !== from) {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
rejection: {
|
|
23
|
+
reason: "from_mismatch",
|
|
24
|
+
detail: `intent.from ${getAddress(intent.from)} does not equal signer.from ${from}`
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (getAddress(intent.token) !== pinned.verifyingContract) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
rejection: {
|
|
32
|
+
reason: "token_not_allowed",
|
|
33
|
+
detail: `intent.token ${getAddress(intent.token)} does not equal the signer's pinned verifyingContract ${pinned.verifyingContract}`
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const state = await spendState?.() ?? { spentPerToken: [] };
|
|
38
|
+
const nowSeconds = BigInt(Math.floor(Date.now() / 1e3));
|
|
39
|
+
const decision = evaluateSpendingPolicy(policy, intent, state, nowSeconds);
|
|
40
|
+
if (!decision.ok) {
|
|
41
|
+
return { ok: false, rejection: decision.rejection };
|
|
42
|
+
}
|
|
43
|
+
const signed = await signTransferWithAuthorization(
|
|
44
|
+
account,
|
|
45
|
+
{
|
|
46
|
+
name: pinned.name,
|
|
47
|
+
version: pinned.version,
|
|
48
|
+
chainId: intent.chainId,
|
|
49
|
+
verifyingContract: pinned.verifyingContract
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
from,
|
|
53
|
+
to: intent.to,
|
|
54
|
+
value: intent.value,
|
|
55
|
+
validAfter: intent.validAfter,
|
|
56
|
+
validBefore: intent.validBefore,
|
|
57
|
+
nonce: intent.nonce
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
return { ok: true, signature: signed.signature, intent };
|
|
61
|
+
},
|
|
62
|
+
describe() {
|
|
63
|
+
return {
|
|
64
|
+
enforcement: "advisory",
|
|
65
|
+
from,
|
|
66
|
+
policyId: policy.session.id,
|
|
67
|
+
notAfter: policy.session.notAfter,
|
|
68
|
+
revoked: policy.revoked
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { createLocalPolicyGatedSigner };
|
|
75
|
+
//# sourceMappingURL=chunk-DYTONQW2.js.map
|
|
76
|
+
//# sourceMappingURL=chunk-DYTONQW2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/signer/local.ts"],"names":[],"mappings":";;;;AAgEO,SAAS,6BACf,MAAA,EACgC;AAChC,EAAA,IAAI,MAAA,CAAO,wBAAwB,IAAA,EAAM;AACxC,IAAA,MAAM,IAAI,4BAAA;AAAA,MACT,qBAAA;AAAA,MACA;AAAA,KACD;AAAA,EACD;AAEA,EAAA,MAAM,EAAE,OAAA,EAAS,MAAA,EAAQ,UAAA,EAAW,GAAI,MAAA;AACxC,EAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,MAAA,CAAO,KAAK,CAAA;AAC7C,EAAA,MAAM,IAAA,GAAO,UAAA,CAAW,OAAA,CAAQ,OAAO,CAAA;AAEvC,EAAA,OAAO;AAAA,IACN,WAAA,EAAa,UAAA;AAAA,IACb,IAAA;AAAA,IACA,MAAM,KAAK,MAAA,EAA4C;AACtD,MAAA,IAAI,UAAA,CAAW,MAAA,CAAO,IAAI,CAAA,KAAM,IAAA,EAAM;AACrC,QAAA,OAAO;AAAA,UACN,EAAA,EAAI,KAAA;AAAA,UACJ,SAAA,EAAW;AAAA,YACV,MAAA,EAAQ,eAAA;AAAA,YACR,QAAQ,CAAA,YAAA,EAAe,UAAA,CAAW,OAAO,IAAI,CAAC,+BAA+B,IAAI,CAAA;AAAA;AAClF,SACD;AAAA,MACD;AACA,MAAA,IAAI,UAAA,CAAW,MAAA,CAAO,KAAK,CAAA,KAAM,OAAO,iBAAA,EAAmB;AAC1D,QAAA,OAAO;AAAA,UACN,EAAA,EAAI,KAAA;AAAA,UACJ,SAAA,EAAW;AAAA,YACV,MAAA,EAAQ,mBAAA;AAAA,YACR,MAAA,EAAQ,gBAAgB,UAAA,CAAW,MAAA,CAAO,KAAK,CAAC,CAAA,sDAAA,EAAyD,OAAO,iBAAiB,CAAA;AAAA;AAClI,SACD;AAAA,MACD;AAEA,MAAA,MAAM,QAAqB,MAAM,UAAA,QAAmB,EAAE,aAAA,EAAe,EAAC,EAAE;AACxE,MAAA,MAAM,UAAA,GAAa,OAAO,IAAA,CAAK,KAAA,CAAM,KAAK,GAAA,EAAI,GAAI,GAAI,CAAC,CAAA;AACvD,MAAA,MAAM,QAAA,GAAW,sBAAA,CAAuB,MAAA,EAAQ,MAAA,EAAQ,OAAO,UAAU,CAAA;AACzE,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACjB,QAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,SAAA,EAAW,SAAS,SAAA,EAAU;AAAA,MACnD;AAEA,MAAA,MAAM,SAAS,MAAM,6BAAA;AAAA,QACpB,OAAA;AAAA,QACA;AAAA,UACC,MAAM,MAAA,CAAO,IAAA;AAAA,UACb,SAAS,MAAA,CAAO,OAAA;AAAA,UAChB,SAAS,MAAA,CAAO,OAAA;AAAA,UAChB,mBAAmB,MAAA,CAAO;AAAA,SAC3B;AAAA,QACA;AAAA,UACC,IAAA;AAAA,UACA,IAAI,MAAA,CAAO,EAAA;AAAA,UACX,OAAO,MAAA,CAAO,KAAA;AAAA,UACd,YAAY,MAAA,CAAO,UAAA;AAAA,UACnB,aAAa,MAAA,CAAO,WAAA;AAAA,UACpB,OAAO,MAAA,CAAO;AAAA;AACf,OACD;AACA,MAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,SAAA,EAAW,MAAA,CAAO,WAAW,MAAA,EAAO;AAAA,IACxD,CAAA;AAAA,IACA,QAAA,GAA8B;AAC7B,MAAA,OAAO;AAAA,QACN,WAAA,EAAa,UAAA;AAAA,QACb,IAAA;AAAA,QACA,QAAA,EAAU,OAAO,OAAA,CAAQ,EAAA;AAAA,QACzB,QAAA,EAAU,OAAO,OAAA,CAAQ,QAAA;AAAA,QACzB,SAAS,MAAA,CAAO;AAAA,OACjB;AAAA,IACD;AAAA,GACD;AACD","file":"chunk-DYTONQW2.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 } 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 }] }),\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\t{\n\t\t\t\t\tname: pinned.name,\n\t\t\t\t\tversion: pinned.version,\n\t\t\t\t\tchainId: intent.chainId,\n\t\t\t\t\tverifyingContract: pinned.verifyingContract,\n\t\t\t\t},\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"]}
|