@hashgraph/hedera-wallet-connect 2.0.4-canary.e767129.0 → 2.0.5-canary.7b24ac9.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
@@ -211,6 +211,141 @@ createAppKit({
211
211
  - [Hedera Wallet Example by Hgraph](https://github.com/hgraph-io/hedera-wallet)
212
212
  - <em>[Add an example, demo, or tool here](https://github.com/hashgraph/hedera-wallet-connect/pulls)</em>
213
213
 
214
+ # Multi-Signature Transactions
215
+
216
+ Multi-signature (multi-sig) workflows allow multiple parties to sign a single transaction before it's executed on the Hedera network. This is commonly used for:
217
+
218
+ - Treasury operations requiring approval from multiple parties
219
+ - Escrow services
220
+ - Joint accounts
221
+ - Backend co-signing for additional security
222
+
223
+ ## Using `hedera_signTransaction` for Multi-Sig Workflows
224
+
225
+ The `hedera_signTransaction` method allows you to collect a signature from a wallet without immediately executing the transaction. This signature can then be combined with additional signatures (such as from a backend service) before final execution.
226
+
227
+ ### Example: Frontend Wallet Signature + Backend Co-Signature
228
+
229
+ This example demonstrates a common pattern where a user signs a transaction in their wallet, and then a backend service adds its signature before executing the transaction.
230
+
231
+ #### Step 1: Create and Sign Transaction on Frontend
232
+
233
+ ```typescript
234
+ import { DAppConnector, HederaJsonRpcMethod } from '@hashgraph/hedera-wallet-connect'
235
+ import { TransferTransaction, Hbar, AccountId } from '@hashgraph/sdk'
236
+
237
+ // Initialize your DAppConnector (see Getting Started section)
238
+ const dAppConnector = new DAppConnector(/* ... */)
239
+
240
+ // Create a transaction
241
+ const transaction = new TransferTransaction()
242
+ .addHbarTransfer(userAccountId, new Hbar(-10))
243
+ .addHbarTransfer(recipientAccountId, new Hbar(10))
244
+ .setTransactionMemo('Multi-sig transfer')
245
+
246
+ // Request signature from wallet (does NOT execute)
247
+ const signer = dAppConnector.getSigner(userAccountId)
248
+ const signedTransaction = await signer.signTransaction(transaction)
249
+
250
+ // Convert signed transaction to bytes for transmission to backend
251
+ const signedTransactionBytes = signedTransaction.toBytes()
252
+
253
+ // Send to backend
254
+ const response = await fetch('/api/execute-transaction', {
255
+ method: 'POST',
256
+ headers: { 'Content-Type': 'application/json' },
257
+ body: JSON.stringify({
258
+ signedTransaction: Buffer.from(signedTransactionBytes).toString('base64'),
259
+ }),
260
+ })
261
+
262
+ const result = await response.json()
263
+ console.log('Transaction executed:', result.transactionId)
264
+ ```
265
+
266
+ #### Step 2: Add Backend Signature and Execute
267
+
268
+ On your backend, use the `addSignatureToTransaction` utility to add your server's signature:
269
+
270
+ ```typescript
271
+ import { Transaction, PrivateKey, Client } from '@hashgraph/sdk'
272
+ import { addSignatureToTransaction } from '@hashgraph/hedera-wallet-connect'
273
+
274
+ // Backend API endpoint
275
+ app.post('/api/execute-transaction', async (req, res) => {
276
+ try {
277
+ // Reconstruct transaction from bytes
278
+ const signedTransactionBytes = Buffer.from(req.body.signedTransaction, 'base64')
279
+ const signedTransaction = Transaction.fromBytes(signedTransactionBytes)
280
+
281
+ // Load your backend private key (store securely!)
282
+ const backendPrivateKey = PrivateKey.fromStringED25519(process.env.BACKEND_PRIVATE_KEY)
283
+
284
+ // Add backend signature to the transaction
285
+ const fullySignedTransaction = await addSignatureToTransaction(
286
+ signedTransaction,
287
+ backendPrivateKey,
288
+ )
289
+
290
+ // Execute the fully signed transaction
291
+ const client = Client.forTestnet() // or Client.forMainnet()
292
+ client.setOperator(backendAccountId, backendPrivateKey)
293
+
294
+ const txResponse = await fullySignedTransaction.execute(client)
295
+ const receipt = await txResponse.getReceipt(client)
296
+
297
+ res.json({
298
+ success: true,
299
+ transactionId: txResponse.transactionId.toString(),
300
+ status: receipt.status.toString(),
301
+ })
302
+ } catch (error) {
303
+ console.error('Error executing transaction:', error)
304
+ res.status(500).json({ error: error.message })
305
+ }
306
+ })
307
+ ```
308
+
309
+ ### Important Notes
310
+
311
+ 1. **Transaction Must Be Frozen**: Before signing, ensure your transaction is frozen.
312
+
313
+ 2. **Signature Order**: Signatures can be added in any order. Hedera validates that all required signatures are present when the transaction is executed.
314
+
315
+ 3. **Security Considerations**:
316
+ - Never expose backend private keys to the frontend
317
+ - Validate transaction contents on the backend before adding your signature
318
+ - Implement proper authentication and authorization
319
+ - Consider implementing transaction limits and approval workflows
320
+
321
+ 4. **Multiple Signatures**: You can add more than two signatures using the same pattern:
322
+
323
+ ```typescript
324
+ // Add multiple signatures sequentially
325
+ let signedTx = await addSignatureToTransaction(transaction, privateKey1)
326
+ signedTx = await addSignatureToTransaction(signedTx, privateKey2)
327
+ signedTx = await addSignatureToTransaction(signedTx, privateKey3)
328
+
329
+ // Execute with all signatures
330
+ await signedTx.execute(client)
331
+ ```
332
+
333
+ 5. **Threshold Keys**: For accounts with threshold key structures, ensure you collect enough signatures to meet the threshold requirement before execution.
334
+
335
+ ### Alternative: Using `hedera_signAndExecuteTransaction`
336
+
337
+ If you don't need backend co-signing and want the wallet to execute the transaction immediately:
338
+
339
+ ```typescript
340
+ // This signs AND executes in one call
341
+ const result = await dAppConnector.signAndExecuteTransaction({
342
+ signerAccountId: `hedera:testnet:${userAccountId}`,
343
+ transactionList: transactionToBase64String(transaction),
344
+ })
345
+ ```
346
+
347
+ Use `hedera_signTransaction` when you need to collect multiple signatures. Use `hedera_signAndExecuteTransaction` when the wallet's signature alone is sufficient to execute the transaction.
348
+
214
349
  # Hedera Wallets
215
350
 
216
351
  - [Hashpack](https://hashpack.app/)
@@ -226,12 +361,10 @@ refer to how to send transactions to wallets using the `hedera:(mainnet|testnet)
226
361
  While minimal, the main breaking changes are:
227
362
 
228
363
  - remove WalletConnect v1 modals
229
-
230
364
  - these are very old, though in the spirit of semver, we kept the dependency until this
231
365
  library's v2 release
232
366
 
233
367
  - remove setting node id's within this library for transactions
234
-
235
368
  - initially, a transaction created by the Hedera Javascript SDK needed to have one or more
236
369
  consensus node ids set to be able to serialize into bytes, sent over a network, and
237
370
  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,8 @@ 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
+ * Uses proto-level manipulation to preserve existing signatures.
279
+ */
280
+ export declare function addSignatureToTransaction<T extends Transaction>(transaction: T, privateKey: PrivateKey): Promise<T>;
@@ -418,3 +418,44 @@ export const accountAndLedgerFromSession = (session) => {
418
418
  };
419
419
  });
420
420
  };
421
+ /**
422
+ * Adds an additional signature to an already-signed transaction.
423
+ * Uses proto-level manipulation to preserve existing signatures.
424
+ */
425
+ export async function addSignatureToTransaction(transaction, privateKey) {
426
+ const originalBytes = transaction.toBytes();
427
+ const originalList = proto.TransactionList.decode(originalBytes);
428
+ const firstTransaction = originalList.transactionList[0];
429
+ let bodyBytes;
430
+ if (firstTransaction.signedTransactionBytes) {
431
+ const signedTx = proto.SignedTransaction.decode(firstTransaction.signedTransactionBytes);
432
+ bodyBytes = signedTx.bodyBytes;
433
+ }
434
+ else {
435
+ bodyBytes = firstTransaction.bodyBytes;
436
+ }
437
+ const signature = await privateKey.sign(bodyBytes);
438
+ const publicKey = privateKey.publicKey;
439
+ const signedTransactionList = originalList.transactionList.map((tx) => {
440
+ const newSigPair = publicKey._toProtobufSignature(signature);
441
+ if (tx.signedTransactionBytes) {
442
+ const signedTx = proto.SignedTransaction.decode(tx.signedTransactionBytes);
443
+ const existingSigMap = signedTx.sigMap || proto.SignatureMap.create({});
444
+ const mergedSigPairs = [...(existingSigMap.sigPair || []), newSigPair];
445
+ const updatedSignedTx = proto.SignedTransaction.encode({
446
+ bodyBytes: signedTx.bodyBytes,
447
+ sigMap: proto.SignatureMap.create({ sigPair: mergedSigPairs }),
448
+ }).finish();
449
+ return { signedTransactionBytes: updatedSignedTx };
450
+ }
451
+ else {
452
+ const existingSigMap = tx.sigMap || proto.SignatureMap.create({});
453
+ const mergedSigPairs = [...(existingSigMap.sigPair || []), newSigPair];
454
+ return Object.assign(Object.assign({}, tx), { sigMap: Object.assign(Object.assign({}, existingSigMap), { sigPair: mergedSigPairs }) });
455
+ }
456
+ });
457
+ const signedBytes = proto.TransactionList.encode({
458
+ transactionList: signedTransactionList,
459
+ }).finish();
460
+ return Transaction.fromBytes(signedBytes);
461
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hashgraph/hedera-wallet-connect",
3
- "version": "2.0.4-canary.e767129.0",
3
+ "version": "2.0.5-canary.7b24ac9.0",
4
4
  "description": "A library to facilitate integrating Hedera with WalletConnect",
5
5
  "repository": {
6
6
  "type": "git",