ox 0.14.44 → 0.14.46

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 (149) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/Frame/package.json +6 -0
  3. package/FrameReceipt/package.json +6 -0
  4. package/FrameSignature/package.json +6 -0
  5. package/TxEnvelopeEip8141/package.json +6 -0
  6. package/_cjs/core/Frame.js +147 -0
  7. package/_cjs/core/Frame.js.map +1 -0
  8. package/_cjs/core/FrameReceipt.js +34 -0
  9. package/_cjs/core/FrameReceipt.js.map +1 -0
  10. package/_cjs/core/FrameSignature.js +203 -0
  11. package/_cjs/core/FrameSignature.js.map +1 -0
  12. package/_cjs/core/Transaction.js +18 -2
  13. package/_cjs/core/Transaction.js.map +1 -1
  14. package/_cjs/core/TransactionReceipt.js +10 -0
  15. package/_cjs/core/TransactionReceipt.js.map +1 -1
  16. package/_cjs/core/TransactionRequest.js +11 -1
  17. package/_cjs/core/TransactionRequest.js.map +1 -1
  18. package/_cjs/core/TxEnvelopeEip8141.js +283 -0
  19. package/_cjs/core/TxEnvelopeEip8141.js.map +1 -0
  20. package/_cjs/core/internal/curves.js +6 -0
  21. package/_cjs/core/internal/curves.js.map +1 -0
  22. package/_cjs/core/internal/quantity.js +10 -0
  23. package/_cjs/core/internal/quantity.js.map +1 -0
  24. package/_cjs/erc4337/UserOperationReceipt.js.map +1 -1
  25. package/_cjs/index.js +6 -2
  26. package/_cjs/index.js.map +1 -1
  27. package/_cjs/tempo/KeyAuthorization.js +43 -10
  28. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  29. package/_cjs/tempo/MultisigConfig.js +33 -11
  30. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  31. package/_cjs/tempo/MultisigOperation.js +18 -94
  32. package/_cjs/tempo/MultisigOperation.js.map +1 -1
  33. package/_cjs/tempo/MultisigSimulation.js +18 -46
  34. package/_cjs/tempo/MultisigSimulation.js.map +1 -1
  35. package/_cjs/tempo/SignatureEnvelope.js +72 -109
  36. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  37. package/_cjs/tempo/TransactionRequest.js +5 -1
  38. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  39. package/_cjs/tempo/index.js.map +1 -1
  40. package/_cjs/version.js +1 -1
  41. package/_esm/core/Frame.js +311 -0
  42. package/_esm/core/Frame.js.map +1 -0
  43. package/_esm/core/FrameReceipt.js +72 -0
  44. package/_esm/core/FrameReceipt.js.map +1 -0
  45. package/_esm/core/FrameSignature.js +422 -0
  46. package/_esm/core/FrameSignature.js.map +1 -0
  47. package/_esm/core/Transaction.js +18 -2
  48. package/_esm/core/Transaction.js.map +1 -1
  49. package/_esm/core/TransactionReceipt.js +10 -0
  50. package/_esm/core/TransactionReceipt.js.map +1 -1
  51. package/_esm/core/TransactionRequest.js +11 -1
  52. package/_esm/core/TransactionRequest.js.map +1 -1
  53. package/_esm/core/TxEnvelopeEip8141.js +490 -0
  54. package/_esm/core/TxEnvelopeEip8141.js.map +1 -0
  55. package/_esm/core/internal/curves.js +19 -0
  56. package/_esm/core/internal/curves.js.map +1 -0
  57. package/_esm/core/internal/quantity.js +14 -0
  58. package/_esm/core/internal/quantity.js.map +1 -0
  59. package/_esm/erc4337/UserOperationReceipt.js.map +1 -1
  60. package/_esm/index.js +73 -0
  61. package/_esm/index.js.map +1 -1
  62. package/_esm/tempo/KeyAuthorization.js +42 -9
  63. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  64. package/_esm/tempo/MultisigConfig.js +41 -22
  65. package/_esm/tempo/MultisigConfig.js.map +1 -1
  66. package/_esm/tempo/MultisigOperation.js +20 -100
  67. package/_esm/tempo/MultisigOperation.js.map +1 -1
  68. package/_esm/tempo/MultisigSimulation.js +18 -46
  69. package/_esm/tempo/MultisigSimulation.js.map +1 -1
  70. package/_esm/tempo/SignatureEnvelope.js +107 -169
  71. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  72. package/_esm/tempo/TransactionRequest.js +5 -1
  73. package/_esm/tempo/TransactionRequest.js.map +1 -1
  74. package/_esm/tempo/index.js +3 -1
  75. package/_esm/tempo/index.js.map +1 -1
  76. package/_esm/version.js +1 -1
  77. package/_types/core/Frame.d.ts +290 -0
  78. package/_types/core/Frame.d.ts.map +1 -0
  79. package/_types/core/FrameReceipt.d.ts +92 -0
  80. package/_types/core/FrameReceipt.d.ts.map +1 -0
  81. package/_types/core/FrameSignature.d.ts +357 -0
  82. package/_types/core/FrameSignature.d.ts.map +1 -0
  83. package/_types/core/Transaction.d.ts +29 -4
  84. package/_types/core/Transaction.d.ts.map +1 -1
  85. package/_types/core/TransactionReceipt.d.ts +15 -6
  86. package/_types/core/TransactionReceipt.d.ts.map +1 -1
  87. package/_types/core/TransactionRequest.d.ts +10 -4
  88. package/_types/core/TransactionRequest.d.ts.map +1 -1
  89. package/_types/core/TxEnvelopeEip8141.d.ts +343 -0
  90. package/_types/core/TxEnvelopeEip8141.d.ts.map +1 -0
  91. package/_types/core/internal/curves.d.ts +19 -0
  92. package/_types/core/internal/curves.d.ts.map +1 -0
  93. package/_types/core/internal/quantity.d.ts +10 -0
  94. package/_types/core/internal/quantity.d.ts.map +1 -0
  95. package/_types/erc4337/UserOperationReceipt.d.ts +1 -1
  96. package/_types/erc4337/UserOperationReceipt.d.ts.map +1 -1
  97. package/_types/index.d.ts +73 -0
  98. package/_types/index.d.ts.map +1 -1
  99. package/_types/tempo/KeyAuthorization.d.ts +48 -38
  100. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  101. package/_types/tempo/MultisigConfig.d.ts +15 -14
  102. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  103. package/_types/tempo/MultisigOperation.d.ts +3 -2
  104. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  105. package/_types/tempo/MultisigSimulation.d.ts +2 -49
  106. package/_types/tempo/MultisigSimulation.d.ts.map +1 -1
  107. package/_types/tempo/SignatureEnvelope.d.ts +69 -128
  108. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  109. package/_types/tempo/Transaction.d.ts +2 -0
  110. package/_types/tempo/Transaction.d.ts.map +1 -1
  111. package/_types/tempo/TransactionReceipt.d.ts +2 -0
  112. package/_types/tempo/TransactionReceipt.d.ts.map +1 -1
  113. package/_types/tempo/TransactionRequest.d.ts +5 -2
  114. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  115. package/_types/tempo/index.d.ts +3 -1
  116. package/_types/tempo/index.d.ts.map +1 -1
  117. package/_types/version.d.ts +1 -1
  118. package/core/Frame.ts +443 -0
  119. package/core/FrameReceipt.ts +117 -0
  120. package/core/FrameSignature.ts +606 -0
  121. package/core/Transaction.ts +73 -4
  122. package/core/TransactionReceipt.ts +41 -4
  123. package/core/TransactionRequest.ts +26 -2
  124. package/core/TxEnvelopeEip8141.ts +717 -0
  125. package/core/internal/curves.ts +22 -0
  126. package/core/internal/quantity.ts +16 -0
  127. package/erc4337/UserOperationReceipt.ts +1 -6
  128. package/index.ts +73 -0
  129. package/package.json +21 -1
  130. package/tempo/AuthorizationTempo.test.ts +8 -4
  131. package/tempo/KeyAuthorization.test-d.ts +68 -33
  132. package/tempo/KeyAuthorization.test.ts +89 -8
  133. package/tempo/KeyAuthorization.ts +124 -61
  134. package/tempo/MultisigConfig.test.ts +34 -26
  135. package/tempo/MultisigConfig.ts +54 -24
  136. package/tempo/MultisigOperation.test.ts +79 -550
  137. package/tempo/MultisigOperation.ts +31 -146
  138. package/tempo/MultisigSimulation.test-d.ts +2 -2
  139. package/tempo/MultisigSimulation.test.ts +21 -110
  140. package/tempo/MultisigSimulation.ts +25 -99
  141. package/tempo/SignatureEnvelope.test-d.ts +29 -67
  142. package/tempo/SignatureEnvelope.test.ts +301 -568
  143. package/tempo/SignatureEnvelope.ts +156 -274
  144. package/tempo/TransactionRequest.test-d.ts +6 -5
  145. package/tempo/TransactionRequest.test.ts +17 -80
  146. package/tempo/TransactionRequest.ts +23 -3
  147. package/tempo/index.ts +3 -1
  148. package/tempo/multisig.e2e.test.ts +322 -861
  149. package/version.ts +1 -1
@@ -3,7 +3,11 @@ import type * as Address from '../core/Address.js'
3
3
  import type * as Errors from '../core/Errors.js'
4
4
  import * as Hash from '../core/Hash.js'
5
5
  import * as Hex from '../core/Hex.js'
6
- import type { Compute, OneOf, UnionPartialBy } from '../core/internal/types.js'
6
+ import type {
7
+ Compute,
8
+ PartialBy,
9
+ UnionPartialBy,
10
+ } from '../core/internal/types.js'
7
11
  import * as Rlp from '../core/Rlp.js'
8
12
  import * as SignatureEnvelope from './SignatureEnvelope.js'
9
13
  import * as TempoAddress from './TempoAddress.js'
@@ -25,7 +29,7 @@ import * as TempoAddress from './TempoAddress.js'
25
29
  * - `chainId`: Chain ID for replay protection (0 = valid on any chain)
26
30
  * - `expiry`: Unix timestamp when the key expires (undefined = never expires)
27
31
  * - `limits`: Per-TIP-20 token spending limits (only applies to `transfer()` and `approve()` calls)
28
- * - `type`: Key type (`secp256k1`, `p256`, or `webAuthn`)
32
+ * - `type`: Key type (`secp256k1`, `p256`, `webAuthn`, or `multisig`)
29
33
  *
30
34
  * [Access Keys Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#access-keys)
31
35
  */
@@ -41,6 +45,8 @@ export type KeyAuthorization<
41
45
  chainId: bigintType
42
46
  /** Unix timestamp when key expires (undefined = never expires). */
43
47
  expiry?: numberType | null | undefined
48
+ /** Whether this authorization provisions an admin access key. */
49
+ isAdmin?: boolean | undefined
44
50
  /** TIP20 spending limits for this key. */
45
51
  limits?:
46
52
  | readonly TokenLimit<bigintType, numberType, addressType>[]
@@ -53,8 +59,6 @@ export type KeyAuthorization<
53
59
  * - `[...]` = only listed contract+selector combinations allowed
54
60
  */
55
61
  scopes?: readonly Scope<addressType>[] | undefined
56
- /** Key type. (secp256k1, P256, WebAuthn). */
57
- type: SignatureEnvelope.Type
58
62
  /**
59
63
  * Optional 32-byte witness bound into the signing hash.
60
64
  *
@@ -65,32 +69,37 @@ export type KeyAuthorization<
65
69
  * [TIP-1053 Specification](https://tips.sh/1053)
66
70
  */
67
71
  witness?: Hex.Hex | undefined
68
- } & OneOf<
69
- // TIP-1049 admin access keys: `account` and `isAdmin` are paired — either
70
- // both are specified or neither. The `account` binding scopes the signing
71
- // hash to a specific account, and `isAdmin: true` provisions an admin
72
- // access key with unrestricted keychain mutator privileges.
73
- //
74
- // [TIP-1049 Specification](https://tips.sh/1049)
72
+ } & (
73
+ | {
74
+ /** Optional account address binding. */
75
+ account?: addressType | undefined
76
+ /** Primitive key type. */
77
+ type: SignatureEnvelope.Type
78
+ }
75
79
  | {
76
- /** Account address this authorization is bound to. */
80
+ /** Required parent account binding for a multisig grant. */
77
81
  account: addressType
78
- /** Whether this authorization provisions an admin access key. */
79
- isAdmin: boolean
82
+ /** Multisig key type. */
83
+ type: 'multisig'
80
84
  }
81
- | {}
82
- > &
85
+ ) &
83
86
  (signed extends true
84
- ? { signature: Signature<bigintType, numberType> }
87
+ ? {
88
+ signature:
89
+ | SignatureEnvelope.Primitive<bigintType, numberType>
90
+ | SignatureEnvelope.Multisig<bigintType, numberType>
91
+ }
85
92
  : {
86
- signature?: Signature<bigintType, numberType> | undefined
93
+ signature?:
94
+ | SignatureEnvelope.Primitive<bigintType, numberType>
95
+ | SignatureEnvelope.Multisig<bigintType, numberType>
96
+ | undefined
87
97
  })
88
98
 
89
99
  /** Signature that can authorize an access key. */
90
- export type Signature<bigintType = bigint, numberType = number> = OneOf<
100
+ export type Signature<bigintType = bigint, numberType = number> =
91
101
  | SignatureEnvelope.Primitive<bigintType, numberType>
92
102
  | SignatureEnvelope.Multisig<bigintType, numberType>
93
- >
94
103
 
95
104
  /** RPC-formatted signature that can authorize an access key. */
96
105
  export type SignatureRpc =
@@ -107,8 +116,6 @@ export type Input = KeyAuthorization<
107
116
 
108
117
  /** RPC representation matching the node's wire format. */
109
118
  export type Rpc = {
110
- /** Optional account address binding (TIP-1049). */
111
- account?: Address.Address | null | undefined
112
119
  /** Allowed call scopes (node field: `allowedCalls`). */
113
120
  allowedCalls?: readonly RpcCallScope[] | undefined
114
121
  /** Chain ID (hex quantity). */
@@ -119,33 +126,44 @@ export type Rpc = {
119
126
  isAdmin?: boolean | null | undefined
120
127
  /** Key identifier. */
121
128
  keyId: Address.Address
122
- /** Key type. */
123
- keyType: SignatureEnvelope.Type
124
129
  /** Token spending limits. */
125
130
  limits?: readonly RpcTokenLimit[] | undefined
126
131
  /** Signature authorizing this key. */
127
132
  signature: SignatureRpc
128
133
  /** Optional 32-byte witness (hex). */
129
134
  witness?: Hex.Hex | null | undefined
130
- }
135
+ } & (
136
+ | {
137
+ /** Optional account address binding (TIP-1049). */
138
+ account?: Address.Address | null | undefined
139
+ /** Primitive key type. */
140
+ keyType: SignatureEnvelope.Type
141
+ }
142
+ | {
143
+ /** Required parent account binding for a multisig grant. */
144
+ account: Address.Address
145
+ /** Multisig key type. */
146
+ keyType: 'multisig'
147
+ }
148
+ )
131
149
 
132
150
  /** RPC representation of a token limit (matches node's `TokenLimit` serde). */
133
151
  export type RpcTokenLimit = {
134
- token: Address.Address
135
152
  limit: Hex.Hex
136
153
  period?: Hex.Hex | undefined
154
+ token: Address.Address
137
155
  }
138
156
 
139
157
  /** RPC representation of a call scope (matches node's `CallScope` serde). */
140
158
  export type RpcCallScope = {
159
+ selectorRules?: readonly RpcSelectorRule[] | undefined
141
160
  target: Address.Address
142
- selectorRules?: readonly RpcSelectorRule[]
143
161
  }
144
162
 
145
163
  /** RPC representation of a selector rule (matches node's `SelectorRule` serde). */
146
164
  export type RpcSelectorRule = {
165
+ recipients?: readonly Address.Address[] | undefined
147
166
  selector: Hex.Hex
148
- recipients?: readonly Address.Address[]
149
167
  }
150
168
 
151
169
  /** Signed representation of a Key Authorization. */
@@ -156,10 +174,11 @@ export type Signed<
156
174
  > = KeyAuthorization<true, bigintType, numberType, addressType>
157
175
 
158
176
  type SignatureValue =
159
- | UnionPartialBy<Signature, 'prehash' | 'type'>
177
+ | SignatureEnvelope.from.MultisigFromConfig
178
+ | UnionPartialBy<SignatureEnvelope.Primitive, 'prehash' | 'type'>
179
+ | PartialBy<SignatureEnvelope.Multisig, 'type'>
160
180
  | SignatureEnvelope.Secp256k1Flat
161
181
  | SignatureEnvelope.Serialized
162
- | SignatureEnvelope.from.MultisigFromConfig
163
182
 
164
183
  type BaseTuple = readonly [
165
184
  chainId: Hex.Hex,
@@ -235,14 +254,6 @@ export type Tuple<signed extends boolean = boolean> = signed extends true
235
254
  export type Scope<addressType = Address.Address> = {
236
255
  /** Target contract address. */
237
256
  address: addressType
238
- /**
239
- * 4-byte function selector, or a human-readable ABI signature
240
- * (e.g. `'transfer(address,uint256)'` or `'function transfer(address,uint256)'`).
241
- *
242
- * Signatures are encoded into a 4-byte selector automatically.
243
- * Omit to allow any selector on this contract.
244
- */
245
- selector?: Hex.Hex | string | undefined
246
257
  /**
247
258
  * Recipient allowlist for this selector (first ABI `address` argument).
248
259
  *
@@ -252,6 +263,8 @@ export type Scope<addressType = Address.Address> = {
252
263
  * Only valid for constrained selectors: `transfer`, `approve`, `transferWithMemo`.
253
264
  */
254
265
  recipients?: readonly addressType[] | undefined
266
+ /** Function selector or human-readable ABI signature. */
267
+ selector?: Hex.Hex | string | undefined
255
268
  }
256
269
 
257
270
  /**
@@ -267,8 +280,6 @@ export type TokenLimit<
267
280
  numberType = number,
268
281
  addressType = Address.Address,
269
282
  > = {
270
- /** Address of the TIP-20 token. */
271
- token: addressType
272
283
  /** Maximum spending amount for this token (enforced over the key's lifetime, or per period if `period` \> 0). */
273
284
  limit: bigintType
274
285
  /**
@@ -278,6 +289,8 @@ export type TokenLimit<
278
289
  * - `\> 0` = periodic limit that resets every `period` seconds
279
290
  */
280
291
  period?: numberType | undefined
292
+ /** Address of the TIP-20 token. */
293
+ token: addressType
281
294
  }
282
295
 
283
296
  /**
@@ -424,6 +437,7 @@ export function from<
424
437
  options: from.Options<signature> = {},
425
438
  ): from.ReturnType<authorization, signature> {
426
439
  if ('keyId' in authorization) return fromRpc(authorization as Rpc) as never
440
+ assertAccountBinding(authorization.type, authorization.account)
427
441
  const auth = authorization as KeyAuthorization & {
428
442
  limits?: readonly { token: TempoAddress.Address; limit: bigint }[]
429
443
  scopes?: readonly {
@@ -436,6 +450,9 @@ export function from<
436
450
  if (auth.signature) assertSignature(auth.signature)
437
451
  const resolved = {
438
452
  ...auth,
453
+ ...(auth.account !== undefined
454
+ ? { account: TempoAddress.resolve(auth.account) }
455
+ : {}),
439
456
  address: TempoAddress.resolve(auth.address as TempoAddress.Address),
440
457
  ...(auth.limits
441
458
  ? {
@@ -479,8 +496,12 @@ export declare namespace from {
479
496
  type Options<
480
497
  signature extends SignatureValue | undefined = SignatureValue | undefined,
481
498
  > = {
482
- /** The signature to attach to the Key Authorization. */
483
- signature?: signature | Signature | undefined
499
+ /** The primitive or multisig signature to attach to the Key Authorization. */
500
+ signature?:
501
+ | signature
502
+ | SignatureEnvelope.Primitive
503
+ | SignatureEnvelope.Multisig
504
+ | undefined
484
505
  }
485
506
 
486
507
  type ReturnType<
@@ -502,7 +523,10 @@ export declare namespace from {
502
523
  >
503
524
  >
504
525
 
505
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
526
+ type ErrorType =
527
+ | InvalidSignatureTypeError
528
+ | MissingAccountError
529
+ | Errors.GlobalErrorType
506
530
  }
507
531
 
508
532
  /**
@@ -536,6 +560,7 @@ export function fromRpc(authorization: Rpc): Signed {
536
560
  const witness = authorization.witness ?? undefined
537
561
  const isAdmin = authorization.isAdmin ?? undefined
538
562
  const account = authorization.account ?? undefined
563
+ assertAccountBinding(keyType, account)
539
564
  const signature = SignatureEnvelope.fromRpc(authorization.signature)
540
565
  assertSignature(signature)
541
566
  if (witness !== undefined) assertWitness(witness)
@@ -557,9 +582,6 @@ export function fromRpc(authorization: Rpc): Signed {
557
582
  })
558
583
  : undefined
559
584
 
560
- const accountBinding =
561
- account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
562
-
563
585
  return {
564
586
  address: keyId,
565
587
  chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
@@ -573,14 +595,20 @@ export function fromRpc(authorization: Rpc): Signed {
573
595
  })),
574
596
  ...(scopes ? { scopes } : {}),
575
597
  signature,
576
- type: keyType,
598
+ ...(keyType === 'multisig'
599
+ ? { account: account!, type: keyType }
600
+ : { type: keyType }),
577
601
  ...(witness !== undefined ? { witness } : {}),
578
- ...accountBinding,
602
+ ...(account !== undefined ? { account } : {}),
603
+ ...(isAdmin ? { isAdmin: true as const } : {}),
579
604
  }
580
605
  }
581
606
 
582
607
  export declare namespace fromRpc {
583
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
608
+ type ErrorType =
609
+ | InvalidSignatureTypeError
610
+ | MissingAccountError
611
+ | Errors.GlobalErrorType
584
612
  }
585
613
 
586
614
  /**
@@ -642,6 +670,8 @@ export function fromTuple<const tuple extends Tuple>(
642
670
  return 'p256'
643
671
  case '0x02':
644
672
  return 'webAuthn'
673
+ case '0x03':
674
+ return 'multisig'
645
675
  default:
646
676
  throw new Error(`Invalid key type: ${keyType_hex}`)
647
677
  }
@@ -697,17 +727,19 @@ export function fromTuple<const tuple extends Tuple>(
697
727
  const account = isAbsent(rawAccount)
698
728
  ? undefined
699
729
  : (rawAccount as Address.Address)
700
- const accountBinding =
701
- account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
730
+ assertAccountBinding(keyType, account)
702
731
  const args: KeyAuthorization = {
703
732
  address: keyId,
704
733
  chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
705
- type: keyType,
734
+ ...(keyType === 'multisig'
735
+ ? { account: account!, type: keyType }
736
+ : { type: keyType }),
706
737
  ...(expiry !== undefined ? { expiry } : {}),
707
738
  ...(limits !== undefined ? { limits } : {}),
708
739
  ...(scopes !== undefined ? { scopes } : {}),
709
740
  ...(witness !== undefined ? { witness } : {}),
710
- ...accountBinding,
741
+ ...(account !== undefined ? { account } : {}),
742
+ ...(isAdmin ? { isAdmin: true as const } : {}),
711
743
  }
712
744
  if (signatureSerialized) {
713
745
  const signature = SignatureEnvelope.deserialize(signatureSerialized)
@@ -722,7 +754,10 @@ export declare namespace fromTuple {
722
754
  KeyAuthorization<authorization extends Tuple<true> ? true : false>
723
755
  >
724
756
 
725
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
757
+ type ErrorType =
758
+ | InvalidSignatureTypeError
759
+ | MissingAccountError
760
+ | Errors.GlobalErrorType
726
761
  }
727
762
 
728
763
  /**
@@ -914,6 +949,7 @@ export declare namespace serialize {
914
949
  * @returns An RPC-formatted Key Authorization.
915
950
  */
916
951
  export function toRpc(authorization: Signed): Rpc {
952
+ assertAccountBinding(authorization.type, authorization.account)
917
953
  const {
918
954
  address,
919
955
  scopes,
@@ -957,13 +993,15 @@ export function toRpc(authorization: Signed): Rpc {
957
993
  chainId: chainId === 0n ? '0x' : Hex.fromNumber(chainId),
958
994
  expiry: typeof expiry === 'number' ? Hex.fromNumber(expiry) : null,
959
995
  keyId: TempoAddress.resolve(address),
960
- keyType: type,
996
+ ...(type === 'multisig' ? { account, keyType: type } : { keyType: type }),
961
997
  limits: limits?.map(({ token, limit, period }) => ({
962
998
  token,
963
999
  limit: Hex.fromNumber(limit),
964
1000
  ...(period ? { period: numberToHex(period) } : {}),
965
1001
  })),
966
- signature: SignatureEnvelope.toRpc(signature) as SignatureRpc,
1002
+ signature: SignatureEnvelope.toRpc(signature) as
1003
+ | SignatureEnvelope.PrimitiveRpc
1004
+ | SignatureEnvelope.MultisigRpc,
967
1005
  ...(allowedCalls ? { allowedCalls } : {}),
968
1006
  ...(witness !== undefined ? { witness } : {}),
969
1007
  ...(isAdmin ? { isAdmin: true } : {}),
@@ -972,7 +1010,10 @@ export function toRpc(authorization: Signed): Rpc {
972
1010
  }
973
1011
 
974
1012
  export declare namespace toRpc {
975
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
1013
+ type ErrorType =
1014
+ | InvalidSignatureTypeError
1015
+ | MissingAccountError
1016
+ | Errors.GlobalErrorType
976
1017
  }
977
1018
 
978
1019
  /**
@@ -1009,6 +1050,7 @@ export declare namespace toRpc {
1009
1050
  export function toTuple<const authorization extends KeyAuthorization>(
1010
1051
  authorization: authorization,
1011
1052
  ): toTuple.ReturnType<authorization> {
1053
+ assertAccountBinding(authorization.type, authorization.account)
1012
1054
  const {
1013
1055
  address,
1014
1056
  chainId,
@@ -1025,16 +1067,19 @@ export function toTuple<const authorization extends KeyAuthorization>(
1025
1067
  assertSignature(authorization.signature)
1026
1068
  return SignatureEnvelope.serialize(authorization.signature)
1027
1069
  })()
1070
+ const keyType = authorization.type
1028
1071
  const type = (() => {
1029
- switch (authorization.type) {
1072
+ switch (keyType) {
1030
1073
  case 'secp256k1':
1031
1074
  return '0x'
1032
1075
  case 'p256':
1033
1076
  return '0x01'
1034
1077
  case 'webAuthn':
1035
1078
  return '0x02'
1079
+ case 'multisig':
1080
+ return '0x03'
1036
1081
  default:
1037
- throw new Error(`Invalid key type: ${authorization.type}`)
1082
+ throw new Error(`Invalid key type: ${keyType}`)
1038
1083
  }
1039
1084
  })()
1040
1085
  const limitsValue = limits?.map((limit) => {
@@ -1083,7 +1128,7 @@ export function toTuple<const authorization extends KeyAuthorization>(
1083
1128
  // single entry to this list with `placeholder: '0x'`.
1084
1129
  const hasTip1053Plus =
1085
1130
  witness !== undefined || isAdmin || account !== undefined
1086
- const optionals: readonly { value: unknown; placeholder: unknown }[] = [
1131
+ const optionals: readonly { placeholder: unknown; value: unknown }[] = [
1087
1132
  {
1088
1133
  value:
1089
1134
  expiry !== null && expiry !== undefined && expiry !== 0
@@ -1120,7 +1165,10 @@ export declare namespace toTuple {
1120
1165
  type ReturnType<authorization extends KeyAuthorization = KeyAuthorization> =
1121
1166
  Compute<Tuple<authorization extends KeyAuthorization<true> ? true : false>>
1122
1167
 
1123
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
1168
+ type ErrorType =
1169
+ | InvalidSignatureTypeError
1170
+ | MissingAccountError
1171
+ | Errors.GlobalErrorType
1124
1172
  }
1125
1173
 
1126
1174
  function bigintToHex(value: bigint): Hex.Hex {
@@ -1154,6 +1202,13 @@ function resolveSelector(
1154
1202
  return AbiItem.getSelector(selector)
1155
1203
  }
1156
1204
 
1205
+ function assertAccountBinding(
1206
+ type: KeyAuthorization['type'],
1207
+ account: TempoAddress.Address | null | undefined,
1208
+ ): void {
1209
+ if (type === 'multisig' && account == null) throw new MissingAccountError()
1210
+ }
1211
+
1157
1212
  function assertWitness(witness: Hex.Hex): void {
1158
1213
  if (Hex.size(witness) !== 32) throw new InvalidWitnessSizeError(witness)
1159
1214
  }
@@ -1198,3 +1253,11 @@ export class InvalidSignatureTypeError extends Error {
1198
1253
  )
1199
1254
  }
1200
1255
  }
1256
+
1257
+ /** Thrown when a multisig key grant omits its parent account binding. */
1258
+ export class MissingAccountError extends Error {
1259
+ override readonly name = 'KeyAuthorization.MissingAccountError'
1260
+ constructor() {
1261
+ super('Multisig key grants require a parent account binding.')
1262
+ }
1263
+ }
@@ -1,6 +1,7 @@
1
1
  import { Hash, Hex, Rlp } from 'ox'
2
2
  import { MultisigConfig } from 'ox/tempo'
3
3
  import { describe, expect, test } from 'vitest'
4
+ import { factory } from '../../test/tempo/multisig.js'
4
5
 
5
6
  const owner_1 = '0x1111111111111111111111111111111111111111'
6
7
  const owner_2 = '0x2222222222222222222222222222222222222222'
@@ -9,7 +10,7 @@ const config = MultisigConfig.from({
9
10
  owners: [{ owner: owner_1, weight: 1 }],
10
11
  threshold: 1,
11
12
  })
12
- const account = MultisigConfig.getAddress(config)
13
+ const account = MultisigConfig.getAddress(config, { factory })
13
14
 
14
15
  describe('from', () => {
15
16
  test('behavior: normalizes an initial configuration', () => {
@@ -101,49 +102,57 @@ describe('fromRpc/toRpc', () => {
101
102
  describe('getAddress', () => {
102
103
  test('example: matches the frozen CREATE2 vector', () => {
103
104
  expect(account).toMatchInlineSnapshot(
104
- `"0xf4b916c5aea0fb199bd942389be00db0690c961f"`,
105
+ `"0x90ba71cb7534da990a1182cf28c9bf415055e03d"`,
105
106
  )
106
107
  expect(account).not.toBe('0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da')
107
108
  })
108
109
 
109
110
  test('behavior: includes salt, threshold, and owners', () => {
110
111
  expect(
111
- MultisigConfig.getAddress({
112
- owners: [
113
- { owner: owner_1, weight: 1 },
114
- { owner: owner_2, weight: 2 },
115
- ],
116
- salt: `0x${'42'.repeat(32)}`,
117
- threshold: 2,
118
- }),
119
- ).toMatchInlineSnapshot(`"0x94040edd3d7b542e0a96e01141bc250d709b4469"`)
112
+ MultisigConfig.getAddress(
113
+ {
114
+ owners: [
115
+ { owner: owner_1, weight: 1 },
116
+ { owner: owner_2, weight: 2 },
117
+ ],
118
+ salt: `0x${'42'.repeat(32)}`,
119
+ threshold: 2,
120
+ },
121
+ { factory },
122
+ ),
123
+ ).toMatchInlineSnapshot(`"0x7898a7d1dd96946d2a968de1540ca884a244320c"`)
120
124
  })
121
125
 
122
126
  test('behavior: is stable and binds the salt', () => {
123
127
  expect({
124
- repeated: MultisigConfig.getAddress(config),
125
- salted: MultisigConfig.getAddress({
126
- ...config,
127
- salt: `0x${'42'.repeat(32)}`,
128
- }),
128
+ repeated: MultisigConfig.getAddress(config, { factory }),
129
+ salted: MultisigConfig.getAddress(
130
+ {
131
+ ...config,
132
+ salt: `0x${'42'.repeat(32)}`,
133
+ },
134
+ { factory },
135
+ ),
129
136
  }).toMatchInlineSnapshot(`
130
137
  {
131
- "repeated": "0xf4b916c5aea0fb199bd942389be00db0690c961f",
132
- "salted": "0x95e771f514fd6ac5b8bbd62a9b37db86eeed7e38",
138
+ "repeated": "0x90ba71cb7534da990a1182cf28c9bf415055e03d",
139
+ "salted": "0xa1a8472f4a01772db729d8ebc73fe46120750b83",
133
140
  }
134
141
  `)
135
142
  })
136
143
 
137
144
  test('error: rejects a current configuration', () => {
138
145
  expect(() =>
139
- MultisigConfig.getAddress({ ...config, version: 1n }),
146
+ MultisigConfig.getAddress({ ...config, version: 1n }, { factory }),
140
147
  ).toThrowErrorMatchingInlineSnapshot(
141
148
  `[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
142
149
  )
143
150
  })
144
151
 
145
152
  test('behavior: accepts numeric zero for an initial configuration', () => {
146
- expect(MultisigConfig.getAddress({ ...config, version: 0 })).toBe(account)
153
+ expect(
154
+ MultisigConfig.getAddress({ ...config, version: 0 }, { factory }),
155
+ ).toBe(account)
147
156
  })
148
157
  })
149
158
 
@@ -168,7 +177,7 @@ describe('getSignPayload', () => {
168
177
  expect(
169
178
  MultisigConfig.getSignPayload({ account, config, payload }),
170
179
  ).toMatchInlineSnapshot(
171
- `"0xdba6b49849aaef399fbc1de73fe26d520f21846c80c3e5d2486ad56e8df3cee3"`,
180
+ `"0x728efe90611b01c91c2be0da56e7eb8e0d2c274c84f97182e274187dde0b8a20"`,
172
181
  )
173
182
  })
174
183
 
@@ -223,14 +232,14 @@ describe('assert/validate', () => {
223
232
  })
224
233
  const rlp = Rlp.fromHex(MultisigConfig.toTuple(boundary))
225
234
  expect({
226
- account: MultisigConfig.getAddress(boundary),
235
+ account: MultisigConfig.getAddress(boundary, { factory }),
227
236
  commitment: MultisigConfig.getCommitment(boundary),
228
237
  rlpHash: Hash.keccak256(rlp),
229
238
  rlpLength: Hex.size(rlp),
230
239
  valid: MultisigConfig.validate(boundary),
231
240
  }).toMatchInlineSnapshot(`
232
241
  {
233
- "account": "0xa832c9a61d254a157c05edcd856b2cbe4dea8c77",
242
+ "account": "0x6acb25715a2b0cf05c098f753a87d9b38471e99f",
234
243
  "commitment": "0x0dc47a7ab45ffa21a01bfd115427e26617b5a57d7ccbea57db2fd4537ba96f56",
235
244
  "rlpHash": "0xbaf0d030add91caaa10815d2e99c942f1e39b0d199216973781adb4fc1af6955",
236
245
  "rlpLength": 1145,
@@ -246,7 +255,7 @@ describe('assert/validate', () => {
246
255
  { owner: owner_1, weight: 128 },
247
256
  { owner: owner_2, weight: 127 },
248
257
  ],
249
- threshold: 255,
258
+ threshold: 8,
250
259
  }),
251
260
  ).toBe(true)
252
261
  })
@@ -312,7 +321,7 @@ describe('assert/validate', () => {
312
321
  { owner: owner_1, weight: 128 },
313
322
  { owner: owner_2, weight: 128 },
314
323
  ],
315
- threshold: 255,
324
+ threshold: 8,
316
325
  },
317
326
  name: 'weight overflow',
318
327
  },
@@ -359,7 +368,6 @@ describe('assert/validate', () => {
359
368
  test('exports', () => {
360
369
  expect(Object.keys(MultisigConfig)).toMatchInlineSnapshot(`
361
370
  [
362
- "maxNestingDepth",
363
371
  "maxOwnerSignatureBytes",
364
372
  "maxOwners",
365
373
  "maxSignatures",