ox 0.14.42 → 0.14.44

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 (74) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/_cjs/tempo/MultisigConfig.js +9 -2
  3. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  4. package/_cjs/tempo/MultisigSimulation.js +115 -0
  5. package/_cjs/tempo/MultisigSimulation.js.map +1 -0
  6. package/_cjs/tempo/SignatureEnvelope.js +12 -21
  7. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  8. package/_cjs/tempo/TransactionRequest.js +7 -7
  9. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  10. package/_cjs/tempo/index.js +2 -2
  11. package/_cjs/tempo/index.js.map +1 -1
  12. package/_cjs/version.js +1 -1
  13. package/_esm/tempo/MultisigConfig.js +14 -4
  14. package/_esm/tempo/MultisigConfig.js.map +1 -1
  15. package/_esm/tempo/MultisigSimulation.js +145 -0
  16. package/_esm/tempo/MultisigSimulation.js.map +1 -0
  17. package/_esm/tempo/SignatureEnvelope.js +12 -21
  18. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  19. package/_esm/tempo/TransactionRequest.js +7 -7
  20. package/_esm/tempo/TransactionRequest.js.map +1 -1
  21. package/_esm/tempo/index.js +4 -4
  22. package/_esm/tempo/index.js.map +1 -1
  23. package/_esm/version.js +1 -1
  24. package/_types/tempo/KeyAuthorization.d.ts +1 -1
  25. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  26. package/_types/tempo/MultisigConfig.d.ts +5 -3
  27. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  28. package/_types/tempo/MultisigOperation.d.ts +2 -2
  29. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  30. package/_types/tempo/MultisigSimulation.d.ts +132 -0
  31. package/_types/tempo/MultisigSimulation.d.ts.map +1 -0
  32. package/_types/tempo/SignatureEnvelope.d.ts +10 -34
  33. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  34. package/_types/tempo/TransactionRequest.d.ts +9 -9
  35. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  36. package/_types/tempo/index.d.ts +4 -4
  37. package/_types/tempo/index.d.ts.map +1 -1
  38. package/_types/version.d.ts +1 -1
  39. package/package.json +9 -9
  40. package/tempo/AuthorizationTempo.test.ts +4 -0
  41. package/tempo/KeyAuthorization.test-d.ts +1 -12
  42. package/tempo/KeyAuthorization.test.ts +37 -9
  43. package/tempo/KeyAuthorization.ts +3 -3
  44. package/tempo/MultisigConfig.test.ts +21 -5
  45. package/tempo/MultisigConfig.ts +18 -6
  46. package/tempo/MultisigOperation.test.ts +37 -37
  47. package/tempo/MultisigOperation.ts +2 -2
  48. package/tempo/MultisigSimulation/package.json +6 -0
  49. package/tempo/MultisigSimulation.test-d/package.json +6 -0
  50. package/tempo/MultisigSimulation.test-d.ts +33 -0
  51. package/tempo/MultisigSimulation.test.ts +284 -0
  52. package/tempo/MultisigSimulation.ts +266 -0
  53. package/tempo/RpcSchemaTempo.test-d.ts +4 -4
  54. package/tempo/SignatureEnvelope.test-d.ts +8 -44
  55. package/tempo/SignatureEnvelope.test.ts +106 -65
  56. package/tempo/SignatureEnvelope.ts +27 -61
  57. package/tempo/Transaction.test.ts +61 -0
  58. package/tempo/TransactionRequest.test-d.ts +9 -9
  59. package/tempo/TransactionRequest.test.ts +122 -49
  60. package/tempo/TransactionRequest.ts +20 -16
  61. package/tempo/index.ts +4 -4
  62. package/tempo/multisig.e2e.test.ts +71 -21
  63. package/version.ts +1 -1
  64. package/_cjs/tempo/MultisigWitness.js +0 -92
  65. package/_cjs/tempo/MultisigWitness.js.map +0 -1
  66. package/_esm/tempo/MultisigWitness.js +0 -123
  67. package/_esm/tempo/MultisigWitness.js.map +0 -1
  68. package/_types/tempo/MultisigWitness.d.ts +0 -106
  69. package/_types/tempo/MultisigWitness.d.ts.map +0 -1
  70. package/tempo/MultisigWitness/package.json +0 -6
  71. package/tempo/MultisigWitness.test-d/package.json +0 -6
  72. package/tempo/MultisigWitness.test-d.ts +0 -42
  73. package/tempo/MultisigWitness.test.ts +0 -289
  74. package/tempo/MultisigWitness.ts +0 -210
@@ -0,0 +1,284 @@
1
+ import { MultisigConfig, MultisigSimulation } from 'ox/tempo'
2
+ import { describe, expect, test } from 'vitest'
3
+
4
+ const config =
5
+ '0xf852a011111111111111111111111111111111111111111111111111111111111111118002eed694111111111111111111111111111111111111111101d694bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb01' as const
6
+ const nestedConfig =
7
+ '0xf83ba022222222222222222222222222222222222222222222222222222222222222220101d7d694222222222222222222222222222222222222222201' as const
8
+ const rpc = {
9
+ account: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
10
+ approvals: [
11
+ {
12
+ keyData: '0x0102030405',
13
+ keyType: 'webAuthn',
14
+ owner: '0x1111111111111111111111111111111111111111',
15
+ type: 'primitive',
16
+ },
17
+ {
18
+ spec: {
19
+ account: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
20
+ approvals: [
21
+ {
22
+ keyData: '0x010203040506',
23
+ keyType: 'secp256k1',
24
+ owner: '0x2222222222222222222222222222222222222222',
25
+ },
26
+ ],
27
+ config: nestedConfig,
28
+ },
29
+ type: 'multisig',
30
+ },
31
+ ],
32
+ config,
33
+ } as const satisfies MultisigSimulation.Rpc
34
+
35
+ describe('fromRpc', () => {
36
+ test('behavior: decodes root and nested configurations', () => {
37
+ expect(MultisigSimulation.fromRpc(rpc)).toMatchInlineSnapshot(`
38
+ {
39
+ "account": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
40
+ "approvals": [
41
+ {
42
+ "keyData": "0x0102030405",
43
+ "keyType": "webAuthn",
44
+ "owner": "0x1111111111111111111111111111111111111111",
45
+ "type": "primitive",
46
+ },
47
+ {
48
+ "spec": {
49
+ "account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
50
+ "approvals": [
51
+ {
52
+ "keyData": "0x010203040506",
53
+ "keyType": "secp256k1",
54
+ "owner": "0x2222222222222222222222222222222222222222",
55
+ },
56
+ ],
57
+ "config": {
58
+ "owners": [
59
+ {
60
+ "owner": "0x2222222222222222222222222222222222222222",
61
+ "weight": 1,
62
+ },
63
+ ],
64
+ "salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
65
+ "threshold": 1,
66
+ "version": 1n,
67
+ },
68
+ },
69
+ "type": "multisig",
70
+ },
71
+ ],
72
+ "config": {
73
+ "owners": [
74
+ {
75
+ "owner": "0x1111111111111111111111111111111111111111",
76
+ "weight": 1,
77
+ },
78
+ {
79
+ "owner": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
80
+ "weight": 1,
81
+ },
82
+ ],
83
+ "salt": "0x1111111111111111111111111111111111111111111111111111111111111111",
84
+ "threshold": 2,
85
+ "version": 0n,
86
+ },
87
+ }
88
+ `)
89
+ })
90
+
91
+ test('behavior: preserves the maximum uint64 configuration version', () => {
92
+ const config =
93
+ '0xf843a0000000000000000000000000000000000000000000000000000000000000000088ffffffffffffffff01d7d694111111111111111111111111111111111111111101' as const
94
+
95
+ expect(
96
+ MultisigSimulation.fromRpc({ ...rpc, approvals: [], config }).config
97
+ .version,
98
+ ).toMatchInlineSnapshot(`18446744073709551615n`)
99
+ })
100
+
101
+ test('error: rejects excess root approvals', () => {
102
+ expect(() =>
103
+ MultisigSimulation.fromRpc({
104
+ ...rpc,
105
+ approvals: Array.from({ length: 9 }, () => rpc.approvals[0]),
106
+ }),
107
+ ).toThrowErrorMatchingInlineSnapshot(
108
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
109
+ )
110
+ })
111
+
112
+ test('error: rejects excess nested approvals', () => {
113
+ expect(() =>
114
+ MultisigSimulation.fromRpc({
115
+ ...rpc,
116
+ approvals: [
117
+ {
118
+ ...rpc.approvals[1],
119
+ spec: {
120
+ ...rpc.approvals[1].spec,
121
+ approvals: Array.from(
122
+ { length: 9 },
123
+ () => rpc.approvals[1].spec.approvals[0],
124
+ ),
125
+ },
126
+ },
127
+ ],
128
+ }),
129
+ ).toThrowErrorMatchingInlineSnapshot(
130
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
131
+ )
132
+ })
133
+
134
+ test('error: rejects trailing configuration bytes', () => {
135
+ expect(() =>
136
+ MultisigSimulation.fromRpc({ ...rpc, config: `${config}00` }),
137
+ ).toThrowErrorMatchingInlineSnapshot(
138
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: noncanonical config encoding.]`,
139
+ )
140
+ })
141
+
142
+ test('error: rejects malformed configuration RLP', () => {
143
+ expect(() =>
144
+ MultisigSimulation.fromRpc({ ...rpc, config: '0xf8' }),
145
+ ).toThrowErrorMatchingInlineSnapshot(
146
+ `[Cursor.PositionOutOfBoundsError: Position \`1\` is out of bounds (\`0 < position < 1\`).]`,
147
+ )
148
+ })
149
+
150
+ test('error: rejects a non-list configuration', () => {
151
+ expect(() =>
152
+ MultisigSimulation.fromRpc({ ...rpc, config: '0x01' }),
153
+ ).toThrowErrorMatchingInlineSnapshot(
154
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: invalid config encoding.]`,
155
+ )
156
+ })
157
+
158
+ test('error: rejects noncanonical configuration integers', () => {
159
+ const noncanonical = config
160
+ .replace('0xf852', '0xf853')
161
+ .replace('118002', '11810002') as `0x${string}`
162
+
163
+ expect(() =>
164
+ MultisigSimulation.fromRpc({ ...rpc, config: noncanonical }),
165
+ ).toThrowErrorMatchingInlineSnapshot(
166
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: invalid config encoding.]`,
167
+ )
168
+ })
169
+ })
170
+
171
+ describe('toRpc', () => {
172
+ test('behavior: encodes configurations and shims long key data', () => {
173
+ expect(
174
+ MultisigSimulation.toRpc(MultisigSimulation.fromRpc(rpc)),
175
+ ).toMatchInlineSnapshot(`
176
+ {
177
+ "account": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
178
+ "approvals": [
179
+ {
180
+ "keyData": "0x0005",
181
+ "keyType": "webAuthn",
182
+ "owner": "0x1111111111111111111111111111111111111111",
183
+ "type": "primitive",
184
+ },
185
+ {
186
+ "spec": {
187
+ "account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
188
+ "approvals": [
189
+ {
190
+ "keyData": "0x0006",
191
+ "keyType": "secp256k1",
192
+ "owner": "0x2222222222222222222222222222222222222222",
193
+ },
194
+ ],
195
+ "config": "${nestedConfig}",
196
+ },
197
+ "type": "multisig",
198
+ },
199
+ ],
200
+ "config": "${config}",
201
+ }
202
+ `)
203
+ })
204
+
205
+ test('behavior: preserves short key data', () => {
206
+ const spec = MultisigSimulation.fromRpc({
207
+ ...rpc,
208
+ approvals: [{ ...rpc.approvals[0], keyData: '0x0102' }],
209
+ })
210
+
211
+ expect(MultisigSimulation.toRpc(spec).approvals).toMatchInlineSnapshot(`
212
+ [
213
+ {
214
+ "keyData": "0x0102",
215
+ "keyType": "webAuthn",
216
+ "owner": "0x1111111111111111111111111111111111111111",
217
+ "type": "primitive",
218
+ },
219
+ ]
220
+ `)
221
+ })
222
+
223
+ test('behavior: encodes the maximum uint64 configuration version', () => {
224
+ const spec = MultisigSimulation.fromRpc(rpc)
225
+
226
+ expect(
227
+ MultisigSimulation.toRpc({
228
+ ...spec,
229
+ approvals: [],
230
+ config: MultisigConfig.from({
231
+ owners: [
232
+ {
233
+ owner: '0x1111111111111111111111111111111111111111',
234
+ weight: 1,
235
+ },
236
+ ],
237
+ threshold: 1,
238
+ version: MultisigConfig.maxVersion,
239
+ }),
240
+ }).config,
241
+ ).toMatchInlineSnapshot(
242
+ `"0xf843a0000000000000000000000000000000000000000000000000000000000000000088ffffffffffffffff01d7d694111111111111111111111111111111111111111101"`,
243
+ )
244
+ })
245
+
246
+ test('error: rejects excess root approvals', () => {
247
+ const spec = MultisigSimulation.fromRpc(rpc)
248
+
249
+ expect(() =>
250
+ MultisigSimulation.toRpc({
251
+ ...spec,
252
+ approvals: Array.from({ length: 9 }, () => spec.approvals[0]!),
253
+ }),
254
+ ).toThrowErrorMatchingInlineSnapshot(
255
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
256
+ )
257
+ })
258
+
259
+ test('error: rejects excess nested approvals', () => {
260
+ const spec = MultisigSimulation.fromRpc(rpc)
261
+ const nested = spec.approvals[1]!
262
+ if (nested.type !== 'multisig') throw new Error('unreachable')
263
+
264
+ expect(() =>
265
+ MultisigSimulation.toRpc({
266
+ ...spec,
267
+ approvals: [
268
+ {
269
+ ...nested,
270
+ spec: {
271
+ ...nested.spec,
272
+ approvals: Array.from(
273
+ { length: 9 },
274
+ () => nested.spec.approvals[0]!,
275
+ ),
276
+ },
277
+ },
278
+ ],
279
+ }),
280
+ ).toThrowErrorMatchingInlineSnapshot(
281
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
282
+ )
283
+ })
284
+ })
@@ -0,0 +1,266 @@
1
+ import * as Address from '../core/Address.js'
2
+ import * as Errors from '../core/Errors.js'
3
+ import * as Hex from '../core/Hex.js'
4
+ import type { Compute } from '../core/internal/types.js'
5
+ import * as Rlp from '../core/Rlp.js'
6
+ import * as MultisigConfig from './MultisigConfig.js'
7
+ import type * as SignatureEnvelope from './SignatureEnvelope.js'
8
+
9
+ /** Native multisig owner approval used for RPC simulation. */
10
+ export type Approval = NestedApproval | PrimitiveApproval
11
+
12
+ /** JSON-RPC native multisig owner approval used for RPC simulation. */
13
+ export type ApprovalRpc = NestedApprovalRpc | PrimitiveApproval
14
+
15
+ /** Nested native multisig owner approval used for RPC simulation. */
16
+ export type NestedApproval = {
17
+ /** Depth-2 multisig simulation spec. */
18
+ spec: NestedSpec
19
+ /** Approval type. */
20
+ type: 'multisig'
21
+ }
22
+
23
+ /** JSON-RPC nested native multisig owner approval used for RPC simulation. */
24
+ export type NestedApprovalRpc = {
25
+ /** Depth-2 multisig simulation spec. */
26
+ spec: NestedSpecRpc
27
+ /** Approval type. */
28
+ type: 'multisig'
29
+ }
30
+
31
+ /** Primitive approval in a depth-2 multisig simulation spec. */
32
+ export type NestedPrimitiveApproval = {
33
+ /** Optional signature-specific gas-estimation data. */
34
+ keyData?: Hex.Hex | undefined
35
+ /** Signature type to model. Omission uses a maximum-size WebAuthn signature. */
36
+ keyType?: SignatureEnvelope.Type | undefined
37
+ /** Configured owner address. */
38
+ owner: Address.Address
39
+ }
40
+
41
+ /** Depth-2 native multisig spec used for RPC simulation. */
42
+ export type NestedSpec = {
43
+ /** Nested multisig account. */
44
+ account: Address.Address
45
+ /** Primitive owner approvals to model. */
46
+ approvals: readonly NestedPrimitiveApproval[]
47
+ /** Complete applicable configuration. */
48
+ config: MultisigConfig.Config
49
+ }
50
+
51
+ /** JSON-RPC depth-2 native multisig spec used for RPC simulation. */
52
+ export type NestedSpecRpc = {
53
+ /** Nested multisig account. */
54
+ account: Address.Address
55
+ /** Primitive owner approvals to model. */
56
+ approvals: readonly NestedPrimitiveApproval[]
57
+ /** Canonical RLP-encoded applicable configuration. */
58
+ config: Hex.Hex
59
+ }
60
+
61
+ /** Primitive owner approval used for RPC simulation. */
62
+ export type PrimitiveApproval = {
63
+ /** Optional signature-specific gas-estimation data. */
64
+ keyData?: Hex.Hex | undefined
65
+ /** Signature type to model. Omission uses a maximum-size WebAuthn signature. */
66
+ keyType?: SignatureEnvelope.Type | undefined
67
+ /** Configured owner address. */
68
+ owner: Address.Address
69
+ /** Approval type. */
70
+ type: 'primitive'
71
+ }
72
+
73
+ /** JSON-RPC representation of a native multisig simulation spec. */
74
+ export type Rpc = Compute<{
75
+ /** Account authorized by this spec. */
76
+ account: Address.Address
77
+ /** Owner approvals to model. */
78
+ approvals: readonly ApprovalRpc[]
79
+ /** Canonical RLP-encoded applicable configuration. */
80
+ config: Hex.Hex
81
+ }>
82
+
83
+ /** Native multisig spec used to construct an RPC simulation signature. */
84
+ export type Spec = Compute<{
85
+ /** Account authorized by this spec. */
86
+ account: Address.Address
87
+ /** Owner approvals to model. */
88
+ approvals: readonly Approval[]
89
+ /** Complete applicable configuration. */
90
+ config: MultisigConfig.Config
91
+ }>
92
+
93
+ /**
94
+ * Converts a JSON-RPC multisig simulation spec to its domain representation.
95
+ *
96
+ * @example
97
+ * ```ts twoslash
98
+ * // @noErrors
99
+ * import { MultisigSimulation } from 'ox/tempo'
100
+ *
101
+ * const spec = MultisigSimulation.fromRpc(specRpc)
102
+ * ```
103
+ *
104
+ * @param spec - JSON-RPC multisig simulation spec.
105
+ * @returns The multisig simulation spec with decoded configurations.
106
+ */
107
+ export function fromRpc(spec: Rpc): Spec {
108
+ const { account, approvals, config } = spec
109
+ assertApprovalCount(approvals)
110
+ return {
111
+ account,
112
+ approvals: approvals.map((approval) => {
113
+ if (approval.type === 'primitive') return approval
114
+ assertApprovalCount(approval.spec.approvals)
115
+ return {
116
+ spec: {
117
+ account: approval.spec.account,
118
+ approvals: approval.spec.approvals,
119
+ config: deserializeConfig(approval.spec.config),
120
+ },
121
+ type: 'multisig',
122
+ }
123
+ }),
124
+ config: deserializeConfig(config),
125
+ }
126
+ }
127
+
128
+ export declare namespace fromRpc {
129
+ /** Error type for `fromRpc`. */
130
+ export type ErrorType =
131
+ | Hex.isEqual.ErrorType
132
+ | InvalidSimulationError
133
+ | MultisigConfig.assert.ErrorType
134
+ | Rlp.fromHex.ErrorType
135
+ | Rlp.toHex.ErrorType
136
+ | Errors.GlobalErrorType
137
+ }
138
+
139
+ /**
140
+ * Converts a multisig simulation spec to its JSON-RPC representation.
141
+ *
142
+ * @example
143
+ * ```ts twoslash
144
+ * // @noErrors
145
+ * import { MultisigSimulation } from 'ox/tempo'
146
+ *
147
+ * const specRpc = MultisigSimulation.toRpc(spec)
148
+ * ```
149
+ *
150
+ * @param spec - Multisig simulation spec.
151
+ * @returns The JSON-RPC multisig simulation spec with encoded configurations.
152
+ */
153
+ export function toRpc(spec: Spec): Rpc {
154
+ const { account, approvals, config } = spec
155
+ assertApprovalCount(approvals)
156
+ return {
157
+ account,
158
+ approvals: approvals.map((approval) => {
159
+ if (approval.type === 'primitive')
160
+ return {
161
+ ...approval,
162
+ ...(typeof approval.keyData !== 'undefined'
163
+ ? { keyData: shimKeyData(approval.keyData) }
164
+ : {}),
165
+ }
166
+ assertApprovalCount(approval.spec.approvals)
167
+ return {
168
+ spec: {
169
+ account: approval.spec.account,
170
+ approvals: approval.spec.approvals.map((approval) => ({
171
+ ...approval,
172
+ ...(typeof approval.keyData !== 'undefined'
173
+ ? { keyData: shimKeyData(approval.keyData) }
174
+ : {}),
175
+ })),
176
+ config: serializeConfig(approval.spec.config),
177
+ },
178
+ type: 'multisig',
179
+ }
180
+ }),
181
+ config: serializeConfig(config),
182
+ }
183
+ }
184
+
185
+ export declare namespace toRpc {
186
+ /** Error type for `toRpc`. */
187
+ export type ErrorType =
188
+ | Hex.fromNumber.ErrorType
189
+ | InvalidSimulationError
190
+ | MultisigConfig.assert.ErrorType
191
+ | Rlp.fromHex.ErrorType
192
+ | Errors.GlobalErrorType
193
+ }
194
+
195
+ /** @internal */
196
+ function assertApprovalCount(approvals: readonly unknown[]) {
197
+ if (approvals.length > MultisigConfig.maxSignatures)
198
+ throw new InvalidSimulationError({
199
+ reason: `approval count exceeds ${MultisigConfig.maxSignatures}`,
200
+ })
201
+ }
202
+
203
+ /** @internal */
204
+ function deserializeConfig(config: Hex.Hex): MultisigConfig.Config {
205
+ const tuple = Rlp.toHex(config)
206
+ if (!Array.isArray(tuple) || tuple.length !== 4)
207
+ throw new InvalidSimulationError({ reason: 'invalid config encoding' })
208
+ const [salt, version, threshold, owners] = tuple
209
+ if (
210
+ Array.isArray(salt) ||
211
+ Hex.size(salt) !== 32 ||
212
+ Array.isArray(version) ||
213
+ Hex.size(version) > 8 ||
214
+ (version !== '0x' && Hex.slice(version, 0, 1) === '0x00') ||
215
+ Array.isArray(threshold) ||
216
+ Hex.size(threshold) > 1 ||
217
+ !Array.isArray(owners) ||
218
+ owners.some(
219
+ (owner) =>
220
+ !Array.isArray(owner) ||
221
+ owner.length !== 2 ||
222
+ owner.some(Array.isArray) ||
223
+ !Address.validate(owner[0]) ||
224
+ Hex.size(owner[1] as Hex.Hex) > 1,
225
+ )
226
+ )
227
+ throw new InvalidSimulationError({ reason: 'invalid config encoding' })
228
+ const value = MultisigConfig.fromTuple(
229
+ tuple as unknown as MultisigConfig.Tuple,
230
+ )
231
+ MultisigConfig.assert(value)
232
+ if (!Hex.isEqual(config, serializeConfig(value)))
233
+ throw new InvalidSimulationError({ reason: 'noncanonical config encoding' })
234
+ return value
235
+ }
236
+
237
+ /** @internal */
238
+ function serializeConfig(config: MultisigConfig.Input): Hex.Hex {
239
+ return Rlp.fromHex(MultisigConfig.toTuple(config))
240
+ }
241
+
242
+ /**
243
+ * Shims long key data into the length hint accepted by Tempo's gas estimator.
244
+ * @internal
245
+ */
246
+ function shimKeyData(data: Hex.Hex): Hex.Hex {
247
+ const size = Hex.size(data)
248
+ if (size <= 4) return data
249
+ return Hex.fromNumber(size, { size: 2 })
250
+ }
251
+
252
+ /** Thrown when a native multisig simulation spec is invalid. */
253
+ export class InvalidSimulationError extends Errors.BaseError {
254
+ override readonly name = 'MultisigSimulation.InvalidSimulationError'
255
+ constructor(options: InvalidSimulationError.Options) {
256
+ super(`Invalid multisig simulation: ${options.reason}.`)
257
+ }
258
+ }
259
+
260
+ export declare namespace InvalidSimulationError {
261
+ /** Error construction options. */
262
+ export type Options = {
263
+ /** Validation failure. */
264
+ reason: string
265
+ }
266
+ }
@@ -3,7 +3,7 @@ import type {
3
3
  KeyAuthorization,
4
4
  MultisigConfig,
5
5
  MultisigOperation,
6
- MultisigWitness,
6
+ MultisigSimulation,
7
7
  RpcSchemaTempo,
8
8
  } from 'ox/tempo'
9
9
  import { expectTypeOf, test } from 'vitest'
@@ -17,7 +17,7 @@ declare const tempoProvider: Provider.Provider<{
17
17
  declare const address: Address.Address
18
18
  declare const hash: Hex.Hex
19
19
  declare const keyAuthorization: KeyAuthorization.Rpc
20
- declare const multisigWitness: MultisigWitness.Rpc
20
+ declare const multisigSimulation: MultisigSimulation.Rpc
21
21
  declare const serializedTransaction: Hex.Hex
22
22
  declare const signature: Hex.Hex
23
23
 
@@ -70,7 +70,7 @@ test('multisig provider methods', () => {
70
70
  ).resolves.toEqualTypeOf<MultisigOperation.Rpc | null>()
71
71
  })
72
72
 
73
- test('tempo simulation accepts multisig witnesses', () => {
73
+ test('tempo simulation accepts multisig specs', () => {
74
74
  tempoProvider.request({
75
75
  method: 'tempo_simulateV1',
76
76
  params: [
@@ -79,7 +79,7 @@ test('tempo simulation accepts multisig witnesses', () => {
79
79
  {
80
80
  calls: [
81
81
  {
82
- multisigWitness,
82
+ multisigSimulation,
83
83
  },
84
84
  ],
85
85
  },
@@ -34,20 +34,10 @@ test('toRpc preserves the signature type', () => {
34
34
  ).toEqualTypeOf<SignatureEnvelope.Secp256k1Rpc>()
35
35
  })
36
36
 
37
- test('MultisigRpc carries one complete witness shape', () => {
38
- const rpc = {
39
- account: '0x1111111111111111111111111111111111111111',
40
- config: { ...config, version: 0 },
41
- signatures: [signatureRpc],
42
- } as const satisfies SignatureEnvelope.MultisigRpc
37
+ test('MultisigRpc contains an untagged serialized multisig signature', () => {
38
+ const rpc = '0xf8' as const satisfies SignatureEnvelope.MultisigRpc
43
39
 
44
- expectTypeOf(rpc.config).toMatchTypeOf<MultisigConfig.Config<number>>()
45
- expectTypeOf(rpc.signatures).toMatchTypeOf<
46
- readonly SignatureEnvelope.SignatureEnvelopeRpc[]
47
- >()
48
- expectTypeOf<
49
- SignatureEnvelope.GetType<typeof rpc>
50
- >().toEqualTypeOf<'multisig'>()
40
+ expectTypeOf(rpc).toMatchTypeOf<`0x${string}`>()
51
41
  })
52
42
 
53
43
  test('from derives an initial account', () => {
@@ -75,40 +65,14 @@ test('from requires an account for a current config', () => {
75
65
  expectTypeOf(multisig).toMatchTypeOf<SignatureEnvelope.Multisig>()
76
66
  })
77
67
 
78
- test('MultisigRpc rejects old witness shapes', () => {
79
- const accountOnly = {
80
- account: '0x1111111111111111111111111111111111111111',
81
- signatures: [signatureRpc],
82
- } as const
83
- // @ts-expect-error Multisig RPC signatures require config.
84
- const accountOnlyRpc: SignatureEnvelope.MultisigRpc = accountOnly
85
-
86
- const init = {
87
- init: config,
88
- signatures: [signatureRpc],
89
- } as const
90
- // @ts-expect-error Multisig RPC signatures no longer accept init.
91
- const initRpc: SignatureEnvelope.MultisigRpc = init
92
-
93
- const serialized = {
94
- account: '0x1111111111111111111111111111111111111111',
95
- config: { ...config, version: 0 },
96
- signatures: ['0x1234'],
97
- } as const
98
- // @ts-expect-error Owner approvals use structured RPC envelopes.
99
- const serializedRpc: SignatureEnvelope.MultisigRpc = serialized
100
-
101
- const tagged = {
68
+ test('MultisigRpc rejects structured signatures', () => {
69
+ const structured = {
102
70
  account: '0x1111111111111111111111111111111111111111',
103
71
  config: { ...config, version: 0 },
104
72
  signatures: [signatureRpc],
105
- type: 'multisig',
106
73
  } as const
107
- // @ts-expect-error Multisig RPC signatures are untagged.
108
- const taggedRpc: SignatureEnvelope.MultisigRpc = tagged
74
+ // @ts-expect-error Multisig RPC signatures are serialized hex.
75
+ const rpc: SignatureEnvelope.MultisigRpc = structured
109
76
 
110
- expectTypeOf(accountOnlyRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
111
- expectTypeOf(initRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
112
- expectTypeOf(serializedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
113
- expectTypeOf(taggedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
77
+ expectTypeOf(rpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
114
78
  })