ox 1.7.2 → 1.7.5

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 (142) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/core/AbiEvent.d.ts.map +1 -1
  3. package/dist/core/AbiEvent.js +10 -9
  4. package/dist/core/AbiEvent.js.map +1 -1
  5. package/dist/core/Blobs.d.ts.map +1 -1
  6. package/dist/core/Blobs.js +6 -2
  7. package/dist/core/Blobs.js.map +1 -1
  8. package/dist/core/Signature.d.ts +6 -5
  9. package/dist/core/Signature.d.ts.map +1 -1
  10. package/dist/core/Signature.js +17 -7
  11. package/dist/core/Signature.js.map +1 -1
  12. package/dist/core/Siwe.d.ts.map +1 -1
  13. package/dist/core/Siwe.js +21 -1
  14. package/dist/core/Siwe.js.map +1 -1
  15. package/dist/tempo/KeyAuthorization.d.ts +43 -39
  16. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  17. package/dist/tempo/KeyAuthorization.js +24 -4
  18. package/dist/tempo/KeyAuthorization.js.map +1 -1
  19. package/dist/tempo/MultisigConfig.d.ts +18 -15
  20. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  21. package/dist/tempo/MultisigConfig.js +49 -20
  22. package/dist/tempo/MultisigConfig.js.map +1 -1
  23. package/dist/tempo/MultisigOperation.d.ts +45 -28
  24. package/dist/tempo/MultisigOperation.d.ts.map +1 -1
  25. package/dist/tempo/MultisigOperation.js +81 -131
  26. package/dist/tempo/MultisigOperation.js.map +1 -1
  27. package/dist/tempo/MultisigSimulation.d.ts +85 -0
  28. package/dist/tempo/MultisigSimulation.d.ts.map +1 -0
  29. package/dist/tempo/MultisigSimulation.js +112 -0
  30. package/dist/tempo/MultisigSimulation.js.map +1 -0
  31. package/dist/tempo/RpcSchemaTempo.d.ts +20 -10
  32. package/dist/tempo/RpcSchemaTempo.d.ts.map +1 -1
  33. package/dist/tempo/SignatureEnvelope.d.ts +74 -153
  34. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  35. package/dist/tempo/SignatureEnvelope.js +130 -187
  36. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  37. package/dist/tempo/TransactionRequest.d.ts +9 -7
  38. package/dist/tempo/TransactionRequest.d.ts.map +1 -1
  39. package/dist/tempo/TransactionRequest.js +12 -7
  40. package/dist/tempo/TransactionRequest.js.map +1 -1
  41. package/dist/tempo/index.d.ts +7 -5
  42. package/dist/tempo/index.d.ts.map +1 -1
  43. package/dist/tempo/index.js +7 -5
  44. package/dist/tempo/index.js.map +1 -1
  45. package/dist/zod/tempo/AuthorizationTempo.d.ts +360 -140
  46. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  47. package/dist/zod/tempo/KeyAuthorization.d.ts +247 -487
  48. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  49. package/dist/zod/tempo/KeyAuthorization.js +29 -31
  50. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  51. package/dist/zod/tempo/MultisigOperation.d.ts +0 -10
  52. package/dist/zod/tempo/MultisigOperation.d.ts.map +1 -1
  53. package/dist/zod/tempo/MultisigOperation.js +0 -3
  54. package/dist/zod/tempo/MultisigOperation.js.map +1 -1
  55. package/dist/zod/tempo/MultisigSimulation.d.ts +52 -0
  56. package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -0
  57. package/dist/zod/tempo/MultisigSimulation.js +44 -0
  58. package/dist/zod/tempo/MultisigSimulation.js.map +1 -0
  59. package/dist/zod/tempo/RpcSchemaTempo.d.ts +357 -422
  60. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  61. package/dist/zod/tempo/RpcSchemaTempo.js +19 -10
  62. package/dist/zod/tempo/RpcSchemaTempo.js.map +1 -1
  63. package/dist/zod/tempo/SignatureEnvelope.d.ts +108 -42
  64. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  65. package/dist/zod/tempo/SignatureEnvelope.js +21 -19
  66. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  67. package/dist/zod/tempo/Transaction.d.ts +678 -666
  68. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  69. package/dist/zod/tempo/TransactionRequest.d.ts +580 -619
  70. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  71. package/dist/zod/tempo/TransactionRequest.js +18 -10
  72. package/dist/zod/tempo/TransactionRequest.js.map +1 -1
  73. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
  74. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  75. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
  76. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  77. package/dist/zod/tempo/z.d.ts +1 -1
  78. package/dist/zod/tempo/z.d.ts.map +1 -1
  79. package/dist/zod/tempo/z.js +1 -1
  80. package/dist/zod/tempo/z.js.map +1 -1
  81. package/package.json +9 -9
  82. package/src/core/AbiEvent.ts +11 -9
  83. package/src/core/Blobs.ts +6 -2
  84. package/src/core/Signature.ts +41 -11
  85. package/src/core/Siwe.ts +20 -1
  86. package/src/core/_test/AbiEvent.test.ts +45 -0
  87. package/src/core/_test/Blobs.test.ts +22 -0
  88. package/src/core/_test/Signature.test.ts +166 -0
  89. package/src/core/_test/Siwe.test.ts +100 -1
  90. package/src/tempo/AuthorizationTempo.test.ts +8 -0
  91. package/src/tempo/KeyAuthorization.test-d.ts +16 -74
  92. package/src/tempo/KeyAuthorization.test.ts +113 -14
  93. package/src/tempo/KeyAuthorization.ts +110 -66
  94. package/src/tempo/MultisigConfig.test.ts +44 -20
  95. package/src/tempo/MultisigConfig.ts +65 -23
  96. package/src/tempo/MultisigOperation.test-d.ts +8 -6
  97. package/src/tempo/MultisigOperation.test.ts +187 -591
  98. package/src/tempo/MultisigOperation.ts +129 -205
  99. package/src/tempo/MultisigSimulation.test-d.ts +33 -0
  100. package/src/tempo/MultisigSimulation.test.ts +194 -0
  101. package/src/tempo/MultisigSimulation.ts +192 -0
  102. package/src/tempo/RpcSchemaTempo.test-d.ts +45 -10
  103. package/src/tempo/RpcSchemaTempo.ts +15 -6
  104. package/src/tempo/SignatureEnvelope.test-d.ts +27 -101
  105. package/src/tempo/SignatureEnvelope.test.ts +330 -533
  106. package/src/tempo/SignatureEnvelope.ts +198 -310
  107. package/src/tempo/Transaction.test.ts +61 -0
  108. package/src/tempo/TransactionRequest.test-d.ts +7 -7
  109. package/src/tempo/TransactionRequest.test.ts +81 -223
  110. package/src/tempo/TransactionRequest.ts +34 -13
  111. package/src/tempo/index.ts +7 -5
  112. package/src/tempo/multisig.e2e.test.ts +329 -818
  113. package/src/version.ts +1 -1
  114. package/src/zod/tempo/KeyAuthorization.ts +50 -39
  115. package/src/zod/tempo/MultisigOperation.ts +0 -3
  116. package/src/zod/tempo/MultisigSimulation.ts +53 -0
  117. package/src/zod/tempo/RpcSchemaTempo.ts +20 -10
  118. package/src/zod/tempo/SignatureEnvelope.ts +46 -59
  119. package/src/zod/tempo/TransactionRequest.ts +28 -14
  120. package/src/zod/tempo/_test/KeyAuthorization.test.ts +66 -30
  121. package/src/zod/tempo/_test/MultisigOperation.test.ts +0 -2
  122. package/src/zod/tempo/_test/MultisigSimulation.test-d.ts +13 -0
  123. package/src/zod/tempo/_test/MultisigSimulation.test.ts +53 -0
  124. package/src/zod/tempo/_test/RpcSchemaTempo.test-d.ts +19 -2
  125. package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +46 -4
  126. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +108 -129
  127. package/src/zod/tempo/_test/TransactionRequest.test.ts +17 -168
  128. package/src/zod/tempo/z.ts +1 -1
  129. package/dist/tempo/MultisigWitness.d.ts +0 -106
  130. package/dist/tempo/MultisigWitness.d.ts.map +0 -1
  131. package/dist/tempo/MultisigWitness.js +0 -118
  132. package/dist/tempo/MultisigWitness.js.map +0 -1
  133. package/dist/zod/tempo/MultisigWitness.d.ts +0 -152
  134. package/dist/zod/tempo/MultisigWitness.d.ts.map +0 -1
  135. package/dist/zod/tempo/MultisigWitness.js +0 -76
  136. package/dist/zod/tempo/MultisigWitness.js.map +0 -1
  137. package/src/tempo/MultisigWitness.test-d.ts +0 -42
  138. package/src/tempo/MultisigWitness.test.ts +0 -288
  139. package/src/tempo/MultisigWitness.ts +0 -210
  140. package/src/zod/tempo/MultisigWitness.ts +0 -101
  141. package/src/zod/tempo/_test/MultisigWitness.test-d.ts +0 -13
  142. package/src/zod/tempo/_test/MultisigWitness.test.ts +0 -149
@@ -4,7 +4,11 @@ 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
6
  import * as Quantity from '../core/internal/quantity.js'
7
- import type { Compute, OneOf, UnionPartialBy } from '../core/internal/types.js'
7
+ import type {
8
+ Compute,
9
+ PartialBy,
10
+ UnionPartialBy,
11
+ } from '../core/internal/types.js'
8
12
  import * as Rlp from '../core/Rlp.js'
9
13
  import * as SignatureEnvelope from './SignatureEnvelope.js'
10
14
 
@@ -25,7 +29,7 @@ import * as SignatureEnvelope from './SignatureEnvelope.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
  */
@@ -34,12 +38,16 @@ export type KeyAuthorization<
34
38
  bigintType = bigint,
35
39
  numberType = number,
36
40
  > = {
41
+ /** Account address this authorization is bound to. Required for multisig grants. */
42
+ account?: Address.Address | undefined
37
43
  /** Address derived from the public key of the key type. */
38
44
  address: Address.Address
39
45
  /** Chain ID for replay protection. */
40
46
  chainId: bigintType
41
47
  /** Unix timestamp when key expires (undefined = never expires). */
42
48
  expiry?: numberType | null | undefined
49
+ /** Whether this authorization provisions an admin access key. */
50
+ isAdmin?: boolean | undefined
43
51
  /** TIP20 spending limits for this key. */
44
52
  limits?: readonly TokenLimit<bigintType, numberType>[] | undefined
45
53
  /**
@@ -50,8 +58,8 @@ export type KeyAuthorization<
50
58
  * - `[...]` = only listed contract+selector combinations allowed
51
59
  */
52
60
  scopes?: readonly Scope[] | undefined
53
- /** Key type. (secp256k1, P256, WebAuthn). */
54
- type: SignatureEnvelope.Type
61
+ /** Key type (secp256k1, P256, WebAuthn, or multisig). */
62
+ type: SignatureEnvelope.Type | 'multisig'
55
63
  /**
56
64
  * Optional 32-byte witness bound into the signing hash.
57
65
  *
@@ -62,37 +70,28 @@ export type KeyAuthorization<
62
70
  * [TIP-1053 Specification](https://tips.sh/1053)
63
71
  */
64
72
  witness?: Hex.Hex | undefined
65
- } & OneOf<
66
- // TIP-1049 admin access keys: `account` and `isAdmin` are paired — either
67
- // both are specified or neither. The `account` binding scopes the signing
68
- // hash to a specific account, and `isAdmin: true` provisions an admin
69
- // access key with unrestricted keychain mutator privileges.
70
- //
71
- // [TIP-1049 Specification](https://tips.sh/1049)
72
- | {
73
- /** Account address this authorization is bound to. */
74
- account: Address.Address
75
- /** Whether this authorization provisions an admin access key. */
76
- isAdmin: boolean
73
+ } & (signed extends true
74
+ ? {
75
+ signature:
76
+ | SignatureEnvelope.Primitive<numberType>
77
+ | SignatureEnvelope.Multisig<numberType>
77
78
  }
78
- | {}
79
- > &
80
- (signed extends true
81
- ? { signature: Signature<numberType> }
82
- : {
83
- signature?: Signature<numberType> | undefined
84
- })
79
+ : {
80
+ signature?:
81
+ | SignatureEnvelope.Primitive<numberType>
82
+ | SignatureEnvelope.Multisig<numberType>
83
+ | undefined
84
+ })
85
85
 
86
86
  /** Signature that can authorize an access key. */
87
- export type Signature<numberType = number> = OneOf<
87
+ export type Signature<numberType = number> =
88
88
  | SignatureEnvelope.Primitive<numberType>
89
89
  | SignatureEnvelope.Multisig<numberType>
90
- >
91
90
 
92
91
  /** RPC-formatted signature that can authorize an access key. */
93
- export type SignatureRpc = OneOf<
94
- SignatureEnvelope.PrimitiveRpc | SignatureEnvelope.MultisigRpc
95
- >
92
+ export type SignatureRpc =
93
+ | SignatureEnvelope.MultisigRpc
94
+ | SignatureEnvelope.PrimitiveRpc
96
95
 
97
96
  /** Input type for a Key Authorization. */
98
97
  export type Input = KeyAuthorization<false, bigint, number>
@@ -112,32 +111,32 @@ export type Rpc = {
112
111
  /** Key identifier. */
113
112
  keyId: Address.Address
114
113
  /** Key type. */
115
- keyType: SignatureEnvelope.Type
114
+ keyType: SignatureEnvelope.Type | 'multisig'
116
115
  /** Token spending limits. */
117
116
  limits?: readonly RpcTokenLimit[] | null | undefined
118
- /** Signature authorizing this key. */
119
- signature: SignatureRpc
117
+ /** Primitive or multisig signature authorizing this key. */
118
+ signature: SignatureEnvelope.PrimitiveRpc | SignatureEnvelope.MultisigRpc
120
119
  /** Optional 32-byte witness (hex). */
121
120
  witness?: Hex.Hex | null | undefined
122
121
  }
123
122
 
124
123
  /** RPC representation of a token limit (matches node's `TokenLimit` serde). */
125
124
  export type RpcTokenLimit = {
126
- token: Address.Address
127
125
  limit: Hex.Hex
128
126
  period?: Hex.Hex | null | undefined
127
+ token: Address.Address
129
128
  }
130
129
 
131
130
  /** RPC representation of a call scope (matches node's `CallScope` serde). */
132
131
  export type RpcCallScope = {
133
- target: Address.Address
134
132
  selectorRules?: readonly RpcSelectorRule[] | null | undefined
133
+ target: Address.Address
135
134
  }
136
135
 
137
136
  /** RPC representation of a selector rule (matches node's `SelectorRule` serde). */
138
137
  export type RpcSelectorRule = {
139
- selector: Hex.Hex
140
138
  recipients?: readonly Address.Address[] | null | undefined
139
+ selector: Hex.Hex
141
140
  }
142
141
 
143
142
  /** Signed representation of a Key Authorization. */
@@ -148,10 +147,11 @@ export type Signed<bigintType = bigint, numberType = number> = KeyAuthorization<
148
147
  >
149
148
 
150
149
  type SignatureValue =
151
- | UnionPartialBy<Signature, 'prehash' | 'type'>
150
+ | SignatureEnvelope.from.MultisigFromConfig
151
+ | UnionPartialBy<SignatureEnvelope.Primitive, 'prehash' | 'type'>
152
+ | PartialBy<SignatureEnvelope.Multisig, 'type'>
152
153
  | SignatureEnvelope.Secp256k1Flat
153
154
  | SignatureEnvelope.Serialized
154
- | SignatureEnvelope.from.MultisigFromConfig
155
155
 
156
156
  type BaseTuple = readonly [
157
157
  chainId: Hex.Hex,
@@ -227,14 +227,6 @@ export type Tuple<signed extends boolean = boolean> = signed extends true
227
227
  export type Scope = {
228
228
  /** Target contract address. */
229
229
  address: Address.Address
230
- /**
231
- * 4-byte function selector, or a human-readable ABI signature
232
- * (e.g. `'transfer(address,uint256)'` or `'function transfer(address,uint256)'`).
233
- *
234
- * Signatures are encoded into a 4-byte selector automatically.
235
- * Omit to allow any selector on this contract.
236
- */
237
- selector?: Hex.Hex | string | undefined
238
230
  /**
239
231
  * Recipient allowlist for this selector (first ABI `address` argument).
240
232
  *
@@ -244,6 +236,14 @@ export type Scope = {
244
236
  * Only valid for constrained selectors: `transfer`, `approve`, `transferWithMemo`.
245
237
  */
246
238
  recipients?: readonly Address.Address[] | undefined
239
+ /**
240
+ * 4-byte function selector, or a human-readable ABI signature
241
+ * (e.g. `'transfer(address,uint256)'` or `'function transfer(address,uint256)'`).
242
+ *
243
+ * Signatures are encoded into a 4-byte selector automatically.
244
+ * Omit to allow any selector on this contract.
245
+ */
246
+ selector?: Hex.Hex | string | undefined
247
247
  }
248
248
 
249
249
  /**
@@ -255,8 +255,6 @@ export type Scope = {
255
255
  * [Access Keys Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#access-keys)
256
256
  */
257
257
  export type TokenLimit<bigintType = bigint, numberType = number> = {
258
- /** Address of the TIP-20 token. */
259
- token: Address.Address
260
258
  /** Maximum spending amount for this token (enforced over the key's lifetime, or per period if `period` \> 0). */
261
259
  limit: bigintType
262
260
  /**
@@ -266,6 +264,8 @@ export type TokenLimit<bigintType = bigint, numberType = number> = {
266
264
  * - `\> 0` = periodic limit that resets every `period` seconds
267
265
  */
268
266
  period?: numberType | undefined
267
+ /** Address of the TIP-20 token. */
268
+ token: Address.Address
269
269
  }
270
270
 
271
271
  /**
@@ -433,12 +433,13 @@ export function from<
433
433
  options: from.Options<signature> = {},
434
434
  ): from.ReturnType<authorization, signature> {
435
435
  if ('keyId' in authorization) return fromRpc(authorization as Rpc) as never
436
+ assertAccountBinding(authorization.type, authorization.account)
436
437
  const auth = authorization as KeyAuthorization & {
437
- limits?: readonly { token: Address.Address; limit: bigint }[]
438
+ limits?: readonly { limit: bigint; token: Address.Address }[]
438
439
  scopes?: readonly {
439
440
  address: Address.Address
440
- selector?: Hex.Hex | string
441
441
  recipients?: readonly Address.Address[]
442
+ selector?: Hex.Hex | string
442
443
  }[]
443
444
  }
444
445
  if (auth.witness !== undefined) assertWitness(auth.witness)
@@ -471,8 +472,12 @@ export declare namespace from {
471
472
  type Options<
472
473
  signature extends SignatureValue | undefined = SignatureValue | undefined,
473
474
  > = {
474
- /** The signature to attach to the Key Authorization. */
475
- signature?: signature | Signature | undefined
475
+ /** The primitive or multisig signature to attach to the Key Authorization. */
476
+ signature?:
477
+ | signature
478
+ | SignatureEnvelope.Primitive
479
+ | SignatureEnvelope.Multisig
480
+ | undefined
476
481
  }
477
482
 
478
483
  type ReturnType<
@@ -486,13 +491,16 @@ export declare namespace from {
486
491
  ? {
487
492
  signature: Extract<
488
493
  SignatureEnvelope.from.ReturnValue<signature>,
489
- Signature
494
+ SignatureEnvelope.Primitive | SignatureEnvelope.Multisig
490
495
  >
491
496
  }
492
497
  : {})
493
498
  >
494
499
 
495
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
500
+ type ErrorType =
501
+ | InvalidSignatureTypeError
502
+ | MissingAccountError
503
+ | Errors.GlobalErrorType
496
504
  }
497
505
 
498
506
  /**
@@ -531,6 +539,7 @@ export function fromRpc(authorization: Rpc): Signed {
531
539
  const witness = authorization.witness ?? undefined
532
540
  const isAdmin = authorization.isAdmin ?? undefined
533
541
  const account = authorization.account ?? undefined
542
+ assertAccountBinding(keyType, account)
534
543
  const signature = SignatureEnvelope.fromRpc(authorization.signature)
535
544
  assertSignature(signature)
536
545
  if (witness !== undefined) assertWitness(witness)
@@ -552,9 +561,6 @@ export function fromRpc(authorization: Rpc): Signed {
552
561
  })
553
562
  : undefined
554
563
 
555
- const accountBinding =
556
- account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
557
-
558
564
  return {
559
565
  address: keyId,
560
566
  chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
@@ -570,12 +576,16 @@ export function fromRpc(authorization: Rpc): Signed {
570
576
  signature,
571
577
  type: keyType,
572
578
  ...(witness !== undefined ? { witness } : {}),
573
- ...accountBinding,
579
+ ...(account !== undefined ? { account } : {}),
580
+ ...(isAdmin ? { isAdmin: true as const } : {}),
574
581
  }
575
582
  }
576
583
 
577
584
  export declare namespace fromRpc {
578
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
585
+ type ErrorType =
586
+ | InvalidSignatureTypeError
587
+ | MissingAccountError
588
+ | Errors.GlobalErrorType
579
589
  }
580
590
 
581
591
  /**
@@ -647,6 +657,8 @@ export function fromTuple<const tuple extends Tuple>(
647
657
  return 'p256'
648
658
  case '0x02':
649
659
  return 'webAuthn'
660
+ case '0x03':
661
+ return 'multisig'
650
662
  default:
651
663
  throw new Error(`Invalid key type: ${keyType_hex}`)
652
664
  }
@@ -702,8 +714,7 @@ export function fromTuple<const tuple extends Tuple>(
702
714
  const account = isAbsent(rawAccount)
703
715
  ? undefined
704
716
  : (rawAccount as Address.Address)
705
- const accountBinding =
706
- account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
717
+ assertAccountBinding(keyType, account)
707
718
  const args: KeyAuthorization = {
708
719
  address: keyId,
709
720
  chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
@@ -712,7 +723,8 @@ export function fromTuple<const tuple extends Tuple>(
712
723
  ...(limits !== undefined ? { limits } : {}),
713
724
  ...(scopes !== undefined ? { scopes } : {}),
714
725
  ...(witness !== undefined ? { witness } : {}),
715
- ...accountBinding,
726
+ ...(account !== undefined ? { account } : {}),
727
+ ...(isAdmin ? { isAdmin: true as const } : {}),
716
728
  }
717
729
  if (signatureSerialized) {
718
730
  const signature = SignatureEnvelope.deserialize(signatureSerialized)
@@ -727,7 +739,10 @@ export declare namespace fromTuple {
727
739
  KeyAuthorization<authorization extends Tuple<true> ? true : false>
728
740
  >
729
741
 
730
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
742
+ type ErrorType =
743
+ | InvalidSignatureTypeError
744
+ | MissingAccountError
745
+ | Errors.GlobalErrorType
731
746
  }
732
747
 
733
748
  /**
@@ -933,6 +948,7 @@ export declare namespace serialize {
933
948
  * @returns An RPC-formatted Key Authorization.
934
949
  */
935
950
  export function toRpc(authorization: toRpc.Input): Rpc {
951
+ assertAccountBinding(authorization.type, authorization.account)
936
952
  const {
937
953
  address,
938
954
  scopes,
@@ -984,7 +1000,9 @@ export function toRpc(authorization: toRpc.Input): Rpc {
984
1000
  limit: Quantity.fromNumberish(limit),
985
1001
  ...(period ? { period: Quantity.fromNumberish(period) } : {}),
986
1002
  })),
987
- signature: SignatureEnvelope.toRpc(signature) as SignatureRpc,
1003
+ signature: SignatureEnvelope.toRpc(signature) as
1004
+ | SignatureEnvelope.PrimitiveRpc
1005
+ | SignatureEnvelope.MultisigRpc,
988
1006
  ...(allowedCalls ? { allowedCalls } : {}),
989
1007
  ...(witness !== undefined ? { witness } : {}),
990
1008
  ...(isAdmin ? { isAdmin: true } : {}),
@@ -996,7 +1014,10 @@ export declare namespace toRpc {
996
1014
  /** Numberish input accepted by {@link ox#KeyAuthorization.(toRpc:function)}. */
997
1015
  type Input = Signed<Hex.Hex | bigint | number, Hex.Hex | number>
998
1016
 
999
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
1017
+ type ErrorType =
1018
+ | InvalidSignatureTypeError
1019
+ | MissingAccountError
1020
+ | Errors.GlobalErrorType
1000
1021
  }
1001
1022
 
1002
1023
  /**
@@ -1035,6 +1056,7 @@ export declare namespace toRpc {
1035
1056
  export function toTuple<const authorization extends KeyAuthorization>(
1036
1057
  authorization: authorization,
1037
1058
  ): toTuple.ReturnType<authorization> {
1059
+ assertAccountBinding(authorization.type, authorization.account)
1038
1060
  const {
1039
1061
  address,
1040
1062
  chainId,
@@ -1059,6 +1081,8 @@ export function toTuple<const authorization extends KeyAuthorization>(
1059
1081
  return '0x01'
1060
1082
  case 'webAuthn':
1061
1083
  return '0x02'
1084
+ case 'multisig':
1085
+ return '0x03'
1062
1086
  default:
1063
1087
  throw new Error(`Invalid key type: ${authorization.type}`)
1064
1088
  }
@@ -1109,7 +1133,7 @@ export function toTuple<const authorization extends KeyAuthorization>(
1109
1133
  // single entry to this list with `placeholder: '0x'`.
1110
1134
  const hasTip1053Plus =
1111
1135
  witness !== undefined || isAdmin || account !== undefined
1112
- const optionals: readonly { value: unknown; placeholder: unknown }[] = [
1136
+ const optionals: readonly { placeholder: unknown; value: unknown }[] = [
1113
1137
  {
1114
1138
  value:
1115
1139
  expiry !== null && expiry !== undefined && expiry !== 0
@@ -1146,7 +1170,10 @@ export declare namespace toTuple {
1146
1170
  type ReturnType<authorization extends KeyAuthorization = KeyAuthorization> =
1147
1171
  Compute<Tuple<authorization extends KeyAuthorization<true> ? true : false>>
1148
1172
 
1149
- type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
1173
+ type ErrorType =
1174
+ | InvalidSignatureTypeError
1175
+ | MissingAccountError
1176
+ | Errors.GlobalErrorType
1150
1177
  }
1151
1178
 
1152
1179
  function bigintToHex(value: bigint): Hex.Hex {
@@ -1180,13 +1207,22 @@ function resolveSelector(
1180
1207
  return AbiItem.getSelector(selector)
1181
1208
  }
1182
1209
 
1210
+ function assertAccountBinding(
1211
+ type: KeyAuthorization['type'],
1212
+ account: Address.Address | null | undefined,
1213
+ ): void {
1214
+ if (type === 'multisig' && account == null) throw new MissingAccountError()
1215
+ }
1216
+
1183
1217
  function assertWitness(witness: Hex.Hex): void {
1184
1218
  if (Hex.size(witness) !== 32) throw new InvalidWitnessSizeError(witness)
1185
1219
  }
1186
1220
 
1187
1221
  function assertSignature<numberType>(
1188
1222
  signature: SignatureEnvelope.SignatureEnvelope<numberType>,
1189
- ): asserts signature is Signature<numberType> {
1223
+ ): asserts signature is
1224
+ | SignatureEnvelope.Primitive<numberType>
1225
+ | SignatureEnvelope.Multisig<numberType> {
1190
1226
  if (signature.type === 'keychain')
1191
1227
  throw new InvalidSignatureTypeError(signature.type)
1192
1228
  }
@@ -1224,3 +1260,11 @@ export class InvalidSignatureTypeError extends Error {
1224
1260
  )
1225
1261
  }
1226
1262
  }
1263
+
1264
+ /** Thrown when a multisig key grant omits its parent account binding. */
1265
+ export class MissingAccountError extends Error {
1266
+ override readonly name = 'KeyAuthorization.MissingAccountError'
1267
+ constructor() {
1268
+ super('Multisig key grants require a parent account binding.')
1269
+ }
1270
+ }
@@ -1,3 +1,4 @@
1
+ import { factory } from '../../test/tempo/multisig.js'
1
2
  import { Hash, Hex, Rlp } from 'ox'
2
3
  import { MultisigConfig } from 'ox/tempo'
3
4
  import { describe, expect, test } from 'vp/test'
@@ -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', () => {
@@ -98,35 +99,59 @@ describe('fromRpc/toRpc', () => {
98
99
  })
99
100
 
100
101
  describe('getAddress', () => {
101
- test('example: matches the frozen version-0 vector', () => {
102
+ test('example: matches the frozen CREATE2 vector', () => {
102
103
  expect(account).toMatchInlineSnapshot(
103
- `"0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da"`,
104
+ `"0x90ba71cb7534da990a1182cf28c9bf415055e03d"`,
104
105
  )
106
+ expect(account).not.toBe('0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da')
105
107
  })
106
108
 
107
109
  test('behavior: includes salt, threshold, and owners', () => {
108
110
  expect(
109
- MultisigConfig.getAddress({
110
- owners: [
111
- { owner: owner_1, weight: 1 },
112
- { owner: owner_2, weight: 2 },
113
- ],
114
- salt: `0x${'42'.repeat(32)}`,
115
- threshold: 2,
116
- }),
117
- ).toMatchInlineSnapshot(`"0x0773e28146400643e42cb28f6659b74e7c0b451d"`)
111
+ MultisigConfig.getAddress(
112
+ {
113
+ owners: [
114
+ { owner: owner_1, weight: 1 },
115
+ { owner: owner_2, weight: 2 },
116
+ ],
117
+ salt: `0x${'42'.repeat(32)}`,
118
+ threshold: 2,
119
+ },
120
+ { factory },
121
+ ),
122
+ ).toMatchInlineSnapshot(`"0x7898a7d1dd96946d2a968de1540ca884a244320c"`)
123
+ })
124
+
125
+ test('behavior: is stable and binds the salt', () => {
126
+ expect({
127
+ repeated: MultisigConfig.getAddress(config, { factory }),
128
+ salted: MultisigConfig.getAddress(
129
+ {
130
+ ...config,
131
+ salt: `0x${'42'.repeat(32)}`,
132
+ },
133
+ { factory },
134
+ ),
135
+ }).toMatchInlineSnapshot(`
136
+ {
137
+ "repeated": "0x90ba71cb7534da990a1182cf28c9bf415055e03d",
138
+ "salted": "0xa1a8472f4a01772db729d8ebc73fe46120750b83",
139
+ }
140
+ `)
118
141
  })
119
142
 
120
143
  test('error: rejects a current configuration', () => {
121
144
  expect(() =>
122
- MultisigConfig.getAddress({ ...config, version: 1n }),
145
+ MultisigConfig.getAddress({ ...config, version: 1n }, { factory }),
123
146
  ).toThrowErrorMatchingInlineSnapshot(
124
147
  `[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
125
148
  )
126
149
  })
127
150
 
128
151
  test('behavior: accepts numeric zero for an initial configuration', () => {
129
- expect(MultisigConfig.getAddress({ ...config, version: 0 })).toBe(account)
152
+ expect(
153
+ MultisigConfig.getAddress({ ...config, version: 0 }, { factory }),
154
+ ).toBe(account)
130
155
  })
131
156
  })
132
157
 
@@ -151,7 +176,7 @@ describe('getSignPayload', () => {
151
176
  expect(
152
177
  MultisigConfig.getSignPayload({ account, config, payload }),
153
178
  ).toMatchInlineSnapshot(
154
- `"0xbf944a7a752b2cfab0418d5fb4591c5a7ff62976488edce11794d7f35fb34f41"`,
179
+ `"0x728efe90611b01c91c2be0da56e7eb8e0d2c274c84f97182e274187dde0b8a20"`,
155
180
  )
156
181
  })
157
182
 
@@ -206,14 +231,14 @@ describe('assert/validate', () => {
206
231
  })
207
232
  const rlp = Rlp.fromHex(MultisigConfig.toTuple(boundary))
208
233
  expect({
209
- account: MultisigConfig.getAddress(boundary),
234
+ account: MultisigConfig.getAddress(boundary, { factory }),
210
235
  commitment: MultisigConfig.getCommitment(boundary),
211
236
  rlpHash: Hash.keccak256(rlp),
212
237
  rlpLength: Hex.size(rlp),
213
238
  valid: MultisigConfig.validate(boundary),
214
239
  }).toMatchInlineSnapshot(`
215
240
  {
216
- "account": "0x6c67c57e0eed05341137dbf88e4e7a90dc46ef50",
241
+ "account": "0x6acb25715a2b0cf05c098f753a87d9b38471e99f",
217
242
  "commitment": "0x0dc47a7ab45ffa21a01bfd115427e26617b5a57d7ccbea57db2fd4537ba96f56",
218
243
  "rlpHash": "0xbaf0d030add91caaa10815d2e99c942f1e39b0d199216973781adb4fc1af6955",
219
244
  "rlpLength": 1145,
@@ -229,7 +254,7 @@ describe('assert/validate', () => {
229
254
  { owner: owner_1, weight: 128 },
230
255
  { owner: owner_2, weight: 127 },
231
256
  ],
232
- threshold: 255,
257
+ threshold: 8,
233
258
  }),
234
259
  ).toBe(true)
235
260
  })
@@ -295,7 +320,7 @@ describe('assert/validate', () => {
295
320
  { owner: owner_1, weight: 128 },
296
321
  { owner: owner_2, weight: 128 },
297
322
  ],
298
- threshold: 255,
323
+ threshold: 8,
299
324
  },
300
325
  name: 'weight overflow',
301
326
  },
@@ -342,7 +367,6 @@ describe('assert/validate', () => {
342
367
  test('exports', () => {
343
368
  expect(Object.keys(MultisigConfig)).toMatchInlineSnapshot(`
344
369
  [
345
- "maxNestingDepth",
346
370
  "maxOwnerSignatureBytes",
347
371
  "maxOwners",
348
372
  "maxSignatures",