@hashgraph/hedera-wallet-connect 2.0.4-canary.5aac7b5.0 → 2.0.4-canary.6b57886.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/README.md CHANGED
@@ -64,7 +64,7 @@ reviewing the [Reown docs](https://docs.reown.com/overview).
64
64
  1. Add Hedera dependencies to your project:
65
65
 
66
66
  ```sh
67
- npm install @hashgraph/hedera-wallet-connect@2.0.4-canary.3ca04e9.0 @hashgraph/sdk @walletconnect/modal
67
+ npm install @hashgraph/hedera-wallet-connect @hashgraph/sdk @walletconnect/modal
68
68
  ```
69
69
 
70
70
  2. Initialize dApp Connector
@@ -226,12 +226,10 @@ refer to how to send transactions to wallets using the `hedera:(mainnet|testnet)
226
226
  While minimal, the main breaking changes are:
227
227
 
228
228
  - remove WalletConnect v1 modals
229
-
230
229
  - these are very old, though in the spirit of semver, we kept the dependency until this
231
230
  library's v2 release
232
231
 
233
232
  - remove setting node id's within this library for transactions
234
-
235
233
  - initially, a transaction created by the Hedera Javascript SDK needed to have one or more
236
234
  consensus node ids set to be able to serialize into bytes, sent over a network, and
237
235
  deserialized by the SDK
@@ -146,7 +146,15 @@ export class DAppSigner {
146
146
  * @returns transaction - `Transaction` object with signature
147
147
  */
148
148
  async signTransaction(transaction) {
149
- const transactionBody = transactionToTransactionBody(transaction);
149
+ var _a, _b;
150
+ // Ensure transaction is frozen with node account IDs before signing
151
+ // This is required so the transaction can be executed later by any client
152
+ if (!transaction.isFrozen()) {
153
+ transaction.freezeWith(this._getHederaClient());
154
+ }
155
+ // Extract the first node account ID from the frozen transaction to preserve it in the transaction body
156
+ const nodeAccountId = (_b = (_a = transaction.nodeAccountIds) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null;
157
+ const transactionBody = transactionToTransactionBody(transaction, nodeAccountId);
150
158
  if (!transactionBody)
151
159
  throw new Error('Failed to serialize transaction body');
152
160
  const transactionBodyBase64 = transactionBodyToBase64String(transactionBody);
@@ -158,9 +166,44 @@ export class DAppSigner {
158
166
  },
159
167
  });
160
168
  const sigMap = base64StringToSignatureMap(signatureMap);
161
- const bodyBytes = base64StringToUint8Array(transactionBodyBase64);
162
- const bytes = proto.Transaction.encode({ bodyBytes, sigMap }).finish();
163
- return Transaction.fromBytes(bytes);
169
+ // Get the original transaction bytes to preserve the full transaction structure
170
+ // including all node account IDs
171
+ const originalTransactionBytes = transaction.toBytes();
172
+ const originalTransactionList = proto.TransactionList.decode(originalTransactionBytes);
173
+ // Add the signature to all transactions in the list
174
+ // Each transaction in the list corresponds to a different node
175
+ const signedTransactionList = originalTransactionList.transactionList.map((tx) => {
176
+ // Check if the transaction has signedTransactionBytes (frozen transactions)
177
+ if (tx.signedTransactionBytes) {
178
+ // Decode the SignedTransaction to access the bodyBytes and existing sigMap
179
+ const signedTx = proto.SignedTransaction.decode(tx.signedTransactionBytes);
180
+ const existingSigMap = signedTx.sigMap || proto.SignatureMap.create({});
181
+ // Merge the new signatures with existing signatures
182
+ const mergedSigPairs = [...(existingSigMap.sigPair || []), ...(sigMap.sigPair || [])];
183
+ // Create updated SignedTransaction with merged signatures
184
+ const updatedSignedTx = proto.SignedTransaction.encode({
185
+ bodyBytes: signedTx.bodyBytes,
186
+ sigMap: proto.SignatureMap.create({
187
+ sigPair: mergedSigPairs,
188
+ }),
189
+ }).finish();
190
+ return {
191
+ signedTransactionBytes: updatedSignedTx,
192
+ };
193
+ }
194
+ else {
195
+ // Transaction has bodyBytes and sigMap at the top level (not frozen)
196
+ const existingSigMap = tx.sigMap || proto.SignatureMap.create({});
197
+ // Merge the new signatures with existing signatures
198
+ const mergedSigPairs = [...(existingSigMap.sigPair || []), ...(sigMap.sigPair || [])];
199
+ return Object.assign(Object.assign({}, tx), { sigMap: Object.assign(Object.assign({}, existingSigMap), { sigPair: mergedSigPairs }) });
200
+ }
201
+ });
202
+ // Encode the signed transaction list back to bytes
203
+ const signedBytes = proto.TransactionList.encode({
204
+ transactionList: signedTransactionList,
205
+ }).finish();
206
+ return Transaction.fromBytes(signedBytes);
164
207
  }
165
208
  async _tryExecuteTransactionRequest(request) {
166
209
  try {
@@ -1,4 +1,4 @@
1
- import { AccountId, PublicKey, Transaction, LedgerId, Query, SignerSignature } from '@hashgraph/sdk';
1
+ import { AccountId, PublicKey, PrivateKey, Transaction, LedgerId, Query, SignerSignature } from '@hashgraph/sdk';
2
2
  import { ProposalTypes, SessionTypes } from '@walletconnect/types';
3
3
  import { proto } from '@hashgraph/proto';
4
4
  /**
@@ -273,3 +273,35 @@ export declare const accountAndLedgerFromSession: (session: SessionTypes.Struct)
273
273
  network: LedgerId;
274
274
  account: AccountId;
275
275
  }[];
276
+ /**
277
+ * Adds an additional signature to an already-signed transaction.
278
+ *
279
+ * This function is critical for multi-signature workflows where a transaction
280
+ * has already been signed by one party (e.g., via WalletConnect) and needs
281
+ * an additional signature from another party (e.g., backend co-signer).
282
+ *
283
+ * IMPORTANT: The standard SDK `.sign()` method does NOT work on reconstructed
284
+ * transactions because it clears the `_transactions` array (Transaction.js:825),
285
+ * which deletes existing signatures. This function uses proto-level manipulation
286
+ * to preserve all existing signatures while adding new ones.
287
+ *
288
+ * This implementation mirrors the signature merging pattern used in
289
+ * `DAppSigner.signTransaction()` (DAppSigner.ts:248-273).
290
+ *
291
+ * @param transaction - Transaction that already has one or more signatures
292
+ * @param privateKey - Private key to create additional signature
293
+ * @returns New transaction instance with all signatures (existing + new)
294
+ *
295
+ * @example
296
+ * ```typescript
297
+ * // Wallet signs first
298
+ * const walletSigned = await signer.signTransaction(transaction)
299
+ *
300
+ * // Backend adds signature
301
+ * const fullySigned = await addSignatureToTransaction(walletSigned, backendPrivateKey)
302
+ *
303
+ * // Now has both signatures
304
+ * await fullySigned.execute(client)
305
+ * ```
306
+ */
307
+ export declare function addSignatureToTransaction<T extends Transaction>(transaction: T, privateKey: PublicKey | PrivateKey): Promise<T>;
@@ -18,7 +18,7 @@
18
18
  *
19
19
  */
20
20
  import { Buffer } from 'buffer';
21
- import { AccountId, Transaction, LedgerId, Query, } from '@hashgraph/sdk';
21
+ import { AccountId, PublicKey, Transaction, LedgerId, Query, } from '@hashgraph/sdk';
22
22
  import { proto } from '@hashgraph/proto';
23
23
  /**
24
24
  * Converts `Transaction` to a Base64-string.
@@ -418,3 +418,93 @@ export const accountAndLedgerFromSession = (session) => {
418
418
  };
419
419
  });
420
420
  };
421
+ /**
422
+ * Adds an additional signature to an already-signed transaction.
423
+ *
424
+ * This function is critical for multi-signature workflows where a transaction
425
+ * has already been signed by one party (e.g., via WalletConnect) and needs
426
+ * an additional signature from another party (e.g., backend co-signer).
427
+ *
428
+ * IMPORTANT: The standard SDK `.sign()` method does NOT work on reconstructed
429
+ * transactions because it clears the `_transactions` array (Transaction.js:825),
430
+ * which deletes existing signatures. This function uses proto-level manipulation
431
+ * to preserve all existing signatures while adding new ones.
432
+ *
433
+ * This implementation mirrors the signature merging pattern used in
434
+ * `DAppSigner.signTransaction()` (DAppSigner.ts:248-273).
435
+ *
436
+ * @param transaction - Transaction that already has one or more signatures
437
+ * @param privateKey - Private key to create additional signature
438
+ * @returns New transaction instance with all signatures (existing + new)
439
+ *
440
+ * @example
441
+ * ```typescript
442
+ * // Wallet signs first
443
+ * const walletSigned = await signer.signTransaction(transaction)
444
+ *
445
+ * // Backend adds signature
446
+ * const fullySigned = await addSignatureToTransaction(walletSigned, backendPrivateKey)
447
+ *
448
+ * // Now has both signatures
449
+ * await fullySigned.execute(client)
450
+ * ```
451
+ */
452
+ export async function addSignatureToTransaction(transaction, privateKey) {
453
+ // Step 1: Get original transaction bytes (preserves ALL existing signatures)
454
+ const originalBytes = transaction.toBytes();
455
+ const originalList = proto.TransactionList.decode(originalBytes);
456
+ // Step 2: Extract transaction body bytes to sign
457
+ const firstTransaction = originalList.transactionList[0];
458
+ let bodyBytes;
459
+ if (firstTransaction.signedTransactionBytes) {
460
+ const signedTx = proto.SignedTransaction.decode(firstTransaction.signedTransactionBytes);
461
+ bodyBytes = signedTx.bodyBytes;
462
+ }
463
+ else {
464
+ bodyBytes = firstTransaction.bodyBytes;
465
+ }
466
+ // Step 3: Create signature with the provided private key
467
+ const publicKey = privateKey instanceof PublicKey ? privateKey : privateKey.publicKey;
468
+ const signature = privateKey instanceof PublicKey
469
+ ? (() => {
470
+ throw new Error('Cannot sign with PublicKey, PrivateKey required');
471
+ })()
472
+ : await privateKey.sign(bodyBytes);
473
+ // Step 4: Add new signature to ALL transactions in the list
474
+ // Each transaction in the list corresponds to a different node
475
+ const signedTransactionList = originalList.transactionList.map((tx) => {
476
+ // Create the new signature pair using SDK's internal method
477
+ const newSigPair = publicKey._toProtobufSignature(signature);
478
+ // Check if the transaction has signedTransactionBytes (frozen transactions)
479
+ if (tx.signedTransactionBytes) {
480
+ // Decode the SignedTransaction to access the bodyBytes and existing sigMap
481
+ const signedTx = proto.SignedTransaction.decode(tx.signedTransactionBytes);
482
+ const existingSigMap = signedTx.sigMap || proto.SignatureMap.create({});
483
+ // Merge existing signatures with new signature
484
+ const mergedSigPairs = [...(existingSigMap.sigPair || []), newSigPair];
485
+ // Create updated SignedTransaction with merged signatures
486
+ const updatedSignedTx = proto.SignedTransaction.encode({
487
+ bodyBytes: signedTx.bodyBytes,
488
+ sigMap: proto.SignatureMap.create({
489
+ sigPair: mergedSigPairs,
490
+ }),
491
+ }).finish();
492
+ return {
493
+ signedTransactionBytes: updatedSignedTx,
494
+ };
495
+ }
496
+ else {
497
+ // Transaction has bodyBytes and sigMap at the top level (not frozen)
498
+ const existingSigMap = tx.sigMap || proto.SignatureMap.create({});
499
+ // Merge existing signatures with new signature
500
+ const mergedSigPairs = [...(existingSigMap.sigPair || []), newSigPair];
501
+ return Object.assign(Object.assign({}, tx), { sigMap: Object.assign(Object.assign({}, existingSigMap), { sigPair: mergedSigPairs }) });
502
+ }
503
+ });
504
+ // Step 5: Encode the signed transaction list back to bytes
505
+ const signedBytes = proto.TransactionList.encode({
506
+ transactionList: signedTransactionList,
507
+ }).finish();
508
+ // Step 6: Return reconstructed transaction with all signatures
509
+ return Transaction.fromBytes(signedBytes);
510
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hashgraph/hedera-wallet-connect",
3
- "version": "2.0.4-canary.5aac7b5.0",
3
+ "version": "2.0.4-canary.6b57886.0",
4
4
  "description": "A library to facilitate integrating Hedera with WalletConnect",
5
5
  "repository": {
6
6
  "type": "git",