ox 1.0.0-beta.11 → 1.0.0-beta.13

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 (48) hide show
  1. package/dist/core/internal/rpcSchemas/eth.d.ts +16 -0
  2. package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  3. package/dist/tempo/MultisigConfig.d.ts +37 -85
  4. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  5. package/dist/tempo/MultisigConfig.js +39 -83
  6. package/dist/tempo/MultisigConfig.js.map +1 -1
  7. package/dist/tempo/SignatureEnvelope.d.ts +32 -30
  8. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  9. package/dist/tempo/SignatureEnvelope.js +56 -51
  10. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  11. package/dist/tempo/index.d.ts +2 -2
  12. package/dist/tempo/index.js +2 -2
  13. package/dist/zod/RpcSchema.d.ts +1 -0
  14. package/dist/zod/RpcSchema.d.ts.map +1 -1
  15. package/dist/zod/internal/rpcSchemas/Eth.d.ts +1 -0
  16. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  17. package/dist/zod/internal/rpcSchemas/Eth.js +5 -0
  18. package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
  19. package/dist/zod/tempo/AuthorizationTempo.d.ts +0 -20
  20. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  21. package/dist/zod/tempo/KeyAuthorization.d.ts +0 -11
  22. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  23. package/dist/zod/tempo/RpcSchemaTempo.d.ts +0 -12
  24. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  25. package/dist/zod/tempo/SignatureEnvelope.d.ts +0 -6
  26. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  27. package/dist/zod/tempo/SignatureEnvelope.js +0 -2
  28. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  29. package/dist/zod/tempo/Transaction.d.ts +0 -24
  30. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  31. package/dist/zod/tempo/TransactionRequest.d.ts +0 -21
  32. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  33. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +0 -9
  34. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  35. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +0 -3
  36. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/core/internal/rpcSchemas/eth.ts +16 -0
  39. package/src/tempo/MultisigConfig.test.ts +54 -37
  40. package/src/tempo/MultisigConfig.ts +59 -134
  41. package/src/tempo/SignatureEnvelope.test.ts +118 -17
  42. package/src/tempo/SignatureEnvelope.ts +77 -72
  43. package/src/tempo/e2e.test.ts +221 -1
  44. package/src/tempo/index.ts +2 -2
  45. package/src/version.ts +1 -1
  46. package/src/zod/internal/rpcSchemas/Eth.ts +6 -0
  47. package/src/zod/tempo/SignatureEnvelope.ts +0 -2
  48. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +0 -2
@@ -3,6 +3,7 @@ import {
3
3
  Hex,
4
4
  P256,
5
5
  PublicKey,
6
+ Rlp,
6
7
  Secp256k1,
7
8
  Signature,
8
9
  WebAuthnP256,
@@ -1125,7 +1126,7 @@ describe('from', () => {
1125
1126
  ],
1126
1127
  })
1127
1128
 
1128
- test('behavior: derives `account`/`genesisConfigId` from `genesisConfig`', () => {
1129
+ test('behavior: derives `account` from `genesisConfig`', () => {
1129
1130
  const envelope = SignatureEnvelope.from({
1130
1131
  genesisConfig,
1131
1132
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
@@ -1134,7 +1135,6 @@ describe('from', () => {
1134
1135
  expect(envelope).toMatchObject({
1135
1136
  type: 'multisig',
1136
1137
  account: MultisigConfig.getAddress(genesisConfig),
1137
- genesisConfigId: MultisigConfig.toId(genesisConfig),
1138
1138
  })
1139
1139
  expect('genesisConfig' in envelope).toBe(false)
1140
1140
  expect((envelope as SignatureEnvelope.Multisig).init).toBeUndefined()
@@ -1171,19 +1171,16 @@ describe('from', () => {
1171
1171
  expect((envelope as SignatureEnvelope.Multisig).init).toEqual(otherConfig)
1172
1172
  })
1173
1173
 
1174
- test('behavior: `{account, genesisConfigId}` form still works', () => {
1174
+ test('behavior: `{ account }` form still works', () => {
1175
1175
  const account = MultisigConfig.getAddress(genesisConfig)
1176
- const genesisConfigId = MultisigConfig.toId(genesisConfig)
1177
1176
  const envelope = SignatureEnvelope.from({
1178
1177
  account,
1179
- genesisConfigId,
1180
1178
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
1181
1179
  })
1182
1180
 
1183
1181
  expect(envelope).toMatchObject({
1184
1182
  type: 'multisig',
1185
1183
  account,
1186
- genesisConfigId,
1187
1184
  })
1188
1185
  })
1189
1186
  })
@@ -1860,9 +1857,8 @@ describe('sortMultisigApprovals', () => {
1860
1857
  expect(recovered).toEqual(ascending.map((owner) => owner.address))
1861
1858
  })
1862
1859
 
1863
- test('behavior: `genesisConfig` and `{account, genesisConfigId}` produce identical ordering', () => {
1860
+ test('behavior: `genesisConfig` and `{ account }` produce identical ordering', () => {
1864
1861
  const account = MultisigConfig.getAddress(genesisConfig)
1865
- const genesisConfigId = MultisigConfig.toId(genesisConfig)
1866
1862
  const signatures = owners.map((owner) => owner.signature)
1867
1863
 
1868
1864
  const fromConfig = SignatureEnvelope.sortMultisigApprovals({
@@ -1870,13 +1866,12 @@ describe('sortMultisigApprovals', () => {
1870
1866
  payload,
1871
1867
  signatures,
1872
1868
  })
1873
- const fromIds = SignatureEnvelope.sortMultisigApprovals({
1869
+ const fromAccount = SignatureEnvelope.sortMultisigApprovals({
1874
1870
  account,
1875
- genesisConfigId,
1876
1871
  payload,
1877
1872
  signatures,
1878
1873
  })
1879
- expect(fromConfig).toEqual(fromIds)
1874
+ expect(fromConfig).toEqual(fromAccount)
1880
1875
  })
1881
1876
  })
1882
1877
 
@@ -2883,8 +2878,6 @@ describe('CoercionError', () => {
2883
2878
 
2884
2879
  describe('multisig', () => {
2885
2880
  const account = '0x8ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7ae'
2886
- const genesisConfigId =
2887
- '0x01781fe551182476f2422c759e82d81c92e3263737afbbad57def6e8b69d21f5'
2888
2881
 
2889
2882
  // P256 signatures do not carry `yParity` in the wire format, so use a clean
2890
2883
  // inner signature for round-trip equality checks.
@@ -2897,7 +2890,6 @@ describe('multisig', () => {
2897
2890
  const envelope = SignatureEnvelope.from({
2898
2891
  type: 'multisig',
2899
2892
  account,
2900
- genesisConfigId,
2901
2893
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2902
2894
  })
2903
2895
 
@@ -2906,11 +2898,32 @@ describe('multisig', () => {
2906
2898
  expect(serialized.startsWith('0x05')).toBe(true)
2907
2899
  })
2908
2900
 
2901
+ test('serialize: wire shape is `0x05 || rlp([account, signatures])`', () => {
2902
+ const deterministic = SignatureEnvelope.from({
2903
+ type: 'multisig',
2904
+ account,
2905
+ signatures: [innerP256],
2906
+ })
2907
+ expect(SignatureEnvelope.serialize(deterministic)).toMatchInlineSnapshot(
2908
+ `"0x05f89b948ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7aef884b88201ccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba001"`,
2909
+ )
2910
+ })
2911
+
2909
2912
  test('serialize/deserialize round-trip', () => {
2910
2913
  const serialized = SignatureEnvelope.serialize(envelope)
2911
2914
  expect(SignatureEnvelope.deserialize(serialized)).toEqual(envelope)
2912
2915
  })
2913
2916
 
2917
+ test('deserialize: rejects the empty `init` placeholder (`0x80`)', () => {
2918
+ const withPlaceholder = Hex.concat(
2919
+ '0x05',
2920
+ Rlp.fromHex([account, [SignatureEnvelope.serialize(innerP256)], '0x']),
2921
+ )
2922
+ expect(() => SignatureEnvelope.deserialize(withPlaceholder)).toThrowError(
2923
+ SignatureEnvelope.InvalidSerializedError,
2924
+ )
2925
+ })
2926
+
2914
2927
  test('getType', () => {
2915
2928
  expect(SignatureEnvelope.getType(envelope)).toBe('multisig')
2916
2929
  })
@@ -2936,6 +2949,97 @@ describe('multisig', () => {
2936
2949
  ).toThrowError()
2937
2950
  })
2938
2951
 
2952
+ test('assert: rejects keychain owner approvals', () => {
2953
+ expect(() =>
2954
+ SignatureEnvelope.assert({
2955
+ type: 'multisig',
2956
+ account,
2957
+ signatures: [signature_keychain_secp256k1],
2958
+ } as never),
2959
+ ).toThrowErrorMatchingInlineSnapshot(
2960
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: keychain owner approvals are not allowed.]`,
2961
+ )
2962
+ })
2963
+
2964
+ describe('nested approvals', () => {
2965
+ const nestedAccount = '0x1111111111111111111111111111111111111111'
2966
+ const nested = SignatureEnvelope.from({
2967
+ type: 'multisig',
2968
+ account: nestedAccount,
2969
+ signatures: [innerP256],
2970
+ })
2971
+
2972
+ test('serialize/deserialize round-trip with a nested multisig approval', () => {
2973
+ const parent = SignatureEnvelope.from({
2974
+ type: 'multisig',
2975
+ account,
2976
+ signatures: [innerP256, nested],
2977
+ })
2978
+ const serialized = SignatureEnvelope.serialize(parent)
2979
+ expect(SignatureEnvelope.deserialize(serialized)).toEqual(parent)
2980
+ })
2981
+
2982
+ test('assert: accepts the maximum nesting depth', () => {
2983
+ const depth3 = SignatureEnvelope.from({
2984
+ type: 'multisig',
2985
+ account,
2986
+ signatures: [
2987
+ SignatureEnvelope.from({
2988
+ type: 'multisig',
2989
+ account: nestedAccount,
2990
+ signatures: [nested],
2991
+ }),
2992
+ ],
2993
+ })
2994
+ expect(() => SignatureEnvelope.assert(depth3)).not.toThrowError()
2995
+ })
2996
+
2997
+ test('assert: rejects nesting deeper than the maximum', () => {
2998
+ const depth4 = SignatureEnvelope.from({
2999
+ type: 'multisig',
3000
+ account,
3001
+ signatures: [
3002
+ SignatureEnvelope.from({
3003
+ type: 'multisig',
3004
+ account: nestedAccount,
3005
+ signatures: [
3006
+ SignatureEnvelope.from({
3007
+ type: 'multisig',
3008
+ account: nestedAccount,
3009
+ signatures: [nested],
3010
+ }),
3011
+ ],
3012
+ }),
3013
+ ],
3014
+ })
3015
+ expect(() =>
3016
+ SignatureEnvelope.assert(depth4),
3017
+ ).toThrowErrorMatchingInlineSnapshot(
3018
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 3.]`,
3019
+ )
3020
+ })
3021
+
3022
+ test('assert: rejects nested approvals carrying `init`', () => {
3023
+ expect(() =>
3024
+ SignatureEnvelope.assert({
3025
+ type: 'multisig',
3026
+ account,
3027
+ signatures: [
3028
+ {
3029
+ ...nested,
3030
+ init: {
3031
+ threshold: 1,
3032
+ owners: [{ owner: nestedAccount, weight: 1 }],
3033
+ },
3034
+ },
3035
+ ],
3036
+ } as never),
3037
+ ).toThrowErrorMatchingInlineSnapshot(
3038
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: nested multisig owner approvals cannot carry \`init\`.]`,
3039
+ )
3040
+ })
3041
+ })
3042
+
2939
3043
  describe('init (bootstrap)', () => {
2940
3044
  const init = {
2941
3045
  salt: `0x${'00'.repeat(32)}` as const,
@@ -2951,7 +3055,6 @@ describe('multisig', () => {
2951
3055
  const bootstrapEnvelope = SignatureEnvelope.from({
2952
3056
  type: 'multisig',
2953
3057
  account,
2954
- genesisConfigId,
2955
3058
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2956
3059
  init,
2957
3060
  })
@@ -2967,7 +3070,6 @@ describe('multisig', () => {
2967
3070
  const salted = SignatureEnvelope.from({
2968
3071
  type: 'multisig',
2969
3072
  account,
2970
- genesisConfigId,
2971
3073
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2972
3074
  init: { ...init, salt: `0x${'42'.repeat(32)}` },
2973
3075
  })
@@ -3021,7 +3123,6 @@ describe('multisig', () => {
3021
3123
  SignatureEnvelope.assert({
3022
3124
  type: 'multisig',
3023
3125
  account,
3024
- genesisConfigId,
3025
3126
  signatures: [],
3026
3127
  init: { threshold: 1, owners: [] },
3027
3128
  } as never),
@@ -86,7 +86,6 @@ export type GetType<
86
86
  ? 'keychain'
87
87
  : envelope extends {
88
88
  account: Address.Address
89
- genesisConfigId: `0x${string}`
90
89
  signatures: any
91
90
  }
92
91
  ? 'multisig'
@@ -162,9 +161,10 @@ export type KeychainRpc = {
162
161
  /**
163
162
  * Native multisig signature (type `0x05`).
164
163
  *
165
- * Wraps a set of primitive owner approvals (secp256k1, p256, or webAuthn) over the
166
- * multisig owner approval digest. The transaction sender is the derived `account`,
167
- * authorized once the recovered owner weights meet the configured threshold.
164
+ * Wraps a set of owner approvals (secp256k1, p256, webAuthn, or nested
165
+ * multisig) over the multisig owner approval digest. The transaction sender is
166
+ * the derived `account`, authorized once the recovered owner weights meet the
167
+ * configured threshold.
168
168
  *
169
169
  * [TIP-1061](https://tips.sh/1061)
170
170
  */
@@ -172,9 +172,11 @@ export type Multisig<numberType = number> = {
172
172
  type: 'multisig'
173
173
  /** Native multisig account address. */
174
174
  account: Address.Address
175
- /** Permanent config ID derived from the initial multisig config. */
176
- genesisConfigId: Hex.Hex
177
- /** Primitive owner approvals over the multisig owner approval digest. */
175
+ /**
176
+ * Owner approvals over the multisig owner approval digest. Each approval is
177
+ * either a primitive signature or a nested multisig signature (keychain
178
+ * approvals are invalid).
179
+ */
178
180
  signatures: readonly SignatureEnvelope<numberType>[]
179
181
  /**
180
182
  * Initial native multisig config for bootstrapping this account. Present only on
@@ -188,14 +190,8 @@ export type MultisigRpc = {
188
190
  type: 'multisig'
189
191
  account: Address.Address
190
192
  /**
191
- * The permanent multisig config ID (TIP-1061 wire field `config_id`).
192
- * Maps to `genesisConfigId` on the typed
193
- * {@link ox#SignatureEnvelope.Multisig} envelope.
194
- */
195
- configId: Hex.Hex
196
- /**
197
- * Encoded primitive owner approvals (raw serialized signatures), matching the
198
- * node's `Vec<Bytes>` representation.
193
+ * Encoded owner approvals (raw serialized signatures), matching the node's
194
+ * `Vec<Bytes>` representation.
199
195
  */
200
196
  signatures: readonly Serialized[]
201
197
  init?: MultisigConfig.Config | undefined
@@ -345,11 +341,24 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
345
341
  const multisig = envelope as Multisig
346
342
  const missing: string[] = []
347
343
  if (!multisig.account) missing.push('account')
348
- if (!multisig.genesisConfigId) missing.push('genesisConfigId')
349
344
  if (!Array.isArray(multisig.signatures)) missing.push('signatures')
350
345
  if (missing.length > 0)
351
346
  throw new MissingPropertiesError({ envelope, missing, type: 'multisig' })
352
- for (const inner of multisig.signatures) assert(inner)
347
+ if (getNestingDepth(multisig) > MultisigConfig.maxNestingDepth)
348
+ throw new InvalidMultisigApprovalError({
349
+ reason: `multisig nesting depth exceeds ${MultisigConfig.maxNestingDepth}`,
350
+ })
351
+ for (const inner of multisig.signatures) {
352
+ if (inner.type === 'keychain')
353
+ throw new InvalidMultisigApprovalError({
354
+ reason: 'keychain owner approvals are not allowed',
355
+ })
356
+ if (inner.type === 'multisig' && inner.init)
357
+ throw new InvalidMultisigApprovalError({
358
+ reason: 'nested multisig owner approvals cannot carry `init`',
359
+ })
360
+ assert(inner)
361
+ }
353
362
  if (multisig.init) MultisigConfig.assert(multisig.init)
354
363
  return
355
364
  }
@@ -358,12 +367,22 @@ export function assert(envelope: PartialBy<SignatureEnvelope, 'type'>): void {
358
367
  export declare namespace assert {
359
368
  type ErrorType =
360
369
  | CoercionError
370
+ | InvalidMultisigApprovalError
361
371
  | MissingPropertiesError
362
372
  | MultisigConfig.assert.ErrorType
363
373
  | Signature.assert.ErrorType
364
374
  | Errors.GlobalErrorType
365
375
  }
366
376
 
377
+ // Returns the deepest multisig path length within a multisig envelope.
378
+ function getNestingDepth(envelope: Multisig): number {
379
+ let deepest = 0
380
+ for (const inner of envelope.signatures)
381
+ if (inner.type === 'multisig')
382
+ deepest = Math.max(deepest, getNestingDepth(inner))
383
+ return 1 + deepest
384
+ }
385
+
367
386
  /**
368
387
  * Extracts the address of the signer from a {@link ox#SignatureEnvelope.SignatureEnvelope}.
369
388
  *
@@ -630,24 +649,26 @@ export function deserialize(value: Serialized): SignatureEnvelope {
630
649
  }
631
650
 
632
651
  if (typeId === serializedMultisigType) {
633
- // Wire format: `0x05 || rlp([account, genesisConfigId, signatures, init])`. `init`
634
- // is optional: absent when the element is missing or the `0x80` placeholder
635
- // (decoded as the empty string `0x`), otherwise the bootstrap config list.
636
- const [account, genesisConfigId, signatures, init] = Rlp.toHex(data) as [
637
- Hex.Hex,
652
+ // Wire format: `0x05 || rlp([account, signatures, init?])`. `init` is a
653
+ // trailing optional: the bootstrap config list when present, otherwise
654
+ // omitted entirely (the empty `0x80` placeholder is rejected).
655
+ const [account, signatures, init] = Rlp.toHex(data) as [
638
656
  Hex.Hex,
639
657
  readonly Hex.Hex[],
640
658
  (Hex.Hex | MultisigConfig.Tuple)?,
641
659
  ]
660
+ if (typeof init !== 'undefined' && !Array.isArray(init))
661
+ throw new InvalidSerializedError({
662
+ reason:
663
+ 'Invalid multisig `init`: expected the bootstrap config list or an omitted trailing element',
664
+ serialized,
665
+ })
642
666
  return {
643
667
  type: 'multisig',
644
668
  account,
645
- genesisConfigId,
646
669
  signatures: signatures.map((signature) => deserialize(signature)),
647
- ...(init && init !== '0x'
648
- ? {
649
- init: MultisigConfig.fromTuple(init as MultisigConfig.Tuple),
650
- }
670
+ ...(init
671
+ ? { init: MultisigConfig.fromTuple(init as MultisigConfig.Tuple) }
651
672
  : {}),
652
673
  } satisfies Multisig
653
674
  }
@@ -786,9 +807,9 @@ export function deserialize(value: Serialized): SignatureEnvelope {
786
807
  * @example
787
808
  * ### Multisig (from genesis config)
788
809
  *
789
- * Pass `genesisConfig` to derive `account` and `genesisConfigId` automatically.
790
- * Set `init: true` to opt into bootstrap (uses `genesisConfig` as the
791
- * bootstrap `init`); omit `init` for subsequent (non-bootstrap) transactions.
810
+ * Pass `genesisConfig` to derive `account` automatically. Set `init: true` to
811
+ * opt into bootstrap (uses `genesisConfig` as the bootstrap `init`); omit
812
+ * `init` for subsequent (non-bootstrap) transactions.
792
813
  *
793
814
  * ```ts twoslash
794
815
  * import { Secp256k1 } from 'ox'
@@ -849,18 +870,12 @@ export function from<const value extends from.Value>(
849
870
  init?: MultisigConfig.Config | boolean | undefined
850
871
  }
851
872
  const { genesisConfig, init, ...rest } = multisig
852
- // Derive `account`/`genesisConfigId` from `genesisConfig` when not provided
853
- // explicitly.
873
+ // Derive `account` from `genesisConfig` when not provided explicitly.
854
874
  const account = (() => {
855
875
  if (rest.account) return rest.account
856
876
  if (genesisConfig) return MultisigConfig.getAddress(genesisConfig)
857
877
  return rest.account
858
878
  })()
859
- const genesisConfigId = (() => {
860
- if (rest.genesisConfigId) return rest.genesisConfigId
861
- if (genesisConfig) return MultisigConfig.toId(genesisConfig)
862
- return rest.genesisConfigId
863
- })()
864
879
  // `init: true` opts into bootstrap using the supplied `genesisConfig`.
865
880
  // Otherwise, `init` is treated as the explicit bootstrap config (or
866
881
  // omitted).
@@ -868,7 +883,6 @@ export function from<const value extends from.Value>(
868
883
  return {
869
884
  ...rest,
870
885
  account,
871
- genesisConfigId,
872
886
  signatures: rest.signatures.map((signature) => from(signature)),
873
887
  // Normalize the bootstrap config (sorts owners, defaults the salt) so the
874
888
  // in-memory envelope matches what `deserialize` reconstructs.
@@ -920,9 +934,9 @@ export declare namespace from {
920
934
  }
921
935
 
922
936
  /**
923
- * Multisig envelope input variant where `account` and `genesisConfigId` are derived
924
- * from the supplied `genesisConfig`. Pass `init: true` to opt into bootstrap
925
- * (uses `genesisConfig` as the bootstrap `init`); omit `init` for subsequent
937
+ * Multisig envelope input variant where `account` is derived from the
938
+ * supplied `genesisConfig`. Pass `init: true` to opt into bootstrap (uses
939
+ * `genesisConfig` as the bootstrap `init`); omit `init` for subsequent
926
940
  * (non-bootstrap) transactions.
927
941
  */
928
942
  type MultisigFromGenesisConfig = {
@@ -1063,17 +1077,12 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
1063
1077
 
1064
1078
  if (
1065
1079
  envelope.type === 'multisig' ||
1066
- ('account' in envelope &&
1067
- 'configId' in envelope &&
1068
- 'signatures' in envelope)
1080
+ ('account' in envelope && 'signatures' in envelope)
1069
1081
  ) {
1070
1082
  const multisig = envelope as MultisigRpc
1071
1083
  return {
1072
1084
  type: 'multisig',
1073
1085
  account: multisig.account,
1074
- // Map RPC wire field `configId` (TIP-1061 spec name) to the typed
1075
- // envelope's `genesisConfigId`.
1076
- genesisConfigId: multisig.configId,
1077
1086
  // Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
1078
1087
  signatures: multisig.signatures.map((signature) =>
1079
1088
  deserialize(signature),
@@ -1164,8 +1173,7 @@ export function getType<
1164
1173
 
1165
1174
  // Detect Multisig signature
1166
1175
  if (
1167
- (('account' in envelope && 'genesisConfigId' in envelope) ||
1168
- 'genesisConfig' in envelope) &&
1176
+ ('account' in envelope || 'genesisConfig' in envelope) &&
1169
1177
  'signatures' in envelope
1170
1178
  )
1171
1179
  return 'multisig' as never
@@ -1269,17 +1277,15 @@ export function serialize(
1269
1277
 
1270
1278
  if (type === 'multisig') {
1271
1279
  const multisig = envelope as Multisig
1272
- // Format: `0x05 || rlp([account, genesisConfigId, signatures, init])`, where each
1273
- // owner approval is an encoded primitive signature. `init` is the bootstrap
1274
- // config (an RLP list) when present, otherwise the canonical empty-string
1275
- // placeholder (`0x` → RLP `0x80`).
1280
+ // Format: `0x05 || rlp([account, signatures, init?])`, where each owner
1281
+ // approval is an encoded signature. `init` is a trailing optional: the
1282
+ // bootstrap config list when present, otherwise omitted entirely.
1276
1283
  return Hex.concat(
1277
1284
  serializedMultisigType,
1278
1285
  Rlp.fromHex([
1279
1286
  multisig.account,
1280
- multisig.genesisConfigId,
1281
1287
  multisig.signatures.map((signature) => serialize(signature)),
1282
- multisig.init ? MultisigConfig.toTuple(multisig.init) : '0x',
1288
+ ...(multisig.init ? [MultisigConfig.toTuple(multisig.init)] : []),
1283
1289
  ]),
1284
1290
  options.magic ? magicBytes : '0x',
1285
1291
  )
@@ -1307,10 +1313,10 @@ export declare namespace serialize {
1307
1313
  * ({@link ox#MultisigConfig.(getSignPayload:function)}), so the signer of
1308
1314
  * every approval is recovered against that digest and the list is sorted by the
1309
1315
  * recovered owner address. Works for any owner key type (secp256k1, p256,
1310
- * webAuthn, keychain).
1316
+ * webAuthn).
1311
1317
  *
1312
- * Config updates never change `account`/`genesisConfigId`, so the genesis
1313
- * config is the correct input even for post-update transactions.
1318
+ * Config updates never change `account`, so the genesis config is the correct
1319
+ * input even for post-update transactions.
1314
1320
  *
1315
1321
  * @example
1316
1322
  * ```ts twoslash
@@ -1370,12 +1376,7 @@ export function sortMultisigApprovals(
1370
1376
  const digest = MultisigConfig.getSignPayload(
1371
1377
  'genesisConfig' in value && value.genesisConfig
1372
1378
  ? { payload, genesisConfig: value.genesisConfig }
1373
- : {
1374
- payload,
1375
- account: (value as { account: Address.Address }).account,
1376
- genesisConfigId: (value as { genesisConfigId: Hex.Hex })
1377
- .genesisConfigId,
1378
- },
1379
+ : { payload, account: (value as { account: Address.Address }).account },
1379
1380
  )
1380
1381
  // Recover each signer once (decorate–sort–undecorate) rather than inside the
1381
1382
  // comparator.
@@ -1392,20 +1393,17 @@ export declare namespace sortMultisigApprovals {
1392
1393
  type Value = {
1393
1394
  /** The inner transaction sign payload (`tx.signature_hash()`). */
1394
1395
  payload: Hex.Hex | Bytes.Bytes
1395
- /** The primitive owner approvals to order. */
1396
+ /** The owner approvals to order. */
1396
1397
  signatures: readonly SignatureEnvelope[]
1397
1398
  } & OneOf<
1398
1399
  | {
1399
1400
  /** The native multisig account address. */
1400
1401
  account: Address.Address
1401
- /** The permanent config ID. */
1402
- genesisConfigId: Hex.Hex
1403
1402
  }
1404
1403
  | {
1405
1404
  /**
1406
1405
  * The initial multisig config (the bootstrap config that derived the
1407
- * permanent `account` and `genesisConfigId`). Used to derive both values
1408
- * automatically.
1406
+ * permanent `account`). Used to derive the account automatically.
1409
1407
  */
1410
1408
  genesisConfig: MultisigConfig.Config
1411
1409
  }
@@ -1493,9 +1491,6 @@ export function toRpc(envelope: toRpc.Input): SignatureEnvelopeRpc {
1493
1491
  return {
1494
1492
  type: 'multisig',
1495
1493
  account: multisig.account,
1496
- // Map the typed envelope's `genesisConfigId` to the RPC wire field
1497
- // `configId` (TIP-1061 spec name).
1498
- configId: multisig.genesisConfigId,
1499
1494
  // Owner approvals are raw serialized signatures (node `Vec<Bytes>`).
1500
1495
  signatures: multisig.signatures.map((signature) => serialize(signature)),
1501
1496
  ...(multisig.init ? { init: multisig.init } : {}),
@@ -1780,6 +1775,16 @@ export class InvalidSerializedError extends Errors.BaseError {
1780
1775
  }
1781
1776
  }
1782
1777
 
1778
+ /**
1779
+ * Error thrown when a native multisig owner approval is invalid.
1780
+ */
1781
+ export class InvalidMultisigApprovalError extends Errors.BaseError {
1782
+ override readonly name = 'SignatureEnvelope.InvalidMultisigApprovalError'
1783
+ constructor({ reason }: { reason: string }) {
1784
+ super(`Invalid native multisig owner approval: ${reason}.`)
1785
+ }
1786
+ }
1787
+
1783
1788
  /**
1784
1789
  * Error thrown when a signature envelope fails to verify.
1785
1790
  */