@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/payload.ts ADDED
@@ -0,0 +1,825 @@
1
+ import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex } from 'ox'
2
+ import { getSignPayload } from 'ox/TypedData'
3
+ import { RECOVER_SAPIENT_SIGNATURE } from './constants.js'
4
+ import { Attestation } from './index.js'
5
+ import { minBytesFor } from './utils.js'
6
+
7
+ export const KIND_TRANSACTIONS = 0x00
8
+ export const KIND_MESSAGE = 0x01
9
+ export const KIND_CONFIG_UPDATE = 0x02
10
+ export const KIND_DIGEST = 0x03
11
+
12
+ export const BEHAVIOR_IGNORE_ERROR = 0x00
13
+ export const BEHAVIOR_REVERT_ON_ERROR = 0x01
14
+ export const BEHAVIOR_ABORT_ON_ERROR = 0x02
15
+
16
+ interface SolidityCall {
17
+ to: Address.Address
18
+ value: bigint
19
+ data: Hex.Hex
20
+ gasLimit: bigint
21
+ delegateCall: boolean
22
+ onlyFallback: boolean
23
+ behaviorOnError: bigint
24
+ }
25
+
26
+ export interface SolidityDecoded {
27
+ kind: number
28
+ noChainId: boolean
29
+ calls: SolidityCall[]
30
+ space: bigint
31
+ nonce: bigint
32
+ message: Hex.Hex
33
+ imageHash: Hex.Hex
34
+ digest: Hex.Hex
35
+ parentWallets: Address.Address[]
36
+ }
37
+
38
+ export type Call = {
39
+ to: Address.Address
40
+ value: bigint
41
+ data: Hex.Hex
42
+ gasLimit: bigint
43
+ delegateCall: boolean
44
+ onlyFallback: boolean
45
+ behaviorOnError: 'ignore' | 'revert' | 'abort'
46
+ }
47
+
48
+ export type Calls = {
49
+ type: 'call'
50
+ space: bigint
51
+ nonce: bigint
52
+ calls: Call[]
53
+ }
54
+
55
+ export type Message = {
56
+ type: 'message'
57
+ message: Hex.Hex
58
+ }
59
+
60
+ export type ConfigUpdate = {
61
+ type: 'config-update'
62
+ imageHash: Hex.Hex
63
+ }
64
+
65
+ export type Digest = {
66
+ type: 'digest'
67
+ digest: Hex.Hex
68
+ }
69
+
70
+ export type SessionImplicitAuthorize = {
71
+ type: 'session-implicit-authorize'
72
+ sessionAddress: Address.Address
73
+ attestation: Attestation.Attestation
74
+ }
75
+
76
+ export type Parent = {
77
+ parentWallets?: Address.Address[]
78
+ }
79
+
80
+ export type Recovery<T extends Calls | Message | ConfigUpdate | Digest> = T & {
81
+ recovery: true
82
+ }
83
+
84
+ export type MayRecoveryPayload = Calls | Message | ConfigUpdate | Digest
85
+
86
+ export type Payload =
87
+ | Calls
88
+ | Message
89
+ | ConfigUpdate
90
+ | Digest
91
+ | Recovery<Calls | Message | ConfigUpdate | Digest>
92
+ | SessionImplicitAuthorize
93
+
94
+ export type Parented = Payload & Parent
95
+
96
+ export type TypedDataToSign = {
97
+ domain: {
98
+ name: string
99
+ version: string
100
+ chainId: number
101
+ verifyingContract: Address.Address
102
+ }
103
+ types: Record<string, Array<{ name: string; type: string }>>
104
+ primaryType: string
105
+ message: Record<string, unknown>
106
+ }
107
+
108
+ export function fromMessage(message: Hex.Hex): Message {
109
+ return {
110
+ type: 'message',
111
+ message,
112
+ }
113
+ }
114
+
115
+ export function fromConfigUpdate(imageHash: Hex.Hex): ConfigUpdate {
116
+ return {
117
+ type: 'config-update',
118
+ imageHash,
119
+ }
120
+ }
121
+
122
+ export function fromDigest(digest: Hex.Hex): Digest {
123
+ return {
124
+ type: 'digest',
125
+ digest,
126
+ }
127
+ }
128
+
129
+ export function fromCall(nonce: bigint, space: bigint, calls: Call[]): Calls {
130
+ return {
131
+ type: 'call',
132
+ nonce,
133
+ space,
134
+ calls,
135
+ }
136
+ }
137
+
138
+ export function isCalls(payload: Payload): payload is Calls {
139
+ return payload.type === 'call'
140
+ }
141
+
142
+ export function isMessage(payload: Payload): payload is Message {
143
+ return payload.type === 'message'
144
+ }
145
+
146
+ export function isConfigUpdate(payload: Payload): payload is ConfigUpdate {
147
+ return payload.type === 'config-update'
148
+ }
149
+
150
+ export function isDigest(payload: Payload): payload is Digest {
151
+ return payload.type === 'digest'
152
+ }
153
+
154
+ export function isRecovery<T extends MayRecoveryPayload>(payload: Payload): payload is Recovery<T> {
155
+ if (isSessionImplicitAuthorize(payload)) {
156
+ return false
157
+ }
158
+
159
+ return (payload as Recovery<T>).recovery === true
160
+ }
161
+
162
+ export function toRecovery<T extends MayRecoveryPayload>(payload: T): Recovery<T> {
163
+ if (isRecovery(payload)) {
164
+ return payload
165
+ }
166
+
167
+ return {
168
+ ...payload,
169
+ recovery: true,
170
+ }
171
+ }
172
+
173
+ export function isSessionImplicitAuthorize(payload: Payload): payload is SessionImplicitAuthorize {
174
+ return payload.type === 'session-implicit-authorize'
175
+ }
176
+
177
+ export function encode(payload: Calls, self?: Address.Address): Bytes.Bytes {
178
+ const callsLen = payload.calls.length
179
+ const nonceBytesNeeded = minBytesFor(payload.nonce)
180
+ if (nonceBytesNeeded > 15) {
181
+ throw new Error('Nonce is too large')
182
+ }
183
+
184
+ /*
185
+ globalFlag layout:
186
+ bit 0: spaceZeroFlag => 1 if space == 0, else 0
187
+ bits [1..3]: how many bytes we use to encode nonce
188
+ bit 4: singleCallFlag => 1 if there's exactly one call
189
+ bit 5: callsCountSizeFlag => 1 if #calls stored in 2 bytes, 0 if in 1 byte
190
+ (bits [6..7] are unused/free)
191
+ */
192
+ let globalFlag = 0
193
+
194
+ if (payload.space === 0n) {
195
+ globalFlag |= 0x01
196
+ }
197
+
198
+ // bits [1..3] => how many bytes for the nonce
199
+ globalFlag |= nonceBytesNeeded << 1
200
+
201
+ // bit [4] => singleCallFlag
202
+ if (callsLen === 1) {
203
+ globalFlag |= 0x10
204
+ }
205
+
206
+ /*
207
+ If there's more than one call, we decide if we store the #calls in 1 or 2 bytes.
208
+ bit [5] => callsCountSizeFlag: 1 => 2 bytes, 0 => 1 byte
209
+ */
210
+ let callsCountSize = 0
211
+ if (callsLen !== 1) {
212
+ if (callsLen < 256) {
213
+ callsCountSize = 1
214
+ } else if (callsLen < 65536) {
215
+ callsCountSize = 2
216
+ globalFlag |= 0x20
217
+ } else {
218
+ throw new Error('Too many calls')
219
+ }
220
+ }
221
+
222
+ // Start building the output
223
+ // We'll accumulate in a Bytes object as we go
224
+ let out = Bytes.fromNumber(globalFlag, { size: 1 })
225
+
226
+ // If space isn't 0, store it as exactly 20 bytes (like uint160)
227
+ if (payload.space !== 0n) {
228
+ const spaceBytes = Bytes.padLeft(Bytes.fromNumber(payload.space), 20)
229
+ out = Bytes.concat(out, spaceBytes)
230
+ }
231
+
232
+ // Encode nonce in nonceBytesNeeded
233
+ if (nonceBytesNeeded > 0) {
234
+ // We'll store nonce in exactly nonceBytesNeeded bytes
235
+ const nonceBytes = Bytes.padLeft(Bytes.fromNumber(payload.nonce), nonceBytesNeeded)
236
+ out = Bytes.concat(out, nonceBytes)
237
+ }
238
+
239
+ // Store callsLen if not single-call
240
+ if (callsLen !== 1) {
241
+ if (callsCountSize === 1) {
242
+ out = Bytes.concat(out, Bytes.fromNumber(callsLen, { size: 1 }))
243
+ } else {
244
+ // callsCountSize === 2
245
+ out = Bytes.concat(out, Bytes.fromNumber(callsLen, { size: 2 }))
246
+ }
247
+ }
248
+
249
+ // Now encode each call
250
+ for (const call of payload.calls) {
251
+ /*
252
+ call flags layout (1 byte):
253
+ bit 0 => toSelf (call.to == this)
254
+ bit 1 => hasValue (call.value != 0)
255
+ bit 2 => hasData (call.data.length > 0)
256
+ bit 3 => hasGasLimit (call.gasLimit != 0)
257
+ bit 4 => delegateCall
258
+ bit 5 => onlyFallback
259
+ bits [6..7] => behaviorOnError => 0=ignore, 1=revert, 2=abort
260
+ */
261
+ let flags = 0
262
+
263
+ if (self && Address.isEqual(call.to, self)) {
264
+ flags |= 0x01
265
+ }
266
+
267
+ if (call.value !== 0n) {
268
+ flags |= 0x02
269
+ }
270
+
271
+ if (call.data && call.data.length > 0) {
272
+ flags |= 0x04
273
+ }
274
+
275
+ if (call.gasLimit !== 0n) {
276
+ flags |= 0x08
277
+ }
278
+
279
+ if (call.delegateCall) {
280
+ flags |= 0x10
281
+ }
282
+
283
+ if (call.onlyFallback) {
284
+ flags |= 0x20
285
+ }
286
+
287
+ flags |= encodeBehaviorOnError(call.behaviorOnError) << 6
288
+
289
+ out = Bytes.concat(out, Bytes.fromNumber(flags, { size: 1 }))
290
+
291
+ // If toSelf bit not set, store 20-byte address
292
+ if ((flags & 0x01) === 0) {
293
+ const addrBytes = Bytes.fromHex(call.to)
294
+ if (addrBytes.length !== 20) {
295
+ throw new Error(`Invalid 'to' address: ${call.to}`)
296
+ }
297
+ out = Bytes.concat(out, addrBytes)
298
+ }
299
+
300
+ // If hasValue, store 32 bytes of value
301
+ if ((flags & 0x02) !== 0) {
302
+ const valueBytes = Bytes.padLeft(Bytes.fromNumber(call.value), 32)
303
+ out = Bytes.concat(out, valueBytes)
304
+ }
305
+
306
+ // If hasData, store 3 bytes of data length + data
307
+ if ((flags & 0x04) !== 0) {
308
+ const dataLen = Bytes.fromHex(call.data).length
309
+ if (dataLen > 0xffffff) {
310
+ throw new Error('Data too large')
311
+ }
312
+ // 3 bytes => up to 16,777,215
313
+ const dataLenBytes = Bytes.fromNumber(dataLen, { size: 3 })
314
+ out = Bytes.concat(out, dataLenBytes, Bytes.fromHex(call.data))
315
+ }
316
+
317
+ // If hasGasLimit, store 32 bytes of gasLimit
318
+ if ((flags & 0x08) !== 0) {
319
+ const gasBytes = Bytes.padLeft(Bytes.fromNumber(call.gasLimit), 32)
320
+ out = Bytes.concat(out, gasBytes)
321
+ }
322
+ }
323
+
324
+ return out
325
+ }
326
+
327
+ export function encodeSapient(
328
+ chainId: bigint,
329
+ payload: Parented,
330
+ ): Exclude<AbiFunction.encodeData.Args<typeof RECOVER_SAPIENT_SIGNATURE>[0], undefined>[0] {
331
+ const encoded: ReturnType<typeof encodeSapient> = {
332
+ kind: 0,
333
+ noChainId: !chainId,
334
+ calls: [],
335
+ space: 0n,
336
+ nonce: 0n,
337
+ message: '0x',
338
+ imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
339
+ digest: '0x0000000000000000000000000000000000000000000000000000000000000000',
340
+ parentWallets: payload.parentWallets ?? [],
341
+ }
342
+
343
+ switch (payload.type) {
344
+ case 'call':
345
+ encoded.kind = 0
346
+ encoded.calls = payload.calls.map((call) => ({
347
+ ...call,
348
+ data: call.data,
349
+ behaviorOnError: BigInt(encodeBehaviorOnError(call.behaviorOnError)),
350
+ }))
351
+ encoded.space = payload.space
352
+ encoded.nonce = payload.nonce
353
+ break
354
+
355
+ case 'message':
356
+ encoded.kind = 1
357
+ encoded.message = payload.message
358
+ break
359
+
360
+ case 'config-update':
361
+ encoded.kind = 2
362
+ encoded.imageHash = payload.imageHash
363
+ break
364
+
365
+ case 'digest':
366
+ encoded.kind = 3
367
+ encoded.digest = payload.digest
368
+ break
369
+ }
370
+
371
+ return encoded
372
+ }
373
+
374
+ export function hash(wallet: Address.Address, chainId: bigint, payload: Parented): Bytes.Bytes {
375
+ if (isDigest(payload)) {
376
+ return Bytes.fromHex(payload.digest)
377
+ }
378
+ if (isSessionImplicitAuthorize(payload)) {
379
+ return Attestation.hash(payload.attestation)
380
+ }
381
+ const typedData = toTyped(wallet, chainId, payload)
382
+ return Bytes.fromHex(getSignPayload(typedData))
383
+ }
384
+
385
+ function domainFor(
386
+ payload: Payload,
387
+ wallet: Address.Address,
388
+ chainId: bigint,
389
+ ): {
390
+ name: string
391
+ version: string
392
+ chainId: number
393
+ verifyingContract: Address.Address
394
+ } {
395
+ if (isRecovery(payload)) {
396
+ return {
397
+ name: 'Sequence Wallet - Recovery Mode',
398
+ version: '1',
399
+ chainId: Number(chainId),
400
+ verifyingContract: wallet,
401
+ }
402
+ }
403
+
404
+ return {
405
+ name: 'Sequence Wallet',
406
+ version: '3',
407
+ chainId: Number(chainId),
408
+ verifyingContract: wallet,
409
+ }
410
+ }
411
+
412
+ export function toTyped(wallet: Address.Address, chainId: bigint, payload: Parented): TypedDataToSign {
413
+ const domain = domainFor(payload, wallet, chainId)
414
+
415
+ switch (payload.type) {
416
+ case 'call': {
417
+ // This matches the EIP712 structure used in our hash() function
418
+ const types = {
419
+ Calls: [
420
+ { name: 'calls', type: 'Call[]' },
421
+ { name: 'space', type: 'uint256' },
422
+ { name: 'nonce', type: 'uint256' },
423
+ { name: 'wallets', type: 'address[]' },
424
+ ],
425
+ Call: [
426
+ { name: 'to', type: 'address' },
427
+ { name: 'value', type: 'uint256' },
428
+ { name: 'data', type: 'bytes' },
429
+ { name: 'gasLimit', type: 'uint256' },
430
+ { name: 'delegateCall', type: 'bool' },
431
+ { name: 'onlyFallback', type: 'bool' },
432
+ { name: 'behaviorOnError', type: 'uint256' },
433
+ ],
434
+ }
435
+
436
+ // We ensure 'behaviorOnError' is turned into a numeric value
437
+ const message = {
438
+ calls: payload.calls.map((call) => ({
439
+ to: call.to,
440
+ value: call.value.toString(),
441
+ data: call.data,
442
+ gasLimit: call.gasLimit.toString(),
443
+ delegateCall: call.delegateCall,
444
+ onlyFallback: call.onlyFallback,
445
+ behaviorOnError: BigInt(encodeBehaviorOnError(call.behaviorOnError)).toString(),
446
+ })),
447
+ space: payload.space.toString(),
448
+ nonce: payload.nonce.toString(),
449
+ wallets: payload.parentWallets ?? [],
450
+ }
451
+
452
+ return {
453
+ domain,
454
+ types,
455
+ primaryType: 'Calls',
456
+ message,
457
+ }
458
+ }
459
+
460
+ case 'message': {
461
+ const types = {
462
+ Message: [
463
+ { name: 'message', type: 'bytes' },
464
+ { name: 'wallets', type: 'address[]' },
465
+ ],
466
+ }
467
+
468
+ const message = {
469
+ message: payload.message,
470
+ wallets: payload.parentWallets ?? [],
471
+ }
472
+
473
+ return {
474
+ domain,
475
+ types,
476
+ primaryType: 'Message',
477
+ message,
478
+ }
479
+ }
480
+
481
+ case 'config-update': {
482
+ const types = {
483
+ ConfigUpdate: [
484
+ { name: 'imageHash', type: 'bytes32' },
485
+ { name: 'wallets', type: 'address[]' },
486
+ ],
487
+ }
488
+
489
+ const message = {
490
+ imageHash: payload.imageHash,
491
+ wallets: payload.parentWallets ?? [],
492
+ }
493
+
494
+ return {
495
+ domain,
496
+ types,
497
+ primaryType: 'ConfigUpdate',
498
+ message,
499
+ }
500
+ }
501
+
502
+ case 'digest': {
503
+ throw new Error('Digest does not support typed data - Use message instead')
504
+ }
505
+
506
+ case 'session-implicit-authorize': {
507
+ throw new Error('Payload does not support typed data')
508
+ }
509
+ }
510
+ }
511
+
512
+ export function encodeBehaviorOnError(behaviorOnError: Call['behaviorOnError']): number {
513
+ switch (behaviorOnError) {
514
+ case 'ignore':
515
+ return BEHAVIOR_IGNORE_ERROR
516
+ case 'revert':
517
+ return BEHAVIOR_REVERT_ON_ERROR
518
+ case 'abort':
519
+ return BEHAVIOR_ABORT_ON_ERROR
520
+ }
521
+ }
522
+
523
+ export function hashCall(call: Call): Hex.Hex {
524
+ const CALL_TYPEHASH = Hash.keccak256(
525
+ Bytes.fromString(
526
+ 'Call(address to,uint256 value,bytes data,uint256 gasLimit,bool delegateCall,bool onlyFallback,uint256 behaviorOnError)',
527
+ ),
528
+ )
529
+
530
+ return Hash.keccak256(
531
+ AbiParameters.encode(
532
+ [
533
+ { type: 'bytes32' },
534
+ { type: 'address' },
535
+ { type: 'uint256' },
536
+ { type: 'bytes32' },
537
+ { type: 'uint256' },
538
+ { type: 'bool' },
539
+ { type: 'bool' },
540
+ { type: 'uint256' },
541
+ ],
542
+ [
543
+ Hex.from(CALL_TYPEHASH),
544
+ Hex.from(call.to),
545
+ call.value,
546
+ Hex.from(Hash.keccak256(call.data)),
547
+ call.gasLimit,
548
+ call.delegateCall,
549
+ call.onlyFallback,
550
+ BigInt(encodeBehaviorOnError(call.behaviorOnError)),
551
+ ],
552
+ ),
553
+ )
554
+ }
555
+
556
+ export function decode(packed: Bytes.Bytes, self?: Address.Address): Calls {
557
+ let pointer = 0
558
+ if (packed.length < 1) {
559
+ throw new Error('Invalid packed data: missing globalFlag')
560
+ }
561
+
562
+ // Read globalFlag
563
+ const globalFlag = Bytes.toNumber(packed.slice(pointer, pointer + 1))
564
+ pointer += 1
565
+
566
+ // bit 0 => spaceZeroFlag
567
+ const spaceZeroFlag = (globalFlag & 0x01) === 0x01
568
+ let space = 0n
569
+ if (!spaceZeroFlag) {
570
+ if (pointer + 20 > packed.length) {
571
+ throw new Error('Invalid packed data: not enough bytes for space')
572
+ }
573
+ space = Bytes.toBigInt(packed.slice(pointer, pointer + 20))
574
+ pointer += 20
575
+ }
576
+
577
+ // bits [1..3] => nonceSize
578
+ const nonceSize = (globalFlag >> 1) & 0x07
579
+ let nonce = 0n
580
+ if (nonceSize > 0) {
581
+ if (pointer + nonceSize > packed.length) {
582
+ throw new Error('Invalid packed data: not enough bytes for nonce')
583
+ }
584
+ nonce = Bytes.toBigInt(packed.slice(pointer, pointer + nonceSize))
585
+ pointer += nonceSize
586
+ }
587
+
588
+ // bit [4] => singleCallFlag
589
+ let callsCount = 1
590
+ const singleCallFlag = (globalFlag & 0x10) === 0x10
591
+ if (!singleCallFlag) {
592
+ // bit [5] => callsCountSizeFlag => 1 => 2 bytes, 0 => 1 byte
593
+ const callsCountSizeFlag = (globalFlag & 0x20) === 0x20
594
+ const countSize = callsCountSizeFlag ? 2 : 1
595
+ if (pointer + countSize > packed.length) {
596
+ throw new Error('Invalid packed data: not enough bytes for callsCount')
597
+ }
598
+ callsCount = Bytes.toNumber(packed.slice(pointer, pointer + countSize))
599
+ pointer += countSize
600
+ }
601
+
602
+ const calls: Call[] = []
603
+ for (let i = 0; i < callsCount; i++) {
604
+ if (pointer + 1 > packed.length) {
605
+ throw new Error('Invalid packed data: missing call flags')
606
+ }
607
+ const flags = Bytes.toNumber(packed.slice(pointer, pointer + 1))
608
+ pointer += 1
609
+
610
+ // bit 0 => toSelf
611
+ let to: Address.Address
612
+ if ((flags & 0x01) === 0x01) {
613
+ if (!self) {
614
+ throw new Error('Missing "self" address for toSelf call')
615
+ }
616
+ to = self
617
+ } else {
618
+ if (pointer + 20 > packed.length) {
619
+ throw new Error('Invalid packed data: not enough bytes for address')
620
+ }
621
+ to = Bytes.toHex(packed.slice(pointer, pointer + 20)) as Address.Address
622
+ pointer += 20
623
+ }
624
+
625
+ // bit 1 => hasValue
626
+ let value = 0n
627
+ if ((flags & 0x02) === 0x02) {
628
+ if (pointer + 32 > packed.length) {
629
+ throw new Error('Invalid packed data: not enough bytes for value')
630
+ }
631
+ value = Bytes.toBigInt(packed.slice(pointer, pointer + 32))
632
+ pointer += 32
633
+ }
634
+
635
+ // bit 2 => hasData
636
+ let data = Bytes.fromHex('0x')
637
+ if ((flags & 0x04) === 0x04) {
638
+ if (pointer + 3 > packed.length) {
639
+ throw new Error('Invalid packed data: not enough bytes for data length')
640
+ }
641
+ const dataLen = Bytes.toNumber(packed.slice(pointer, pointer + 3))
642
+ pointer += 3
643
+ if (pointer + dataLen > packed.length) {
644
+ throw new Error('Invalid packed data: not enough bytes for call data')
645
+ }
646
+ data = packed.slice(pointer, pointer + dataLen)
647
+ pointer += dataLen
648
+ }
649
+
650
+ // bit 3 => hasGasLimit
651
+ let gasLimit = 0n
652
+ if ((flags & 0x08) === 0x08) {
653
+ if (pointer + 32 > packed.length) {
654
+ throw new Error('Invalid packed data: not enough bytes for gasLimit')
655
+ }
656
+ gasLimit = Bytes.toBigInt(packed.slice(pointer, pointer + 32))
657
+ pointer += 32
658
+ }
659
+
660
+ // bits 4..5 => delegateCall, onlyFallback
661
+ const delegateCall = (flags & 0x10) === 0x10
662
+ const onlyFallback = (flags & 0x20) === 0x20
663
+
664
+ // bits 6..7 => behaviorOnError
665
+ const behaviorCode = (flags & 0xc0) >> 6
666
+ const behaviorOnError = decodeBehaviorOnError(behaviorCode)
667
+
668
+ calls.push({
669
+ to,
670
+ value,
671
+ data: Bytes.toHex(data),
672
+ gasLimit,
673
+ delegateCall,
674
+ onlyFallback,
675
+ behaviorOnError,
676
+ })
677
+ }
678
+
679
+ return {
680
+ type: 'call',
681
+ space,
682
+ nonce,
683
+ calls,
684
+ }
685
+ }
686
+
687
+ export function decodeBehaviorOnError(value: number): Call['behaviorOnError'] {
688
+ switch (value) {
689
+ case 0:
690
+ return 'ignore'
691
+ case 1:
692
+ return 'revert'
693
+ case 2:
694
+ return 'abort'
695
+ default:
696
+ throw new Error(`Invalid behaviorOnError value: ${value}`)
697
+ }
698
+ }
699
+
700
+ function parseBehaviorOnError(behavior: number): 'ignore' | 'revert' | 'abort' {
701
+ switch (behavior) {
702
+ case BEHAVIOR_IGNORE_ERROR:
703
+ return 'ignore'
704
+ case BEHAVIOR_REVERT_ON_ERROR:
705
+ return 'revert'
706
+ case BEHAVIOR_ABORT_ON_ERROR:
707
+ return 'abort'
708
+ default:
709
+ throw new Error(`Unknown behavior: ${behavior}`)
710
+ }
711
+ }
712
+
713
+ export function fromAbiFormat(decoded: SolidityDecoded): Parented {
714
+ if (decoded.kind === KIND_TRANSACTIONS) {
715
+ return {
716
+ type: 'call',
717
+ nonce: decoded.nonce,
718
+ space: decoded.space,
719
+ calls: decoded.calls.map((call) => ({
720
+ to: Address.from(call.to),
721
+ value: call.value,
722
+ data: call.data as `0x${string}`,
723
+ gasLimit: call.gasLimit,
724
+ delegateCall: call.delegateCall,
725
+ onlyFallback: call.onlyFallback,
726
+ behaviorOnError: parseBehaviorOnError(Number(call.behaviorOnError)),
727
+ })),
728
+ parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)),
729
+ }
730
+ }
731
+
732
+ if (decoded.kind === KIND_MESSAGE) {
733
+ return {
734
+ type: 'message',
735
+ message: decoded.message as `0x${string}`,
736
+ parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)),
737
+ }
738
+ }
739
+
740
+ if (decoded.kind === KIND_CONFIG_UPDATE) {
741
+ return {
742
+ type: 'config-update',
743
+ imageHash: decoded.imageHash as `0x${string}`,
744
+ parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)),
745
+ }
746
+ }
747
+
748
+ if (decoded.kind === KIND_DIGEST) {
749
+ return {
750
+ type: 'digest',
751
+ digest: decoded.digest as `0x${string}`,
752
+ parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)),
753
+ }
754
+ }
755
+
756
+ throw new Error('Not implemented')
757
+ }
758
+
759
+ export function toAbiFormat(payload: Parented): SolidityDecoded {
760
+ if (payload.type === 'call') {
761
+ return {
762
+ kind: KIND_TRANSACTIONS,
763
+ noChainId: false,
764
+ calls: payload.calls.map((call) => ({
765
+ to: call.to,
766
+ value: call.value,
767
+ data: call.data,
768
+ gasLimit: call.gasLimit,
769
+ delegateCall: call.delegateCall,
770
+ onlyFallback: call.onlyFallback,
771
+ behaviorOnError: BigInt(encodeBehaviorOnError(call.behaviorOnError)),
772
+ })),
773
+ space: payload.space,
774
+ nonce: payload.nonce,
775
+ message: '0x',
776
+ imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
777
+ digest: '0x0000000000000000000000000000000000000000000000000000000000000000',
778
+ parentWallets: payload.parentWallets ?? [],
779
+ }
780
+ }
781
+
782
+ if (payload.type === 'message') {
783
+ return {
784
+ kind: KIND_MESSAGE,
785
+ noChainId: false,
786
+ calls: [],
787
+ space: 0n,
788
+ nonce: 0n,
789
+ message: payload.message,
790
+ imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
791
+ digest: '0x0000000000000000000000000000000000000000000000000000000000000000',
792
+ parentWallets: payload.parentWallets ?? [],
793
+ }
794
+ }
795
+
796
+ if (payload.type === 'config-update') {
797
+ return {
798
+ kind: KIND_CONFIG_UPDATE,
799
+ noChainId: false,
800
+ calls: [],
801
+ space: 0n,
802
+ nonce: 0n,
803
+ message: '0x',
804
+ imageHash: payload.imageHash,
805
+ digest: '0x0000000000000000000000000000000000000000000000000000000000000000',
806
+ parentWallets: payload.parentWallets ?? [],
807
+ }
808
+ }
809
+
810
+ if (payload.type === 'digest') {
811
+ return {
812
+ kind: KIND_DIGEST,
813
+ noChainId: false,
814
+ calls: [],
815
+ space: 0n,
816
+ nonce: 0n,
817
+ message: '0x',
818
+ imageHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
819
+ digest: payload.digest,
820
+ parentWallets: payload.parentWallets ?? [],
821
+ }
822
+ }
823
+
824
+ throw new Error('Invalid payload type')
825
+ }