@sovereign-sdk/web3 0.14.1 → 0.15.0

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.
package/dist/index.mjs CHANGED
@@ -124,10 +124,26 @@ var Rollup = class {
124
124
  * TODO: How to request a specific uniqueness strategy explicitly
125
125
  */
126
126
  async dedup(publicKey) {
127
- const credentialId = bytesToHex(publicKey);
127
+ return this.dedupByCredentialId(bytesToHex(publicKey));
128
+ }
129
+ /**
130
+ * Retrieve dedup information about the provided credential ID.
131
+ */
132
+ async dedupByCredentialId(credentialId) {
128
133
  const response = await this.rollup.addresses.dedup(credentialId);
129
134
  return response;
130
135
  }
136
+ /**
137
+ * Builds an unsigned transaction for the provided runtime call.
138
+ */
139
+ async buildUnsignedTransaction(runtimeCall, params) {
140
+ const context = {
141
+ runtimeCall,
142
+ rollup: this,
143
+ overrides: params?.overrides ?? {}
144
+ };
145
+ return this._typeBuilder.unsignedTransaction(context);
146
+ }
131
147
  /**
132
148
  * Submits a transaction to the rollup.
133
149
  *
@@ -185,12 +201,9 @@ var Rollup = class {
185
201
  * @param {SovereignClient.RequestOptions} options - The options for the request.
186
202
  */
187
203
  async call(runtimeCall, { signer, overrides }, options) {
188
- const context = {
189
- runtimeCall,
190
- rollup: this,
191
- overrides: overrides ?? {}
192
- };
193
- const unsignedTx = await this._typeBuilder.unsignedTransaction(context);
204
+ const unsignedTx = await this.buildUnsignedTransaction(runtimeCall, {
205
+ overrides
206
+ });
194
207
  return this.signAndSubmitTransaction(
195
208
  unsignedTx,
196
209
  {
@@ -211,18 +224,15 @@ var Rollup = class {
211
224
  * @returns A promise that resolves to the signed transaction.
212
225
  */
213
226
  async prepareCall(runtimeCall, { signer, overrides }) {
214
- const context = {
215
- runtimeCall,
216
- rollup: this,
217
- overrides: overrides ?? {}
218
- };
219
- const unsignedTx = await this._typeBuilder.unsignedTransaction(context);
227
+ const unsignedTx = await this.buildUnsignedTransaction(runtimeCall, {
228
+ overrides
229
+ });
220
230
  return this.signTransaction(unsignedTx, signer);
221
231
  }
222
232
  /**
223
233
  * Signs an unsigned transaction using the provided signer.
224
- * Creates a signature by combining the serialized unsigned transaction with the chain hash,
225
- * then constructs a fully signed transaction.
234
+ * Creates a signature over the serialized transaction signing payload, then constructs a fully
235
+ * signed transaction.
226
236
  *
227
237
  * @param unsignedTx - The unsigned transaction to sign.
228
238
  * @param signer - The signer to use for signing the transaction.
@@ -230,11 +240,15 @@ var Rollup = class {
230
240
  */
231
241
  async signTransaction(unsignedTx, signer) {
232
242
  const serializer = await this.serializer();
233
- const serializedUnsignedTx = serializer.serializeUnsignedTx(unsignedTx);
234
- const chainHash = await this.chainHash();
235
- const signature = await signer.sign(
236
- new Uint8Array([...serializedUnsignedTx, ...chainHash])
243
+ const transactionSigningPayload = await this._typeBuilder.transactionSigningPayload({
244
+ unsignedTx,
245
+ chainHash: await this.chainHash(),
246
+ rollup: this
247
+ });
248
+ const signingBytes = serializer.serializeSigningPayload(
249
+ transactionSigningPayload
237
250
  );
251
+ const signature = await signer.sign(signingBytes);
238
252
  const publicKey = await signer.publicKey();
239
253
  const context = {
240
254
  unsignedTx,
@@ -322,10 +336,12 @@ var Rollup = class {
322
336
  }
323
337
  };
324
338
  function isVersionMismatchError(e) {
325
- if (
326
- // biome-ignore lint/suspicious/noExplicitAny: yolo
327
- e.error?.details?.error?.includes("Signature verification failed")
328
- ) {
339
+ const details = e.error?.details;
340
+ if (details?.code === "invalid_chain_hash_fragment") {
341
+ return true;
342
+ }
343
+ const error = details?.error;
344
+ if (typeof error === "string" && error.includes("Signature verification failed")) {
329
345
  return true;
330
346
  }
331
347
  return false;
@@ -335,6 +351,36 @@ function isVersionMismatchError(e) {
335
351
  import SovereignClient2 from "@sovereign-sdk/client";
336
352
  import { JsSerializer } from "@sovereign-sdk/serializers";
337
353
  import { bytesToHex as bytesToHex2 } from "@sovereign-sdk/utils";
354
+
355
+ // src/rollup/chain-hash.ts
356
+ function chainHashFragment(chainHash) {
357
+ if (chainHash.length < 8) {
358
+ throw new Error("chain hash must contain at least 8 bytes");
359
+ }
360
+ return new DataView(
361
+ chainHash.buffer,
362
+ chainHash.byteOffset,
363
+ chainHash.byteLength
364
+ ).getBigUint64(0, true).toString();
365
+ }
366
+ function refreshChainHashFragment(unsignedTx, chainHash) {
367
+ unsignedTx.details = {
368
+ ...unsignedTx.details,
369
+ chain_hash_fragment: chainHashFragment(chainHash)
370
+ };
371
+ return unsignedTx;
372
+ }
373
+ function assertChainHashFragment(unsignedTx, chainHash) {
374
+ const expectedFragment = chainHashFragment(chainHash);
375
+ const actualFragment = unsignedTx.details.chain_hash_fragment;
376
+ if (actualFragment !== expectedFragment) {
377
+ throw new Error(
378
+ `Cannot sign transaction: chain_hash_fragment ${actualFragment} does not match the current chain hash fragment ${expectedFragment}`
379
+ );
380
+ }
381
+ }
382
+
383
+ // src/rollup/standard-rollup.ts
338
384
  var useOrFetchUniqueness = async ({
339
385
  overrides
340
386
  }) => {
@@ -347,7 +393,7 @@ function standardTypeBuilder() {
347
393
  return {
348
394
  async unsignedTransaction(context) {
349
395
  const { rollup, runtimeCall } = context;
350
- const { uniqueness: _, ...overrides } = context.overrides;
396
+ const overrides = context.overrides;
351
397
  const uniqueness = await useOrFetchUniqueness(context);
352
398
  const details = {
353
399
  ...rollup.context.defaultTxDetails,
@@ -356,7 +402,8 @@ function standardTypeBuilder() {
356
402
  return {
357
403
  runtime_call: runtimeCall,
358
404
  uniqueness,
359
- details
405
+ details,
406
+ address_override: overrides.address_override ?? null
360
407
  };
361
408
  },
362
409
  async transaction({
@@ -371,10 +418,67 @@ function standardTypeBuilder() {
371
418
  ...unsignedTx
372
419
  }
373
420
  };
421
+ },
422
+ async transactionSigningPayload({
423
+ unsignedTx,
424
+ chainHash
425
+ }) {
426
+ const normalizedUnsignedTx = refreshChainHashFragment(
427
+ unsignedTx,
428
+ chainHash
429
+ );
430
+ return {
431
+ V0: {
432
+ ...normalizedUnsignedTx,
433
+ chain_hash: Array.from(chainHash)
434
+ }
435
+ };
374
436
  }
375
437
  };
376
438
  }
377
439
  var StandardRollup = class extends Rollup {
440
+ updateDefaultChainHashFragment(chainHash) {
441
+ this.context.defaultTxDetails.chain_hash_fragment = chainHashFragment(chainHash);
442
+ }
443
+ async credentialAddressFromId(credentialId) {
444
+ if (this.context.credentialIdToAddress) {
445
+ const serializer = await this.serializer();
446
+ return this.context.credentialIdToAddress(
447
+ credentialId,
448
+ serializer.schema
449
+ );
450
+ }
451
+ return addressFromPublicKey(credentialId, "sov");
452
+ }
453
+ async submitTransaction(transaction, options) {
454
+ try {
455
+ return await super.submitTransaction(transaction, options);
456
+ } catch (error) {
457
+ if (error instanceof VersionMismatchError) {
458
+ this.updateDefaultChainHashFragment(await super.chainHash());
459
+ }
460
+ throw error;
461
+ }
462
+ }
463
+ async hydrate() {
464
+ await super.hydrate();
465
+ this.updateDefaultChainHashFragment(await super.chainHash());
466
+ }
467
+ async multisigSigningBytes(unsignedTx, multisig) {
468
+ const serializer = await this.serializer();
469
+ const chainHash = await this.chainHash();
470
+ assertChainHashFragment(unsignedTx, chainHash);
471
+ const signingPayload = {
472
+ V1: {
473
+ ...unsignedTx,
474
+ chain_hash: Array.from(chainHash),
475
+ credential_address: await this.credentialAddressFromId(
476
+ multisig.getMultisigAddress()
477
+ )
478
+ }
479
+ };
480
+ return serializer.serializeSigningPayload(signingPayload);
481
+ }
378
482
  /**
379
483
  * Simulates a runtime call transaction.
380
484
  *
@@ -382,11 +486,15 @@ var StandardRollup = class extends Rollup {
382
486
  *
383
487
  * @param runtimeMessage - The runtime message to call.
384
488
  */
385
- async simulate(runtimeMessage, { signer }) {
489
+ async simulate(runtimeMessage, { signer, ...params }) {
386
490
  const publicKey = await signer.publicKey();
387
491
  const sender = bytesToHex2(publicKey);
388
492
  const call = runtimeMessage;
389
- return this.rollup.simulate({ sender, call });
493
+ return this.rollup.simulate({
494
+ ...params,
495
+ sender,
496
+ call
497
+ });
390
498
  }
391
499
  };
392
500
  var DEFAULT_TX_DETAILS = {
@@ -394,26 +502,24 @@ var DEFAULT_TX_DETAILS = {
394
502
  max_fee: "100000000",
395
503
  gas_limit: null
396
504
  };
397
- async function buildContext(client, context) {
505
+ function buildContext(context, credentialIdToAddress) {
398
506
  const defaultTxDetails = {
399
507
  ...DEFAULT_TX_DETAILS,
400
508
  ...context?.defaultTxDetails
401
509
  };
402
- if (!defaultTxDetails.chain_id) {
403
- const { chain_id } = await client.rollup.constants();
404
- defaultTxDetails.chain_id = chain_id;
405
- }
406
510
  return {
407
- defaultTxDetails
511
+ ...context,
512
+ defaultTxDetails,
513
+ credentialIdToAddress: credentialIdToAddress ?? context?.credentialIdToAddress
408
514
  };
409
515
  }
410
516
  async function createStandardRollup(rollupConfig, typeBuilderOverrides) {
411
517
  const config = rollupConfig ?? {};
412
518
  const client = config.client ?? new SovereignClient2({ baseURL: config.url });
413
519
  const getSerializer = config.getSerializer ?? ((schema) => new JsSerializer(schema));
414
- const context = await buildContext(client, config.context);
520
+ const context = buildContext(config.context, config.credentialIdToAddress);
415
521
  const txSubmissionEndpoint = config.txSubmissionEndpoint ?? "/sequencer/txs";
416
- return new StandardRollup(
522
+ const rollup = new StandardRollup(
417
523
  {
418
524
  ...config,
419
525
  client,
@@ -426,11 +532,20 @@ async function createStandardRollup(rollupConfig, typeBuilderOverrides) {
426
532
  ...typeBuilderOverrides
427
533
  }
428
534
  );
535
+ if (!context.defaultTxDetails.chain_hash_fragment) {
536
+ await rollup.hydrate();
537
+ }
538
+ return rollup;
429
539
  }
430
540
 
431
541
  // src/rollup/solana-signable-rollup.ts
542
+ import { MAX_SIGNERS, Multisig } from "@sovereign-sdk/multisig";
432
543
  import { isLedgerSolanaSigner } from "@sovereign-sdk/signers";
433
- import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes2 } from "@sovereign-sdk/utils";
544
+ import {
545
+ bytesToHex as bytesToHex3,
546
+ hexToBytes as hexToBytes2,
547
+ normalizeHexString
548
+ } from "@sovereign-sdk/utils";
434
549
  import bs58 from "bs58";
435
550
  import { Base64 as Base642 } from "js-base64";
436
551
  var MULTISIG_SIMPLE_DISCRIMINATOR = 128;
@@ -439,7 +554,6 @@ var CHAIN_HASH_SIZE = 32;
439
554
  var PUBKEY_SIZE = 32;
440
555
  var SIGNATURE_SIZE = 64;
441
556
  var MULTISIG_PREAMBLE_FIXED_LENGTH = 53;
442
- var MAX_MULTISIG_SIGNERS = 21;
443
557
  var SIGNING_DOMAIN = new Uint8Array([
444
558
  255,
445
559
  ...new TextEncoder().encode("solana offchain")
@@ -456,9 +570,9 @@ function compareByteArrays(left, right) {
456
570
  return left.length - right.length;
457
571
  }
458
572
  function createSolanaPreamble(pubkeys, chainHash, messageLength) {
459
- if (pubkeys.length < 1 || pubkeys.length > MAX_MULTISIG_SIGNERS) {
573
+ if (pubkeys.length < 1 || pubkeys.length > MAX_SIGNERS) {
460
574
  throw new Error(
461
- `Invalid signer count: expected 1-${MAX_MULTISIG_SIGNERS} signers, got ${pubkeys.length}`
575
+ `Invalid signer count: expected 1-${MAX_SIGNERS} signers, got ${pubkeys.length}`
462
576
  );
463
577
  }
464
578
  const preamble = new Uint8Array(
@@ -504,45 +618,37 @@ var SolanaSignableRollup = class {
504
618
  /**
505
619
  * Submits serialized data to the Solana endpoint.
506
620
  */
507
- async submitSerializedMessage(serializedMessage) {
621
+ async submitSerializedMessage(serializedMessage, options) {
622
+ const { body: _, query: __, path, ...requestOptions } = options || {};
508
623
  return await this.inner.http.post(
509
- this.solanaEndpoint,
624
+ path || this.solanaEndpoint,
510
625
  {
511
626
  // Match AcceptTx shape used by standard sequencer endpoints.
512
- body: { body: Base642.fromUint8Array(serializedMessage) }
627
+ body: { body: Base642.fromUint8Array(serializedMessage) },
628
+ ...requestOptions
513
629
  }
514
630
  );
515
631
  }
516
632
  /**
517
633
  * Submits a Solana offchain message to the rollup.
518
634
  */
519
- async submitSolanaMessage(solanaMessage) {
635
+ async submitSolanaMessage(solanaMessage, options) {
520
636
  const serializedMessage = this.serializeSolanaMessage(solanaMessage);
521
- return this.submitSerializedMessage(serializedMessage);
637
+ return this.submitSerializedMessage(serializedMessage, options);
522
638
  }
523
639
  /**
524
640
  * Submits a Solana spec-compliant message to the rollup.
525
641
  */
526
- async submitSolanaSpecMessage(solanaMessage) {
642
+ async submitSolanaSpecMessage(solanaMessage, options) {
527
643
  const serializedMessage = this.serializeSolanaSpecMessage(solanaMessage);
528
- return this.submitSerializedMessage(serializedMessage);
644
+ return this.submitSerializedMessage(serializedMessage, options);
529
645
  }
530
646
  /**
531
647
  * Submits a Solana spec-compliant multisig message to the rollup.
532
648
  */
533
- async submitSolanaSpecMultisigMessage(solanaMessage) {
649
+ async submitSolanaSpecMultisigMessage(solanaMessage, options) {
534
650
  const serializedMessage = this.serializeSolanaSpecMultisigMessage(solanaMessage);
535
- return this.submitSerializedMessage(serializedMessage);
536
- }
537
- /**
538
- * Helper to build an unsigned transaction using the standard type builder.
539
- */
540
- async buildUnsignedTransaction(runtimeCall, overrides) {
541
- return this.typeBuilder.unsignedTransaction({
542
- runtimeCall,
543
- overrides: overrides ?? {},
544
- rollup: this.inner
545
- });
651
+ return this.submitSerializedMessage(serializedMessage, options);
546
652
  }
547
653
  /**
548
654
  * Helper to build a transaction result object.
@@ -570,18 +676,15 @@ var SolanaSignableRollup = class {
570
676
  * The canonical order is lexicographic by raw pubkey bytes.
571
677
  */
572
678
  canonicalizeMultisigPubkeys(multisigPubkeys) {
573
- if (!multisigPubkeys) {
679
+ if (multisigPubkeys.length < 1 || multisigPubkeys.length > MAX_SIGNERS) {
574
680
  throw new Error(
575
- "multisigPubkeys is required for Solana multisig transactions"
576
- );
577
- }
578
- if (multisigPubkeys.length < 2 || multisigPubkeys.length > MAX_MULTISIG_SIGNERS) {
579
- throw new Error(
580
- `Invalid multisig signer count: expected 2-${MAX_MULTISIG_SIGNERS} signers, got ${multisigPubkeys.length}`
681
+ `Invalid multisig signer count: expected 1-${MAX_SIGNERS} signers, got ${multisigPubkeys.length}`
581
682
  );
582
683
  }
583
684
  const seenPubkeys = /* @__PURE__ */ new Set();
584
- for (const pubkey of multisigPubkeys) {
685
+ const normalizedPubkeys = multisigPubkeys.map(normalizeHexString);
686
+ const pubkeyBytes = normalizedPubkeys.map((pubkey) => hexToBytes2(pubkey));
687
+ for (const pubkey of pubkeyBytes) {
585
688
  if (pubkey.length !== PUBKEY_SIZE) {
586
689
  throw new Error(
587
690
  `Invalid public key length: expected ${PUBKEY_SIZE} bytes, got ${pubkey.length}`
@@ -593,31 +696,36 @@ var SolanaSignableRollup = class {
593
696
  }
594
697
  seenPubkeys.add(pubkeyHex);
595
698
  }
596
- return [...multisigPubkeys].sort(compareByteArrays);
699
+ return pubkeyBytes.sort(compareByteArrays);
597
700
  }
598
701
  /**
599
- * Helper to create and serialize a SolanaOffchainUnsignedTransaction to JSON bytes.
702
+ * Helper to create and serialize a SolanaOffchainSigningPayloadV0 to JSON bytes.
600
703
  */
601
704
  async createSolanaJsonBytes(unsignedTx) {
602
705
  const serializer = await this.inner.serializer();
603
706
  const schema = serializer.schema;
604
707
  const chainName = schema.chain_data.chain_name || "";
605
- const solanaUnsignedTx = {
708
+ const solanaSigningPayload = {
606
709
  runtime_call: unsignedTx.runtime_call,
607
710
  uniqueness: unsignedTx.uniqueness,
608
711
  details: unsignedTx.details,
609
- chain_name: chainName
712
+ chain_name: chainName,
713
+ ...unsignedTx.address_override !== null && unsignedTx.address_override !== void 0 && {
714
+ address_override: unsignedTx.address_override
715
+ },
716
+ version: 0
610
717
  };
611
- return new TextEncoder().encode(JSON.stringify(solanaUnsignedTx));
718
+ return new TextEncoder().encode(JSON.stringify(solanaSigningPayload));
612
719
  }
613
720
  /**
614
721
  * Signs an unsigned transaction using Solana offchain simple signing and submits it.
615
722
  * Returns the transaction result in the same format as standard rollup.
616
723
  */
617
- async signWithSolanaSimpleAndSubmit(unsignedTx, signer) {
724
+ async signWithSolanaSimpleAndSubmit(unsignedTx, signer, options) {
725
+ const chainHash = await this.inner.chainHash();
726
+ refreshChainHashFragment(unsignedTx, chainHash);
618
727
  const jsonBytes = await this.createSolanaJsonBytes(unsignedTx);
619
728
  const pubkey = await signer.publicKey();
620
- const chainHash = await this.inner.chainHash();
621
729
  const signature = await signer.sign(jsonBytes);
622
730
  const solanaMessage = {
623
731
  signed_message: jsonBytes,
@@ -625,17 +733,18 @@ var SolanaSignableRollup = class {
625
733
  pubkey,
626
734
  signature
627
735
  };
628
- const response = await this.submitSolanaMessage(solanaMessage);
736
+ const response = await this.submitSolanaMessage(solanaMessage, options);
629
737
  return this.buildTransactionResult(response, unsignedTx, pubkey, signature);
630
738
  }
631
739
  /**
632
740
  * Signs an unsigned transaction using Solana spec-compliant signing and submits it.
633
741
  * Returns the transaction result in the same format as standard rollup.
634
742
  */
635
- async signWithSolanaSpecAndSubmit(unsignedTx, signer) {
743
+ async signWithSolanaSpecAndSubmit(unsignedTx, signer, options) {
744
+ const chainHash = await this.inner.chainHash();
745
+ refreshChainHashFragment(unsignedTx, chainHash);
636
746
  const jsonBytes = await this.createSolanaJsonBytes(unsignedTx);
637
747
  const pubkey = await signer.publicKey();
638
- const chainHash = await this.inner.chainHash();
639
748
  const preamble = createSolanaPreamble(
640
749
  [pubkey],
641
750
  chainHash,
@@ -650,7 +759,7 @@ var SolanaSignableRollup = class {
650
759
  signed_message_with_preamble: signedMessageWithPreamble,
651
760
  signature
652
761
  };
653
- const response = await this.submitSolanaSpecMessage(solanaMessage);
762
+ const response = await this.submitSolanaSpecMessage(solanaMessage, options);
654
763
  return this.buildTransactionResult(response, unsignedTx, pubkey, signature);
655
764
  }
656
765
  /**
@@ -674,18 +783,30 @@ var SolanaSignableRollup = class {
674
783
  options
675
784
  );
676
785
  case "solanaSimple": {
677
- const unsignedTx = await this.buildUnsignedTransaction(
786
+ const unsignedTx = await this.inner.buildUnsignedTransaction(
678
787
  runtimeCall,
679
- params.overrides
788
+ {
789
+ overrides: params.overrides
790
+ }
791
+ );
792
+ return this.signWithSolanaSimpleAndSubmit(
793
+ unsignedTx,
794
+ params.signer,
795
+ options
680
796
  );
681
- return this.signWithSolanaSimpleAndSubmit(unsignedTx, params.signer);
682
797
  }
683
798
  case "solana": {
684
- const unsignedTx = await this.buildUnsignedTransaction(
799
+ const unsignedTx = await this.inner.buildUnsignedTransaction(
685
800
  runtimeCall,
686
- params.overrides
801
+ {
802
+ overrides: params.overrides
803
+ }
804
+ );
805
+ return this.signWithSolanaSpecAndSubmit(
806
+ unsignedTx,
807
+ params.signer,
808
+ options
687
809
  );
688
- return this.signWithSolanaSpecAndSubmit(unsignedTx, params.signer);
689
810
  }
690
811
  default:
691
812
  throw new Error(`Unsupported authenticator: ${authenticator}`);
@@ -711,132 +832,134 @@ var SolanaSignableRollup = class {
711
832
  options
712
833
  );
713
834
  case "solanaSimple":
714
- return this.signWithSolanaSimpleAndSubmit(unsignedTx, params.signer);
835
+ return this.signWithSolanaSimpleAndSubmit(
836
+ unsignedTx,
837
+ params.signer,
838
+ options
839
+ );
715
840
  case "solana":
716
- return this.signWithSolanaSpecAndSubmit(unsignedTx, params.signer);
841
+ return this.signWithSolanaSpecAndSubmit(
842
+ unsignedTx,
843
+ params.signer,
844
+ options
845
+ );
717
846
  default:
718
847
  throw new Error(`Unsupported authenticator: ${authenticator}`);
719
848
  }
720
849
  }
850
+ async buildUnsignedTransaction(runtimeCall, params) {
851
+ return this.inner.buildUnsignedTransaction(runtimeCall, params);
852
+ }
721
853
  /**
722
854
  * Creates V1 JSON bytes for a multisig transaction, including multisig_id and version.
723
855
  * The resulting JSON is what each signer signs directly (no discriminator prefix).
724
856
  */
725
- async createMultisigJsonBytes(unsignedTx, multisigAddress) {
857
+ async createMultisigJsonBytes(unsignedTx, multisigId) {
726
858
  const serializer = await this.inner.serializer();
727
859
  const schema = serializer.schema;
728
860
  const chainName = schema.chain_data.chain_name || "";
729
- const solanaUnsignedTx = {
861
+ const solanaSigningPayload = {
730
862
  runtime_call: unsignedTx.runtime_call,
731
863
  uniqueness: unsignedTx.uniqueness,
732
864
  details: unsignedTx.details,
733
865
  chain_name: chainName,
734
- // Hardcoded to base58 encoding, only correct for rollups using Base58Address as their
735
- // primary address type. Will be replaced with rollup-aware address formatting once the
736
- // SDK supports flexible address encoding (see #2673).
737
- multisig_id: bs58.encode(multisigAddress),
866
+ multisig_id: multisigId,
867
+ ...unsignedTx.address_override !== null && unsignedTx.address_override !== void 0 && {
868
+ address_override: unsignedTx.address_override
869
+ },
738
870
  version: 1
739
871
  };
740
- return new TextEncoder().encode(JSON.stringify(solanaUnsignedTx));
872
+ return new TextEncoder().encode(JSON.stringify(solanaSigningPayload));
741
873
  }
742
874
  /**
743
- * Creates the preamble+JSON bytes signed by every signer in a spec-compliant multisig flow.
875
+ * Formats the multisig credential in the rollup's configured address format.
744
876
  */
745
- async createSpecCompliantMultisigSignedMessage(unsignedTx, multisigAddress, multisigPubkeys) {
746
- const jsonBytes = await this.createMultisigJsonBytes(
747
- unsignedTx,
748
- multisigAddress
749
- );
750
- const chainHash = await this.inner.chainHash();
751
- const preamble = createSolanaPreamble(
752
- multisigPubkeys,
753
- chainHash,
754
- jsonBytes.length
755
- );
756
- return this.combinePreambleAndMessage(preamble, jsonBytes);
877
+ async multisigIdFromMultisig(multisig) {
878
+ const credentialId = multisig.getMultisigAddress();
879
+ if (this.inner.context.credentialIdToAddress) {
880
+ const serializer = await this.inner.serializer();
881
+ return this.inner.context.credentialIdToAddress(
882
+ credentialId,
883
+ serializer.schema
884
+ );
885
+ }
886
+ return bs58.encode(credentialId);
757
887
  }
758
888
  /**
759
- * Signs an unsigned transaction using Solana offchain simple multisig signing.
889
+ * Creates multisig state from a finalized transaction envelope.
760
890
  */
761
- async signForSolanaSimpleMultisig(unsignedTx, signer, multisigAddress, multisigPubkeys) {
762
- const pubkey = await signer.publicKey();
763
- const signerPubkeyHex = bytesToHex3(pubkey);
764
- const multisigPubkeyHexes = multisigPubkeys.map(bytesToHex3);
765
- if (!multisigPubkeyHexes.includes(signerPubkeyHex)) {
766
- throw new Error(
767
- `Signer public key ${signerPubkeyHex} is not present in multisigPubkeys`
768
- );
769
- }
770
- const jsonBytes = await this.createMultisigJsonBytes(
771
- unsignedTx,
772
- multisigAddress
773
- );
774
- const signature = await signer.sign(jsonBytes);
775
- return this.typeBuilder.transaction({
776
- unsignedTx,
777
- sender: pubkey,
778
- signature,
779
- rollup: this.inner
891
+ multisigFromTransaction(tx) {
892
+ return new Multisig({
893
+ signatures: tx.signatures,
894
+ unusedPubKeys: tx.unused_pub_keys,
895
+ minSigners: tx.min_signers
780
896
  });
781
897
  }
898
+ normalizeMultisigTransaction(tx) {
899
+ return {
900
+ ...tx,
901
+ signatures: tx.signatures.map((signature) => ({
902
+ signature: normalizeHexString(signature.signature),
903
+ pub_key: normalizeHexString(signature.pub_key)
904
+ })),
905
+ unused_pub_keys: tx.unused_pub_keys.map(normalizeHexString)
906
+ };
907
+ }
782
908
  /**
783
- * Signs an unsigned transaction using the spec-compliant multisig preamble.
909
+ * Extracts the V0-shaped unsigned fields from a finalized multisig transaction.
784
910
  */
785
- async signForSolanaSpecMultisig(unsignedTx, signer, multisigAddress, multisigPubkeys) {
786
- const pubkey = await signer.publicKey();
787
- const signerPubkeyHex = bytesToHex3(pubkey);
788
- const multisigPubkeyHexes = multisigPubkeys.map(bytesToHex3);
789
- if (!multisigPubkeyHexes.includes(signerPubkeyHex)) {
790
- throw new Error(
791
- `Signer public key ${signerPubkeyHex} is not present in multisigPubkeys`
792
- );
793
- }
794
- const signedMessageWithPreamble = await this.createSpecCompliantMultisigSignedMessage(
795
- unsignedTx,
796
- multisigAddress,
797
- multisigPubkeys
798
- );
799
- const signature = await signer.sign(signedMessageWithPreamble);
800
- return this.typeBuilder.transaction({
801
- unsignedTx,
802
- sender: pubkey,
803
- signature,
804
- rollup: this.inner
805
- });
911
+ unsignedTxFromTransaction(tx) {
912
+ return {
913
+ runtime_call: tx.runtime_call,
914
+ uniqueness: tx.uniqueness,
915
+ details: tx.details,
916
+ address_override: tx.address_override
917
+ };
806
918
  }
807
919
  /**
808
- * Signs an unsigned transaction for use in a Solana offchain multisig.
809
- *
810
- * `authenticator: "standard"` delegates directly to the wrapped standard rollup.
811
- * `authenticator: "solanaSimple"` signs the V1 JSON payload directly.
812
- * `authenticator: "solana"` signs the spec-compliant preamble+JSON bytes.
813
- * Solana multisig authenticators treat `multisigPubkeys` as an unordered set and
814
- * canonicalize it before signing.
920
+ * Creates the preamble+JSON bytes signed by every signer in a spec-compliant multisig flow.
815
921
  */
816
- async signTransactionForMultisig(unsignedTx, params) {
817
- switch (params.authenticator) {
922
+ async createSpecCompliantMultisigSignedMessage(unsignedTx, multisig) {
923
+ const chainHash = await this.inner.chainHash();
924
+ assertChainHashFragment(unsignedTx, chainHash);
925
+ const jsonBytes = await this.createMultisigJsonBytes(
926
+ unsignedTx,
927
+ await this.multisigIdFromMultisig(multisig)
928
+ );
929
+ const preamble = createSolanaPreamble(
930
+ this.canonicalizeMultisigPubkeys([...multisig.allPubKeys]),
931
+ chainHash,
932
+ jsonBytes.length
933
+ );
934
+ return this.combinePreambleAndMessage(preamble, jsonBytes);
935
+ }
936
+ async multisigSigningBytes(unsignedTx, multisig, authenticator) {
937
+ switch (authenticator) {
818
938
  case "standard":
819
- return this.inner.signTransaction(unsignedTx, params.signer);
820
- case "solanaSimple":
821
- return this.signForSolanaSimpleMultisig(
939
+ return this.inner.multisigSigningBytes(unsignedTx, multisig);
940
+ case "solanaSimple": {
941
+ assertChainHashFragment(unsignedTx, await this.inner.chainHash());
942
+ return this.createMultisigJsonBytes(
822
943
  unsignedTx,
823
- params.signer,
824
- params.multisigAddress,
825
- this.canonicalizeMultisigPubkeys(params.multisigPubkeys)
944
+ await this.multisigIdFromMultisig(multisig)
826
945
  );
946
+ }
827
947
  case "solana":
828
- return this.signForSolanaSpecMultisig(
948
+ return this.createSpecCompliantMultisigSignedMessage(
829
949
  unsignedTx,
830
- params.signer,
831
- params.multisigAddress,
832
- this.canonicalizeMultisigPubkeys(params.multisigPubkeys)
950
+ multisig
833
951
  );
834
952
  }
953
+ throw new Error(`Unsupported authenticator: ${authenticator}`);
835
954
  }
836
955
  /**
837
- * Builds a spec-compliant multisig envelope from a V1 transaction and ordered multisig pubkeys.
956
+ * Builds a spec-compliant multisig envelope from a V1 transaction.
838
957
  */
839
- buildSpecCompliantMultisigEnvelope(tx, signedMessageWithPreamble, multisigPubkeys) {
958
+ buildSpecCompliantMultisigEnvelope(tx, signedMessageWithPreamble) {
959
+ const multisigPubkeys = this.canonicalizeMultisigPubkeys([
960
+ ...tx.signatures.map((signer) => signer.pub_key),
961
+ ...tx.unused_pub_keys
962
+ ]);
840
963
  const multisigPubkeyHexes = multisigPubkeys.map(bytesToHex3);
841
964
  const indexByPubkey = /* @__PURE__ */ new Map();
842
965
  multisigPubkeyHexes.forEach((pubkeyHex, index) => {
@@ -846,37 +969,42 @@ var SolanaSignableRollup = class {
846
969
  const signaturesByIndex = /* @__PURE__ */ new Map();
847
970
  let signerBitfield = 0;
848
971
  for (const signer of tx.signatures) {
849
- const signerIndex = indexByPubkey.get(signer.pub_key);
972
+ const normalizedSignerPubkey = normalizeHexString(signer.pub_key);
973
+ const signerIndex = indexByPubkey.get(normalizedSignerPubkey);
850
974
  if (signerIndex === void 0) {
851
975
  throw new Error(
852
- `Signed pubkey ${signer.pub_key} is not present in multisigPubkeys`
976
+ `Signed pubkey ${normalizedSignerPubkey} is not present in multisigPubkeys`
853
977
  );
854
978
  }
855
- if (accountedPubkeys.has(signer.pub_key)) {
979
+ if (accountedPubkeys.has(normalizedSignerPubkey)) {
856
980
  throw new Error(
857
- `Duplicate signed pubkey in multisig transaction: ${signer.pub_key}`
981
+ `Duplicate signed pubkey in multisig transaction: ${normalizedSignerPubkey}`
858
982
  );
859
983
  }
860
- accountedPubkeys.add(signer.pub_key);
861
- signaturesByIndex.set(signerIndex, hexToBytes2(signer.signature));
984
+ accountedPubkeys.add(normalizedSignerPubkey);
985
+ signaturesByIndex.set(
986
+ signerIndex,
987
+ hexToBytes2(normalizeHexString(signer.signature))
988
+ );
862
989
  signerBitfield = (signerBitfield | 1 << signerIndex) >>> 0;
863
990
  }
864
991
  for (const unusedPubkey of tx.unused_pub_keys) {
865
- if (!indexByPubkey.has(unusedPubkey)) {
992
+ const normalizedUnusedPubkey = normalizeHexString(unusedPubkey);
993
+ if (!indexByPubkey.has(normalizedUnusedPubkey)) {
866
994
  throw new Error(
867
- `Unused pubkey ${unusedPubkey} is not present in multisigPubkeys`
995
+ `Unused pubkey ${normalizedUnusedPubkey} is not present in multisigPubkeys`
868
996
  );
869
997
  }
870
- if (accountedPubkeys.has(unusedPubkey)) {
998
+ if (accountedPubkeys.has(normalizedUnusedPubkey)) {
871
999
  throw new Error(
872
- `Duplicate pubkey in multisig transaction payload: ${unusedPubkey}`
1000
+ `Duplicate pubkey in multisig transaction payload: ${normalizedUnusedPubkey}`
873
1001
  );
874
1002
  }
875
- accountedPubkeys.add(unusedPubkey);
1003
+ accountedPubkeys.add(normalizedUnusedPubkey);
876
1004
  }
877
1005
  if (accountedPubkeys.size !== multisigPubkeys.length) {
878
1006
  throw new Error(
879
- "multisigPubkeys must contain every signer and unused pubkey exactly once"
1007
+ "Multisig transaction payload must contain every signer exactly once"
880
1008
  );
881
1009
  }
882
1010
  const orderedSignatures = Array.from(signaturesByIndex.entries()).sort(([leftIndex], [rightIndex]) => leftIndex - rightIndex).map(([, signature]) => signature);
@@ -888,72 +1016,100 @@ var SolanaSignableRollup = class {
888
1016
  };
889
1017
  }
890
1018
  /**
891
- * Submits a multisig transaction using the Solana offchain simple multisig format.
892
- *
893
- * Accepts the V1 transaction produced by `MultisigTransaction.asTransaction()`.
894
- * `authenticator: "standard"` delegates directly to the wrapped standard rollup.
895
- * Solana authenticators rebuild the appropriate Solana multisig envelope and
896
- * submit it to the Solana offchain endpoint.
1019
+ * Submits a finalized V1 multisig transaction using a Solana authenticator.
897
1020
  */
898
- async submitMultisigTransaction(multisigTx, params) {
899
- const { V1: tx } = multisigTx;
900
- const unsignedTx = {
901
- runtime_call: tx.runtime_call,
902
- uniqueness: tx.uniqueness,
903
- details: tx.details
1021
+ async submitMultisigTransactionWithAuthenticator(transaction, authenticator, options) {
1022
+ const finalizedTx = {
1023
+ V1: this.normalizeMultisigTransaction(transaction.V1)
904
1024
  };
905
- switch (params.authenticator) {
906
- case "standard":
907
- return this.inner.submitTransaction(
908
- multisigTx
909
- );
1025
+ const multisig = this.multisigFromTransaction(finalizedTx.V1);
1026
+ if (!multisig.isComplete) {
1027
+ throw new Error("Multisig transaction is incomplete");
1028
+ }
1029
+ const unsignedTx = this.unsignedTxFromTransaction(finalizedTx.V1);
1030
+ switch (authenticator) {
910
1031
  case "solanaSimple": {
911
- this.canonicalizeMultisigPubkeys(params.multisigPubkeys);
1032
+ const chainHash = await this.inner.chainHash();
1033
+ assertChainHashFragment(unsignedTx, chainHash);
912
1034
  const jsonBytes = await this.createMultisigJsonBytes(
913
1035
  unsignedTx,
914
- params.multisigAddress
1036
+ await this.multisigIdFromMultisig(multisig)
915
1037
  );
916
1038
  const wireBytes = new Uint8Array(1 + jsonBytes.length);
917
1039
  wireBytes[0] = MULTISIG_SIMPLE_DISCRIMINATOR;
918
1040
  wireBytes.set(jsonBytes, 1);
919
- const chainHash = await this.inner.chainHash();
920
1041
  const serialized = this.serializeSolanaMultisigMessage({
921
1042
  wire_bytes: wireBytes,
922
1043
  chain_hash: chainHash,
923
- signatures: tx.signatures.map((s) => ({
1044
+ signatures: finalizedTx.V1.signatures.map((s) => ({
924
1045
  signature: hexToBytes2(s.signature),
925
1046
  pub_key: hexToBytes2(s.pub_key)
926
1047
  })),
927
- unused_pub_keys: tx.unused_pub_keys.map((pk) => hexToBytes2(pk)),
928
- min_signers: tx.min_signers
1048
+ unused_pub_keys: finalizedTx.V1.unused_pub_keys.map(
1049
+ (pk) => hexToBytes2(pk)
1050
+ ),
1051
+ min_signers: finalizedTx.V1.min_signers
929
1052
  });
930
- return this.submitSerializedMessage(serialized);
1053
+ return this.submitSerializedMessage(serialized, options);
931
1054
  }
932
1055
  case "solana": {
933
- const multisigPubkeys = this.canonicalizeMultisigPubkeys(
934
- params.multisigPubkeys
935
- );
936
1056
  const signedMessageWithPreamble = await this.createSpecCompliantMultisigSignedMessage(
937
1057
  unsignedTx,
938
- params.multisigAddress,
939
- multisigPubkeys
1058
+ multisig
940
1059
  );
941
1060
  const message = this.buildSpecCompliantMultisigEnvelope(
942
- tx,
943
- signedMessageWithPreamble,
944
- multisigPubkeys
1061
+ finalizedTx.V1,
1062
+ signedMessageWithPreamble
1063
+ );
1064
+ return this.submitSolanaSpecMultisigMessage(message, options);
1065
+ }
1066
+ }
1067
+ }
1068
+ /**
1069
+ * Submits a finalized V0 transaction using a Solana authenticator.
1070
+ */
1071
+ async submitSingleSignatureTransactionWithAuthenticator(transaction, authenticator, options) {
1072
+ const unsignedTx = {
1073
+ runtime_call: transaction.runtime_call,
1074
+ uniqueness: transaction.uniqueness,
1075
+ details: transaction.details,
1076
+ address_override: transaction.address_override
1077
+ };
1078
+ const pubkey = hexToBytes2(normalizeHexString(transaction.pub_key));
1079
+ const signature = hexToBytes2(normalizeHexString(transaction.signature));
1080
+ const chainHash = await this.inner.chainHash();
1081
+ assertChainHashFragment(unsignedTx, chainHash);
1082
+ switch (authenticator) {
1083
+ case "solanaSimple": {
1084
+ const signedMessage = await this.createSolanaJsonBytes(unsignedTx);
1085
+ const message = {
1086
+ signed_message: signedMessage,
1087
+ chain_hash: chainHash,
1088
+ pubkey,
1089
+ signature
1090
+ };
1091
+ return this.submitSolanaMessage(message, options);
1092
+ }
1093
+ case "solana": {
1094
+ const signedMessage = await this.createSolanaJsonBytes(unsignedTx);
1095
+ const preamble = createSolanaPreamble(
1096
+ [pubkey],
1097
+ chainHash,
1098
+ signedMessage.length
945
1099
  );
946
- return this.submitSolanaSpecMultisigMessage(message);
1100
+ const message = {
1101
+ signed_message_with_preamble: this.combinePreambleAndMessage(
1102
+ preamble,
1103
+ signedMessage
1104
+ ),
1105
+ signature
1106
+ };
1107
+ return this.submitSolanaSpecMessage(message, options);
947
1108
  }
948
1109
  }
949
1110
  }
950
1111
  /**
951
1112
  * Submits a transaction with the specified authenticator.
952
- *
953
- * @param transaction - Either a standard transaction or a Solana message
954
- * @param authenticator - The authenticator type to use
955
- * @param options - Optional request options
956
- * @returns The transaction response
957
1113
  */
958
1114
  async submitTransaction(transaction, authenticator, options) {
959
1115
  switch (authenticator) {
@@ -962,17 +1118,34 @@ var SolanaSignableRollup = class {
962
1118
  transaction,
963
1119
  options
964
1120
  );
965
- case "solanaSimple": {
966
- const solanaMessage = transaction;
967
- return await this.submitSolanaMessage(solanaMessage);
968
- }
969
- case "solana": {
970
- const solanaMessage = transaction;
971
- return await this.submitSolanaSpecMessage(solanaMessage);
972
- }
973
- default:
974
- throw new Error(`Unsupported authenticator: ${authenticator}`);
1121
+ case "solanaSimple":
1122
+ if ("V1" in transaction) {
1123
+ return this.submitMultisigTransactionWithAuthenticator(
1124
+ transaction,
1125
+ "solanaSimple",
1126
+ options
1127
+ );
1128
+ }
1129
+ return this.submitSingleSignatureTransactionWithAuthenticator(
1130
+ transaction.V0,
1131
+ "solanaSimple",
1132
+ options
1133
+ );
1134
+ case "solana":
1135
+ if ("V1" in transaction) {
1136
+ return this.submitMultisigTransactionWithAuthenticator(
1137
+ transaction,
1138
+ "solana",
1139
+ options
1140
+ );
1141
+ }
1142
+ return this.submitSingleSignatureTransactionWithAuthenticator(
1143
+ transaction.V0,
1144
+ "solana",
1145
+ options
1146
+ );
975
1147
  }
1148
+ throw new Error(`Unsupported authenticator: ${authenticator}`);
976
1149
  }
977
1150
  async simulate(runtimeMessage, params) {
978
1151
  return this.inner.simulate(runtimeMessage, params);
@@ -980,6 +1153,9 @@ var SolanaSignableRollup = class {
980
1153
  async dedup(address) {
981
1154
  return this.inner.dedup(address);
982
1155
  }
1156
+ async dedupByCredentialId(credentialId) {
1157
+ return this.inner.dedupByCredentialId(credentialId);
1158
+ }
983
1159
  async serializer() {
984
1160
  return this.inner.serializer();
985
1161
  }
@@ -1015,7 +1191,7 @@ var SolanaSignableRollup = class {
1015
1191
  return this.inner.http;
1016
1192
  }
1017
1193
  /**
1018
- * Helper method to serialize a SolanaOffchainSimpleMessage using borsh encoding.
1194
+ * Helper method to serialize a SolanaOffchainSimpleEnvelope using borsh encoding.
1019
1195
  */
1020
1196
  serializeSolanaMessage(message) {
1021
1197
  if (message.chain_hash.length !== CHAIN_HASH_SIZE) {
@@ -1049,7 +1225,7 @@ var SolanaSignableRollup = class {
1049
1225
  return buffer;
1050
1226
  }
1051
1227
  /**
1052
- * Helper method to serialize a SolanaOffchainSpecCompliantMessage using borsh encoding.
1228
+ * Helper method to serialize a SolanaOffchainSpecCompliantEnvelope using borsh encoding.
1053
1229
  */
1054
1230
  serializeSolanaSpecMessage(message) {
1055
1231
  if (message.signature.length !== SIGNATURE_SIZE) {
@@ -1069,7 +1245,7 @@ var SolanaSignableRollup = class {
1069
1245
  return buffer;
1070
1246
  }
1071
1247
  /**
1072
- * Serializes a SolanaOffchainSimpleMultisigMessage using borsh encoding.
1248
+ * Serializes a SolanaOffchainSimpleMultisigEnvelope using borsh encoding.
1073
1249
  * Layout matches the Rust struct:
1074
1250
  * [u32 LE: wire_bytes.len][wire_bytes]
1075
1251
  * [32 bytes: chain_hash]
@@ -1130,7 +1306,7 @@ var SolanaSignableRollup = class {
1130
1306
  return buffer;
1131
1307
  }
1132
1308
  /**
1133
- * Serializes a SolanaOffchainSpecCompliantMultisigMessage using borsh encoding.
1309
+ * Serializes a SolanaOffchainSpecCompliantMultisigEnvelope using borsh encoding.
1134
1310
  * Layout matches the Rust struct:
1135
1311
  * [u32 LE: signed_message_with_preamble.len][signed_message_with_preamble]
1136
1312
  * [u32 LE: signatures.len]
@@ -1183,6 +1359,7 @@ export {
1183
1359
  StandardRollup,
1184
1360
  VersionMismatchError,
1185
1361
  addressFromPublicKey,
1362
+ chainHashFragment,
1186
1363
  createSolanaSignableRollup,
1187
1364
  createStandardRollup,
1188
1365
  standardTypeBuilder