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/README.md +146 -0
- package/dist/index.cjs +740 -0
- package/dist/index.d.cts +3637 -0
- package/dist/index.d.mts +3637 -0
- package/dist/index.mjs +717 -0
- package/package.json +59 -0
- package/src/actions/runOnPcl.ts +117 -0
- package/src/codec/policy.ts +381 -0
- package/src/constants.ts +57 -0
- package/src/core/precompileActions.ts +215 -0
- package/src/decorators/maroo.ts +114 -0
- package/src/decorators/precompiles.ts +44 -0
- package/src/errors/pcl.ts +137 -0
- package/src/index.ts +58 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { ExtractAbiFunction, ExtractAbiFunctionNames } from 'abitype'
|
|
2
|
+
import type {
|
|
3
|
+
Abi,
|
|
4
|
+
Account,
|
|
5
|
+
Address,
|
|
6
|
+
Chain,
|
|
7
|
+
Client,
|
|
8
|
+
ContractFunctionArgs,
|
|
9
|
+
ContractFunctionName,
|
|
10
|
+
ReadContractReturnType,
|
|
11
|
+
Transport,
|
|
12
|
+
WriteContractReturnType,
|
|
13
|
+
} from 'viem'
|
|
14
|
+
import { readContract, writeContract } from 'viem/actions'
|
|
15
|
+
import { getAction } from 'viem/utils'
|
|
16
|
+
|
|
17
|
+
type ReadName<abi extends Abi> = ContractFunctionName<abi, 'pure' | 'view'>
|
|
18
|
+
type WriteName<abi extends Abi> = ContractFunctionName<abi, 'nonpayable' | 'payable'>
|
|
19
|
+
|
|
20
|
+
/** Per-call escape hatch: precompile addresses are chain params, not universal. */
|
|
21
|
+
export type PrecompileCallOptions = {
|
|
22
|
+
/** Override the precompile address for this call. */
|
|
23
|
+
address?: Address | undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type ReadArgs<abi extends Abi, name extends ReadName<abi>> = ContractFunctionArgs<
|
|
27
|
+
abi,
|
|
28
|
+
'pure' | 'view',
|
|
29
|
+
name
|
|
30
|
+
>
|
|
31
|
+
type WriteArgs<abi extends Abi, name extends WriteName<abi>> = ContractFunctionArgs<
|
|
32
|
+
abi,
|
|
33
|
+
'nonpayable' | 'payable',
|
|
34
|
+
name
|
|
35
|
+
>
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The parameters tuple for a generated action: `args` (and the whole object)
|
|
39
|
+
* is optional only when the function takes no inputs. This preserves viem's
|
|
40
|
+
* own required-args typing — omitting mandatory args must fail at compile
|
|
41
|
+
* time, not as an AbiEncodingLengthMismatchError at runtime.
|
|
42
|
+
*/
|
|
43
|
+
type ParametersFor<args, extra> = readonly [] extends args
|
|
44
|
+
? [parameters?: ({ args?: args } & extra) | undefined]
|
|
45
|
+
: [parameters: { args: args } & extra]
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A read call as a plain descriptor, not an executed promise. Its shape is
|
|
49
|
+
* exactly what `multicall`, `simulateContract`, and `estimateContractGas`
|
|
50
|
+
* accept, so batching precompile reads (e.g. one PeriodicVolume per user)
|
|
51
|
+
* costs one round-trip instead of N.
|
|
52
|
+
*/
|
|
53
|
+
export type PrecompileReadCall<abi extends Abi, name extends ReadName<abi>> = {
|
|
54
|
+
address: Address
|
|
55
|
+
abi: abi
|
|
56
|
+
functionName: name
|
|
57
|
+
args: ReadArgs<abi, name>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type PrecompileReadActions<abi extends Abi> = {
|
|
61
|
+
[name in ReadName<abi>]: ((
|
|
62
|
+
...parameters: ParametersFor<ReadArgs<abi, name>, PrecompileCallOptions>
|
|
63
|
+
) => Promise<ReadContractReturnType<abi, name>>) & {
|
|
64
|
+
/** Build a multicall/simulate descriptor for this read instead of executing it. */
|
|
65
|
+
call: (
|
|
66
|
+
...parameters: ParametersFor<ReadArgs<abi, name>, PrecompileCallOptions>
|
|
67
|
+
) => PrecompileReadCall<abi, name>
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* `value` is only accepted for `payable` functions. On this chain no
|
|
73
|
+
* precompile write is payable — `runOnPcl` carries its forwarded value as an
|
|
74
|
+
* argument, and the precompile neither forwards nor refunds `msg.value`, so
|
|
75
|
+
* attaching it to the outer tx strands funds at the precompile address.
|
|
76
|
+
* Typing it `never` (and stripping it at runtime) closes that path.
|
|
77
|
+
*
|
|
78
|
+
* Payability is derived from `ExtractAbiFunction` rather than viem's
|
|
79
|
+
* `ContractFunctionName<abi, 'payable'>`: the latter widens to `string` when
|
|
80
|
+
* an ABI has NO payable functions, which would make every name "payable" —
|
|
81
|
+
* the exact opposite of the guard. With overloads, `value` is allowed when
|
|
82
|
+
* any overload is payable.
|
|
83
|
+
*/
|
|
84
|
+
type WriteExtra<abi extends Abi, name> = PrecompileCallOptions & {
|
|
85
|
+
gas?: bigint | undefined
|
|
86
|
+
} & ([name] extends [ExtractAbiFunctionNames<abi>]
|
|
87
|
+
? 'payable' extends ExtractAbiFunction<abi, name>['stateMutability']
|
|
88
|
+
? { value?: bigint | undefined }
|
|
89
|
+
: { value?: never }
|
|
90
|
+
: { value?: bigint | undefined })
|
|
91
|
+
|
|
92
|
+
export type PrecompileWriteActions<abi extends Abi> = {
|
|
93
|
+
[name in WriteName<abi>]: (
|
|
94
|
+
...parameters: ParametersFor<WriteArgs<abi, name>, WriteExtra<abi, name>>
|
|
95
|
+
) => Promise<WriteContractReturnType>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The public shape of every decorator this factory produces.
|
|
100
|
+
*
|
|
101
|
+
* Declared as an explicit named alias — and used as the annotated return type
|
|
102
|
+
* of {@link precompileActions} — so TypeScript's declaration emit references
|
|
103
|
+
* it by name instead of structurally re-expanding the inferred type at each
|
|
104
|
+
* `export const xActions = precompileActions(...)` site. Without this the
|
|
105
|
+
* emitted `.d.ts` grows superlinearly with the number of decorators.
|
|
106
|
+
*/
|
|
107
|
+
export type PrecompileActions<
|
|
108
|
+
abi extends Abi,
|
|
109
|
+
mode extends 'read' | 'write' | 'all' = 'all',
|
|
110
|
+
> = mode extends 'read'
|
|
111
|
+
? PrecompileReadActions<abi>
|
|
112
|
+
: mode extends 'write'
|
|
113
|
+
? PrecompileWriteActions<abi>
|
|
114
|
+
: PrecompileReadActions<abi> & PrecompileWriteActions<abi>
|
|
115
|
+
|
|
116
|
+
export type PrecompileActionsFactory<abi extends Abi, mode extends 'read' | 'write' | 'all'> = (
|
|
117
|
+
config?: PrecompileCallOptions,
|
|
118
|
+
) => <transport extends Transport, chain extends Chain | undefined, account extends Account | undefined>(
|
|
119
|
+
client: Client<transport, chain, account>,
|
|
120
|
+
) => PrecompileActions<abi, mode>
|
|
121
|
+
|
|
122
|
+
const isRead = (m: string) => m === 'view' || m === 'pure'
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build a typed action map from an `as const` ABI by walking it at runtime.
|
|
126
|
+
*
|
|
127
|
+
* Replaces per-precompile codegen: every function in `abi` becomes one method,
|
|
128
|
+
* dispatched to `readContract` or `writeContract` by its state mutability.
|
|
129
|
+
*
|
|
130
|
+
* Overloads that agree on mutability share one dispatcher (viem resolves the
|
|
131
|
+
* overload from `args`). Overloads that mix read and write mutability throw at
|
|
132
|
+
* factory construction — a single dispatcher would silently misroute one side.
|
|
133
|
+
*
|
|
134
|
+
* @param abi - The precompile interface, as an `as const` ABI.
|
|
135
|
+
* @param defaultAddress - Where the precompile lives on a stock Maroo chain.
|
|
136
|
+
* @param options.mode - Emit only reads, only writes, or both.
|
|
137
|
+
* @param options.exclude - Function names to omit (codegen dummies, proxy hooks).
|
|
138
|
+
*/
|
|
139
|
+
export function precompileActions<
|
|
140
|
+
const abi extends Abi,
|
|
141
|
+
mode extends 'read' | 'write' | 'all' = 'all',
|
|
142
|
+
>(
|
|
143
|
+
abi: abi,
|
|
144
|
+
defaultAddress: Address,
|
|
145
|
+
options: { mode?: mode; exclude?: readonly string[] } = {},
|
|
146
|
+
): PrecompileActionsFactory<abi, mode> {
|
|
147
|
+
const { mode = 'all' as mode, exclude = [] } = options
|
|
148
|
+
const excluded = new Set<string>(exclude)
|
|
149
|
+
|
|
150
|
+
// Fail fast on mixed-mutability overloads, before any client exists.
|
|
151
|
+
const classification = new Map<string, boolean>()
|
|
152
|
+
const payableNames = new Set<string>()
|
|
153
|
+
for (const item of abi) {
|
|
154
|
+
if (item.type !== 'function' || excluded.has(item.name)) continue
|
|
155
|
+
const read = isRead(item.stateMutability)
|
|
156
|
+
const prev = classification.get(item.name)
|
|
157
|
+
if (prev !== undefined && prev !== read)
|
|
158
|
+
throw new Error(
|
|
159
|
+
`precompileActions: "${item.name}" has overloads with mixed read/write mutability; ` +
|
|
160
|
+
'add it to options.exclude and wrap it by hand',
|
|
161
|
+
)
|
|
162
|
+
classification.set(item.name, read)
|
|
163
|
+
if (item.stateMutability === 'payable') payableNames.add(item.name)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (config = {}) =>
|
|
167
|
+
(client) => {
|
|
168
|
+
const actions: Record<string, unknown> = {}
|
|
169
|
+
|
|
170
|
+
for (const item of abi) {
|
|
171
|
+
if (item.type !== 'function') continue
|
|
172
|
+
if (excluded.has(item.name)) continue
|
|
173
|
+
if (actions[item.name]) continue // same-mutability overload: one dispatcher
|
|
174
|
+
|
|
175
|
+
const read = isRead(item.stateMutability)
|
|
176
|
+
if (mode === 'read' && !read) continue
|
|
177
|
+
if (mode === 'write' && read) continue
|
|
178
|
+
|
|
179
|
+
const functionName = item.name
|
|
180
|
+
// Any same-name overload being payable admits `value` (matches WriteExtra).
|
|
181
|
+
const payable = payableNames.has(functionName)
|
|
182
|
+
|
|
183
|
+
const resolveCall = (parameters: Record<string, unknown>) => {
|
|
184
|
+
// `value` is deliberately dropped here; only payable writes re-add it.
|
|
185
|
+
const { address, args, value: _value, ...rest } = parameters
|
|
186
|
+
return {
|
|
187
|
+
...rest,
|
|
188
|
+
abi,
|
|
189
|
+
functionName,
|
|
190
|
+
args: (args as readonly unknown[] | undefined) ?? [],
|
|
191
|
+
address: (address as Address) ?? config.address ?? defaultAddress,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const fn = (parameters: Record<string, unknown> = {}) => {
|
|
196
|
+
const call = resolveCall(parameters)
|
|
197
|
+
if (read) return getAction(client, readContract, 'readContract')(call as never)
|
|
198
|
+
const value = payable ? (parameters.value as bigint | undefined) : undefined
|
|
199
|
+
return getAction(
|
|
200
|
+
client,
|
|
201
|
+
writeContract,
|
|
202
|
+
'writeContract',
|
|
203
|
+
)({ ...call, ...(value !== undefined ? { value } : {}) } as never)
|
|
204
|
+
}
|
|
205
|
+
// Reads also expose `.call` — a descriptor for multicall/simulate.
|
|
206
|
+
if (read)
|
|
207
|
+
(fn as { call?: unknown }).call = (parameters: Record<string, unknown> = {}) =>
|
|
208
|
+
resolveCall(parameters)
|
|
209
|
+
|
|
210
|
+
actions[functionName] = fn
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return actions as PrecompileActions<abi, mode>
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Abi, Account, Chain, Client, ContractFunctionName, Transport } from 'viem'
|
|
2
|
+
import { type RunOnPclParameters, type RunOnPclReturnType, runOnPcl } from '../actions/runOnPcl.js'
|
|
3
|
+
import {
|
|
4
|
+
type AgentReadActions,
|
|
5
|
+
type EasReadActions,
|
|
6
|
+
type OkrwReadActions,
|
|
7
|
+
type OkrwWriteActions,
|
|
8
|
+
type PclReadActions,
|
|
9
|
+
type PclWriteActions,
|
|
10
|
+
agentReadActions,
|
|
11
|
+
easReadActions,
|
|
12
|
+
okrwReadActions,
|
|
13
|
+
okrwWriteActions,
|
|
14
|
+
pclReadActions,
|
|
15
|
+
pclWriteActions,
|
|
16
|
+
} from './precompiles.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Namespaces are not stylistic here. All four precompiles expose a method
|
|
20
|
+
* named `getParams`, so a flat merge is impossible without renaming.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// ─── public ──────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export type MarooPublicActions = {
|
|
26
|
+
pcl: PclReadActions
|
|
27
|
+
okrw: OkrwReadActions
|
|
28
|
+
eas: EasReadActions
|
|
29
|
+
agent: AgentReadActions
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read actions for every Maroo precompile.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const client = createPublicClient({ chain: marooTestnet, transport: http() })
|
|
38
|
+
* .extend(marooPublicActions())
|
|
39
|
+
*
|
|
40
|
+
* const { policyAdmin, entrypoints } = await client.pcl.getParams()
|
|
41
|
+
* const { policies } = await client.pcl.globalPolicies()
|
|
42
|
+
* const [ids, page] = await client.agent.getAgentIds({ args: [wallet, pageRequest] })
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function marooPublicActions() {
|
|
46
|
+
return <transport extends Transport, chain extends Chain | undefined, account extends Account | undefined>(
|
|
47
|
+
client: Client<transport, chain, account>,
|
|
48
|
+
): MarooPublicActions => ({
|
|
49
|
+
pcl: pclReadActions()(client),
|
|
50
|
+
okrw: okrwReadActions()(client),
|
|
51
|
+
eas: easReadActions()(client),
|
|
52
|
+
agent: agentReadActions()(client),
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── wallet ──────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Write actions, grouped by who may actually call them.
|
|
60
|
+
*
|
|
61
|
+
* Everything under `pcl` and `okrw` is reachable, but most of it reverts for
|
|
62
|
+
* ordinary accounts:
|
|
63
|
+
*
|
|
64
|
+
* - `pcl.runOnPcl`, `pcl.deployPclProxy`, `pcl.registerContractPolicies`
|
|
65
|
+
* — callable by anyone.
|
|
66
|
+
* - `pcl.changeContractPolicies`, `pcl.removeContractPolicies`
|
|
67
|
+
* — only that contract's admin, fixed at registration time.
|
|
68
|
+
* - `pcl.setGlobalPolicies`, `pcl.removeGlobalPolicies`,
|
|
69
|
+
* `pcl.registerPolicyTemplate`, `pcl.removePolicyTemplate`
|
|
70
|
+
* — only `PclParams.policyAdmin`, a chain parameter.
|
|
71
|
+
* - `okrw.mint` — only `OkrwParams.minter`, a chain parameter.
|
|
72
|
+
*/
|
|
73
|
+
export type MarooWalletActions<
|
|
74
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
75
|
+
account extends Account | undefined = Account | undefined,
|
|
76
|
+
> = {
|
|
77
|
+
pcl: PclWriteActions & {
|
|
78
|
+
/**
|
|
79
|
+
* Call a contract through PCL so its contract-scoped policies apply.
|
|
80
|
+
* A direct call enforces global policies only, silently.
|
|
81
|
+
*/
|
|
82
|
+
runOnPcl: <
|
|
83
|
+
const abi extends Abi | readonly unknown[],
|
|
84
|
+
functionName extends ContractFunctionName<abi, 'nonpayable' | 'payable'>,
|
|
85
|
+
>(
|
|
86
|
+
parameters: RunOnPclParameters<abi, functionName>,
|
|
87
|
+
) => Promise<RunOnPclReturnType>
|
|
88
|
+
}
|
|
89
|
+
okrw: OkrwWriteActions
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* const client = createWalletClient({ chain: marooTestnet, account, transport: http() })
|
|
96
|
+
* .extend(marooWalletActions())
|
|
97
|
+
*
|
|
98
|
+
* const hash = await client.pcl.runOnPcl({
|
|
99
|
+
* address: token, abi: erc20Abi, functionName: 'transfer', args: [to, amount],
|
|
100
|
+
* })
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export function marooWalletActions() {
|
|
104
|
+
return <transport extends Transport, chain extends Chain | undefined, account extends Account | undefined>(
|
|
105
|
+
client: Client<transport, chain, account>,
|
|
106
|
+
): MarooWalletActions<chain, account> =>
|
|
107
|
+
({
|
|
108
|
+
pcl: {
|
|
109
|
+
...pclWriteActions()(client),
|
|
110
|
+
runOnPcl: (parameters: never) => runOnPcl(client, parameters),
|
|
111
|
+
},
|
|
112
|
+
okrw: okrwWriteActions()(client),
|
|
113
|
+
}) as MarooWalletActions<chain, account>
|
|
114
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { iAgentAbi } from '@maroo-chain/contracts/abi/precompiles/agent/IAgent'
|
|
2
|
+
import { iEasAbi } from '@maroo-chain/contracts/abi/precompiles/eas/IEas'
|
|
3
|
+
import { iOkrwAbi } from '@maroo-chain/contracts/abi/precompiles/okrw/IOkrw'
|
|
4
|
+
import { iPclAbi } from '@maroo-chain/contracts/abi/precompiles/pcl/IPcl'
|
|
5
|
+
import { type PrecompileActions, precompileActions } from '../core/precompileActions.js'
|
|
6
|
+
import { excludedFunctions, precompileAddresses } from '../constants.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Per-precompile decorators, generated from the shipped ABIs.
|
|
10
|
+
*
|
|
11
|
+
* `eas` and `agent` are view-only; `okrw` exposes exactly one write (`mint`,
|
|
12
|
+
* restricted to `OkrwParams.minter`). None of them need hand-written actions.
|
|
13
|
+
*
|
|
14
|
+
* Each type alias below is named and exported deliberately — see the note on
|
|
15
|
+
* {@link PrecompileActions}.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ─── read ────────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export type PclReadActions = PrecompileActions<typeof iPclAbi, 'read'>
|
|
21
|
+
export const pclReadActions = precompileActions(iPclAbi, precompileAddresses.pcl, {
|
|
22
|
+
mode: 'read',
|
|
23
|
+
exclude: excludedFunctions.pcl,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export type OkrwReadActions = PrecompileActions<typeof iOkrwAbi, 'read'>
|
|
27
|
+
export const okrwReadActions = precompileActions(iOkrwAbi, precompileAddresses.okrw, { mode: 'read' })
|
|
28
|
+
|
|
29
|
+
export type EasReadActions = PrecompileActions<typeof iEasAbi, 'read'>
|
|
30
|
+
export const easReadActions = precompileActions(iEasAbi, precompileAddresses.eas, { mode: 'read' })
|
|
31
|
+
|
|
32
|
+
export type AgentReadActions = PrecompileActions<typeof iAgentAbi, 'read'>
|
|
33
|
+
export const agentReadActions = precompileActions(iAgentAbi, precompileAddresses.agent, { mode: 'read' })
|
|
34
|
+
|
|
35
|
+
// ─── write ───────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export type PclWriteActions = PrecompileActions<typeof iPclAbi, 'write'>
|
|
38
|
+
export const pclWriteActions = precompileActions(iPclAbi, precompileAddresses.pcl, {
|
|
39
|
+
mode: 'write',
|
|
40
|
+
exclude: excludedFunctions.pcl,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export type OkrwWriteActions = PrecompileActions<typeof iOkrwAbi, 'write'>
|
|
44
|
+
export const okrwWriteActions = precompileActions(iOkrwAbi, precompileAddresses.okrw, { mode: 'write' })
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { iPclAbi } from '@maroo-chain/contracts/abi/precompiles/pcl/IPcl'
|
|
2
|
+
import { BaseError, ContractFunctionRevertedError, type Hex, decodeErrorResult } from 'viem'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A PCL policy rejection, decoded into something a dApp can act on.
|
|
6
|
+
*
|
|
7
|
+
* viem already decodes the custom error name and args from the revert data
|
|
8
|
+
* once it has the ABI. What it cannot do is tell you which of the 40 errors
|
|
9
|
+
* mean "a policy rejected this user" as opposed to "you passed bad input" or
|
|
10
|
+
* "you are not the admin" — nor turn `resetAt` into a `Date`.
|
|
11
|
+
*/
|
|
12
|
+
export type PclViolation =
|
|
13
|
+
| { kind: 'denylist'; sender: Hex }
|
|
14
|
+
| { kind: 'volume-below-min'; minLimit: bigint; value: bigint }
|
|
15
|
+
| { kind: 'volume-above-max'; maxLimit: bigint; value: bigint }
|
|
16
|
+
| { kind: 'periodic-volume'; maxLimit: bigint; value: bigint; resetAt: Date }
|
|
17
|
+
| { kind: 'non-eas-limit'; maxLimit: bigint; value: bigint }
|
|
18
|
+
| { kind: 'agent-transfer-limit'; maxLimit: bigint; value: bigint }
|
|
19
|
+
| {
|
|
20
|
+
kind: 'attestation'
|
|
21
|
+
reason: 'required' | 'none-received' | 'lookup-failed' | 'revoked' | 'expired'
|
|
22
|
+
sender: Hex
|
|
23
|
+
}
|
|
24
|
+
/** `AnyOfRejected` — every branch of a LOGICAL(Or) failed. Each child revert, decoded. */
|
|
25
|
+
| { kind: 'any-of-rejected'; children: (PclViolation | { kind: 'unknown'; name: string })[] }
|
|
26
|
+
| { kind: 'unauthorized' }
|
|
27
|
+
| { kind: 'unknown'; name: string; args: readonly unknown[] }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The errors PCL actually declares. Used to refuse errors we do not own —
|
|
31
|
+
* `decodeErrorResult` also recognises the built-in `Error(string)` / `Panic`
|
|
32
|
+
* signatures, and those must decode to `null`, not to a policy verdict.
|
|
33
|
+
*/
|
|
34
|
+
const PCL_ERROR_NAMES: ReadonlySet<string> = new Set(
|
|
35
|
+
iPclAbi.flatMap((item) => (item.type === 'error' ? [item.name] : [])),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `AnyOfRejected` nests (a LOGICAL(Or) branch can itself be a LOGICAL(Or)),
|
|
40
|
+
* and revert bytes are attacker-controlled via `eth_call` — cap the recursion
|
|
41
|
+
* like the policy codec caps its tree depth. Past the cap, children surface
|
|
42
|
+
* as `undecodable` instead of overflowing the stack inside an error handler.
|
|
43
|
+
*/
|
|
44
|
+
const MAX_ERROR_DEPTH = 8
|
|
45
|
+
|
|
46
|
+
type AttestationReason = Extract<PclViolation, { kind: 'attestation' }>['reason']
|
|
47
|
+
|
|
48
|
+
const ATTESTATION: Record<string, AttestationReason> = {
|
|
49
|
+
EasAttestationRequired: 'required',
|
|
50
|
+
EasNoAttestationReceived: 'none-received',
|
|
51
|
+
EasAttestationLookupFailed: 'lookup-failed',
|
|
52
|
+
EasAttestationRevoked: 'revoked',
|
|
53
|
+
EasAttestationExpired: 'expired',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toViolation(name: string, args: readonly unknown[], depth: number): PclViolation {
|
|
57
|
+
const a = args as never[]
|
|
58
|
+
switch (name) {
|
|
59
|
+
case 'InDenylist':
|
|
60
|
+
return { kind: 'denylist', sender: a[0] }
|
|
61
|
+
case 'VolumeBelowMinLimit':
|
|
62
|
+
return { kind: 'volume-below-min', minLimit: a[0], value: a[1] }
|
|
63
|
+
case 'VolumeAboveMaxLimit':
|
|
64
|
+
return { kind: 'volume-above-max', maxLimit: a[0], value: a[1] }
|
|
65
|
+
case 'ExceededPeriodicVolume':
|
|
66
|
+
return {
|
|
67
|
+
kind: 'periodic-volume',
|
|
68
|
+
maxLimit: a[0],
|
|
69
|
+
value: a[1],
|
|
70
|
+
resetAt: new Date(Number(a[2] as bigint) * 1000),
|
|
71
|
+
}
|
|
72
|
+
case 'ReachedLimitOfNonEAS':
|
|
73
|
+
return { kind: 'non-eas-limit', maxLimit: a[0], value: a[1] }
|
|
74
|
+
case 'ExceededAgentTransferLimit':
|
|
75
|
+
return { kind: 'agent-transfer-limit', maxLimit: a[0], value: a[1] }
|
|
76
|
+
case 'Unauthorized':
|
|
77
|
+
return { kind: 'unauthorized' }
|
|
78
|
+
case 'AnyOfRejected':
|
|
79
|
+
return {
|
|
80
|
+
kind: 'any-of-rejected',
|
|
81
|
+
children: ((a[0] as Hex[]) ?? []).map(
|
|
82
|
+
(child) => decode(child, depth + 1) ?? { kind: 'unknown', name: 'undecodable' },
|
|
83
|
+
),
|
|
84
|
+
}
|
|
85
|
+
default:
|
|
86
|
+
if (name in ATTESTATION) return { kind: 'attestation', reason: ATTESTATION[name], sender: a[0] }
|
|
87
|
+
return { kind: 'unknown', name, args }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function decode(source: Hex | unknown, depth: number): PclViolation | null {
|
|
92
|
+
if (depth > MAX_ERROR_DEPTH) return null
|
|
93
|
+
|
|
94
|
+
let data: Hex | undefined
|
|
95
|
+
|
|
96
|
+
if (typeof source === 'string' && source.startsWith('0x')) {
|
|
97
|
+
data = source as Hex
|
|
98
|
+
} else if (source instanceof BaseError) {
|
|
99
|
+
const revert = source.walk((e) => e instanceof ContractFunctionRevertedError)
|
|
100
|
+
// Always decode from the raw revert bytes, never from viem's pre-decoded
|
|
101
|
+
// errorName: viem decoded against the *call's* ABI, and other precompiles
|
|
102
|
+
// declare errors sharing a PCL error's name with a different signature
|
|
103
|
+
// (okrw: InvalidAddress(address), agent: InvalidAddress(string,uint256,string),
|
|
104
|
+
// pcl: InvalidAddress(string)). Only the 4-byte selector — which
|
|
105
|
+
// decodeErrorResult matches below — tells them apart.
|
|
106
|
+
if (revert instanceof ContractFunctionRevertedError) data = revert.raw
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!data || data === '0x') return null
|
|
110
|
+
try {
|
|
111
|
+
const { errorName, args } = decodeErrorResult({ abi: iPclAbi, data })
|
|
112
|
+
if (!PCL_ERROR_NAMES.has(errorName)) return null
|
|
113
|
+
return toViolation(errorName, args ?? [], depth)
|
|
114
|
+
} catch {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Decode PCL revert data — or a thrown viem error wrapping it — into a
|
|
121
|
+
* {@link PclViolation}. Returns `null` if the revert is not a PCL error.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* try {
|
|
126
|
+
* await client.pcl.runOnPcl({ ... })
|
|
127
|
+
* } catch (err) {
|
|
128
|
+
* const v = decodePclError(err)
|
|
129
|
+
* if (v?.kind === 'periodic-volume')
|
|
130
|
+
* throw new Error(`24h limit hit. Resets ${v.resetAt.toISOString()}`)
|
|
131
|
+
* throw err
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function decodePclError(source: Hex | unknown): PclViolation | null {
|
|
136
|
+
return decode(source, 0)
|
|
137
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// biome-ignore lint/performance/noBarrelFile: entrypoint module
|
|
2
|
+
|
|
3
|
+
export { precompileAddresses, easContracts, SELECTOR_ALL, NO_MAX_LIMIT } from './constants.js'
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
type PrecompileActions,
|
|
7
|
+
type PrecompileCallOptions,
|
|
8
|
+
type PrecompileReadActions,
|
|
9
|
+
type PrecompileReadCall,
|
|
10
|
+
type PrecompileWriteActions,
|
|
11
|
+
precompileActions,
|
|
12
|
+
} from './core/precompileActions.js'
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
type MarooPublicActions,
|
|
16
|
+
type MarooWalletActions,
|
|
17
|
+
marooPublicActions,
|
|
18
|
+
marooWalletActions,
|
|
19
|
+
} from './decorators/maroo.js'
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
type AgentReadActions,
|
|
23
|
+
type EasReadActions,
|
|
24
|
+
type OkrwReadActions,
|
|
25
|
+
type OkrwWriteActions,
|
|
26
|
+
type PclReadActions,
|
|
27
|
+
type PclWriteActions,
|
|
28
|
+
agentReadActions,
|
|
29
|
+
easReadActions,
|
|
30
|
+
okrwReadActions,
|
|
31
|
+
okrwWriteActions,
|
|
32
|
+
pclReadActions,
|
|
33
|
+
pclWriteActions,
|
|
34
|
+
} from './decorators/precompiles.js'
|
|
35
|
+
|
|
36
|
+
export { type RunOnPclParameters, type RunOnPclReturnType, runOnPcl } from './actions/runOnPcl.js'
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
type DecodedPolicy,
|
|
40
|
+
type PolicyInput,
|
|
41
|
+
type PolicyTemplateId,
|
|
42
|
+
type RawPolicySet,
|
|
43
|
+
ForEachQuantifier,
|
|
44
|
+
ForEachSubject,
|
|
45
|
+
LogicalQuantifier,
|
|
46
|
+
decodePolicySet,
|
|
47
|
+
encodePolicy,
|
|
48
|
+
normalizeSelector,
|
|
49
|
+
policyCodecs,
|
|
50
|
+
policyTemplateIds,
|
|
51
|
+
} from './codec/policy.js'
|
|
52
|
+
|
|
53
|
+
export { type PclViolation, decodePclError } from './errors/pcl.js'
|
|
54
|
+
|
|
55
|
+
// `marooTestnet` is deliberately NOT re-exported here: it ships in viem only
|
|
56
|
+
// from 2.55+, and re-exporting it would crash this whole package at import
|
|
57
|
+
// time for consumers on older viem within our ^2.44.0 peer range. Import it
|
|
58
|
+
// from 'viem/chains' directly.
|