@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.
- package/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +202 -0
- package/dist/address.d.ts +5 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +7 -0
- package/dist/address.js.map +1 -0
- package/dist/attestation.d.ts +24 -0
- package/dist/attestation.d.ts.map +1 -0
- package/dist/attestation.js +77 -0
- package/dist/attestation.js.map +1 -0
- package/dist/config.d.ts +85 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +381 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +173 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +31 -0
- package/dist/constants.js.map +1 -0
- package/dist/context.d.ts +9 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +8 -0
- package/dist/context.js.map +1 -0
- package/dist/erc-6492.d.ts +19 -0
- package/dist/erc-6492.d.ts.map +1 -0
- package/dist/erc-6492.js +64 -0
- package/dist/erc-6492.js.map +1 -0
- package/dist/extensions/index.d.ts +9 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +7 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/passkeys.d.ts +31 -0
- package/dist/extensions/passkeys.d.ts.map +1 -0
- package/dist/extensions/passkeys.js +224 -0
- package/dist/extensions/passkeys.js.map +1 -0
- package/dist/extensions/recovery.d.ts +310 -0
- package/dist/extensions/recovery.d.ts.map +1 -0
- package/dist/extensions/recovery.js +444 -0
- package/dist/extensions/recovery.js.map +1 -0
- package/dist/generic-tree.d.ts +14 -0
- package/dist/generic-tree.d.ts.map +1 -0
- package/dist/generic-tree.js +34 -0
- package/dist/generic-tree.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/network.d.ts +15 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +24 -0
- package/dist/network.js.map +1 -0
- package/dist/payload.d.ts +108 -0
- package/dist/payload.d.ts.map +1 -0
- package/dist/payload.js +627 -0
- package/dist/payload.js.map +1 -0
- package/dist/permission.d.ts +73 -0
- package/dist/permission.d.ts.map +1 -0
- package/dist/permission.js +188 -0
- package/dist/permission.js.map +1 -0
- package/dist/session-config.d.ts +113 -0
- package/dist/session-config.d.ts.map +1 -0
- package/dist/session-config.js +554 -0
- package/dist/session-config.js.map +1 -0
- package/dist/session-signature.d.ts +24 -0
- package/dist/session-signature.d.ts.map +1 -0
- package/dist/session-signature.js +141 -0
- package/dist/session-signature.js.map +1 -0
- package/dist/signature.d.ts +108 -0
- package/dist/signature.d.ts.map +1 -0
- package/dist/signature.js +1079 -0
- package/dist/signature.js.map +1 -0
- package/dist/utils.d.ts +45 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +100 -0
- package/dist/utils.js.map +1 -0
- package/eslint.config.mjs +4 -0
- package/package.json +27 -0
- package/src/address.ts +19 -0
- package/src/attestation.ts +114 -0
- package/src/config.ts +521 -0
- package/src/constants.ts +39 -0
- package/src/context.ts +16 -0
- package/src/erc-6492.ts +97 -0
- package/src/extensions/index.ts +14 -0
- package/src/extensions/passkeys.ts +283 -0
- package/src/extensions/recovery.ts +542 -0
- package/src/generic-tree.ts +55 -0
- package/src/index.ts +15 -0
- package/src/network.ts +37 -0
- package/src/payload.ts +825 -0
- package/src/permission.ts +252 -0
- package/src/session-config.ts +681 -0
- package/src/session-signature.ts +197 -0
- package/src/signature.ts +1398 -0
- package/src/utils.ts +114 -0
- 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
|
+
}
|