viem 2.52.0 → 2.52.2

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 (102) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/_cjs/actions/wallet/prepareTransactionRequest.js.map +1 -1
  3. package/_cjs/chains/definitions/citrate.js +17 -0
  4. package/_cjs/chains/definitions/citrate.js.map +1 -0
  5. package/_cjs/chains/definitions/grav.js +24 -0
  6. package/_cjs/chains/definitions/grav.js.map +1 -0
  7. package/_cjs/chains/definitions/ladyChain.js +22 -0
  8. package/_cjs/chains/definitions/ladyChain.js.map +1 -0
  9. package/_cjs/chains/definitions/tempoModerato.js +1 -0
  10. package/_cjs/chains/definitions/tempoModerato.js.map +1 -1
  11. package/_cjs/chains/definitions/valygoNft.js +37 -0
  12. package/_cjs/chains/definitions/valygoNft.js.map +1 -0
  13. package/_cjs/chains/definitions/valygoSmartchain.js +37 -0
  14. package/_cjs/chains/definitions/valygoSmartchain.js.map +1 -0
  15. package/_cjs/chains/index.js +23 -13
  16. package/_cjs/chains/index.js.map +1 -1
  17. package/_cjs/errors/version.js +1 -1
  18. package/_cjs/tempo/Account.js +35 -3
  19. package/_cjs/tempo/Account.js.map +1 -1
  20. package/_cjs/tempo/Formatters.js +2 -1
  21. package/_cjs/tempo/Formatters.js.map +1 -1
  22. package/_cjs/tempo/Transaction.js +22 -0
  23. package/_cjs/tempo/Transaction.js.map +1 -1
  24. package/_cjs/tempo/chainConfig.js +10 -0
  25. package/_cjs/tempo/chainConfig.js.map +1 -1
  26. package/_cjs/tempo/index.js +2 -1
  27. package/_cjs/tempo/index.js.map +1 -1
  28. package/_esm/actions/wallet/prepareTransactionRequest.js.map +1 -1
  29. package/_esm/chains/definitions/citrate.js +14 -0
  30. package/_esm/chains/definitions/citrate.js.map +1 -0
  31. package/_esm/chains/definitions/grav.js +21 -0
  32. package/_esm/chains/definitions/grav.js.map +1 -0
  33. package/_esm/chains/definitions/ladyChain.js +19 -0
  34. package/_esm/chains/definitions/ladyChain.js.map +1 -0
  35. package/_esm/chains/definitions/tempoModerato.js +1 -0
  36. package/_esm/chains/definitions/tempoModerato.js.map +1 -1
  37. package/_esm/chains/definitions/valygoNft.js +34 -0
  38. package/_esm/chains/definitions/valygoNft.js.map +1 -0
  39. package/_esm/chains/definitions/valygoSmartchain.js +34 -0
  40. package/_esm/chains/definitions/valygoSmartchain.js.map +1 -0
  41. package/_esm/chains/index.js +5 -0
  42. package/_esm/chains/index.js.map +1 -1
  43. package/_esm/errors/version.js +1 -1
  44. package/_esm/tempo/Account.js +81 -4
  45. package/_esm/tempo/Account.js.map +1 -1
  46. package/_esm/tempo/Formatters.js +5 -1
  47. package/_esm/tempo/Formatters.js.map +1 -1
  48. package/_esm/tempo/Transaction.js +34 -0
  49. package/_esm/tempo/Transaction.js.map +1 -1
  50. package/_esm/tempo/chainConfig.js +24 -1
  51. package/_esm/tempo/chainConfig.js.map +1 -1
  52. package/_esm/tempo/index.js +1 -1
  53. package/_esm/tempo/index.js.map +1 -1
  54. package/_types/actions/wallet/prepareTransactionRequest.d.ts +12 -1
  55. package/_types/actions/wallet/prepareTransactionRequest.d.ts.map +1 -1
  56. package/_types/chains/definitions/citrate.d.ts +57 -0
  57. package/_types/chains/definitions/citrate.d.ts.map +1 -0
  58. package/_types/chains/definitions/grav.d.ts +43 -0
  59. package/_types/chains/definitions/grav.d.ts.map +1 -0
  60. package/_types/chains/definitions/ladyChain.d.ts +50 -0
  61. package/_types/chains/definitions/ladyChain.d.ts.map +1 -0
  62. package/_types/chains/definitions/tempo.d.ts +36 -0
  63. package/_types/chains/definitions/tempo.d.ts.map +1 -1
  64. package/_types/chains/definitions/tempoDevnet.d.ts +36 -0
  65. package/_types/chains/definitions/tempoDevnet.d.ts.map +1 -1
  66. package/_types/chains/definitions/tempoLocalnet.d.ts +36 -0
  67. package/_types/chains/definitions/tempoLocalnet.d.ts.map +1 -1
  68. package/_types/chains/definitions/tempoModerato.d.ts +38 -0
  69. package/_types/chains/definitions/tempoModerato.d.ts.map +1 -1
  70. package/_types/chains/definitions/valygoNft.d.ts +48 -0
  71. package/_types/chains/definitions/valygoNft.d.ts.map +1 -0
  72. package/_types/chains/definitions/valygoSmartchain.d.ts +48 -0
  73. package/_types/chains/definitions/valygoSmartchain.d.ts.map +1 -0
  74. package/_types/chains/index.d.ts +5 -0
  75. package/_types/chains/index.d.ts.map +1 -1
  76. package/_types/errors/version.d.ts +1 -1
  77. package/_types/tempo/Account.d.ts +48 -1
  78. package/_types/tempo/Account.d.ts.map +1 -1
  79. package/_types/tempo/Formatters.d.ts.map +1 -1
  80. package/_types/tempo/Transaction.d.ts +5 -1
  81. package/_types/tempo/Transaction.d.ts.map +1 -1
  82. package/_types/tempo/chainConfig.d.ts +34 -15
  83. package/_types/tempo/chainConfig.d.ts.map +1 -1
  84. package/_types/tempo/index.d.ts +1 -1
  85. package/_types/tempo/index.d.ts.map +1 -1
  86. package/_types/tempo/zones/zone.d.ts +108 -0
  87. package/_types/tempo/zones/zone.d.ts.map +1 -1
  88. package/actions/wallet/prepareTransactionRequest.ts +40 -5
  89. package/chains/definitions/citrate.ts +14 -0
  90. package/chains/definitions/grav.ts +21 -0
  91. package/chains/definitions/ladyChain.ts +19 -0
  92. package/chains/definitions/tempoModerato.ts +1 -0
  93. package/chains/definitions/valygoNft.ts +34 -0
  94. package/chains/definitions/valygoSmartchain.ts +34 -0
  95. package/chains/index.ts +5 -0
  96. package/errors/version.ts +1 -1
  97. package/package.json +3 -4
  98. package/tempo/Account.ts +97 -4
  99. package/tempo/Formatters.ts +12 -1
  100. package/tempo/Transaction.ts +42 -0
  101. package/tempo/chainConfig.ts +32 -3
  102. package/tempo/index.ts +1 -0
@@ -0,0 +1,34 @@
1
+ import { defineChain } from '../../utils/chain/defineChain.js'
2
+
3
+ export const valygoSmartchain = defineChain({
4
+ id: 7_771_777,
5
+ name: 'VALYGO Smartchain',
6
+ nativeCurrency: {
7
+ decimals: 18,
8
+ name: 'VYO',
9
+ symbol: 'VYO',
10
+ },
11
+ rpcUrls: {
12
+ default: {
13
+ http: [
14
+ 'https://rpc-gw-1.vyoscan.com/ext/bc/2t51dXsuxUvd9teY9TKEJmgxmxMk3CRF88UYTA4HQgjeYZqzSX/rpc',
15
+ 'https://rpc-gw-2.vyoscan.com/ext/bc/2t51dXsuxUvd9teY9TKEJmgxmxMk3CRF88UYTA4HQgjeYZqzSX/rpc',
16
+ ],
17
+ webSocket: [
18
+ 'wss://ws.vyoscan.com/ext/bc/2t51dXsuxUvd9teY9TKEJmgxmxMk3CRF88UYTA4HQgjeYZqzSX/ws',
19
+ ],
20
+ },
21
+ },
22
+ blockExplorers: {
23
+ default: {
24
+ name: 'VYOScan',
25
+ url: 'https://vyoscan.com',
26
+ apiUrl: 'https://vyoscan.com/api',
27
+ },
28
+ },
29
+ contracts: {
30
+ multicall3: {
31
+ address: '0xeFa3c632BD275750597cE9ca2346A5becAA0F344',
32
+ },
33
+ },
34
+ })
package/chains/index.ts CHANGED
@@ -114,6 +114,7 @@ export { celoSepolia } from './definitions/celoSepolia.js'
114
114
  export { chang } from './definitions/chang.js'
115
115
  export { chiliz } from './definitions/chiliz.js'
116
116
  export { chips } from './definitions/chips.js'
117
+ export { citrate } from './definitions/citrate.js'
117
118
  export { citrea } from './definitions/citrea.js'
118
119
  export { citreaTestnet } from './definitions/citreaTestnet.js'
119
120
  export { classic } from './definitions/classic.js'
@@ -251,6 +252,7 @@ export { godwoken } from './definitions/godwoken.js'
251
252
  export { goerli } from './definitions/goerli.js'
252
253
  export { graphite } from './definitions/graphite.js'
253
254
  export { graphiteTestnet } from './definitions/graphiteTestnet.js'
255
+ export { grav } from './definitions/grav.js'
254
256
  export { gravity } from './definitions/gravity.js'
255
257
  export { gunz } from './definitions/gunz.js'
256
258
  export { guruNetwork } from './definitions/guruNetwork.js'
@@ -350,6 +352,7 @@ export { kromaSepolia } from './definitions/kromaSepolia.js'
350
352
  export { krown } from './definitions/krown.js'
351
353
  export { l3x } from './definitions/l3x.js'
352
354
  export { l3xTestnet } from './definitions/l3xTestnet.js'
355
+ export { ladyChain } from './definitions/ladyChain.js'
353
356
  export { lavita } from './definitions/lavita.js'
354
357
  export { lens } from './definitions/lens.js'
355
358
  export { lensTestnet } from './definitions/lensTestnet.js'
@@ -675,6 +678,8 @@ export { unique } from './definitions/unique.js'
675
678
  export { uniqueOpal } from './definitions/uniqueOpal.js'
676
679
  export { uniqueQuartz } from './definitions/uniqueQuartz.js'
677
680
  export { unreal } from './definitions/unreal.js'
681
+ export { valygoNft } from './definitions/valygoNft.js'
682
+ export { valygoSmartchain } from './definitions/valygoSmartchain.js'
678
683
  export { vana } from './definitions/vana.js'
679
684
  export { vanaMoksha } from './definitions/vanaMoksha.js'
680
685
  export { vanar } from './definitions/vanar.js'
package/errors/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '2.52.0'
1
+ export const version = '2.52.2'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "viem",
3
3
  "description": "TypeScript Interface for Ethereum",
4
- "version": "2.52.0",
4
+ "version": "2.52.2",
5
5
  "main": "./_cjs/index.js",
6
6
  "module": "./_esm/index.js",
7
7
  "types": "./_types/index.d.ts",
@@ -15,8 +15,7 @@
15
15
  "!**/*.test.ts.snap",
16
16
  "!**/*.test-d.ts",
17
17
  "!**/*.tsbuildinfo",
18
- "!tsconfig.build.json",
19
- "!jsr.json"
18
+ "!tsconfig.build.json"
20
19
  ],
21
20
  "exports": {
22
21
  ".": {
@@ -230,7 +229,7 @@
230
229
  "@scure/bip39": "1.6.0",
231
230
  "abitype": "1.2.3",
232
231
  "isows": "1.0.7",
233
- "ox": "0.14.27",
232
+ "ox": "0.14.29",
234
233
  "ws": "8.20.1"
235
234
  },
236
235
  "license": "MIT",
package/tempo/Account.ts CHANGED
@@ -4,7 +4,12 @@ import * as P256 from 'ox/P256'
4
4
  import * as PublicKey from 'ox/PublicKey'
5
5
  import * as Secp256k1 from 'ox/Secp256k1'
6
6
  import * as Signature from 'ox/Signature'
7
- import { Channel, KeyAuthorization, SignatureEnvelope } from 'ox/tempo'
7
+ import {
8
+ Channel,
9
+ KeyAuthorization,
10
+ MultisigConfig,
11
+ SignatureEnvelope,
12
+ } from 'ox/tempo'
8
13
  import * as WebAuthnP256 from 'ox/WebAuthnP256'
9
14
  import * as WebCryptoP256 from 'ox/WebCryptoP256'
10
15
  import type {
@@ -269,6 +274,78 @@ export declare namespace fromSecp256k1 {
269
274
  from.ReturnValue<options>
270
275
  }
271
276
 
277
+ /**
278
+ * Instantiates a synthetic Account for a native multisig (TIP-1061) config.
279
+ *
280
+ * The returned account does not hold a key. It is used purely to drive the
281
+ * standard `sendTransaction` flow: it derives the multisig address from the
282
+ * config and passes the prepared request (carrying the collected owner
283
+ * `signatures`) through to the chain serializer, which combines the approvals
284
+ * into the multisig signature envelope.
285
+ *
286
+ * Owner approvals are produced separately by signing with `multisig` request
287
+ * metadata (see `signTransaction`), and provided here via `signatures`.
288
+ *
289
+ * Accepts a raw config and normalizes it internally (via `MultisigConfig.from`),
290
+ * so callers don't need to call `MultisigConfig.from` themselves.
291
+ *
292
+ * @example
293
+ * ```ts
294
+ * import { Account } from 'viem/tempo'
295
+ *
296
+ * const account = Account.fromMultisig({
297
+ * threshold: 2,
298
+ * owners: [
299
+ * { owner: owner_1.address, weight: 1 },
300
+ * { owner: owner_2.address, weight: 1 },
301
+ * ],
302
+ * })
303
+ *
304
+ * // Pass the account to `prepareTransactionRequest` — the multisig config is
305
+ * // inferred from it, so no `multisig` field is needed.
306
+ * const request = await client.prepareTransactionRequest({ account, ...rest })
307
+ *
308
+ * // The prepared request carries the multisig account as sender, so it doesn't
309
+ * // need to be re-passed to `sendTransaction`.
310
+ * const transaction = await client.sendTransaction({
311
+ * ...request,
312
+ * signatures: [signature_1, signature_2],
313
+ * })
314
+ * ```
315
+ *
316
+ * @param config Multisig config (raw or from `MultisigConfig.from`).
317
+ * @returns Multisig account.
318
+ */
319
+ export function fromMultisig(config: MultisigConfig.Config): MultisigAccount {
320
+ const normalized = MultisigConfig.from(config)
321
+ const address = Address.checksum(MultisigConfig.getAddress(normalized))
322
+ return {
323
+ address,
324
+ config: normalized,
325
+ publicKey: '0x',
326
+ source: 'multisig',
327
+ type: 'local',
328
+ async sign() {
329
+ throw new Error('`sign` is not supported for multisig accounts.')
330
+ },
331
+ async signMessage() {
332
+ throw new Error('`signMessage` is not supported for multisig accounts.')
333
+ },
334
+ async signTransaction(transaction, options) {
335
+ const { serializer = Transaction.serialize } = options ?? {}
336
+ return (await serializer(transaction as never)) as Hex.Hex
337
+ },
338
+ async signTypedData() {
339
+ throw new Error('`signTypedData` is not supported for multisig accounts.')
340
+ },
341
+ }
342
+ }
343
+
344
+ export type MultisigAccount = LocalAccount<'multisig'> & {
345
+ /** Multisig config (from `MultisigConfig.from`). */
346
+ config: MultisigConfig.Config
347
+ }
348
+
272
349
  /**
273
350
  * Instantiates an Account from a WebAuthn credential.
274
351
  *
@@ -594,9 +671,25 @@ function fromBase(parameters: fromBase.Parameters): Account_base {
594
671
  return { ...transaction, feePayerSignature: null }
595
672
  return transaction
596
673
  })()
597
- const signature = await sign({
598
- hash: keccak256(await serializer(presign)),
599
- })
674
+
675
+ const payload = keccak256(await serializer(presign))
676
+
677
+ // Native multisig (TIP-1061): return this owner's approval — a serialized
678
+ // primitive signature over the multisig owner approval digest — instead of
679
+ // a full serialized transaction. Approvals are combined later in
680
+ // `sendTransaction({ signatures })`.
681
+ const multisig = (
682
+ transaction as { multisig?: MultisigConfig.Config | undefined }
683
+ ).multisig
684
+ if (multisig) {
685
+ const digest = MultisigConfig.getSignPayload({
686
+ payload,
687
+ genesisConfig: multisig,
688
+ })
689
+ return await sign({ hash: digest, raw: true })
690
+ }
691
+
692
+ const signature = await sign({ hash: payload })
600
693
  const envelope = SignatureEnvelope.from(signature)
601
694
  return await serializer(transaction, envelope as never)
602
695
  },
@@ -79,6 +79,8 @@ export function formatTransactionRequest(
79
79
  keyData?: Hex.Hex | undefined
80
80
  keyId?: Address | undefined
81
81
  keyType?: 'p256' | 'secp256k1' | 'webAuthn' | undefined
82
+ multisig?: unknown
83
+ signatures?: unknown
82
84
  }
83
85
  const account = request.account
84
86
  ? parseAccount<Account | viem_Account | Address>(request.account)
@@ -116,8 +118,17 @@ export function formatTransactionRequest(
116
118
  if (request.feePayer === true && !request.feePayerSignature)
117
119
  delete request.feeToken
118
120
 
121
+ // `multisig` / `signatures` are client-side only (TIP-1061). They drive
122
+ // sender derivation, owner signing, and final envelope assembly, but are
123
+ // never sent as raw RPC fields — the wire payload is the serialized tx.
124
+ const {
125
+ multisig: _multisig,
126
+ signatures: _signatures,
127
+ ...rpcRequest
128
+ } = request
129
+
119
130
  const rpc = ox_TransactionRequest.toRpc({
120
- ...request,
131
+ ...rpcRequest,
121
132
  type: 'tempo',
122
133
  } as never)
123
134
 
@@ -7,6 +7,7 @@ import * as Signature from 'ox/Signature'
7
7
  import {
8
8
  type AuthorizationTempo,
9
9
  type KeyAuthorization,
10
+ type MultisigConfig,
10
11
  type TransactionReceipt as ox_TransactionReceipt,
11
12
  SignatureEnvelope,
12
13
  type TempoAddress,
@@ -124,7 +125,9 @@ export type TransactionRequestTempo<
124
125
  feePayer?: Account | true | undefined
125
126
  feeToken?: TempoAddress.Address | bigint | undefined
126
127
  keyAuthorization?: KeyAuthorization.Signed<quantity, index> | undefined
128
+ multisig?: MultisigConfig.Config<index> | undefined
127
129
  nonceKey?: 'expiring' | quantity | undefined
130
+ signatures?: readonly SignatureEnvelope.Serialized[] | undefined
128
131
  validBefore?: index | undefined
129
132
  validAfter?: index | undefined
130
133
  }
@@ -145,8 +148,10 @@ export type TransactionSerializableTempo<
145
148
  feePayerSignature?: viem_Signature | null | undefined
146
149
  from?: Address | undefined
147
150
  keyAuthorization?: KeyAuthorization.Signed<quantity, index> | undefined
151
+ multisig?: MultisigConfig.Config<index> | undefined
148
152
  nonceKey?: quantity | undefined
149
153
  signature?: SignatureEnvelope.SignatureEnvelope<quantity, index> | undefined
154
+ signatures?: readonly SignatureEnvelope.Serialized[] | undefined
150
155
  validBefore?: index | undefined
151
156
  validAfter?: index | undefined
152
157
  type?: 'tempo' | undefined
@@ -170,12 +175,16 @@ export function getType(
170
175
  if (
171
176
  (account?.keyType && account.keyType !== 'secp256k1') ||
172
177
  account?.source === 'accessKey' ||
178
+ account?.source === 'multisig' ||
173
179
  typeof transaction.calls !== 'undefined' ||
174
180
  typeof transaction.feePayer !== 'undefined' ||
181
+ typeof transaction.feePayerSignature !== 'undefined' ||
175
182
  typeof transaction.feeToken !== 'undefined' ||
176
183
  typeof transaction.keyAuthorization !== 'undefined' ||
184
+ typeof transaction.multisig !== 'undefined' ||
177
185
  typeof transaction.nonceKey !== 'undefined' ||
178
186
  typeof transaction.signature !== 'undefined' ||
187
+ typeof transaction.signatures !== 'undefined' ||
179
188
  typeof transaction.validBefore !== 'undefined' ||
180
189
  typeof transaction.validAfter !== 'undefined'
181
190
  )
@@ -337,6 +346,39 @@ async function serializeTempo(
337
346
  // the fee payer.
338
347
  if (shouldStripFeeTokenForSponsorship) delete transaction_ox.feeToken
339
348
 
349
+ // Native multisig (TIP-1061): combine the collected owner approvals into the
350
+ // multisig signature envelope and serialize the broadcast transaction. The
351
+ // approvals are recovered against the multisig owner digest and ordered into
352
+ // the strictly-ascending owner address order the node requires.
353
+ //
354
+ // Bootstrap (`init`) is auto-detected from the nonce: the protocol requires
355
+ // (and consumes) nonce `0` for the first transaction from a derived account,
356
+ // so `nonce == 0` uniquely identifies a bootstrap. This matches the serialized
357
+ // nonce above (a falsy `nonce` serializes as `0`). The owner approval digest
358
+ // doesn't commit to `init`, so attaching it here (rather than at owner-signing)
359
+ // is safe.
360
+ // NOTE: fee-payer + multisig is handled in a later phase.
361
+ if (transaction.multisig && transaction.signatures && !feePayer) {
362
+ const payload = TxTempo.getSignPayload(TxTempo.from(transaction_ox))
363
+ const signatures = transaction.signatures.map((approval) =>
364
+ SignatureEnvelope.from(approval),
365
+ )
366
+ const sorted = SignatureEnvelope.sortMultisigApprovals({
367
+ payload,
368
+ signatures,
369
+ genesisConfig: transaction.multisig,
370
+ })
371
+ const signature = SignatureEnvelope.from({
372
+ genesisConfig: transaction.multisig,
373
+ signatures: sorted,
374
+ ...(nonce ? {} : { init: true }),
375
+ })
376
+ return TxTempo.serialize(transaction_ox, {
377
+ feePayerSignature: undefined,
378
+ signature,
379
+ })
380
+ }
381
+
340
382
  if (signature && typeof transaction.feePayer === 'object') {
341
383
  const tx = TxTempo.from(transaction_ox, {
342
384
  signature,
@@ -1,5 +1,6 @@
1
+ import type { Address } from 'abitype'
1
2
  import * as Hex from 'ox/Hex'
2
- import { SignatureEnvelope, type TokenId } from 'ox/tempo'
3
+ import { MultisigConfig, SignatureEnvelope, type TokenId } from 'ox/tempo'
3
4
  import { getCode } from '../actions/public/getCode.js'
4
5
  import { verifyHash } from '../actions/public/verifyHash.js'
5
6
  import { maxUint256 } from '../constants/number.js'
@@ -12,7 +13,7 @@ import { defineTransactionRequest } from '../utils/formatters/transactionRequest
12
13
  import { getAction } from '../utils/getAction.js'
13
14
  import { keccak256 } from '../utils/hash/keccak256.js'
14
15
  import type { SerializeTransactionFn } from '../utils/transaction/serializeTransaction.js'
15
- import type { Account } from './Account.js'
16
+ import type { Account, MultisigAccount } from './Account.js'
16
17
  import { getMetadata } from './actions/accessKey.js'
17
18
  import * as Formatters from './Formatters.js'
18
19
  import type { Hardfork } from './Hardfork.js'
@@ -42,13 +43,16 @@ export const chainConfig = {
42
43
  prepareTransactionRequest: [
43
44
  async (r, { client, phase }) => {
44
45
  const request = r as Transaction.TransactionRequest & {
45
- account?: Account | undefined
46
+ account?: Account | MultisigAccount | undefined
46
47
  chainId?: number | undefined
47
48
  chain?:
48
49
  | (Chain & {
49
50
  feeToken?: TokenId.TokenIdOrAddress | undefined
50
51
  })
51
52
  | undefined
53
+ from?: Address | undefined
54
+ multisig?: MultisigConfig.Config | undefined
55
+ signatures?: readonly unknown[] | undefined
52
56
  }
53
57
 
54
58
  // FIXME: node estimates gas with secp256k1 dummy sig + null feePayerSignature.
@@ -60,9 +64,34 @@ export const chainConfig = {
60
64
  else if (request.account?.source === 'accessKey')
61
65
  request.gas = (request.gas ?? 0n) + 10_000n
62
66
  }
67
+
63
68
  return request as unknown as typeof r
64
69
  }
65
70
 
71
+ // Native multisig (TIP-1061). The transaction sender is the derived
72
+ // multisig account, not a signing account (owner accounts only contribute
73
+ // approvals later via `signTransaction`). Derive the sender from the
74
+ // config; core fills nonce/gas/fees for it via `request.from`, and the
75
+ // serializer auto-detects bootstrap (`init`) from `nonce == 0`.
76
+ //
77
+ // The config is taken from an explicit `multisig` field, or inferred from
78
+ // a multisig account (so callers can just pass `account` to
79
+ // `prepareTransactionRequest` without also passing `multisig`).
80
+ const multisig =
81
+ request.multisig ??
82
+ (request.account?.source === 'multisig'
83
+ ? (request.account as MultisigAccount).config
84
+ : undefined)
85
+ if (multisig) {
86
+ request.multisig = multisig
87
+ request.from = MultisigConfig.getAddress(multisig)
88
+ // A non-multisig `account` (e.g. the client's default) isn't the sender,
89
+ // so drop it: core then fills nonce/gas/fees for the multisig sender via
90
+ // `request.from`. A multisig account *is* the sender — keep it so the
91
+ // prepared request can be sent without re-passing `account`.
92
+ if (request.account?.source !== 'multisig') delete request.account
93
+ }
94
+
66
95
  if (
67
96
  !request.keyAuthorization &&
68
97
  request.account?.source === 'accessKey'
package/tempo/index.ts CHANGED
@@ -13,6 +13,7 @@ export type {
13
13
  } from 'ox/tempo'
14
14
  export {
15
15
  Channel,
16
+ MultisigConfig,
16
17
  Period,
17
18
  ReceivePolicyReceipt,
18
19
  TempoAddress,