maroo-viem-poc 0.1.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/index.mjs ADDED
@@ -0,0 +1,717 @@
1
+ import { BaseError, ContractFunctionRevertedError, decodeAbiParameters, decodeErrorResult, encodeAbiParameters, encodeFunctionData, maxUint256 } from "viem";
2
+ import { readContract, writeContract } from "viem/actions";
3
+ import { getAction } from "viem/utils";
4
+ import { iPclAbi } from "@maroo-chain/contracts/abi/precompiles/pcl/IPcl";
5
+ import { iAgentAbi } from "@maroo-chain/contracts/abi/precompiles/agent/IAgent";
6
+ import { iEasAbi } from "@maroo-chain/contracts/abi/precompiles/eas/IEas";
7
+ import { iOkrwAbi } from "@maroo-chain/contracts/abi/precompiles/okrw/IOkrw";
8
+ //#region src/constants.ts
9
+ /**
10
+ * Maroo static precompile addresses.
11
+ *
12
+ * Mirrors the `*_PRECOMPILED_ADDRESS` constants in
13
+ * `@maroo-chain/contracts/precompiles/{okrw,pcl,eas,agent}/I*.sol`.
14
+ *
15
+ * The contracts package does not export these from its TypeScript build
16
+ * (only the `.sol` sources carry them), so we redeclare them here.
17
+ * `test/addresses.test.ts` asserts these still match the installed package.
18
+ */
19
+ const precompileAddresses = {
20
+ okrw: "0x1000000000000000000000000000000000000001",
21
+ pcl: "0x1000000000000000000000000000000000000005",
22
+ /** Returns `eas` module params. Not the EAS contract itself — see {@link easContracts}. */
23
+ eas: "0x1000000000000000000000000000000000000009",
24
+ agent: "0x100000000000000000000000000000000000000A",
25
+ /** Clairveil shielded pool. Not yet deployed on marooTestnet. */
26
+ privacy: "0x100000000000000000000000000000000000000b"
27
+ };
28
+ /**
29
+ * EAS contracts, as reported by `eas.getParams()` on marooTestnet.
30
+ *
31
+ * These sit at precompile-shaped addresses but implement the standard
32
+ * Ethereum Attestation Service interfaces. `@maroo-chain/contracts@0.0.6`
33
+ * ships no ABI for them; use `@ethereum-attestation-service/eas-contracts`.
34
+ *
35
+ * Do not hardcode these in application code — read them from
36
+ * `client.eas.getParams()`, which is authoritative per network.
37
+ */
38
+ const easContracts = {
39
+ schemaRegistry: "0x1000000000000000000000000000000000000006",
40
+ eas: "0x1000000000000000000000000000000000000007",
41
+ indexer: "0x1000000000000000000000000000000000000008"
42
+ };
43
+ /**
44
+ * ABI entries that exist for chain-side codegen or proxy plumbing and must
45
+ * never be surfaced as client actions.
46
+ *
47
+ * - `_policies` is a `pure` no-op declared solely so the Solidity compiler
48
+ * emits the policy structs into the ABI (see the comment at the bottom of
49
+ * `IPcl.sol`). Calling it does nothing.
50
+ * - `preCall`/`postCall` are invoked by the PCL proxy hook with a `principal`
51
+ * the caller cannot forge. Application code goes through {@link runOnPcl}.
52
+ */
53
+ const excludedFunctions = { pcl: [
54
+ "_policies",
55
+ "preCall",
56
+ "postCall"
57
+ ] };
58
+ /** The selector wildcard. An empty `selector` matches every function. */
59
+ const SELECTOR_ALL = "0x";
60
+ /** `VolumeUnitPolicy.maxLimit` sentinel meaning "no upper bound". */
61
+ const NO_MAX_LIMIT = maxUint256;
62
+ //#endregion
63
+ //#region src/core/precompileActions.ts
64
+ const isRead = (m) => m === "view" || m === "pure";
65
+ /**
66
+ * Build a typed action map from an `as const` ABI by walking it at runtime.
67
+ *
68
+ * Replaces per-precompile codegen: every function in `abi` becomes one method,
69
+ * dispatched to `readContract` or `writeContract` by its state mutability.
70
+ *
71
+ * Overloads that agree on mutability share one dispatcher (viem resolves the
72
+ * overload from `args`). Overloads that mix read and write mutability throw at
73
+ * factory construction — a single dispatcher would silently misroute one side.
74
+ *
75
+ * @param abi - The precompile interface, as an `as const` ABI.
76
+ * @param defaultAddress - Where the precompile lives on a stock Maroo chain.
77
+ * @param options.mode - Emit only reads, only writes, or both.
78
+ * @param options.exclude - Function names to omit (codegen dummies, proxy hooks).
79
+ */
80
+ function precompileActions(abi, defaultAddress, options = {}) {
81
+ const { mode = "all", exclude = [] } = options;
82
+ const excluded = new Set(exclude);
83
+ const classification = /* @__PURE__ */ new Map();
84
+ const payableNames = /* @__PURE__ */ new Set();
85
+ for (const item of abi) {
86
+ if (item.type !== "function" || excluded.has(item.name)) continue;
87
+ const read = isRead(item.stateMutability);
88
+ const prev = classification.get(item.name);
89
+ if (prev !== void 0 && prev !== read) throw new Error(`precompileActions: "${item.name}" has overloads with mixed read/write mutability; add it to options.exclude and wrap it by hand`);
90
+ classification.set(item.name, read);
91
+ if (item.stateMutability === "payable") payableNames.add(item.name);
92
+ }
93
+ return (config = {}) => (client) => {
94
+ const actions = {};
95
+ for (const item of abi) {
96
+ if (item.type !== "function") continue;
97
+ if (excluded.has(item.name)) continue;
98
+ if (actions[item.name]) continue;
99
+ const read = isRead(item.stateMutability);
100
+ if (mode === "read" && !read) continue;
101
+ if (mode === "write" && read) continue;
102
+ const functionName = item.name;
103
+ const payable = payableNames.has(functionName);
104
+ const resolveCall = (parameters) => {
105
+ const { address, args, value: _value, ...rest } = parameters;
106
+ return {
107
+ ...rest,
108
+ abi,
109
+ functionName,
110
+ args: args ?? [],
111
+ address: address ?? config.address ?? defaultAddress
112
+ };
113
+ };
114
+ const fn = (parameters = {}) => {
115
+ const call = resolveCall(parameters);
116
+ if (read) return getAction(client, readContract, "readContract")(call);
117
+ const value = payable ? parameters.value : void 0;
118
+ return getAction(client, writeContract, "writeContract")({
119
+ ...call,
120
+ ...value !== void 0 ? { value } : {}
121
+ });
122
+ };
123
+ if (read) fn.call = (parameters = {}) => resolveCall(parameters);
124
+ actions[functionName] = fn;
125
+ }
126
+ return actions;
127
+ };
128
+ }
129
+ //#endregion
130
+ //#region src/actions/runOnPcl.ts
131
+ /**
132
+ * Call a contract *through* the PCL precompile so that contract-scoped
133
+ * policies are enforced.
134
+ *
135
+ * A direct call to the same contract enforces **global policies only**. There
136
+ * is nothing in the target contract's ABI, nor in any revert, that signals
137
+ * this — the call simply succeeds with contract policies unapplied. Route
138
+ * through `runOnPcl` whenever the target has policies registered.
139
+ *
140
+ * For a contract call the parameter shape mirrors `writeContract`, so
141
+ * switching a call site is a one-word change. Omit `abi`/`functionName` to
142
+ * forward a bare native transfer, or pass pre-encoded `data`.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const hash = await client.pcl.runOnPcl({
147
+ * address: token,
148
+ * abi: erc20Abi,
149
+ * functionName: 'transfer',
150
+ * args: [recipient, amount],
151
+ * })
152
+ *
153
+ * // Native transfer, policy-checked:
154
+ * const hash = await client.pcl.runOnPcl({ address: recipient, value: parseEther('1') })
155
+ * ```
156
+ */
157
+ async function runOnPcl(client, parameters) {
158
+ const { address, value = 0n, gas, pclAddress } = parameters;
159
+ const data = parameters.abi ? encodeFunctionData({
160
+ abi: parameters.abi,
161
+ functionName: parameters.functionName,
162
+ args: parameters.args
163
+ }) : parameters.data ?? "0x";
164
+ return getAction(client, writeContract, "writeContract")({
165
+ address: pclAddress ?? precompileAddresses.pcl,
166
+ abi: iPclAbi,
167
+ functionName: "runOnPcl",
168
+ args: [
169
+ address,
170
+ data,
171
+ value
172
+ ],
173
+ gas
174
+ });
175
+ }
176
+ //#endregion
177
+ //#region src/decorators/precompiles.ts
178
+ const pclReadActions = precompileActions(iPclAbi, precompileAddresses.pcl, {
179
+ mode: "read",
180
+ exclude: excludedFunctions.pcl
181
+ });
182
+ const okrwReadActions = precompileActions(iOkrwAbi, precompileAddresses.okrw, { mode: "read" });
183
+ const easReadActions = precompileActions(iEasAbi, precompileAddresses.eas, { mode: "read" });
184
+ const agentReadActions = precompileActions(iAgentAbi, precompileAddresses.agent, { mode: "read" });
185
+ const pclWriteActions = precompileActions(iPclAbi, precompileAddresses.pcl, {
186
+ mode: "write",
187
+ exclude: excludedFunctions.pcl
188
+ });
189
+ const okrwWriteActions = precompileActions(iOkrwAbi, precompileAddresses.okrw, { mode: "write" });
190
+ //#endregion
191
+ //#region src/decorators/maroo.ts
192
+ /**
193
+ * Read actions for every Maroo precompile.
194
+ *
195
+ * @example
196
+ * ```ts
197
+ * const client = createPublicClient({ chain: marooTestnet, transport: http() })
198
+ * .extend(marooPublicActions())
199
+ *
200
+ * const { policyAdmin, entrypoints } = await client.pcl.getParams()
201
+ * const { policies } = await client.pcl.globalPolicies()
202
+ * const [ids, page] = await client.agent.getAgentIds({ args: [wallet, pageRequest] })
203
+ * ```
204
+ */
205
+ function marooPublicActions() {
206
+ return (client) => ({
207
+ pcl: pclReadActions()(client),
208
+ okrw: okrwReadActions()(client),
209
+ eas: easReadActions()(client),
210
+ agent: agentReadActions()(client)
211
+ });
212
+ }
213
+ /**
214
+ * @example
215
+ * ```ts
216
+ * const client = createWalletClient({ chain: marooTestnet, account, transport: http() })
217
+ * .extend(marooWalletActions())
218
+ *
219
+ * const hash = await client.pcl.runOnPcl({
220
+ * address: token, abi: erc20Abi, functionName: 'transfer', args: [to, amount],
221
+ * })
222
+ * ```
223
+ */
224
+ function marooWalletActions() {
225
+ return (client) => ({
226
+ pcl: {
227
+ ...pclWriteActions()(client),
228
+ runOnPcl: (parameters) => runOnPcl(client, parameters)
229
+ },
230
+ okrw: okrwWriteActions()(client)
231
+ });
232
+ }
233
+ //#endregion
234
+ //#region src/codec/policy.ts
235
+ /**
236
+ * PCL policy codec.
237
+ *
238
+ * `PolicySet.policy` is an opaque `bytes` field whose layout is chosen by the
239
+ * sibling `templateId` string. That mapping exists nowhere in the ABI — the
240
+ * `_policies` dummy function only forces the structs into the artifact. This
241
+ * module is the mapping.
242
+ *
243
+ * Verified against `marooTestnet.globalPolicies()` at block 12447862.
244
+ */
245
+ const policyTemplateIds = [
246
+ "EAS_POLICY",
247
+ "DENYLIST_POLICY",
248
+ "VOLUME_POLICY",
249
+ "OKRW_EAS_TRANSFER_LIMIT_POLICY",
250
+ "PERIODIC_VOLUME_POLICY",
251
+ "AGENT_OKRW_TRANSFER_LIMIT_POLICY",
252
+ "OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY",
253
+ "LOGICAL_POLICY",
254
+ "FOR_EACH_POLICY"
255
+ ];
256
+ const LogicalQuantifier = {
257
+ Unspecified: 0,
258
+ And: 1,
259
+ Or: 2
260
+ };
261
+ const ForEachQuantifier = {
262
+ Unspecified: 0,
263
+ Any: 1,
264
+ Every: 2
265
+ };
266
+ const ForEachSubject = {
267
+ Unspecified: 0,
268
+ AgentOwners: 1
269
+ };
270
+ const nameOf = (e, v) => Object.keys(e).find((k) => e[k] === v) ?? "Unspecified";
271
+ const POLICY_SET_COMPONENTS = [
272
+ {
273
+ name: "templateId",
274
+ type: "string"
275
+ },
276
+ {
277
+ name: "policy",
278
+ type: "bytes"
279
+ },
280
+ {
281
+ name: "selector",
282
+ type: "bytes"
283
+ }
284
+ ];
285
+ /**
286
+ * `templateId` → the ABI parameters of its `policy` bytes payload.
287
+ *
288
+ * Each entry is a one-element parameter list wrapping the payload tuple, so
289
+ * `decodeAbiParameters(codec, bytes)[0]` is the decoded struct.
290
+ */
291
+ const policyCodecs = {
292
+ EAS_POLICY: [{
293
+ name: "policy",
294
+ type: "tuple",
295
+ components: [
296
+ {
297
+ name: "easContract",
298
+ type: "address"
299
+ },
300
+ {
301
+ name: "indexContract",
302
+ type: "address"
303
+ },
304
+ {
305
+ name: "schemaUid",
306
+ type: "bytes32"
307
+ }
308
+ ]
309
+ }],
310
+ DENYLIST_POLICY: [{
311
+ name: "policy",
312
+ type: "tuple",
313
+ components: [{
314
+ name: "addresses",
315
+ type: "address[]"
316
+ }]
317
+ }],
318
+ VOLUME_POLICY: [{
319
+ name: "policy",
320
+ type: "tuple",
321
+ components: [{
322
+ name: "tokens",
323
+ type: "string[]"
324
+ }, {
325
+ name: "limits",
326
+ type: "tuple[]",
327
+ components: [{
328
+ name: "minLimit",
329
+ type: "uint256"
330
+ }, {
331
+ name: "maxLimit",
332
+ type: "uint256"
333
+ }]
334
+ }]
335
+ }],
336
+ PERIODIC_VOLUME_POLICY: [{
337
+ name: "policy",
338
+ type: "tuple",
339
+ components: [{
340
+ name: "tokens",
341
+ type: "string[]"
342
+ }, {
343
+ name: "limits",
344
+ type: "tuple[]",
345
+ components: [{
346
+ name: "maxAmount",
347
+ type: "uint256"
348
+ }, {
349
+ name: "resetPeriodSeconds",
350
+ type: "uint64"
351
+ }]
352
+ }]
353
+ }],
354
+ OKRW_EAS_TRANSFER_LIMIT_POLICY: [{
355
+ name: "policy",
356
+ type: "tuple",
357
+ components: [
358
+ {
359
+ name: "easContract",
360
+ type: "address"
361
+ },
362
+ {
363
+ name: "indexContract",
364
+ type: "address"
365
+ },
366
+ {
367
+ name: "schemaUid",
368
+ type: "bytes32"
369
+ },
370
+ {
371
+ name: "transferLimitAmount",
372
+ type: "uint256"
373
+ }
374
+ ]
375
+ }],
376
+ OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY: [{
377
+ name: "policy",
378
+ type: "tuple",
379
+ components: [
380
+ {
381
+ name: "easContract",
382
+ type: "address"
383
+ },
384
+ {
385
+ name: "indexContract",
386
+ type: "address"
387
+ },
388
+ {
389
+ name: "schemaUid",
390
+ type: "bytes32"
391
+ },
392
+ {
393
+ name: "maxAmount",
394
+ type: "uint256"
395
+ },
396
+ {
397
+ name: "resetPeriodSeconds",
398
+ type: "uint64"
399
+ }
400
+ ]
401
+ }],
402
+ AGENT_OKRW_TRANSFER_LIMIT_POLICY: [{
403
+ name: "policy",
404
+ type: "tuple",
405
+ components: [{
406
+ name: "reserved",
407
+ type: "uint256"
408
+ }]
409
+ }],
410
+ LOGICAL_POLICY: [{
411
+ name: "policy",
412
+ type: "tuple",
413
+ components: [{
414
+ name: "quantifier",
415
+ type: "uint8"
416
+ }, {
417
+ name: "children",
418
+ type: "tuple[]",
419
+ components: POLICY_SET_COMPONENTS
420
+ }]
421
+ }],
422
+ FOR_EACH_POLICY: [{
423
+ name: "policy",
424
+ type: "tuple",
425
+ components: [
426
+ {
427
+ name: "quantifier",
428
+ type: "uint8"
429
+ },
430
+ {
431
+ name: "subject",
432
+ type: "uint8"
433
+ },
434
+ {
435
+ name: "child",
436
+ type: "tuple",
437
+ components: POLICY_SET_COMPONENTS
438
+ }
439
+ ]
440
+ }]
441
+ };
442
+ const MAX_DEPTH = 8;
443
+ const isKnown = (id) => Object.prototype.hasOwnProperty.call(policyCodecs, id);
444
+ /**
445
+ * Recursively decode a `PolicySet` returned by `globalPolicies()` or
446
+ * `contractPolicies()` into a readable tree.
447
+ *
448
+ * Unrecognised template ids decode to `{ unknown: true, raw }` rather than
449
+ * throwing, so a chain upgrade that adds a policy type degrades a dApp's
450
+ * policy inspector instead of breaking it.
451
+ */
452
+ function decodePolicySet(set, depth = 0) {
453
+ if (depth > MAX_DEPTH) throw new Error(`policy nesting exceeded depth ${MAX_DEPTH}`);
454
+ const { selector } = set;
455
+ if (!isKnown(set.templateId)) return {
456
+ templateId: set.templateId,
457
+ selector,
458
+ raw: set.policy,
459
+ unknown: true
460
+ };
461
+ switch (set.templateId) {
462
+ case "EAS_POLICY": {
463
+ const [p] = decodeAbiParameters(policyCodecs.EAS_POLICY, set.policy);
464
+ return {
465
+ templateId: "EAS_POLICY",
466
+ selector,
467
+ easContract: p.easContract,
468
+ indexContract: p.indexContract,
469
+ schemaUid: p.schemaUid
470
+ };
471
+ }
472
+ case "DENYLIST_POLICY": {
473
+ const [p] = decodeAbiParameters(policyCodecs.DENYLIST_POLICY, set.policy);
474
+ return {
475
+ templateId: "DENYLIST_POLICY",
476
+ selector,
477
+ addresses: p.addresses
478
+ };
479
+ }
480
+ case "VOLUME_POLICY": {
481
+ const [p] = decodeAbiParameters(policyCodecs.VOLUME_POLICY, set.policy);
482
+ if (p.tokens.length !== p.limits.length) return {
483
+ templateId: set.templateId,
484
+ selector,
485
+ raw: set.policy,
486
+ unknown: true
487
+ };
488
+ return {
489
+ templateId: "VOLUME_POLICY",
490
+ selector,
491
+ limits: p.tokens.map((token, i) => ({
492
+ token,
493
+ minLimit: p.limits[i].minLimit,
494
+ maxLimit: p.limits[i].maxLimit
495
+ }))
496
+ };
497
+ }
498
+ case "PERIODIC_VOLUME_POLICY": {
499
+ const [p] = decodeAbiParameters(policyCodecs.PERIODIC_VOLUME_POLICY, set.policy);
500
+ if (p.tokens.length !== p.limits.length) return {
501
+ templateId: set.templateId,
502
+ selector,
503
+ raw: set.policy,
504
+ unknown: true
505
+ };
506
+ return {
507
+ templateId: "PERIODIC_VOLUME_POLICY",
508
+ selector,
509
+ limits: p.tokens.map((token, i) => ({
510
+ token,
511
+ maxAmount: p.limits[i].maxAmount,
512
+ resetPeriodSeconds: p.limits[i].resetPeriodSeconds
513
+ }))
514
+ };
515
+ }
516
+ case "OKRW_EAS_TRANSFER_LIMIT_POLICY": {
517
+ const [p] = decodeAbiParameters(policyCodecs.OKRW_EAS_TRANSFER_LIMIT_POLICY, set.policy);
518
+ return {
519
+ templateId: "OKRW_EAS_TRANSFER_LIMIT_POLICY",
520
+ selector,
521
+ easContract: p.easContract,
522
+ indexContract: p.indexContract,
523
+ schemaUid: p.schemaUid,
524
+ transferLimitAmount: p.transferLimitAmount
525
+ };
526
+ }
527
+ case "OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY": {
528
+ const [p] = decodeAbiParameters(policyCodecs.OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY, set.policy);
529
+ return {
530
+ templateId: "OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY",
531
+ selector,
532
+ easContract: p.easContract,
533
+ indexContract: p.indexContract,
534
+ schemaUid: p.schemaUid,
535
+ maxAmount: p.maxAmount,
536
+ resetPeriodSeconds: p.resetPeriodSeconds
537
+ };
538
+ }
539
+ case "AGENT_OKRW_TRANSFER_LIMIT_POLICY":
540
+ decodeAbiParameters(policyCodecs.AGENT_OKRW_TRANSFER_LIMIT_POLICY, set.policy);
541
+ return {
542
+ templateId: "AGENT_OKRW_TRANSFER_LIMIT_POLICY",
543
+ selector
544
+ };
545
+ case "LOGICAL_POLICY": {
546
+ const [p] = decodeAbiParameters(policyCodecs.LOGICAL_POLICY, set.policy);
547
+ return {
548
+ templateId: "LOGICAL_POLICY",
549
+ selector,
550
+ quantifier: nameOf(LogicalQuantifier, p.quantifier),
551
+ children: p.children.map((c) => decodePolicySet(c, depth + 1))
552
+ };
553
+ }
554
+ case "FOR_EACH_POLICY": {
555
+ const [p] = decodeAbiParameters(policyCodecs.FOR_EACH_POLICY, set.policy);
556
+ return {
557
+ templateId: "FOR_EACH_POLICY",
558
+ selector,
559
+ quantifier: nameOf(ForEachQuantifier, p.quantifier),
560
+ subject: nameOf(ForEachSubject, p.subject),
561
+ child: decodePolicySet(p.child, depth + 1)
562
+ };
563
+ }
564
+ default: return {
565
+ templateId: set.templateId,
566
+ selector,
567
+ raw: set.policy,
568
+ unknown: true
569
+ };
570
+ }
571
+ }
572
+ /**
573
+ * ABI-encode a policy payload for its template.
574
+ *
575
+ * The `policy` argument is typed per `templateId` via {@link PolicyInput}, so
576
+ * the public signature is fully checked. The single internal cast is the
577
+ * unavoidable one: indexing `policyCodecs` by a generic `id` yields a union
578
+ * that `encodeAbiParameters` cannot unify positionally (viem does the same at
579
+ * this boundary).
580
+ *
581
+ * Admin-facing: only `policyAdmin` may set global policies, and only a
582
+ * contract's registered admin may change its contract policies.
583
+ */
584
+ function encodePolicy(templateId, policy) {
585
+ const codec = policyCodecs[templateId];
586
+ return encodeAbiParameters(codec, [policy]);
587
+ }
588
+ /**
589
+ * Normalize a function selector for a `PolicySet`.
590
+ *
591
+ * The chain accepts either an empty value (wildcard, stored internally as
592
+ * `"__all__"`) or exactly 4 bytes, lowercased. Anything else is rejected with
593
+ * `InvalidSelector`.
594
+ */
595
+ function normalizeSelector(selector) {
596
+ if (!selector || selector === "0x") return "0x";
597
+ const body = selector.slice(2).toLowerCase();
598
+ if (!/^[0-9a-f]{8}$/.test(body)) throw new Error(`selector must be 4 bytes of hex (8 hex chars), got "${selector}"`);
599
+ return `0x${body}`;
600
+ }
601
+ //#endregion
602
+ //#region src/errors/pcl.ts
603
+ /**
604
+ * The errors PCL actually declares. Used to refuse errors we do not own —
605
+ * `decodeErrorResult` also recognises the built-in `Error(string)` / `Panic`
606
+ * signatures, and those must decode to `null`, not to a policy verdict.
607
+ */
608
+ const PCL_ERROR_NAMES = new Set(iPclAbi.flatMap((item) => item.type === "error" ? [item.name] : []));
609
+ /**
610
+ * `AnyOfRejected` nests (a LOGICAL(Or) branch can itself be a LOGICAL(Or)),
611
+ * and revert bytes are attacker-controlled via `eth_call` — cap the recursion
612
+ * like the policy codec caps its tree depth. Past the cap, children surface
613
+ * as `undecodable` instead of overflowing the stack inside an error handler.
614
+ */
615
+ const MAX_ERROR_DEPTH = 8;
616
+ const ATTESTATION = {
617
+ EasAttestationRequired: "required",
618
+ EasNoAttestationReceived: "none-received",
619
+ EasAttestationLookupFailed: "lookup-failed",
620
+ EasAttestationRevoked: "revoked",
621
+ EasAttestationExpired: "expired"
622
+ };
623
+ function toViolation(name, args, depth) {
624
+ const a = args;
625
+ switch (name) {
626
+ case "InDenylist": return {
627
+ kind: "denylist",
628
+ sender: a[0]
629
+ };
630
+ case "VolumeBelowMinLimit": return {
631
+ kind: "volume-below-min",
632
+ minLimit: a[0],
633
+ value: a[1]
634
+ };
635
+ case "VolumeAboveMaxLimit": return {
636
+ kind: "volume-above-max",
637
+ maxLimit: a[0],
638
+ value: a[1]
639
+ };
640
+ case "ExceededPeriodicVolume": return {
641
+ kind: "periodic-volume",
642
+ maxLimit: a[0],
643
+ value: a[1],
644
+ resetAt: /* @__PURE__ */ new Date(Number(a[2]) * 1e3)
645
+ };
646
+ case "ReachedLimitOfNonEAS": return {
647
+ kind: "non-eas-limit",
648
+ maxLimit: a[0],
649
+ value: a[1]
650
+ };
651
+ case "ExceededAgentTransferLimit": return {
652
+ kind: "agent-transfer-limit",
653
+ maxLimit: a[0],
654
+ value: a[1]
655
+ };
656
+ case "Unauthorized": return { kind: "unauthorized" };
657
+ case "AnyOfRejected": return {
658
+ kind: "any-of-rejected",
659
+ children: (a[0] ?? []).map((child) => decode(child, depth + 1) ?? {
660
+ kind: "unknown",
661
+ name: "undecodable"
662
+ })
663
+ };
664
+ default:
665
+ if (name in ATTESTATION) return {
666
+ kind: "attestation",
667
+ reason: ATTESTATION[name],
668
+ sender: a[0]
669
+ };
670
+ return {
671
+ kind: "unknown",
672
+ name,
673
+ args
674
+ };
675
+ }
676
+ }
677
+ function decode(source, depth) {
678
+ if (depth > MAX_ERROR_DEPTH) return null;
679
+ let data;
680
+ if (typeof source === "string" && source.startsWith("0x")) data = source;
681
+ else if (source instanceof BaseError) {
682
+ const revert = source.walk((e) => e instanceof ContractFunctionRevertedError);
683
+ if (revert instanceof ContractFunctionRevertedError) data = revert.raw;
684
+ }
685
+ if (!data || data === "0x") return null;
686
+ try {
687
+ const { errorName, args } = decodeErrorResult({
688
+ abi: iPclAbi,
689
+ data
690
+ });
691
+ if (!PCL_ERROR_NAMES.has(errorName)) return null;
692
+ return toViolation(errorName, args ?? [], depth);
693
+ } catch {
694
+ return null;
695
+ }
696
+ }
697
+ /**
698
+ * Decode PCL revert data — or a thrown viem error wrapping it — into a
699
+ * {@link PclViolation}. Returns `null` if the revert is not a PCL error.
700
+ *
701
+ * @example
702
+ * ```ts
703
+ * try {
704
+ * await client.pcl.runOnPcl({ ... })
705
+ * } catch (err) {
706
+ * const v = decodePclError(err)
707
+ * if (v?.kind === 'periodic-volume')
708
+ * throw new Error(`24h limit hit. Resets ${v.resetAt.toISOString()}`)
709
+ * throw err
710
+ * }
711
+ * ```
712
+ */
713
+ function decodePclError(source) {
714
+ return decode(source, 0);
715
+ }
716
+ //#endregion
717
+ export { ForEachQuantifier, ForEachSubject, LogicalQuantifier, NO_MAX_LIMIT, SELECTOR_ALL, agentReadActions, decodePclError, decodePolicySet, easContracts, easReadActions, encodePolicy, marooPublicActions, marooWalletActions, normalizeSelector, okrwReadActions, okrwWriteActions, pclReadActions, pclWriteActions, policyCodecs, policyTemplateIds, precompileActions, precompileAddresses, runOnPcl };