@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.js CHANGED
@@ -40,6 +40,7 @@ __export(src_exports, {
40
40
  StandardRollup: () => StandardRollup,
41
41
  VersionMismatchError: () => VersionMismatchError,
42
42
  addressFromPublicKey: () => addressFromPublicKey,
43
+ chainHashFragment: () => chainHashFragment,
43
44
  createSolanaSignableRollup: () => createSolanaSignableRollup,
44
45
  createStandardRollup: () => createStandardRollup,
45
46
  standardTypeBuilder: () => standardTypeBuilder
@@ -172,10 +173,26 @@ var Rollup = class {
172
173
  * TODO: How to request a specific uniqueness strategy explicitly
173
174
  */
174
175
  async dedup(publicKey) {
175
- const credentialId = (0, import_utils2.bytesToHex)(publicKey);
176
+ return this.dedupByCredentialId((0, import_utils2.bytesToHex)(publicKey));
177
+ }
178
+ /**
179
+ * Retrieve dedup information about the provided credential ID.
180
+ */
181
+ async dedupByCredentialId(credentialId) {
176
182
  const response = await this.rollup.addresses.dedup(credentialId);
177
183
  return response;
178
184
  }
185
+ /**
186
+ * Builds an unsigned transaction for the provided runtime call.
187
+ */
188
+ async buildUnsignedTransaction(runtimeCall, params) {
189
+ const context = {
190
+ runtimeCall,
191
+ rollup: this,
192
+ overrides: params?.overrides ?? {}
193
+ };
194
+ return this._typeBuilder.unsignedTransaction(context);
195
+ }
179
196
  /**
180
197
  * Submits a transaction to the rollup.
181
198
  *
@@ -233,12 +250,9 @@ var Rollup = class {
233
250
  * @param {SovereignClient.RequestOptions} options - The options for the request.
234
251
  */
235
252
  async call(runtimeCall, { signer, overrides }, options) {
236
- const context = {
237
- runtimeCall,
238
- rollup: this,
239
- overrides: overrides ?? {}
240
- };
241
- const unsignedTx = await this._typeBuilder.unsignedTransaction(context);
253
+ const unsignedTx = await this.buildUnsignedTransaction(runtimeCall, {
254
+ overrides
255
+ });
242
256
  return this.signAndSubmitTransaction(
243
257
  unsignedTx,
244
258
  {
@@ -259,18 +273,15 @@ var Rollup = class {
259
273
  * @returns A promise that resolves to the signed transaction.
260
274
  */
261
275
  async prepareCall(runtimeCall, { signer, overrides }) {
262
- const context = {
263
- runtimeCall,
264
- rollup: this,
265
- overrides: overrides ?? {}
266
- };
267
- const unsignedTx = await this._typeBuilder.unsignedTransaction(context);
276
+ const unsignedTx = await this.buildUnsignedTransaction(runtimeCall, {
277
+ overrides
278
+ });
268
279
  return this.signTransaction(unsignedTx, signer);
269
280
  }
270
281
  /**
271
282
  * Signs an unsigned transaction using the provided signer.
272
- * Creates a signature by combining the serialized unsigned transaction with the chain hash,
273
- * then constructs a fully signed transaction.
283
+ * Creates a signature over the serialized transaction signing payload, then constructs a fully
284
+ * signed transaction.
274
285
  *
275
286
  * @param unsignedTx - The unsigned transaction to sign.
276
287
  * @param signer - The signer to use for signing the transaction.
@@ -278,11 +289,15 @@ var Rollup = class {
278
289
  */
279
290
  async signTransaction(unsignedTx, signer) {
280
291
  const serializer = await this.serializer();
281
- const serializedUnsignedTx = serializer.serializeUnsignedTx(unsignedTx);
282
- const chainHash = await this.chainHash();
283
- const signature = await signer.sign(
284
- new Uint8Array([...serializedUnsignedTx, ...chainHash])
292
+ const transactionSigningPayload = await this._typeBuilder.transactionSigningPayload({
293
+ unsignedTx,
294
+ chainHash: await this.chainHash(),
295
+ rollup: this
296
+ });
297
+ const signingBytes = serializer.serializeSigningPayload(
298
+ transactionSigningPayload
285
299
  );
300
+ const signature = await signer.sign(signingBytes);
286
301
  const publicKey = await signer.publicKey();
287
302
  const context = {
288
303
  unsignedTx,
@@ -370,10 +385,12 @@ var Rollup = class {
370
385
  }
371
386
  };
372
387
  function isVersionMismatchError(e) {
373
- if (
374
- // biome-ignore lint/suspicious/noExplicitAny: yolo
375
- e.error?.details?.error?.includes("Signature verification failed")
376
- ) {
388
+ const details = e.error?.details;
389
+ if (details?.code === "invalid_chain_hash_fragment") {
390
+ return true;
391
+ }
392
+ const error = details?.error;
393
+ if (typeof error === "string" && error.includes("Signature verification failed")) {
377
394
  return true;
378
395
  }
379
396
  return false;
@@ -383,6 +400,36 @@ function isVersionMismatchError(e) {
383
400
  var import_client2 = __toESM(require("@sovereign-sdk/client"));
384
401
  var import_serializers = require("@sovereign-sdk/serializers");
385
402
  var import_utils3 = require("@sovereign-sdk/utils");
403
+
404
+ // src/rollup/chain-hash.ts
405
+ function chainHashFragment(chainHash) {
406
+ if (chainHash.length < 8) {
407
+ throw new Error("chain hash must contain at least 8 bytes");
408
+ }
409
+ return new DataView(
410
+ chainHash.buffer,
411
+ chainHash.byteOffset,
412
+ chainHash.byteLength
413
+ ).getBigUint64(0, true).toString();
414
+ }
415
+ function refreshChainHashFragment(unsignedTx, chainHash) {
416
+ unsignedTx.details = {
417
+ ...unsignedTx.details,
418
+ chain_hash_fragment: chainHashFragment(chainHash)
419
+ };
420
+ return unsignedTx;
421
+ }
422
+ function assertChainHashFragment(unsignedTx, chainHash) {
423
+ const expectedFragment = chainHashFragment(chainHash);
424
+ const actualFragment = unsignedTx.details.chain_hash_fragment;
425
+ if (actualFragment !== expectedFragment) {
426
+ throw new Error(
427
+ `Cannot sign transaction: chain_hash_fragment ${actualFragment} does not match the current chain hash fragment ${expectedFragment}`
428
+ );
429
+ }
430
+ }
431
+
432
+ // src/rollup/standard-rollup.ts
386
433
  var useOrFetchUniqueness = async ({
387
434
  overrides
388
435
  }) => {
@@ -395,7 +442,7 @@ function standardTypeBuilder() {
395
442
  return {
396
443
  async unsignedTransaction(context) {
397
444
  const { rollup, runtimeCall } = context;
398
- const { uniqueness: _, ...overrides } = context.overrides;
445
+ const overrides = context.overrides;
399
446
  const uniqueness = await useOrFetchUniqueness(context);
400
447
  const details = {
401
448
  ...rollup.context.defaultTxDetails,
@@ -404,7 +451,8 @@ function standardTypeBuilder() {
404
451
  return {
405
452
  runtime_call: runtimeCall,
406
453
  uniqueness,
407
- details
454
+ details,
455
+ address_override: overrides.address_override ?? null
408
456
  };
409
457
  },
410
458
  async transaction({
@@ -419,10 +467,67 @@ function standardTypeBuilder() {
419
467
  ...unsignedTx
420
468
  }
421
469
  };
470
+ },
471
+ async transactionSigningPayload({
472
+ unsignedTx,
473
+ chainHash
474
+ }) {
475
+ const normalizedUnsignedTx = refreshChainHashFragment(
476
+ unsignedTx,
477
+ chainHash
478
+ );
479
+ return {
480
+ V0: {
481
+ ...normalizedUnsignedTx,
482
+ chain_hash: Array.from(chainHash)
483
+ }
484
+ };
422
485
  }
423
486
  };
424
487
  }
425
488
  var StandardRollup = class extends Rollup {
489
+ updateDefaultChainHashFragment(chainHash) {
490
+ this.context.defaultTxDetails.chain_hash_fragment = chainHashFragment(chainHash);
491
+ }
492
+ async credentialAddressFromId(credentialId) {
493
+ if (this.context.credentialIdToAddress) {
494
+ const serializer = await this.serializer();
495
+ return this.context.credentialIdToAddress(
496
+ credentialId,
497
+ serializer.schema
498
+ );
499
+ }
500
+ return addressFromPublicKey(credentialId, "sov");
501
+ }
502
+ async submitTransaction(transaction, options) {
503
+ try {
504
+ return await super.submitTransaction(transaction, options);
505
+ } catch (error) {
506
+ if (error instanceof VersionMismatchError) {
507
+ this.updateDefaultChainHashFragment(await super.chainHash());
508
+ }
509
+ throw error;
510
+ }
511
+ }
512
+ async hydrate() {
513
+ await super.hydrate();
514
+ this.updateDefaultChainHashFragment(await super.chainHash());
515
+ }
516
+ async multisigSigningBytes(unsignedTx, multisig) {
517
+ const serializer = await this.serializer();
518
+ const chainHash = await this.chainHash();
519
+ assertChainHashFragment(unsignedTx, chainHash);
520
+ const signingPayload = {
521
+ V1: {
522
+ ...unsignedTx,
523
+ chain_hash: Array.from(chainHash),
524
+ credential_address: await this.credentialAddressFromId(
525
+ multisig.getMultisigAddress()
526
+ )
527
+ }
528
+ };
529
+ return serializer.serializeSigningPayload(signingPayload);
530
+ }
426
531
  /**
427
532
  * Simulates a runtime call transaction.
428
533
  *
@@ -430,11 +535,15 @@ var StandardRollup = class extends Rollup {
430
535
  *
431
536
  * @param runtimeMessage - The runtime message to call.
432
537
  */
433
- async simulate(runtimeMessage, { signer }) {
538
+ async simulate(runtimeMessage, { signer, ...params }) {
434
539
  const publicKey = await signer.publicKey();
435
540
  const sender = (0, import_utils3.bytesToHex)(publicKey);
436
541
  const call = runtimeMessage;
437
- return this.rollup.simulate({ sender, call });
542
+ return this.rollup.simulate({
543
+ ...params,
544
+ sender,
545
+ call
546
+ });
438
547
  }
439
548
  };
440
549
  var DEFAULT_TX_DETAILS = {
@@ -442,26 +551,24 @@ var DEFAULT_TX_DETAILS = {
442
551
  max_fee: "100000000",
443
552
  gas_limit: null
444
553
  };
445
- async function buildContext(client, context) {
554
+ function buildContext(context, credentialIdToAddress) {
446
555
  const defaultTxDetails = {
447
556
  ...DEFAULT_TX_DETAILS,
448
557
  ...context?.defaultTxDetails
449
558
  };
450
- if (!defaultTxDetails.chain_id) {
451
- const { chain_id } = await client.rollup.constants();
452
- defaultTxDetails.chain_id = chain_id;
453
- }
454
559
  return {
455
- defaultTxDetails
560
+ ...context,
561
+ defaultTxDetails,
562
+ credentialIdToAddress: credentialIdToAddress ?? context?.credentialIdToAddress
456
563
  };
457
564
  }
458
565
  async function createStandardRollup(rollupConfig, typeBuilderOverrides) {
459
566
  const config = rollupConfig ?? {};
460
567
  const client = config.client ?? new import_client2.default({ baseURL: config.url });
461
568
  const getSerializer = config.getSerializer ?? ((schema) => new import_serializers.JsSerializer(schema));
462
- const context = await buildContext(client, config.context);
569
+ const context = buildContext(config.context, config.credentialIdToAddress);
463
570
  const txSubmissionEndpoint = config.txSubmissionEndpoint ?? "/sequencer/txs";
464
- return new StandardRollup(
571
+ const rollup = new StandardRollup(
465
572
  {
466
573
  ...config,
467
574
  client,
@@ -474,9 +581,14 @@ async function createStandardRollup(rollupConfig, typeBuilderOverrides) {
474
581
  ...typeBuilderOverrides
475
582
  }
476
583
  );
584
+ if (!context.defaultTxDetails.chain_hash_fragment) {
585
+ await rollup.hydrate();
586
+ }
587
+ return rollup;
477
588
  }
478
589
 
479
590
  // src/rollup/solana-signable-rollup.ts
591
+ var import_multisig = require("@sovereign-sdk/multisig");
480
592
  var import_signers = require("@sovereign-sdk/signers");
481
593
  var import_utils4 = require("@sovereign-sdk/utils");
482
594
  var import_bs58 = __toESM(require("bs58"));
@@ -487,7 +599,6 @@ var CHAIN_HASH_SIZE = 32;
487
599
  var PUBKEY_SIZE = 32;
488
600
  var SIGNATURE_SIZE = 64;
489
601
  var MULTISIG_PREAMBLE_FIXED_LENGTH = 53;
490
- var MAX_MULTISIG_SIGNERS = 21;
491
602
  var SIGNING_DOMAIN = new Uint8Array([
492
603
  255,
493
604
  ...new TextEncoder().encode("solana offchain")
@@ -504,9 +615,9 @@ function compareByteArrays(left, right) {
504
615
  return left.length - right.length;
505
616
  }
506
617
  function createSolanaPreamble(pubkeys, chainHash, messageLength) {
507
- if (pubkeys.length < 1 || pubkeys.length > MAX_MULTISIG_SIGNERS) {
618
+ if (pubkeys.length < 1 || pubkeys.length > import_multisig.MAX_SIGNERS) {
508
619
  throw new Error(
509
- `Invalid signer count: expected 1-${MAX_MULTISIG_SIGNERS} signers, got ${pubkeys.length}`
620
+ `Invalid signer count: expected 1-${import_multisig.MAX_SIGNERS} signers, got ${pubkeys.length}`
510
621
  );
511
622
  }
512
623
  const preamble = new Uint8Array(
@@ -552,45 +663,37 @@ var SolanaSignableRollup = class {
552
663
  /**
553
664
  * Submits serialized data to the Solana endpoint.
554
665
  */
555
- async submitSerializedMessage(serializedMessage) {
666
+ async submitSerializedMessage(serializedMessage, options) {
667
+ const { body: _, query: __, path, ...requestOptions } = options || {};
556
668
  return await this.inner.http.post(
557
- this.solanaEndpoint,
669
+ path || this.solanaEndpoint,
558
670
  {
559
671
  // Match AcceptTx shape used by standard sequencer endpoints.
560
- body: { body: import_js_base642.Base64.fromUint8Array(serializedMessage) }
672
+ body: { body: import_js_base642.Base64.fromUint8Array(serializedMessage) },
673
+ ...requestOptions
561
674
  }
562
675
  );
563
676
  }
564
677
  /**
565
678
  * Submits a Solana offchain message to the rollup.
566
679
  */
567
- async submitSolanaMessage(solanaMessage) {
680
+ async submitSolanaMessage(solanaMessage, options) {
568
681
  const serializedMessage = this.serializeSolanaMessage(solanaMessage);
569
- return this.submitSerializedMessage(serializedMessage);
682
+ return this.submitSerializedMessage(serializedMessage, options);
570
683
  }
571
684
  /**
572
685
  * Submits a Solana spec-compliant message to the rollup.
573
686
  */
574
- async submitSolanaSpecMessage(solanaMessage) {
687
+ async submitSolanaSpecMessage(solanaMessage, options) {
575
688
  const serializedMessage = this.serializeSolanaSpecMessage(solanaMessage);
576
- return this.submitSerializedMessage(serializedMessage);
689
+ return this.submitSerializedMessage(serializedMessage, options);
577
690
  }
578
691
  /**
579
692
  * Submits a Solana spec-compliant multisig message to the rollup.
580
693
  */
581
- async submitSolanaSpecMultisigMessage(solanaMessage) {
694
+ async submitSolanaSpecMultisigMessage(solanaMessage, options) {
582
695
  const serializedMessage = this.serializeSolanaSpecMultisigMessage(solanaMessage);
583
- return this.submitSerializedMessage(serializedMessage);
584
- }
585
- /**
586
- * Helper to build an unsigned transaction using the standard type builder.
587
- */
588
- async buildUnsignedTransaction(runtimeCall, overrides) {
589
- return this.typeBuilder.unsignedTransaction({
590
- runtimeCall,
591
- overrides: overrides ?? {},
592
- rollup: this.inner
593
- });
696
+ return this.submitSerializedMessage(serializedMessage, options);
594
697
  }
595
698
  /**
596
699
  * Helper to build a transaction result object.
@@ -618,18 +721,15 @@ var SolanaSignableRollup = class {
618
721
  * The canonical order is lexicographic by raw pubkey bytes.
619
722
  */
620
723
  canonicalizeMultisigPubkeys(multisigPubkeys) {
621
- if (!multisigPubkeys) {
724
+ if (multisigPubkeys.length < 1 || multisigPubkeys.length > import_multisig.MAX_SIGNERS) {
622
725
  throw new Error(
623
- "multisigPubkeys is required for Solana multisig transactions"
624
- );
625
- }
626
- if (multisigPubkeys.length < 2 || multisigPubkeys.length > MAX_MULTISIG_SIGNERS) {
627
- throw new Error(
628
- `Invalid multisig signer count: expected 2-${MAX_MULTISIG_SIGNERS} signers, got ${multisigPubkeys.length}`
726
+ `Invalid multisig signer count: expected 1-${import_multisig.MAX_SIGNERS} signers, got ${multisigPubkeys.length}`
629
727
  );
630
728
  }
631
729
  const seenPubkeys = /* @__PURE__ */ new Set();
632
- for (const pubkey of multisigPubkeys) {
730
+ const normalizedPubkeys = multisigPubkeys.map(import_utils4.normalizeHexString);
731
+ const pubkeyBytes = normalizedPubkeys.map((pubkey) => (0, import_utils4.hexToBytes)(pubkey));
732
+ for (const pubkey of pubkeyBytes) {
633
733
  if (pubkey.length !== PUBKEY_SIZE) {
634
734
  throw new Error(
635
735
  `Invalid public key length: expected ${PUBKEY_SIZE} bytes, got ${pubkey.length}`
@@ -641,31 +741,36 @@ var SolanaSignableRollup = class {
641
741
  }
642
742
  seenPubkeys.add(pubkeyHex);
643
743
  }
644
- return [...multisigPubkeys].sort(compareByteArrays);
744
+ return pubkeyBytes.sort(compareByteArrays);
645
745
  }
646
746
  /**
647
- * Helper to create and serialize a SolanaOffchainUnsignedTransaction to JSON bytes.
747
+ * Helper to create and serialize a SolanaOffchainSigningPayloadV0 to JSON bytes.
648
748
  */
649
749
  async createSolanaJsonBytes(unsignedTx) {
650
750
  const serializer = await this.inner.serializer();
651
751
  const schema = serializer.schema;
652
752
  const chainName = schema.chain_data.chain_name || "";
653
- const solanaUnsignedTx = {
753
+ const solanaSigningPayload = {
654
754
  runtime_call: unsignedTx.runtime_call,
655
755
  uniqueness: unsignedTx.uniqueness,
656
756
  details: unsignedTx.details,
657
- chain_name: chainName
757
+ chain_name: chainName,
758
+ ...unsignedTx.address_override !== null && unsignedTx.address_override !== void 0 && {
759
+ address_override: unsignedTx.address_override
760
+ },
761
+ version: 0
658
762
  };
659
- return new TextEncoder().encode(JSON.stringify(solanaUnsignedTx));
763
+ return new TextEncoder().encode(JSON.stringify(solanaSigningPayload));
660
764
  }
661
765
  /**
662
766
  * Signs an unsigned transaction using Solana offchain simple signing and submits it.
663
767
  * Returns the transaction result in the same format as standard rollup.
664
768
  */
665
- async signWithSolanaSimpleAndSubmit(unsignedTx, signer) {
769
+ async signWithSolanaSimpleAndSubmit(unsignedTx, signer, options) {
770
+ const chainHash = await this.inner.chainHash();
771
+ refreshChainHashFragment(unsignedTx, chainHash);
666
772
  const jsonBytes = await this.createSolanaJsonBytes(unsignedTx);
667
773
  const pubkey = await signer.publicKey();
668
- const chainHash = await this.inner.chainHash();
669
774
  const signature = await signer.sign(jsonBytes);
670
775
  const solanaMessage = {
671
776
  signed_message: jsonBytes,
@@ -673,17 +778,18 @@ var SolanaSignableRollup = class {
673
778
  pubkey,
674
779
  signature
675
780
  };
676
- const response = await this.submitSolanaMessage(solanaMessage);
781
+ const response = await this.submitSolanaMessage(solanaMessage, options);
677
782
  return this.buildTransactionResult(response, unsignedTx, pubkey, signature);
678
783
  }
679
784
  /**
680
785
  * Signs an unsigned transaction using Solana spec-compliant signing and submits it.
681
786
  * Returns the transaction result in the same format as standard rollup.
682
787
  */
683
- async signWithSolanaSpecAndSubmit(unsignedTx, signer) {
788
+ async signWithSolanaSpecAndSubmit(unsignedTx, signer, options) {
789
+ const chainHash = await this.inner.chainHash();
790
+ refreshChainHashFragment(unsignedTx, chainHash);
684
791
  const jsonBytes = await this.createSolanaJsonBytes(unsignedTx);
685
792
  const pubkey = await signer.publicKey();
686
- const chainHash = await this.inner.chainHash();
687
793
  const preamble = createSolanaPreamble(
688
794
  [pubkey],
689
795
  chainHash,
@@ -698,7 +804,7 @@ var SolanaSignableRollup = class {
698
804
  signed_message_with_preamble: signedMessageWithPreamble,
699
805
  signature
700
806
  };
701
- const response = await this.submitSolanaSpecMessage(solanaMessage);
807
+ const response = await this.submitSolanaSpecMessage(solanaMessage, options);
702
808
  return this.buildTransactionResult(response, unsignedTx, pubkey, signature);
703
809
  }
704
810
  /**
@@ -722,18 +828,30 @@ var SolanaSignableRollup = class {
722
828
  options
723
829
  );
724
830
  case "solanaSimple": {
725
- const unsignedTx = await this.buildUnsignedTransaction(
831
+ const unsignedTx = await this.inner.buildUnsignedTransaction(
726
832
  runtimeCall,
727
- params.overrides
833
+ {
834
+ overrides: params.overrides
835
+ }
836
+ );
837
+ return this.signWithSolanaSimpleAndSubmit(
838
+ unsignedTx,
839
+ params.signer,
840
+ options
728
841
  );
729
- return this.signWithSolanaSimpleAndSubmit(unsignedTx, params.signer);
730
842
  }
731
843
  case "solana": {
732
- const unsignedTx = await this.buildUnsignedTransaction(
844
+ const unsignedTx = await this.inner.buildUnsignedTransaction(
733
845
  runtimeCall,
734
- params.overrides
846
+ {
847
+ overrides: params.overrides
848
+ }
849
+ );
850
+ return this.signWithSolanaSpecAndSubmit(
851
+ unsignedTx,
852
+ params.signer,
853
+ options
735
854
  );
736
- return this.signWithSolanaSpecAndSubmit(unsignedTx, params.signer);
737
855
  }
738
856
  default:
739
857
  throw new Error(`Unsupported authenticator: ${authenticator}`);
@@ -759,132 +877,134 @@ var SolanaSignableRollup = class {
759
877
  options
760
878
  );
761
879
  case "solanaSimple":
762
- return this.signWithSolanaSimpleAndSubmit(unsignedTx, params.signer);
880
+ return this.signWithSolanaSimpleAndSubmit(
881
+ unsignedTx,
882
+ params.signer,
883
+ options
884
+ );
763
885
  case "solana":
764
- return this.signWithSolanaSpecAndSubmit(unsignedTx, params.signer);
886
+ return this.signWithSolanaSpecAndSubmit(
887
+ unsignedTx,
888
+ params.signer,
889
+ options
890
+ );
765
891
  default:
766
892
  throw new Error(`Unsupported authenticator: ${authenticator}`);
767
893
  }
768
894
  }
895
+ async buildUnsignedTransaction(runtimeCall, params) {
896
+ return this.inner.buildUnsignedTransaction(runtimeCall, params);
897
+ }
769
898
  /**
770
899
  * Creates V1 JSON bytes for a multisig transaction, including multisig_id and version.
771
900
  * The resulting JSON is what each signer signs directly (no discriminator prefix).
772
901
  */
773
- async createMultisigJsonBytes(unsignedTx, multisigAddress) {
902
+ async createMultisigJsonBytes(unsignedTx, multisigId) {
774
903
  const serializer = await this.inner.serializer();
775
904
  const schema = serializer.schema;
776
905
  const chainName = schema.chain_data.chain_name || "";
777
- const solanaUnsignedTx = {
906
+ const solanaSigningPayload = {
778
907
  runtime_call: unsignedTx.runtime_call,
779
908
  uniqueness: unsignedTx.uniqueness,
780
909
  details: unsignedTx.details,
781
910
  chain_name: chainName,
782
- // Hardcoded to base58 encoding, only correct for rollups using Base58Address as their
783
- // primary address type. Will be replaced with rollup-aware address formatting once the
784
- // SDK supports flexible address encoding (see #2673).
785
- multisig_id: import_bs58.default.encode(multisigAddress),
911
+ multisig_id: multisigId,
912
+ ...unsignedTx.address_override !== null && unsignedTx.address_override !== void 0 && {
913
+ address_override: unsignedTx.address_override
914
+ },
786
915
  version: 1
787
916
  };
788
- return new TextEncoder().encode(JSON.stringify(solanaUnsignedTx));
917
+ return new TextEncoder().encode(JSON.stringify(solanaSigningPayload));
789
918
  }
790
919
  /**
791
- * Creates the preamble+JSON bytes signed by every signer in a spec-compliant multisig flow.
920
+ * Formats the multisig credential in the rollup's configured address format.
792
921
  */
793
- async createSpecCompliantMultisigSignedMessage(unsignedTx, multisigAddress, multisigPubkeys) {
794
- const jsonBytes = await this.createMultisigJsonBytes(
795
- unsignedTx,
796
- multisigAddress
797
- );
798
- const chainHash = await this.inner.chainHash();
799
- const preamble = createSolanaPreamble(
800
- multisigPubkeys,
801
- chainHash,
802
- jsonBytes.length
803
- );
804
- return this.combinePreambleAndMessage(preamble, jsonBytes);
922
+ async multisigIdFromMultisig(multisig) {
923
+ const credentialId = multisig.getMultisigAddress();
924
+ if (this.inner.context.credentialIdToAddress) {
925
+ const serializer = await this.inner.serializer();
926
+ return this.inner.context.credentialIdToAddress(
927
+ credentialId,
928
+ serializer.schema
929
+ );
930
+ }
931
+ return import_bs58.default.encode(credentialId);
805
932
  }
806
933
  /**
807
- * Signs an unsigned transaction using Solana offchain simple multisig signing.
934
+ * Creates multisig state from a finalized transaction envelope.
808
935
  */
809
- async signForSolanaSimpleMultisig(unsignedTx, signer, multisigAddress, multisigPubkeys) {
810
- const pubkey = await signer.publicKey();
811
- const signerPubkeyHex = (0, import_utils4.bytesToHex)(pubkey);
812
- const multisigPubkeyHexes = multisigPubkeys.map(import_utils4.bytesToHex);
813
- if (!multisigPubkeyHexes.includes(signerPubkeyHex)) {
814
- throw new Error(
815
- `Signer public key ${signerPubkeyHex} is not present in multisigPubkeys`
816
- );
817
- }
818
- const jsonBytes = await this.createMultisigJsonBytes(
819
- unsignedTx,
820
- multisigAddress
821
- );
822
- const signature = await signer.sign(jsonBytes);
823
- return this.typeBuilder.transaction({
824
- unsignedTx,
825
- sender: pubkey,
826
- signature,
827
- rollup: this.inner
936
+ multisigFromTransaction(tx) {
937
+ return new import_multisig.Multisig({
938
+ signatures: tx.signatures,
939
+ unusedPubKeys: tx.unused_pub_keys,
940
+ minSigners: tx.min_signers
828
941
  });
829
942
  }
943
+ normalizeMultisigTransaction(tx) {
944
+ return {
945
+ ...tx,
946
+ signatures: tx.signatures.map((signature) => ({
947
+ signature: (0, import_utils4.normalizeHexString)(signature.signature),
948
+ pub_key: (0, import_utils4.normalizeHexString)(signature.pub_key)
949
+ })),
950
+ unused_pub_keys: tx.unused_pub_keys.map(import_utils4.normalizeHexString)
951
+ };
952
+ }
830
953
  /**
831
- * Signs an unsigned transaction using the spec-compliant multisig preamble.
954
+ * Extracts the V0-shaped unsigned fields from a finalized multisig transaction.
832
955
  */
833
- async signForSolanaSpecMultisig(unsignedTx, signer, multisigAddress, multisigPubkeys) {
834
- const pubkey = await signer.publicKey();
835
- const signerPubkeyHex = (0, import_utils4.bytesToHex)(pubkey);
836
- const multisigPubkeyHexes = multisigPubkeys.map(import_utils4.bytesToHex);
837
- if (!multisigPubkeyHexes.includes(signerPubkeyHex)) {
838
- throw new Error(
839
- `Signer public key ${signerPubkeyHex} is not present in multisigPubkeys`
840
- );
841
- }
842
- const signedMessageWithPreamble = await this.createSpecCompliantMultisigSignedMessage(
843
- unsignedTx,
844
- multisigAddress,
845
- multisigPubkeys
846
- );
847
- const signature = await signer.sign(signedMessageWithPreamble);
848
- return this.typeBuilder.transaction({
849
- unsignedTx,
850
- sender: pubkey,
851
- signature,
852
- rollup: this.inner
853
- });
956
+ unsignedTxFromTransaction(tx) {
957
+ return {
958
+ runtime_call: tx.runtime_call,
959
+ uniqueness: tx.uniqueness,
960
+ details: tx.details,
961
+ address_override: tx.address_override
962
+ };
854
963
  }
855
964
  /**
856
- * Signs an unsigned transaction for use in a Solana offchain multisig.
857
- *
858
- * `authenticator: "standard"` delegates directly to the wrapped standard rollup.
859
- * `authenticator: "solanaSimple"` signs the V1 JSON payload directly.
860
- * `authenticator: "solana"` signs the spec-compliant preamble+JSON bytes.
861
- * Solana multisig authenticators treat `multisigPubkeys` as an unordered set and
862
- * canonicalize it before signing.
965
+ * Creates the preamble+JSON bytes signed by every signer in a spec-compliant multisig flow.
863
966
  */
864
- async signTransactionForMultisig(unsignedTx, params) {
865
- switch (params.authenticator) {
967
+ async createSpecCompliantMultisigSignedMessage(unsignedTx, multisig) {
968
+ const chainHash = await this.inner.chainHash();
969
+ assertChainHashFragment(unsignedTx, chainHash);
970
+ const jsonBytes = await this.createMultisigJsonBytes(
971
+ unsignedTx,
972
+ await this.multisigIdFromMultisig(multisig)
973
+ );
974
+ const preamble = createSolanaPreamble(
975
+ this.canonicalizeMultisigPubkeys([...multisig.allPubKeys]),
976
+ chainHash,
977
+ jsonBytes.length
978
+ );
979
+ return this.combinePreambleAndMessage(preamble, jsonBytes);
980
+ }
981
+ async multisigSigningBytes(unsignedTx, multisig, authenticator) {
982
+ switch (authenticator) {
866
983
  case "standard":
867
- return this.inner.signTransaction(unsignedTx, params.signer);
868
- case "solanaSimple":
869
- return this.signForSolanaSimpleMultisig(
984
+ return this.inner.multisigSigningBytes(unsignedTx, multisig);
985
+ case "solanaSimple": {
986
+ assertChainHashFragment(unsignedTx, await this.inner.chainHash());
987
+ return this.createMultisigJsonBytes(
870
988
  unsignedTx,
871
- params.signer,
872
- params.multisigAddress,
873
- this.canonicalizeMultisigPubkeys(params.multisigPubkeys)
989
+ await this.multisigIdFromMultisig(multisig)
874
990
  );
991
+ }
875
992
  case "solana":
876
- return this.signForSolanaSpecMultisig(
993
+ return this.createSpecCompliantMultisigSignedMessage(
877
994
  unsignedTx,
878
- params.signer,
879
- params.multisigAddress,
880
- this.canonicalizeMultisigPubkeys(params.multisigPubkeys)
995
+ multisig
881
996
  );
882
997
  }
998
+ throw new Error(`Unsupported authenticator: ${authenticator}`);
883
999
  }
884
1000
  /**
885
- * Builds a spec-compliant multisig envelope from a V1 transaction and ordered multisig pubkeys.
1001
+ * Builds a spec-compliant multisig envelope from a V1 transaction.
886
1002
  */
887
- buildSpecCompliantMultisigEnvelope(tx, signedMessageWithPreamble, multisigPubkeys) {
1003
+ buildSpecCompliantMultisigEnvelope(tx, signedMessageWithPreamble) {
1004
+ const multisigPubkeys = this.canonicalizeMultisigPubkeys([
1005
+ ...tx.signatures.map((signer) => signer.pub_key),
1006
+ ...tx.unused_pub_keys
1007
+ ]);
888
1008
  const multisigPubkeyHexes = multisigPubkeys.map(import_utils4.bytesToHex);
889
1009
  const indexByPubkey = /* @__PURE__ */ new Map();
890
1010
  multisigPubkeyHexes.forEach((pubkeyHex, index) => {
@@ -894,37 +1014,42 @@ var SolanaSignableRollup = class {
894
1014
  const signaturesByIndex = /* @__PURE__ */ new Map();
895
1015
  let signerBitfield = 0;
896
1016
  for (const signer of tx.signatures) {
897
- const signerIndex = indexByPubkey.get(signer.pub_key);
1017
+ const normalizedSignerPubkey = (0, import_utils4.normalizeHexString)(signer.pub_key);
1018
+ const signerIndex = indexByPubkey.get(normalizedSignerPubkey);
898
1019
  if (signerIndex === void 0) {
899
1020
  throw new Error(
900
- `Signed pubkey ${signer.pub_key} is not present in multisigPubkeys`
1021
+ `Signed pubkey ${normalizedSignerPubkey} is not present in multisigPubkeys`
901
1022
  );
902
1023
  }
903
- if (accountedPubkeys.has(signer.pub_key)) {
1024
+ if (accountedPubkeys.has(normalizedSignerPubkey)) {
904
1025
  throw new Error(
905
- `Duplicate signed pubkey in multisig transaction: ${signer.pub_key}`
1026
+ `Duplicate signed pubkey in multisig transaction: ${normalizedSignerPubkey}`
906
1027
  );
907
1028
  }
908
- accountedPubkeys.add(signer.pub_key);
909
- signaturesByIndex.set(signerIndex, (0, import_utils4.hexToBytes)(signer.signature));
1029
+ accountedPubkeys.add(normalizedSignerPubkey);
1030
+ signaturesByIndex.set(
1031
+ signerIndex,
1032
+ (0, import_utils4.hexToBytes)((0, import_utils4.normalizeHexString)(signer.signature))
1033
+ );
910
1034
  signerBitfield = (signerBitfield | 1 << signerIndex) >>> 0;
911
1035
  }
912
1036
  for (const unusedPubkey of tx.unused_pub_keys) {
913
- if (!indexByPubkey.has(unusedPubkey)) {
1037
+ const normalizedUnusedPubkey = (0, import_utils4.normalizeHexString)(unusedPubkey);
1038
+ if (!indexByPubkey.has(normalizedUnusedPubkey)) {
914
1039
  throw new Error(
915
- `Unused pubkey ${unusedPubkey} is not present in multisigPubkeys`
1040
+ `Unused pubkey ${normalizedUnusedPubkey} is not present in multisigPubkeys`
916
1041
  );
917
1042
  }
918
- if (accountedPubkeys.has(unusedPubkey)) {
1043
+ if (accountedPubkeys.has(normalizedUnusedPubkey)) {
919
1044
  throw new Error(
920
- `Duplicate pubkey in multisig transaction payload: ${unusedPubkey}`
1045
+ `Duplicate pubkey in multisig transaction payload: ${normalizedUnusedPubkey}`
921
1046
  );
922
1047
  }
923
- accountedPubkeys.add(unusedPubkey);
1048
+ accountedPubkeys.add(normalizedUnusedPubkey);
924
1049
  }
925
1050
  if (accountedPubkeys.size !== multisigPubkeys.length) {
926
1051
  throw new Error(
927
- "multisigPubkeys must contain every signer and unused pubkey exactly once"
1052
+ "Multisig transaction payload must contain every signer exactly once"
928
1053
  );
929
1054
  }
930
1055
  const orderedSignatures = Array.from(signaturesByIndex.entries()).sort(([leftIndex], [rightIndex]) => leftIndex - rightIndex).map(([, signature]) => signature);
@@ -936,72 +1061,100 @@ var SolanaSignableRollup = class {
936
1061
  };
937
1062
  }
938
1063
  /**
939
- * Submits a multisig transaction using the Solana offchain simple multisig format.
940
- *
941
- * Accepts the V1 transaction produced by `MultisigTransaction.asTransaction()`.
942
- * `authenticator: "standard"` delegates directly to the wrapped standard rollup.
943
- * Solana authenticators rebuild the appropriate Solana multisig envelope and
944
- * submit it to the Solana offchain endpoint.
1064
+ * Submits a finalized V1 multisig transaction using a Solana authenticator.
945
1065
  */
946
- async submitMultisigTransaction(multisigTx, params) {
947
- const { V1: tx } = multisigTx;
948
- const unsignedTx = {
949
- runtime_call: tx.runtime_call,
950
- uniqueness: tx.uniqueness,
951
- details: tx.details
1066
+ async submitMultisigTransactionWithAuthenticator(transaction, authenticator, options) {
1067
+ const finalizedTx = {
1068
+ V1: this.normalizeMultisigTransaction(transaction.V1)
952
1069
  };
953
- switch (params.authenticator) {
954
- case "standard":
955
- return this.inner.submitTransaction(
956
- multisigTx
957
- );
1070
+ const multisig = this.multisigFromTransaction(finalizedTx.V1);
1071
+ if (!multisig.isComplete) {
1072
+ throw new Error("Multisig transaction is incomplete");
1073
+ }
1074
+ const unsignedTx = this.unsignedTxFromTransaction(finalizedTx.V1);
1075
+ switch (authenticator) {
958
1076
  case "solanaSimple": {
959
- this.canonicalizeMultisigPubkeys(params.multisigPubkeys);
1077
+ const chainHash = await this.inner.chainHash();
1078
+ assertChainHashFragment(unsignedTx, chainHash);
960
1079
  const jsonBytes = await this.createMultisigJsonBytes(
961
1080
  unsignedTx,
962
- params.multisigAddress
1081
+ await this.multisigIdFromMultisig(multisig)
963
1082
  );
964
1083
  const wireBytes = new Uint8Array(1 + jsonBytes.length);
965
1084
  wireBytes[0] = MULTISIG_SIMPLE_DISCRIMINATOR;
966
1085
  wireBytes.set(jsonBytes, 1);
967
- const chainHash = await this.inner.chainHash();
968
1086
  const serialized = this.serializeSolanaMultisigMessage({
969
1087
  wire_bytes: wireBytes,
970
1088
  chain_hash: chainHash,
971
- signatures: tx.signatures.map((s) => ({
1089
+ signatures: finalizedTx.V1.signatures.map((s) => ({
972
1090
  signature: (0, import_utils4.hexToBytes)(s.signature),
973
1091
  pub_key: (0, import_utils4.hexToBytes)(s.pub_key)
974
1092
  })),
975
- unused_pub_keys: tx.unused_pub_keys.map((pk) => (0, import_utils4.hexToBytes)(pk)),
976
- min_signers: tx.min_signers
1093
+ unused_pub_keys: finalizedTx.V1.unused_pub_keys.map(
1094
+ (pk) => (0, import_utils4.hexToBytes)(pk)
1095
+ ),
1096
+ min_signers: finalizedTx.V1.min_signers
977
1097
  });
978
- return this.submitSerializedMessage(serialized);
1098
+ return this.submitSerializedMessage(serialized, options);
979
1099
  }
980
1100
  case "solana": {
981
- const multisigPubkeys = this.canonicalizeMultisigPubkeys(
982
- params.multisigPubkeys
983
- );
984
1101
  const signedMessageWithPreamble = await this.createSpecCompliantMultisigSignedMessage(
985
1102
  unsignedTx,
986
- params.multisigAddress,
987
- multisigPubkeys
1103
+ multisig
988
1104
  );
989
1105
  const message = this.buildSpecCompliantMultisigEnvelope(
990
- tx,
991
- signedMessageWithPreamble,
992
- multisigPubkeys
1106
+ finalizedTx.V1,
1107
+ signedMessageWithPreamble
1108
+ );
1109
+ return this.submitSolanaSpecMultisigMessage(message, options);
1110
+ }
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Submits a finalized V0 transaction using a Solana authenticator.
1115
+ */
1116
+ async submitSingleSignatureTransactionWithAuthenticator(transaction, authenticator, options) {
1117
+ const unsignedTx = {
1118
+ runtime_call: transaction.runtime_call,
1119
+ uniqueness: transaction.uniqueness,
1120
+ details: transaction.details,
1121
+ address_override: transaction.address_override
1122
+ };
1123
+ const pubkey = (0, import_utils4.hexToBytes)((0, import_utils4.normalizeHexString)(transaction.pub_key));
1124
+ const signature = (0, import_utils4.hexToBytes)((0, import_utils4.normalizeHexString)(transaction.signature));
1125
+ const chainHash = await this.inner.chainHash();
1126
+ assertChainHashFragment(unsignedTx, chainHash);
1127
+ switch (authenticator) {
1128
+ case "solanaSimple": {
1129
+ const signedMessage = await this.createSolanaJsonBytes(unsignedTx);
1130
+ const message = {
1131
+ signed_message: signedMessage,
1132
+ chain_hash: chainHash,
1133
+ pubkey,
1134
+ signature
1135
+ };
1136
+ return this.submitSolanaMessage(message, options);
1137
+ }
1138
+ case "solana": {
1139
+ const signedMessage = await this.createSolanaJsonBytes(unsignedTx);
1140
+ const preamble = createSolanaPreamble(
1141
+ [pubkey],
1142
+ chainHash,
1143
+ signedMessage.length
993
1144
  );
994
- return this.submitSolanaSpecMultisigMessage(message);
1145
+ const message = {
1146
+ signed_message_with_preamble: this.combinePreambleAndMessage(
1147
+ preamble,
1148
+ signedMessage
1149
+ ),
1150
+ signature
1151
+ };
1152
+ return this.submitSolanaSpecMessage(message, options);
995
1153
  }
996
1154
  }
997
1155
  }
998
1156
  /**
999
1157
  * Submits a transaction with the specified authenticator.
1000
- *
1001
- * @param transaction - Either a standard transaction or a Solana message
1002
- * @param authenticator - The authenticator type to use
1003
- * @param options - Optional request options
1004
- * @returns The transaction response
1005
1158
  */
1006
1159
  async submitTransaction(transaction, authenticator, options) {
1007
1160
  switch (authenticator) {
@@ -1010,17 +1163,34 @@ var SolanaSignableRollup = class {
1010
1163
  transaction,
1011
1164
  options
1012
1165
  );
1013
- case "solanaSimple": {
1014
- const solanaMessage = transaction;
1015
- return await this.submitSolanaMessage(solanaMessage);
1016
- }
1017
- case "solana": {
1018
- const solanaMessage = transaction;
1019
- return await this.submitSolanaSpecMessage(solanaMessage);
1020
- }
1021
- default:
1022
- throw new Error(`Unsupported authenticator: ${authenticator}`);
1166
+ case "solanaSimple":
1167
+ if ("V1" in transaction) {
1168
+ return this.submitMultisigTransactionWithAuthenticator(
1169
+ transaction,
1170
+ "solanaSimple",
1171
+ options
1172
+ );
1173
+ }
1174
+ return this.submitSingleSignatureTransactionWithAuthenticator(
1175
+ transaction.V0,
1176
+ "solanaSimple",
1177
+ options
1178
+ );
1179
+ case "solana":
1180
+ if ("V1" in transaction) {
1181
+ return this.submitMultisigTransactionWithAuthenticator(
1182
+ transaction,
1183
+ "solana",
1184
+ options
1185
+ );
1186
+ }
1187
+ return this.submitSingleSignatureTransactionWithAuthenticator(
1188
+ transaction.V0,
1189
+ "solana",
1190
+ options
1191
+ );
1023
1192
  }
1193
+ throw new Error(`Unsupported authenticator: ${authenticator}`);
1024
1194
  }
1025
1195
  async simulate(runtimeMessage, params) {
1026
1196
  return this.inner.simulate(runtimeMessage, params);
@@ -1028,6 +1198,9 @@ var SolanaSignableRollup = class {
1028
1198
  async dedup(address) {
1029
1199
  return this.inner.dedup(address);
1030
1200
  }
1201
+ async dedupByCredentialId(credentialId) {
1202
+ return this.inner.dedupByCredentialId(credentialId);
1203
+ }
1031
1204
  async serializer() {
1032
1205
  return this.inner.serializer();
1033
1206
  }
@@ -1063,7 +1236,7 @@ var SolanaSignableRollup = class {
1063
1236
  return this.inner.http;
1064
1237
  }
1065
1238
  /**
1066
- * Helper method to serialize a SolanaOffchainSimpleMessage using borsh encoding.
1239
+ * Helper method to serialize a SolanaOffchainSimpleEnvelope using borsh encoding.
1067
1240
  */
1068
1241
  serializeSolanaMessage(message) {
1069
1242
  if (message.chain_hash.length !== CHAIN_HASH_SIZE) {
@@ -1097,7 +1270,7 @@ var SolanaSignableRollup = class {
1097
1270
  return buffer;
1098
1271
  }
1099
1272
  /**
1100
- * Helper method to serialize a SolanaOffchainSpecCompliantMessage using borsh encoding.
1273
+ * Helper method to serialize a SolanaOffchainSpecCompliantEnvelope using borsh encoding.
1101
1274
  */
1102
1275
  serializeSolanaSpecMessage(message) {
1103
1276
  if (message.signature.length !== SIGNATURE_SIZE) {
@@ -1117,7 +1290,7 @@ var SolanaSignableRollup = class {
1117
1290
  return buffer;
1118
1291
  }
1119
1292
  /**
1120
- * Serializes a SolanaOffchainSimpleMultisigMessage using borsh encoding.
1293
+ * Serializes a SolanaOffchainSimpleMultisigEnvelope using borsh encoding.
1121
1294
  * Layout matches the Rust struct:
1122
1295
  * [u32 LE: wire_bytes.len][wire_bytes]
1123
1296
  * [32 bytes: chain_hash]
@@ -1178,7 +1351,7 @@ var SolanaSignableRollup = class {
1178
1351
  return buffer;
1179
1352
  }
1180
1353
  /**
1181
- * Serializes a SolanaOffchainSpecCompliantMultisigMessage using borsh encoding.
1354
+ * Serializes a SolanaOffchainSpecCompliantMultisigEnvelope using borsh encoding.
1182
1355
  * Layout matches the Rust struct:
1183
1356
  * [u32 LE: signed_message_with_preamble.len][signed_message_with_preamble]
1184
1357
  * [u32 LE: signatures.len]
@@ -1232,6 +1405,7 @@ var SovereignClient3 = __toESM(require("@sovereign-sdk/client"));
1232
1405
  StandardRollup,
1233
1406
  VersionMismatchError,
1234
1407
  addressFromPublicKey,
1408
+ chainHashFragment,
1235
1409
  createSolanaSignableRollup,
1236
1410
  createStandardRollup,
1237
1411
  standardTypeBuilder