@0xsequence/wallet-primitives 0.0.0-20250520201059

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +7 -0
  3. package/LICENSE +202 -0
  4. package/dist/address.d.ts +5 -0
  5. package/dist/address.d.ts.map +1 -0
  6. package/dist/address.js +7 -0
  7. package/dist/address.js.map +1 -0
  8. package/dist/attestation.d.ts +24 -0
  9. package/dist/attestation.d.ts.map +1 -0
  10. package/dist/attestation.js +77 -0
  11. package/dist/attestation.js.map +1 -0
  12. package/dist/config.d.ts +85 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +381 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/constants.d.ts +173 -0
  17. package/dist/constants.d.ts.map +1 -0
  18. package/dist/constants.js +31 -0
  19. package/dist/constants.js.map +1 -0
  20. package/dist/context.d.ts +9 -0
  21. package/dist/context.d.ts.map +1 -0
  22. package/dist/context.js +8 -0
  23. package/dist/context.js.map +1 -0
  24. package/dist/erc-6492.d.ts +19 -0
  25. package/dist/erc-6492.d.ts.map +1 -0
  26. package/dist/erc-6492.js +64 -0
  27. package/dist/erc-6492.js.map +1 -0
  28. package/dist/extensions/index.d.ts +9 -0
  29. package/dist/extensions/index.d.ts.map +1 -0
  30. package/dist/extensions/index.js +7 -0
  31. package/dist/extensions/index.js.map +1 -0
  32. package/dist/extensions/passkeys.d.ts +31 -0
  33. package/dist/extensions/passkeys.d.ts.map +1 -0
  34. package/dist/extensions/passkeys.js +224 -0
  35. package/dist/extensions/passkeys.js.map +1 -0
  36. package/dist/extensions/recovery.d.ts +310 -0
  37. package/dist/extensions/recovery.d.ts.map +1 -0
  38. package/dist/extensions/recovery.js +444 -0
  39. package/dist/extensions/recovery.js.map +1 -0
  40. package/dist/generic-tree.d.ts +14 -0
  41. package/dist/generic-tree.d.ts.map +1 -0
  42. package/dist/generic-tree.js +34 -0
  43. package/dist/generic-tree.js.map +1 -0
  44. package/dist/index.d.ts +16 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +16 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/network.d.ts +15 -0
  49. package/dist/network.d.ts.map +1 -0
  50. package/dist/network.js +24 -0
  51. package/dist/network.js.map +1 -0
  52. package/dist/payload.d.ts +108 -0
  53. package/dist/payload.d.ts.map +1 -0
  54. package/dist/payload.js +627 -0
  55. package/dist/payload.js.map +1 -0
  56. package/dist/permission.d.ts +73 -0
  57. package/dist/permission.d.ts.map +1 -0
  58. package/dist/permission.js +188 -0
  59. package/dist/permission.js.map +1 -0
  60. package/dist/session-config.d.ts +113 -0
  61. package/dist/session-config.d.ts.map +1 -0
  62. package/dist/session-config.js +554 -0
  63. package/dist/session-config.js.map +1 -0
  64. package/dist/session-signature.d.ts +24 -0
  65. package/dist/session-signature.d.ts.map +1 -0
  66. package/dist/session-signature.js +141 -0
  67. package/dist/session-signature.js.map +1 -0
  68. package/dist/signature.d.ts +108 -0
  69. package/dist/signature.d.ts.map +1 -0
  70. package/dist/signature.js +1079 -0
  71. package/dist/signature.js.map +1 -0
  72. package/dist/utils.d.ts +45 -0
  73. package/dist/utils.d.ts.map +1 -0
  74. package/dist/utils.js +100 -0
  75. package/dist/utils.js.map +1 -0
  76. package/eslint.config.mjs +4 -0
  77. package/package.json +27 -0
  78. package/src/address.ts +19 -0
  79. package/src/attestation.ts +114 -0
  80. package/src/config.ts +521 -0
  81. package/src/constants.ts +39 -0
  82. package/src/context.ts +16 -0
  83. package/src/erc-6492.ts +97 -0
  84. package/src/extensions/index.ts +14 -0
  85. package/src/extensions/passkeys.ts +283 -0
  86. package/src/extensions/recovery.ts +542 -0
  87. package/src/generic-tree.ts +55 -0
  88. package/src/index.ts +15 -0
  89. package/src/network.ts +37 -0
  90. package/src/payload.ts +825 -0
  91. package/src/permission.ts +252 -0
  92. package/src/session-config.ts +681 -0
  93. package/src/session-signature.ts +197 -0
  94. package/src/signature.ts +1398 -0
  95. package/src/utils.ts +114 -0
  96. package/tsconfig.json +10 -0
package/src/config.ts ADDED
@@ -0,0 +1,521 @@
1
+ import { Address, Bytes, Hash, Hex } from 'ox'
2
+ import {
3
+ isRawConfig,
4
+ isRawNestedLeaf,
5
+ isRawSignerLeaf,
6
+ isSignedSapientSignerLeaf,
7
+ isSignedSignerLeaf,
8
+ RawConfig,
9
+ RawTopology,
10
+ SignatureOfSapientSignerLeaf,
11
+ SignatureOfSignerLeaf,
12
+ } from './signature.js'
13
+
14
+ export type SignerLeaf = {
15
+ type: 'signer'
16
+ address: Address.Address
17
+ weight: bigint
18
+ signed?: boolean
19
+ signature?: SignatureOfSignerLeaf
20
+ }
21
+
22
+ export type SapientSignerLeaf = {
23
+ type: 'sapient-signer'
24
+ address: Address.Address
25
+ weight: bigint
26
+ imageHash: Hex.Hex
27
+ signed?: boolean
28
+ signature?: SignatureOfSapientSignerLeaf
29
+ }
30
+
31
+ export type SubdigestLeaf = {
32
+ type: 'subdigest'
33
+ digest: Hex.Hex
34
+ }
35
+
36
+ export type AnyAddressSubdigestLeaf = {
37
+ type: 'any-address-subdigest'
38
+ digest: Hex.Hex
39
+ }
40
+
41
+ export type NestedLeaf = {
42
+ type: 'nested'
43
+ tree: Topology
44
+ weight: bigint
45
+ threshold: bigint
46
+ }
47
+
48
+ export type NodeLeaf = Hex.Hex
49
+
50
+ export type Node = [Topology, Topology]
51
+
52
+ export type Leaf = SignerLeaf | SapientSignerLeaf | SubdigestLeaf | AnyAddressSubdigestLeaf | NestedLeaf | NodeLeaf
53
+
54
+ export type Topology = Node | Leaf
55
+
56
+ export type Config = {
57
+ threshold: bigint
58
+ checkpoint: bigint
59
+ topology: Topology
60
+ checkpointer?: Address.Address
61
+ }
62
+
63
+ export function isSignerLeaf(cand: any): cand is SignerLeaf {
64
+ return typeof cand === 'object' && cand !== null && cand.type === 'signer'
65
+ }
66
+
67
+ export function isSapientSignerLeaf(cand: any): cand is SapientSignerLeaf {
68
+ return typeof cand === 'object' && cand !== null && cand.type === 'sapient-signer'
69
+ }
70
+
71
+ export function isSubdigestLeaf(cand: any): cand is SubdigestLeaf {
72
+ return typeof cand === 'object' && cand !== null && cand.type === 'subdigest'
73
+ }
74
+
75
+ export function isAnyAddressSubdigestLeaf(cand: any): cand is AnyAddressSubdigestLeaf {
76
+ return typeof cand === 'object' && cand !== null && cand.type === 'any-address-subdigest'
77
+ }
78
+
79
+ export function isNodeLeaf(cand: any): cand is NodeLeaf {
80
+ return Hex.validate(cand) && cand.length === 66
81
+ }
82
+
83
+ export function isNestedLeaf(cand: any): cand is NestedLeaf {
84
+ return typeof cand === 'object' && cand !== null && cand.type === 'nested'
85
+ }
86
+
87
+ export function isNode(cand: any): cand is Node {
88
+ return Array.isArray(cand) && cand.length === 2 && isTopology(cand[0]) && isTopology(cand[1])
89
+ }
90
+
91
+ export function isConfig(cand: any): cand is Config {
92
+ return typeof cand === 'object' && 'threshold' in cand && 'checkpoint' in cand && 'topology' in cand
93
+ }
94
+
95
+ export function isLeaf(cand: Topology): cand is Leaf {
96
+ return (
97
+ isSignerLeaf(cand) ||
98
+ isSapientSignerLeaf(cand) ||
99
+ isSubdigestLeaf(cand) ||
100
+ isAnyAddressSubdigestLeaf(cand) ||
101
+ isNodeLeaf(cand) ||
102
+ isNestedLeaf(cand)
103
+ )
104
+ }
105
+
106
+ export function isTopology(cand: any): cand is Topology {
107
+ return isNode(cand) || isLeaf(cand)
108
+ }
109
+
110
+ export function getSigners(configuration: Config | Topology): {
111
+ signers: Address.Address[]
112
+ sapientSigners: { address: Address.Address; imageHash: Hex.Hex }[]
113
+ isComplete: boolean
114
+ } {
115
+ const signers = new Set<Address.Address>()
116
+ const sapientSigners = new Set<{ address: Address.Address; imageHash: Hex.Hex }>()
117
+
118
+ let isComplete = true
119
+
120
+ const scan = (topology: Topology) => {
121
+ if (isNode(topology)) {
122
+ scan(topology[0])
123
+ scan(topology[1])
124
+ } else if (isSignerLeaf(topology)) {
125
+ if (topology.weight) {
126
+ signers.add(topology.address)
127
+ }
128
+ } else if (isSapientSignerLeaf(topology)) {
129
+ sapientSigners.add({ address: topology.address, imageHash: topology.imageHash })
130
+ } else if (isNodeLeaf(topology)) {
131
+ isComplete = false
132
+ } else if (isNestedLeaf(topology)) {
133
+ if (topology.weight) {
134
+ scan(topology.tree)
135
+ }
136
+ }
137
+ }
138
+
139
+ scan(isConfig(configuration) ? configuration.topology : configuration)
140
+ return { signers: Array.from(signers), sapientSigners: Array.from(sapientSigners), isComplete }
141
+ }
142
+
143
+ export function findSignerLeaf(
144
+ configuration: Config | Topology,
145
+ address: Address.Address,
146
+ ): SignerLeaf | SapientSignerLeaf | undefined {
147
+ if (isConfig(configuration)) {
148
+ return findSignerLeaf(configuration.topology, address)
149
+ } else if (isNode(configuration)) {
150
+ return findSignerLeaf(configuration[0], address) || findSignerLeaf(configuration[1], address)
151
+ } else if (isSignerLeaf(configuration)) {
152
+ if (Address.isEqual(configuration.address, address)) {
153
+ return configuration
154
+ }
155
+ } else if (isSapientSignerLeaf(configuration)) {
156
+ if (Address.isEqual(configuration.address, address)) {
157
+ return configuration
158
+ }
159
+ }
160
+ return undefined
161
+ }
162
+
163
+ export function getWeight(
164
+ topology: RawTopology | RawConfig | Config,
165
+ canSign: (signer: SignerLeaf | SapientSignerLeaf) => boolean,
166
+ ): { weight: bigint; maxWeight: bigint } {
167
+ topology = isRawConfig(topology) || isConfig(topology) ? topology.topology : topology
168
+
169
+ if (isSignedSignerLeaf(topology)) {
170
+ return { weight: topology.weight, maxWeight: topology.weight }
171
+ } else if (isSignerLeaf(topology)) {
172
+ return { weight: 0n, maxWeight: canSign(topology) ? topology.weight : 0n }
173
+ } else if (isRawSignerLeaf(topology)) {
174
+ return { weight: topology.weight, maxWeight: topology.weight }
175
+ } else if (isSignedSapientSignerLeaf(topology)) {
176
+ return { weight: topology.weight, maxWeight: topology.weight }
177
+ } else if (isSapientSignerLeaf(topology)) {
178
+ return { weight: 0n, maxWeight: canSign(topology) ? topology.weight : 0n }
179
+ } else if (isSubdigestLeaf(topology)) {
180
+ return { weight: 0n, maxWeight: 0n }
181
+ } else if (isAnyAddressSubdigestLeaf(topology)) {
182
+ return { weight: 0n, maxWeight: 0n }
183
+ } else if (isRawNestedLeaf(topology)) {
184
+ const { weight, maxWeight } = getWeight(topology.tree, canSign)
185
+ return {
186
+ weight: weight >= topology.threshold ? topology.weight : 0n,
187
+ maxWeight: maxWeight >= topology.threshold ? topology.weight : 0n,
188
+ }
189
+ } else if (isNodeLeaf(topology)) {
190
+ return { weight: 0n, maxWeight: 0n }
191
+ } else {
192
+ const [left, right] = [getWeight(topology[0], canSign), getWeight(topology[1], canSign)]
193
+ return { weight: left.weight + right.weight, maxWeight: left.maxWeight + right.maxWeight }
194
+ }
195
+ }
196
+
197
+ export function hashConfiguration(topology: Topology | Config): Bytes.Bytes {
198
+ if (isConfig(topology)) {
199
+ let root = hashConfiguration(topology.topology)
200
+ root = Hash.keccak256(Bytes.concat(root, Bytes.padLeft(Bytes.fromNumber(topology.threshold), 32)))
201
+ root = Hash.keccak256(Bytes.concat(root, Bytes.padLeft(Bytes.fromNumber(topology.checkpoint), 32)))
202
+ root = Hash.keccak256(
203
+ Bytes.concat(
204
+ root,
205
+ Bytes.padLeft(Bytes.fromHex(topology.checkpointer ?? '0x0000000000000000000000000000000000000000'), 32),
206
+ ),
207
+ )
208
+ return root
209
+ }
210
+
211
+ if (isSignerLeaf(topology)) {
212
+ return Hash.keccak256(
213
+ Bytes.concat(
214
+ Bytes.fromString('Sequence signer:\n'),
215
+ Bytes.fromHex(topology.address),
216
+ Bytes.padLeft(Bytes.fromNumber(topology.weight), 32),
217
+ ),
218
+ )
219
+ }
220
+
221
+ if (isSapientSignerLeaf(topology)) {
222
+ return Hash.keccak256(
223
+ Bytes.concat(
224
+ Bytes.fromString('Sequence sapient config:\n'),
225
+ Bytes.fromHex(topology.address),
226
+ Bytes.padLeft(Bytes.fromNumber(topology.weight), 32),
227
+ Bytes.padLeft(Bytes.fromHex(topology.imageHash), 32),
228
+ ),
229
+ )
230
+ }
231
+
232
+ if (isSubdigestLeaf(topology)) {
233
+ return Hash.keccak256(Bytes.concat(Bytes.fromString('Sequence static digest:\n'), Bytes.fromHex(topology.digest)))
234
+ }
235
+
236
+ if (isAnyAddressSubdigestLeaf(topology)) {
237
+ return Hash.keccak256(
238
+ Bytes.concat(Bytes.fromString('Sequence any address subdigest:\n'), Bytes.fromHex(topology.digest)),
239
+ )
240
+ }
241
+
242
+ if (isNodeLeaf(topology)) {
243
+ return Bytes.fromHex(topology)
244
+ }
245
+
246
+ if (isNestedLeaf(topology)) {
247
+ return Hash.keccak256(
248
+ Bytes.concat(
249
+ Bytes.fromString('Sequence nested config:\n'),
250
+ hashConfiguration(topology.tree),
251
+ Bytes.padLeft(Bytes.fromNumber(topology.threshold), 32),
252
+ Bytes.padLeft(Bytes.fromNumber(topology.weight), 32),
253
+ ),
254
+ )
255
+ }
256
+
257
+ if (isNode(topology)) {
258
+ return Hash.keccak256(Bytes.concat(hashConfiguration(topology[0]), hashConfiguration(topology[1])))
259
+ }
260
+
261
+ throw new Error('Invalid topology')
262
+ }
263
+
264
+ export function flatLeavesToTopology(leaves: Leaf[]): Topology {
265
+ if (leaves.length === 0) {
266
+ throw new Error('Cannot create topology from empty leaves')
267
+ }
268
+
269
+ if (leaves.length === 1) {
270
+ return leaves[0]!
271
+ }
272
+
273
+ if (leaves.length === 2) {
274
+ return [leaves[0]!, leaves[1]!]
275
+ }
276
+
277
+ return [
278
+ flatLeavesToTopology(leaves.slice(0, leaves.length / 2)),
279
+ flatLeavesToTopology(leaves.slice(leaves.length / 2)),
280
+ ]
281
+ }
282
+
283
+ export function configToJson(config: Config): string {
284
+ return JSON.stringify({
285
+ threshold: config.threshold.toString(),
286
+ checkpoint: config.checkpoint.toString(),
287
+ topology: encodeTopology(config.topology),
288
+ checkpointer: config.checkpointer,
289
+ })
290
+ }
291
+
292
+ export function configFromJson(json: string): Config {
293
+ const parsed = JSON.parse(json)
294
+ return {
295
+ threshold: BigInt(parsed.threshold),
296
+ checkpoint: BigInt(parsed.checkpoint),
297
+ checkpointer: parsed.checkpointer,
298
+ topology: decodeTopology(parsed.topology),
299
+ }
300
+ }
301
+
302
+ function encodeTopology(top: Topology): any {
303
+ if (isNode(top)) {
304
+ return [encodeTopology(top[0]), encodeTopology(top[1])]
305
+ } else if (isSignerLeaf(top)) {
306
+ return {
307
+ type: 'signer',
308
+ address: top.address,
309
+ weight: top.weight.toString(),
310
+ }
311
+ } else if (isSapientSignerLeaf(top)) {
312
+ return {
313
+ type: 'sapient-signer',
314
+ address: top.address,
315
+ weight: top.weight.toString(),
316
+ imageHash: top.imageHash,
317
+ }
318
+ } else if (isSubdigestLeaf(top)) {
319
+ return {
320
+ type: 'subdigest',
321
+ digest: top.digest,
322
+ }
323
+ } else if (isAnyAddressSubdigestLeaf(top)) {
324
+ return {
325
+ type: 'any-address-subdigest',
326
+ digest: top.digest,
327
+ }
328
+ } else if (isNodeLeaf(top)) {
329
+ return top
330
+ } else if (isNestedLeaf(top)) {
331
+ return {
332
+ type: 'nested',
333
+ tree: encodeTopology(top.tree),
334
+ weight: top.weight.toString(),
335
+ threshold: top.threshold.toString(),
336
+ }
337
+ }
338
+
339
+ throw new Error('Invalid topology')
340
+ }
341
+
342
+ function decodeTopology(obj: any): Topology {
343
+ if (Array.isArray(obj)) {
344
+ if (obj.length !== 2) {
345
+ throw new Error('Invalid node structure in JSON')
346
+ }
347
+ return [decodeTopology(obj[0]), decodeTopology(obj[1])]
348
+ }
349
+
350
+ if (typeof obj === 'string') {
351
+ return obj as Hex.Hex
352
+ }
353
+
354
+ switch (obj.type) {
355
+ case 'signer':
356
+ return {
357
+ type: 'signer',
358
+ address: obj.address,
359
+ weight: BigInt(obj.weight),
360
+ }
361
+ case 'sapient-signer':
362
+ return {
363
+ type: 'sapient-signer',
364
+ address: obj.address,
365
+ weight: BigInt(obj.weight),
366
+ imageHash: obj.imageHash,
367
+ }
368
+ case 'subdigest':
369
+ return {
370
+ type: 'subdigest',
371
+ digest: obj.digest,
372
+ }
373
+ case 'any-address-subdigest':
374
+ return {
375
+ type: 'any-address-subdigest',
376
+ digest: obj.digest,
377
+ }
378
+ case 'nested':
379
+ return {
380
+ type: 'nested',
381
+ tree: decodeTopology(obj.tree),
382
+ weight: BigInt(obj.weight),
383
+ threshold: BigInt(obj.threshold),
384
+ }
385
+ default:
386
+ throw new Error('Invalid type in topology JSON')
387
+ }
388
+ }
389
+
390
+ export type SignerSignature<T> = [T] extends [Promise<unknown>]
391
+ ? never
392
+ : MaybePromise<T> | { signature: Promise<T>; onSignerSignature?: SignerSignatureCallback; onCancel?: CancelCallback }
393
+
394
+ export function normalizeSignerSignature<T>(signature: SignerSignature<T>): {
395
+ signature: Promise<T>
396
+ onSignerSignature?: SignerSignatureCallback
397
+ onCancel?: CancelCallback
398
+ } {
399
+ if (signature instanceof Promise) {
400
+ return { signature }
401
+ } else if (
402
+ typeof signature === 'object' &&
403
+ signature &&
404
+ 'signature' in signature &&
405
+ signature.signature instanceof Promise
406
+ ) {
407
+ return signature as ReturnType<typeof normalizeSignerSignature>
408
+ } else {
409
+ return { signature: Promise.resolve(signature) as Promise<T> }
410
+ }
411
+ }
412
+
413
+ export type SignerErrorCallback = (signer: SignerLeaf | SapientSignerLeaf, error: unknown) => void
414
+
415
+ type SignerSignatureCallback = (topology: RawTopology) => void
416
+ type CancelCallback = (success: boolean) => void
417
+ type MaybePromise<T> = T | Promise<T>
418
+
419
+ export function mergeTopology(a: Topology, b: Topology): Topology {
420
+ if (isNode(a) && isNode(b)) {
421
+ return [mergeTopology(a[0], b[0]), mergeTopology(a[1], b[1])]
422
+ }
423
+
424
+ if (isNode(a) && !isNode(b)) {
425
+ if (!isNodeLeaf(b)) {
426
+ throw new Error('Topology mismatch: cannot merge node with non-node that is not a node leaf')
427
+ }
428
+ const hb = hashConfiguration(b)
429
+ if (!Bytes.isEqual(hb, hashConfiguration(a))) {
430
+ throw new Error('Topology mismatch: node hash does not match')
431
+ }
432
+ return a
433
+ }
434
+
435
+ if (!isNode(a) && isNode(b)) {
436
+ if (!isNodeLeaf(a)) {
437
+ throw new Error('Topology mismatch: cannot merge node with non-node that is not a node leaf')
438
+ }
439
+ const ha = hashConfiguration(a)
440
+ if (!Bytes.isEqual(ha, hashConfiguration(b))) {
441
+ throw new Error('Topology mismatch: node hash does not match')
442
+ }
443
+ return b
444
+ }
445
+
446
+ return mergeLeaf(a as Leaf, b as Leaf)
447
+ }
448
+
449
+ function mergeLeaf(a: Leaf, b: Leaf): Leaf {
450
+ if (isNodeLeaf(a) && isNodeLeaf(b)) {
451
+ if (!Hex.isEqual(a, b)) {
452
+ throw new Error('Topology mismatch: different node leaves')
453
+ }
454
+ return a
455
+ }
456
+
457
+ if (isNodeLeaf(a) && !isNodeLeaf(b)) {
458
+ const hb = hashConfiguration(b)
459
+ if (!Bytes.isEqual(hb, Bytes.fromHex(a))) {
460
+ throw new Error('Topology mismatch: node leaf hash does not match')
461
+ }
462
+ return b
463
+ }
464
+
465
+ if (!isNodeLeaf(a) && isNodeLeaf(b)) {
466
+ const ha = hashConfiguration(a)
467
+ if (!Bytes.isEqual(ha, Bytes.fromHex(b))) {
468
+ throw new Error('Topology mismatch: node leaf hash does not match')
469
+ }
470
+ return a
471
+ }
472
+
473
+ if (isSignerLeaf(a) && isSignerLeaf(b)) {
474
+ if (a.address !== b.address || a.weight !== b.weight) {
475
+ throw new Error('Topology mismatch: signer fields differ')
476
+ }
477
+ if (!!a.signed !== !!b.signed || !!a.signature !== !!b.signature) {
478
+ throw new Error('Topology mismatch: signer signature fields differ')
479
+ }
480
+ return a
481
+ }
482
+
483
+ if (isSapientSignerLeaf(a) && isSapientSignerLeaf(b)) {
484
+ if (a.address !== b.address || a.weight !== b.weight || a.imageHash !== b.imageHash) {
485
+ throw new Error('Topology mismatch: sapient signer fields differ')
486
+ }
487
+ if (!!a.signed !== !!b.signed || !!a.signature !== !!b.signature) {
488
+ throw new Error('Topology mismatch: sapient signature fields differ')
489
+ }
490
+ return a
491
+ }
492
+
493
+ if (isSubdigestLeaf(a) && isSubdigestLeaf(b)) {
494
+ if (!Bytes.isEqual(Bytes.fromHex(a.digest), Bytes.fromHex(b.digest))) {
495
+ throw new Error('Topology mismatch: subdigest fields differ')
496
+ }
497
+ return a
498
+ }
499
+
500
+ if (isAnyAddressSubdigestLeaf(a) && isAnyAddressSubdigestLeaf(b)) {
501
+ if (!Bytes.isEqual(Bytes.fromHex(a.digest), Bytes.fromHex(b.digest))) {
502
+ throw new Error('Topology mismatch: any-address-subdigest fields differ')
503
+ }
504
+ return a
505
+ }
506
+
507
+ if (isNestedLeaf(a) && isNestedLeaf(b)) {
508
+ if (a.weight !== b.weight || a.threshold !== b.threshold) {
509
+ throw new Error('Topology mismatch: nested leaf fields differ')
510
+ }
511
+ const mergedTree = mergeTopology(a.tree, b.tree)
512
+ return {
513
+ type: 'nested',
514
+ weight: a.weight,
515
+ threshold: a.threshold,
516
+ tree: mergedTree,
517
+ }
518
+ }
519
+
520
+ throw new Error('Topology mismatch: incompatible leaf types')
521
+ }
@@ -0,0 +1,39 @@
1
+ import { Abi, Address, Hex } from 'ox'
2
+
3
+ export const DEFAULT_CREATION_CODE: Hex.Hex =
4
+ '0x603e600e3d39601e805130553df33d3d34601c57363d3d373d363d30545af43d82803e903d91601c57fd5bf3'
5
+
6
+ export const DefaultFactory: Address.Address = '0xe068ec288d8b4Aaf7F7FC028Ce0797a7a353EF2d'
7
+ export const DefaultStage1: Address.Address = '0x302608CcdCc540761A0ec89C9d8Fa195dc8049C6'
8
+ export const DefaultStage2: Address.Address = '0x80cF586AFaCb3Cae77d84aFEBcC92382eDCF3A02'
9
+ export const DefaultGuest: Address.Address = '0x75e19AA6241D84C290658131857824B4eeF10dfF'
10
+ export const DefaultSessionManager: Address.Address = '0x81Fa4b986f958CB02A3A6c10aa38056dCd701941'
11
+
12
+ // ERC1271
13
+ export const IS_VALID_SIGNATURE = Abi.from([
14
+ 'function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4 magicValue)',
15
+ ])[0]
16
+
17
+ // Factory
18
+ export const DEPLOY = Abi.from([
19
+ 'function deploy(address _mainModule, bytes32 _salt) public payable returns (address _contract)',
20
+ ])[0]
21
+
22
+ // Stage1Module
23
+ export const GET_IMPLEMENTATION = Abi.from(['function getImplementation() external view returns (address)'])[0]
24
+
25
+ // Stage2Module
26
+ export const IMAGE_HASH = Abi.from(['function imageHash() external view returns (bytes32)'])[0]
27
+ export const READ_NONCE = Abi.from(['function readNonce(uint256 _space) public view returns (uint256)'])[0]
28
+ export const EXECUTE = Abi.from(['function execute(bytes calldata _payload, bytes calldata _signature) external'])[0]
29
+ export const UPDATE_IMAGE_HASH = Abi.from(['function updateImageHash(bytes32 _imageHash) external'])[0]
30
+
31
+ // Sapient
32
+ export const RECOVER_SAPIENT_SIGNATURE = Abi.from([
33
+ 'function recoverSapientSignature((uint8 kind,bool noChainId,(address to,uint256 value,bytes data,uint256 gasLimit,bool delegateCall,bool onlyFallback,uint256 behaviorOnError)[] calls,uint256 space,uint256 nonce,bytes message,bytes32 imageHash,bytes32 digest,address[] parentWallets) calldata _payload, bytes calldata _signature) external view returns (bytes32)',
34
+ ])[0]
35
+
36
+ // SapientCompact
37
+ export const RECOVER_SAPIENT_SIGNATURE_COMPACT = Abi.from([
38
+ 'function recoverSapientSignatureCompact(bytes32 _digest, bytes calldata _signature) external view returns (bytes32)',
39
+ ])[0]
package/src/context.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { Address, Hex } from 'ox'
2
+ import { DEFAULT_CREATION_CODE, DefaultFactory, DefaultStage1, DefaultStage2 } from './constants.js'
3
+
4
+ export type Context = {
5
+ factory: Address.Address
6
+ stage1: Address.Address
7
+ stage2: Address.Address
8
+ creationCode: Hex.Hex
9
+ }
10
+
11
+ export const Dev1: Context = {
12
+ factory: DefaultFactory,
13
+ stage1: DefaultStage1,
14
+ stage2: DefaultStage2,
15
+ creationCode: DEFAULT_CREATION_CODE,
16
+ }
@@ -0,0 +1,97 @@
1
+ import { AbiFunction, AbiParameters, Address, Bytes, Hex, Provider } from 'ox'
2
+ import { WrappedSignature } from 'ox/erc6492'
3
+ import { DEPLOY } from './constants.js'
4
+ import { Context } from './context.js'
5
+
6
+ const EIP_6492_OFFCHAIN_DEPLOY_CODE =
7
+ '0x608060405234801561001057600080fd5b5060405161124a38038061124a83398101604081905261002f91610124565b600060405161003d906100dd565b604051809103906000f080158015610059573d6000803e3d6000fd5b5090506000816001600160a01b0316638f0684308686866040518463ffffffff1660e01b815260040161008e939291906101fb565b6020604051808303816000875af11580156100ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d19190610244565b9050806000526001601ff35b610fdc8061026e83390190565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561011b578181015183820152602001610103565b50506000910152565b60008060006060848603121561013957600080fd5b83516001600160a01b038116811461015057600080fd5b6020850151604086015191945092506001600160401b038082111561017457600080fd5b818601915086601f83011261018857600080fd5b81518181111561019a5761019a6100ea565b604051601f8201601f19908116603f011681019083821181831017156101c2576101c26100ea565b816040528281528960208487010111156101db57600080fd5b6101ec836020830160208801610100565b80955050505050509250925092565b60018060a01b0384168152826020820152606060408201526000825180606084015261022e816080850160208701610100565b601f01601f191691909101608001949350505050565b60006020828403121561025657600080fd5b8151801515811461026657600080fd5b939250505056fe608060405234801561001057600080fd5b50610fbc806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c806376be4cea1161005057806376be4cea146100a65780638f068430146100b957806398ef1ed8146100cc57600080fd5b80631c6453271461006c5780633d787b6314610093575b600080fd5b61007f61007a366004610ad4565b6100df565b604051901515815260200160405180910390f35b61007f6100a1366004610ad4565b61023d565b61007f6100b4366004610b3e565b61031e565b61007f6100c7366004610ad4565b6108e1565b61007f6100da366004610ad4565b61096e565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea9061012890889088908890889088908190600401610bc3565b6020604051808303816000875af1925050508015610181575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261017e91810190610c45565b60015b610232573d8080156101af576040519150601f19603f3d011682016040523d82523d6000602084013e6101b4565b606091505b508051600181900361022757816000815181106101d3576101d3610c69565b6020910101517fff00000000000000000000000000000000000000000000000000000000000000167f0100000000000000000000000000000000000000000000000000000000000000149250610235915050565b600092505050610235565b90505b949350505050565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea906102879088908890889088906001908990600401610bc3565b6020604051808303816000875af19250505080156102e0575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526102dd91810190610c45565b60015b610232573d80801561030e576040519150601f19603f3d011682016040523d82523d6000602084013e610313565b606091505b506000915050610235565b600073ffffffffffffffffffffffffffffffffffffffff87163b6060827f64926492649264926492649264926492649264926492649264926492649264928888610369602082610c98565b610375928b9290610cd8565b61037e91610d02565b1490508015610484576000606089828a610399602082610c98565b926103a693929190610cd8565b8101906103b39190610e18565b955090925090508415806103c45750865b1561047d576000808373ffffffffffffffffffffffffffffffffffffffff16836040516103f19190610eb2565b6000604051808303816000865af19150503d806000811461042e576040519150601f19603f3d011682016040523d82523d6000602084013e610433565b606091505b50915091508161047a57806040517f9d0d6e2d0000000000000000000000000000000000000000000000000000000081526004016104719190610f18565b60405180910390fd5b50505b50506104be565b87878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509294505050505b80806104ca5750600083115b156106bb576040517f1626ba7e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8b1690631626ba7e90610523908c908690600401610f2b565b602060405180830381865afa92505050801561057a575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261057791810190610f44565b60015b61060f573d8080156105a8576040519150601f19603f3d011682016040523d82523d6000602084013e6105ad565b606091505b50851580156105bc5750600084115b156105db576105d08b8b8b8b8b600161031e565b9450505050506108d7565b806040517f6f2a95990000000000000000000000000000000000000000000000000000000081526004016104719190610f18565b7fffffffff0000000000000000000000000000000000000000000000000000000081167f1626ba7e000000000000000000000000000000000000000000000000000000001480158161065f575086155b801561066b5750600085115b1561068b5761067f8c8c8c8c8c600161031e565b955050505050506108d7565b841580156106965750825b80156106a0575087155b156106af57806000526001601ffd5b94506108d79350505050565b6041871461074b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f5369676e617475726556616c696461746f72237265636f7665725369676e657260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610471565b600061075a6020828a8c610cd8565b61076391610d02565b90506000610775604060208b8d610cd8565b61077e91610d02565b905060008a8a604081811061079557610795610c69565b919091013560f81c915050601b81148015906107b557508060ff16601c14155b15610842576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f5369676e617475726556616c696461746f723a20696e76616c6964207369676e60448201527f617475726520762076616c7565000000000000000000000000000000000000006064820152608401610471565b6040805160008152602081018083528e905260ff831691810191909152606081018490526080810183905273ffffffffffffffffffffffffffffffffffffffff8e169060019060a0016020604051602081039080840390855afa1580156108ad573d6000803e3d6000fd5b5050506020604051035173ffffffffffffffffffffffffffffffffffffffff161496505050505050505b9695505050505050565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea9061092b9088908890889088906001908990600401610bc3565b6020604051808303816000875af115801561094a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102329190610c45565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea906109b790889088908890889088908190600401610bc3565b6020604051808303816000875af1925050508015610a10575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252610a0d91810190610c45565b60015b610232573d808015610a3e576040519150601f19603f3d011682016040523d82523d6000602084013e610a43565b606091505b5080516001819003610a6257816000815181106101d3576101d3610c69565b8082fd5b73ffffffffffffffffffffffffffffffffffffffff81168114610a8857600080fd5b50565b60008083601f840112610a9d57600080fd5b50813567ffffffffffffffff811115610ab557600080fd5b602083019150836020828501011115610acd57600080fd5b9250929050565b60008060008060608587031215610aea57600080fd5b8435610af581610a66565b935060208501359250604085013567ffffffffffffffff811115610b1857600080fd5b610b2487828801610a8b565b95989497509550505050565b8015158114610a8857600080fd5b60008060008060008060a08789031215610b5757600080fd5b8635610b6281610a66565b955060208701359450604087013567ffffffffffffffff811115610b8557600080fd5b610b9189828a01610a8b565b9095509350506060870135610ba581610b30565b91506080870135610bb581610b30565b809150509295509295509295565b73ffffffffffffffffffffffffffffffffffffffff8716815285602082015260a060408201528360a0820152838560c0830137600060c085830181019190915292151560608201529015156080820152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016909101019392505050565b600060208284031215610c5757600080fd5b8151610c6281610b30565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b81810381811115610cd2577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b60008085851115610ce857600080fd5b83861115610cf557600080fd5b5050820193919092039150565b80356020831015610cd2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f830112610d7e57600080fd5b813567ffffffffffffffff80821115610d9957610d99610d3e565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908282118183101715610ddf57610ddf610d3e565b81604052838152866020858801011115610df857600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600060608486031215610e2d57600080fd5b8335610e3881610a66565b9250602084013567ffffffffffffffff80821115610e5557600080fd5b610e6187838801610d6d565b93506040860135915080821115610e7757600080fd5b50610e8486828701610d6d565b9150509250925092565b60005b83811015610ea9578181015183820152602001610e91565b50506000910152565b60008251610ec4818460208701610e8e565b9190910192915050565b60008151808452610ee6816020860160208601610e8e565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610c626020830184610ece565b8281526040602082015260006102356040830184610ece565b600060208284031215610f5657600080fd5b81517fffffffff0000000000000000000000000000000000000000000000000000000081168114610c6257600080fdfea26469706673582212201a72aed4b15ffb05b6502997a9bb655992e06590bd26b336dfbb153d7ff6f34b64736f6c63430008120033'
8
+
9
+ export function deploy<T extends Bytes.Bytes | Hex.Hex>(
10
+ deployHash: T,
11
+ context: Context,
12
+ ): { to: Address.Address; data: T } {
13
+ const encoded = AbiFunction.encodeData(DEPLOY, [context.stage1, Hex.from(deployHash)])
14
+
15
+ switch (typeof deployHash) {
16
+ case 'object':
17
+ return { to: context.factory, data: Hex.toBytes(encoded) as T }
18
+ case 'string':
19
+ return { to: context.factory, data: encoded as T }
20
+ }
21
+ }
22
+
23
+ export function wrap<T extends Bytes.Bytes | Hex.Hex>(
24
+ signature: T,
25
+ { to, data }: { to: Address.Address; data: Bytes.Bytes | Hex.Hex },
26
+ ): T {
27
+ const encoded = Hex.concat(
28
+ AbiParameters.encode(
29
+ [{ type: 'address' }, { type: 'bytes' }, { type: 'bytes' }],
30
+ [to, Hex.from(data), Hex.from(signature)],
31
+ ),
32
+ WrappedSignature.magicBytes,
33
+ )
34
+
35
+ switch (typeof signature) {
36
+ case 'object':
37
+ return Hex.toBytes(encoded) as T
38
+ case 'string':
39
+ return encoded as T
40
+ }
41
+ }
42
+
43
+ export function decode<T extends Bytes.Bytes | Hex.Hex>(
44
+ signature: T,
45
+ ): { signature: T; erc6492?: { to: Address.Address; data: T } } {
46
+ switch (typeof signature) {
47
+ case 'object':
48
+ if (
49
+ Bytes.toHex(signature.subarray(-WrappedSignature.magicBytes.slice(2).length / 2)) ===
50
+ WrappedSignature.magicBytes
51
+ ) {
52
+ const [to, data, decoded] = AbiParameters.decode(
53
+ [{ type: 'address' }, { type: 'bytes' }, { type: 'bytes' }],
54
+ signature.subarray(0, -WrappedSignature.magicBytes.slice(2).length / 2),
55
+ )
56
+ return { signature: Hex.toBytes(decoded) as T, erc6492: { to, data: Hex.toBytes(data) as T } }
57
+ } else {
58
+ return { signature }
59
+ }
60
+
61
+ case 'string':
62
+ if (signature.endsWith(WrappedSignature.magicBytes.slice(2))) {
63
+ try {
64
+ const [to, data, decoded] = AbiParameters.decode(
65
+ [{ type: 'address' }, { type: 'bytes' }, { type: 'bytes' }],
66
+ signature.slice(0, -WrappedSignature.magicBytes.slice(2).length) as Hex.Hex,
67
+ )
68
+ return { signature: decoded as T, erc6492: { to, data: data as T } }
69
+ } catch {
70
+ return { signature }
71
+ }
72
+ } else {
73
+ return { signature }
74
+ }
75
+ }
76
+ }
77
+
78
+ export function isValid(
79
+ address: Address.Address,
80
+ messageHash: Bytes.Bytes | Hex.Hex,
81
+ encodedSignature: Bytes.Bytes | Hex.Hex,
82
+ provider: Provider.Provider,
83
+ ): Promise<boolean> {
84
+ // Validate off chain with ERC-6492
85
+ const validationCallData: Hex.Hex = AbiParameters.encode(AbiParameters.from('address, bytes32, bytes'), [
86
+ address,
87
+ Hex.from(messageHash),
88
+ Hex.from(encodedSignature),
89
+ ])
90
+ const callData = Hex.concat(EIP_6492_OFFCHAIN_DEPLOY_CODE, validationCallData)
91
+ return provider
92
+ .request({
93
+ method: 'eth_call',
94
+ params: [{ data: callData }, 'latest'],
95
+ })
96
+ .then((result) => parseInt(result, 16) === 1)
97
+ }