@dynamic-labs/bitcoin 5.3.1 → 5.4.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/CHANGELOG.md CHANGED
@@ -1,4 +1,43 @@
1
1
 
2
+ ## [5.4.0](https://github.com/dynamic-labs/dynamic-auth/compare/v5.3.2...v5.4.0) (2026-08-25)
3
+
4
+
5
+ ### Features
6
+
7
+ * **aleo:** wire up signMessage on the WaaS Aleo connector ([#12235](https://github.com/dynamic-labs/dynamic-auth/issues/12235)) ([e56cbf5](https://github.com/dynamic-labs/dynamic-auth/commit/e56cbf5546bdcc21ce916dece43ae36dcea9b808))
8
+ * **waas:** thread isRawScalarImport through importPrivateKey for ed25519 chains ([#12220](https://github.com/dynamic-labs/dynamic-auth/issues/12220)) ([ced9efa](https://github.com/dynamic-labs/dynamic-auth/commit/ced9efac5fa5a4a1280383419f56fe22733722cd))
9
+ * **webview:** add policies message contract and controller ([#12248](https://github.com/dynamic-labs/dynamic-auth/issues/12248)) ([8557c1e](https://github.com/dynamic-labs/dynamic-auth/commit/8557c1ed3861e12f1f06b2bb610184813b577daa))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **bitcoin:** pass the wallet's address type when signing ([#12265](https://github.com/dynamic-labs/dynamic-auth/issues/12265)) ([b8a8eab](https://github.com/dynamic-labs/dynamic-auth/commit/b8a8eab7e4cc294cc307fb10ea6814e76111a274))
15
+ * remediate high-severity tar vulnerability and prune a dead override ([#12268](https://github.com/dynamic-labs/dynamic-auth/issues/12268)) ([6b1b343](https://github.com/dynamic-labs/dynamic-auth/commit/6b1b343e7947e8da30012446677e342ae9bfe14a))
16
+ * **waas-svm:** fix retry-classification and environmentId gaps in [#12240](https://github.com/dynamic-labs/dynamic-auth/issues/12240) ([#12247](https://github.com/dynamic-labs/dynamic-auth/issues/12247)) ([e79cecc](https://github.com/dynamic-labs/dynamic-auth/commit/e79ceccd424c48c3a7f45fdd6ea288c269ac0171))
17
+ * **wallet-connect:** reopen WalletConnect IndexedDB when the browser closes the connection ([#12262](https://github.com/dynamic-labs/dynamic-auth/issues/12262)) ([1a20dde](https://github.com/dynamic-labs/dynamic-auth/commit/1a20ddeaeec590ae3390c9e47ef3c14738379b83))
18
+
19
+
20
+ ### Performance Improvements
21
+
22
+ * **ethereum-gasless-core:** parallel 7702 auth signing and long-poll sponsored-tx status ([#12225](https://github.com/dynamic-labs/dynamic-auth/issues/12225)) ([53ca916](https://github.com/dynamic-labs/dynamic-auth/commit/53ca91668f8dd839c52d55a0e5d7bb5257f7f415))
23
+
24
+ ### [5.3.2](https://github.com/dynamic-labs/dynamic-auth/compare/v5.3.1...v5.3.2) (2026-08-19)
25
+
26
+
27
+ ### Features
28
+
29
+ * **bitcoin:** add allowedSighash to embedded wallet signPsbt ([#12233](https://github.com/dynamic-labs/dynamic-auth/issues/12233)) ([d0ff829](https://github.com/dynamic-labs/dynamic-auth/commit/d0ff8291a3d1771d5ca356172acc10af94349b15))
30
+ * **bitcoin:** forward signingIndexes to embedded PSBT signing ([#12211](https://github.com/dynamic-labs/dynamic-auth/issues/12211)) ([733da3d](https://github.com/dynamic-labs/dynamic-auth/commit/733da3d32f39a9ae6d813df84fcff6350ba12eb5))
31
+ * expose createOfflineRecoveryShares SDK function in webview and legacy client ([#12239](https://github.com/dynamic-labs/dynamic-auth/issues/12239)) ([c8056e8](https://github.com/dynamic-labs/dynamic-auth/commit/c8056e8d5a1c66ac0146b046bc7cc198de118f0b))
32
+
33
+
34
+ ### Bug Fixes
35
+
36
+ * **bitcoin:** parse PSBTs with the active network's address encoding ([#12219](https://github.com/dynamic-labs/dynamic-auth/issues/12219)) ([8b151e2](https://github.com/dynamic-labs/dynamic-auth/commit/8b151e23440094cfa0d48728e1d0f19de567ca05))
37
+ * **bitcoin:** stop ensureActiveAccount from resetting a switched network ([#12215](https://github.com/dynamic-labs/dynamic-auth/issues/12215)) ([86668b7](https://github.com/dynamic-labs/dynamic-auth/commit/86668b79d23cd89b7e96518551cdaecab7dd5ce3))
38
+ * remediate high-severity dependency vulnerabilities ([#12238](https://github.com/dynamic-labs/dynamic-auth/issues/12238)) ([a4fc78c](https://github.com/dynamic-labs/dynamic-auth/commit/a4fc78ca27ee44ce6a1038c4e0d07f55528b4994))
39
+ * **waas-svm:** retry SVM gas sponsorship before falling back, then throw ([#12240](https://github.com/dynamic-labs/dynamic-auth/issues/12240)) ([f599a7a](https://github.com/dynamic-labs/dynamic-auth/commit/f599a7af55727e06373772acf574ee7c43e56bbb))
40
+
2
41
  ### [5.3.1](https://github.com/dynamic-labs/dynamic-auth/compare/v5.3.0...v5.3.1) (2026-08-14)
3
42
 
4
43
  ## [5.3.0](https://github.com/dynamic-labs/dynamic-auth/compare/v5.2.0...v5.3.0) (2026-08-12)
package/package.cjs CHANGED
@@ -3,6 +3,6 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "5.3.1";
6
+ var version = "5.4.0";
7
7
 
8
8
  exports.version = version;
package/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use client'
2
- var version = "5.3.1";
2
+ var version = "5.4.0";
3
3
 
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-labs/bitcoin",
3
- "version": "5.3.1",
3
+ "version": "5.4.0",
4
4
  "description": "A React SDK for implementing wallet web3 authentication and authorization to your website.",
5
5
  "author": "Dynamic Labs, Inc.",
6
6
  "license": "MIT",
@@ -18,24 +18,24 @@
18
18
  },
19
19
  "homepage": "https://www.dynamic.xyz/",
20
20
  "dependencies": {
21
- "@dynamic-labs-wallet/browser-wallet-client": "1.0.92",
21
+ "@dynamic-labs-wallet/browser-wallet-client": "1.0.106",
22
22
  "@dynamic-labs-wallet/forward-mpc-client": "1.0.1",
23
23
  "@bitcoinerlab/secp256k1": "1.1.1",
24
24
  "@btckit/types": "0.0.19",
25
- "@dynamic-labs/sdk-api-core": "0.0.1118",
25
+ "@dynamic-labs/sdk-api-core": "0.12.0",
26
26
  "@wallet-standard/app": "1.0.1",
27
27
  "@wallet-standard/base": "1.0.1",
28
28
  "bitcoinjs-lib": "6.1.5",
29
29
  "ecpair": "2.1.0",
30
30
  "sats-connect": "4.2.1",
31
31
  "jsontokens": "4.0.1",
32
- "@dynamic-labs/assert-package-version": "5.3.1",
33
- "@dynamic-labs/logger": "5.3.1",
34
- "@dynamic-labs/types": "5.3.1",
35
- "@dynamic-labs/utils": "5.3.1",
36
- "@dynamic-labs/waas": "5.3.1",
37
- "@dynamic-labs/wallet-book": "5.3.1",
38
- "@dynamic-labs/wallet-connector-core": "5.3.1",
32
+ "@dynamic-labs/assert-package-version": "5.4.0",
33
+ "@dynamic-labs/logger": "5.4.0",
34
+ "@dynamic-labs/types": "5.4.0",
35
+ "@dynamic-labs/utils": "5.4.0",
36
+ "@dynamic-labs/waas": "5.4.0",
37
+ "@dynamic-labs/wallet-book": "5.4.0",
38
+ "@dynamic-labs/wallet-connector-core": "5.4.0",
39
39
  "eventemitter3": "5.0.1"
40
40
  },
41
41
  "peerDependencies": {}
@@ -17,6 +17,7 @@ var BitcoinWalletConnector = require('../BitcoinWalletConnector.cjs');
17
17
  require('@wallet-standard/app');
18
18
  require('jsontokens');
19
19
  var btcToSatoshis = require('../../utils/btcToSatoshis/btcToSatoshis.cjs');
20
+ var getBitcoinNetwork = require('../../utils/getBitcoinNetwork/getBitcoinNetwork.cjs');
20
21
  var PsbtParser = require('../../utils/psbtParser/PsbtParser.cjs');
21
22
  var BitcoinUiTransaction = require('../../utils/BitcoinUiTransaction/BitcoinUiTransaction.cjs');
22
23
 
@@ -135,6 +136,32 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
135
136
  : 'mainnet');
136
137
  });
137
138
  }
139
+ /**
140
+ * The address type the wallet's key was derived under, read back from the
141
+ * address itself.
142
+ *
143
+ * `bitcoinConfig.addressType` is the only thing that selects a BTC
144
+ * derivation path downstream (`getMPCChainConfig` throws without it), and a
145
+ * wallet created by an import carries no server-side derivation metadata to
146
+ * fall back on — so signing an imported wallet failed while a
147
+ * Dynamic-created one signed fine. The address already encodes the answer:
148
+ * witness v0 is P2WPKH (native segwit), v1 is P2TR (taproot), which are
149
+ * exactly the two types `importPrivateKey` accepts.
150
+ *
151
+ * Falls back to native segwit for an address that is not bech32 (a legacy
152
+ * P2PKH/P2SH encoding cannot come from this connector's own import or
153
+ * create paths) so a decode failure can never make signing impossible.
154
+ */
155
+ getAddressTypeForSigning(walletAddress) {
156
+ try {
157
+ const { version } = bitcoinjsLib.address.fromBech32(walletAddress);
158
+ return (version === 1 ? 'taproot' : 'native_segwit');
159
+ }
160
+ catch (error) {
161
+ this.logger.warn('Could not read the address type from the wallet address; assuming native segwit', { error, walletAddress });
162
+ return 'native_segwit';
163
+ }
164
+ }
138
165
  supportsNetworkSwitching() {
139
166
  return true;
140
167
  }
@@ -161,26 +188,65 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
161
188
  });
162
189
  }
163
190
  /**
164
- * Finds the credential that owns the active address, either as its primary
165
- * (mainnet) address or as one of its network-tagged additional addresses.
191
+ * Finds the credential that owns `address`, either as its primary (mainnet)
192
+ * address or as one of its network-tagged additional addresses.
166
193
  *
167
194
  * This connector is shared across every Bitcoin wallet of the user —
168
195
  * `verifiedCredentials` holds them all — so the owning credential must be
169
- * looked up by the active address. Reading `verifiedCredential` instead
170
- * would always answer with the user's *first* wallet.
196
+ * looked up by address. Reading `verifiedCredential` instead would always
197
+ * answer with the user's *first* wallet.
171
198
  */
172
- findCredentialForActiveAddress() {
199
+ findCredentialForAddress(address) {
173
200
  var _a;
174
- const { activeAccountAddress } = this;
175
- if (!activeAccountAddress) {
201
+ if (!address) {
176
202
  return undefined;
177
203
  }
178
204
  return ((_a = this.verifiedCredentials) !== null && _a !== void 0 ? _a : []).find((credential) => {
179
205
  var _a;
180
- return (credential === null || credential === void 0 ? void 0 : credential.address) === activeAccountAddress ||
181
- ((_a = credential === null || credential === void 0 ? void 0 : credential.walletAdditionalAddresses) !== null && _a !== void 0 ? _a : []).some((additionalAddress) => additionalAddress.address === activeAccountAddress);
206
+ return (credential === null || credential === void 0 ? void 0 : credential.address) === address ||
207
+ ((_a = credential === null || credential === void 0 ? void 0 : credential.walletAdditionalAddresses) !== null && _a !== void 0 ? _a : []).some((additionalAddress) => additionalAddress.address === address);
182
208
  });
183
209
  }
210
+ /** @see findCredentialForAddress */
211
+ findCredentialForActiveAddress() {
212
+ return this.findCredentialForAddress(this.activeAccountAddress);
213
+ }
214
+ /**
215
+ * True when `accountAddress` belongs to the same wallet as the currently
216
+ * active address — including when the active address is a *different*
217
+ * network-tagged encoding of that same wallet (e.g. the testnet address
218
+ * set by switchNetwork, compared against the wallet's primary mainnet
219
+ * address).
220
+ *
221
+ * Used by getWalletClientByAddress to avoid clobbering an explicit
222
+ * switchNetwork: callers like ensureActiveAccount re-assert the wallet's
223
+ * primary address before every send/sign, and setting activeAccountAddress
224
+ * back to that primary address would silently undo the network switch —
225
+ * UTXO/PSBT/broadcast calls would then run against the wrong (likely
226
+ * unfunded) encoding while getNetwork still reports the switched network.
227
+ *
228
+ * Compares the two resolved credential *objects* by reference rather than
229
+ * by id: both lookups run against the same verifiedCredentials array in
230
+ * the same synchronous call, so `.find()` returns the identical element
231
+ * for both addresses whenever they belong to the same wallet. Comparing
232
+ * `.id` instead would be wrong whenever accountAddress doesn't resolve to
233
+ * any known credential yet (e.g. a brand-new wallet not yet synced) —
234
+ * `undefined === undefined` would falsely read as "same wallet".
235
+ */
236
+ isSameWalletAddress(accountAddress) {
237
+ if (!this.activeAccountAddress) {
238
+ return false;
239
+ }
240
+ if (accountAddress === this.activeAccountAddress) {
241
+ return true;
242
+ }
243
+ const activeCredential = this.findCredentialForActiveAddress();
244
+ if (!activeCredential) {
245
+ return false;
246
+ }
247
+ const candidateCredential = this.findCredentialForAddress(accountAddress);
248
+ return (Boolean(candidateCredential) && candidateCredential === activeCredential);
249
+ }
184
250
  /**
185
251
  * Wallet identity: keyshares/wallet records are keyed by the wallet's
186
252
  * primary (mainnet) address. On testnet the active address is the same
@@ -302,7 +368,11 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
302
368
  * Signs a Partially Signed Bitcoin Transaction (PSBT) for embedded wallets
303
369
  *
304
370
  * Embedded wallets only support PSBT format and automatically sign all inputs
305
- * that belong to the wallet address. Always uses SIGHASH_ALL (0x01).
371
+ * that belong to the wallet address, unless `signingIndexes` narrows that set.
372
+ *
373
+ * The sighash type is read from the PSBT itself, falling back to
374
+ * SIGHASH_DEFAULT (0x00) for Taproot and SIGHASH_ALL (0x01) otherwise.
375
+ * `allowedSighash` restricts which declared types are accepted.
306
376
  *
307
377
  * @param request - The PSBT signing request. Only unsignedPsbtBase64 is required.
308
378
  * @returns The signed (but not finalized) PSBT response
@@ -331,12 +401,14 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
331
401
  accountAddress: walletIdentityAddress,
332
402
  });
333
403
  const signedTransaction = yield walletClient.signTransaction({
404
+ allowedSighash: request.allowedSighash,
334
405
  authToken: (_d = this.getAuthToken) === null || _d === void 0 ? void 0 : _d.call(this),
335
406
  elevatedAccessToken,
336
407
  mfaToken,
337
408
  password,
338
409
  senderAddress: walletIdentityAddress,
339
410
  signedSessionId,
411
+ signingIndexes: request.signingIndexes,
340
412
  transaction: request.unsignedPsbtBase64,
341
413
  });
342
414
  return {
@@ -472,12 +544,18 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
472
544
  });
473
545
  }
474
546
  /**
475
- * Gets the wallet client for a specific account address and sets it as active
547
+ * Gets the wallet client for a specific account address and sets it as
548
+ * active — unless `accountAddress` is just a different network-tagged
549
+ * encoding of the wallet that's already active (see isSameWalletAddress),
550
+ * in which case the current (possibly switchNetwork'd) active address is
551
+ * left untouched.
476
552
  * @param accountAddress - The Bitcoin account address
477
553
  * @returns The WaaS wallet client instance
478
554
  */
479
555
  getWalletClientByAddress({ accountAddress }) {
480
- this.setActiveAccountAddress(accountAddress);
556
+ if (!this.isSameWalletAddress(accountAddress)) {
557
+ this.setActiveAccountAddress(accountAddress);
558
+ }
481
559
  return this.getWaasWalletClient();
482
560
  }
483
561
  /**
@@ -497,7 +575,7 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
497
575
  */
498
576
  validateActiveWallet(expectedAddress) {
499
577
  return _tslib.__awaiter(this, void 0, void 0, function* () {
500
- var _a, _b, _c, _d;
578
+ var _a;
501
579
  const walletClient = yield this.getWaasWalletClient();
502
580
  const targetWallet = yield walletClient.getWallet({
503
581
  accountAddress: expectedAddress,
@@ -511,8 +589,12 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
511
589
  const isWalletActive = walletConnectorCore.isSameAddress(targetWallet.accountAddress, this.activeAccountAddress || '', this.connectedChain);
512
590
  // The active address may be a per-network encoding of this same wallet
513
591
  // (one key, network-tagged additional addresses). Resetting it here would
514
- // silently un-switch the selected network.
515
- const isPerNetworkVariantOfWallet = (_d = (_c = (_b = this.verifiedCredential) === null || _b === void 0 ? void 0 : _b.walletAdditionalAddresses) === null || _c === void 0 ? void 0 : _c.some((addr) => addr.address === this.activeAccountAddress)) !== null && _d !== void 0 ? _d : false;
592
+ // silently un-switch the selected network. Resolved via isSameWalletAddress
593
+ // (which looks up the credential that actually owns the active address)
594
+ // rather than `verifiedCredential`, which is always the user's *first*
595
+ // wallet — using it here would wrongly reset the address for any other
596
+ // wallet the user validates.
597
+ const isPerNetworkVariantOfWallet = this.isSameWalletAddress(targetWallet.accountAddress);
516
598
  if (!isWalletActive && !isPerNetworkVariantOfWallet) {
517
599
  this.activeAccountAddress = targetWallet.accountAddress;
518
600
  }
@@ -547,10 +629,14 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
547
629
  accountAddress: this.getWalletIdentityAddress(),
548
630
  });
549
631
  const messageString = typeof message === 'string' ? message : message.raw;
632
+ const walletIdentityAddress = this.getWalletIdentityAddress();
550
633
  return walletClient.signMessage({
551
- accountAddress: this.getWalletIdentityAddress(),
634
+ accountAddress: walletIdentityAddress,
552
635
  authToken: (_e = this.getAuthToken) === null || _e === void 0 ? void 0 : _e.call(this),
553
- bitcoinConfig: { network: yield this.getBitcoinNetworkEnum() },
636
+ bitcoinConfig: {
637
+ addressType: this.getAddressTypeForSigning(walletIdentityAddress),
638
+ network: yield this.getBitcoinNetworkEnum(),
639
+ },
554
640
  context,
555
641
  elevatedAccessToken,
556
642
  message: messageString,
@@ -665,7 +751,7 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
665
751
  * @returns Parsed transaction data with inputs and outputs, or null if parsing fails
666
752
  */
667
753
  parsePsbt(psbtInput) {
668
- return PsbtParser.PsbtParser.parsePsbt(psbtInput);
754
+ return PsbtParser.PsbtParser.parsePsbt(psbtInput, this.getPsbtNetwork());
669
755
  }
670
756
  /**
671
757
  * Parse a PSBT or finalized transaction from hex format
@@ -673,7 +759,7 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
673
759
  * @returns Parsed transaction data with inputs and outputs, or null if parsing fails
674
760
  */
675
761
  parseHexPsbt(psbtHex) {
676
- return PsbtParser.PsbtParser.parseHexPsbt(psbtHex);
762
+ return PsbtParser.PsbtParser.parseHexPsbt(psbtHex, this.getPsbtNetwork());
677
763
  }
678
764
  /**
679
765
  * Parse a PSBT or finalized transaction from base64 format
@@ -681,7 +767,17 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
681
767
  * @returns Parsed transaction data with inputs and outputs, or null if parsing fails
682
768
  */
683
769
  parseBase64Psbt(psbtBase64) {
684
- return PsbtParser.PsbtParser.parseBase64Psbt(psbtBase64);
770
+ return PsbtParser.PsbtParser.parseBase64Psbt(psbtBase64, this.getPsbtNetwork());
771
+ }
772
+ /**
773
+ * bitcoinjs network for the address encoding the parsers should emit.
774
+ * Derived from the active address rather than activeNetworkId, which is
775
+ * in-memory only and can report mainnet while a testnet address is active.
776
+ */
777
+ getPsbtNetwork() {
778
+ return this.activeAccountAddress
779
+ ? getBitcoinNetwork.getBitcoinNetwork(this.activeAccountAddress)
780
+ : bitcoinjsLib.networks.bitcoin;
685
781
  }
686
782
  /**
687
783
  * Gets the public key for the active account
@@ -772,6 +868,7 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
772
868
  accountAddress: this.getWalletIdentityAddress(),
773
869
  authToken: (_d = this.getAuthToken) === null || _d === void 0 ? void 0 : _d.call(this),
774
870
  bitcoinConfig: {
871
+ addressType: this.getAddressTypeForSigning(this.getWalletIdentityAddress()),
775
872
  network: yield this.getBitcoinNetworkEnum(),
776
873
  },
777
874
  elevatedAccessToken,
@@ -92,13 +92,14 @@ declare const DynamicWaasBitcoinConnector_base: (abstract new (...args: any[]) =
92
92
  }): Promise<{
93
93
  shareSetId: string;
94
94
  }>;
95
- importPrivateKey({ privateKey, thresholdSignatureScheme, publicAddressCheck, addressType, legacyWalletId, password, }: {
95
+ importPrivateKey({ privateKey, thresholdSignatureScheme, publicAddressCheck, addressType, legacyWalletId, password, isRawScalarImport, }: {
96
96
  privateKey: string;
97
97
  thresholdSignatureScheme?: string | undefined;
98
98
  publicAddressCheck?: string | undefined;
99
99
  addressType?: string | undefined;
100
100
  legacyWalletId?: string | undefined;
101
101
  password?: string | undefined;
102
+ isRawScalarImport?: boolean | undefined;
102
103
  }): Promise<void>;
103
104
  migrateFromFireblocks({ deviceId, jwt, walletPassword, ncwStorage, expectedAddresses, coinTypes, password, }: {
104
105
  deviceId: string;
@@ -189,13 +190,30 @@ declare const DynamicWaasBitcoinConnector_base: (abstract new (...args: any[]) =
189
190
  getWalletRecoveryState({ accountAddress, }: {
190
191
  accountAddress: string;
191
192
  }): Promise<import("@dynamic-labs-wallet/browser-wallet-client").WalletRecoveryState>;
193
+ createOfflineRecoveryShares({ accountAddress, password, cloudProviderAccessToken, mfaToken, }: {
194
+ accountAddress: string;
195
+ password?: string | undefined;
196
+ cloudProviderAccessToken?: string | undefined;
197
+ mfaToken?: string | undefined;
198
+ }): Promise<void>;
192
199
  endSession(reason?: import("@dynamic-labs-sdk/client").LogoutReason | undefined): Promise<void>;
193
200
  getActiveAccountAddress(): Promise<string | undefined>;
194
201
  getConnectedAccounts(): Promise<string[]>;
195
202
  generateTraceId(): string;
196
203
  buildErrorInstrumentContext(error: unknown): {
197
204
  errorCode: string | import("@dynamic-labs/utils").ErrorCode;
198
- errorMessage: string;
205
+ errorMessage: string; /**
206
+ * Wallet identity: keyshares/wallet records are keyed by the wallet's
207
+ * primary (mainnet) address. On testnet the active address is the same
208
+ * wallet's per-network encoding — WaaS lookups (password, signing) must use
209
+ * the primary address (the same key signs both encodings).
210
+ *
211
+ * Resolved from the credential that owns the active address, so a user with
212
+ * several Bitcoin wallets gets the identity of the wallet being spent from.
213
+ * Never falls back to another wallet's address: a substituted key produces a
214
+ * signature that looks valid locally and is rejected by the network, so the
215
+ * failure has to surface here instead.
216
+ */
199
217
  errorType: string | undefined;
200
218
  } | {
201
219
  errorCode: string;
@@ -281,6 +299,23 @@ export declare class DynamicWaasBitcoinConnector extends DynamicWaasBitcoinConne
281
299
  * the active address automatically.
282
300
  */
283
301
  private getBitcoinNetworkEnum;
302
+ /**
303
+ * The address type the wallet's key was derived under, read back from the
304
+ * address itself.
305
+ *
306
+ * `bitcoinConfig.addressType` is the only thing that selects a BTC
307
+ * derivation path downstream (`getMPCChainConfig` throws without it), and a
308
+ * wallet created by an import carries no server-side derivation metadata to
309
+ * fall back on — so signing an imported wallet failed while a
310
+ * Dynamic-created one signed fine. The address already encodes the answer:
311
+ * witness v0 is P2WPKH (native segwit), v1 is P2TR (taproot), which are
312
+ * exactly the two types `importPrivateKey` accepts.
313
+ *
314
+ * Falls back to native segwit for an address that is not bech32 (a legacy
315
+ * P2PKH/P2SH encoding cannot come from this connector's own import or
316
+ * create paths) so a decode failure can never make signing impossible.
317
+ */
318
+ private getAddressTypeForSigning;
284
319
  supportsNetworkSwitching(): boolean;
285
320
  /**
286
321
  * Testnet when the testnet network (id 2) is selected — drives the widget's
@@ -295,15 +330,40 @@ export declare class DynamicWaasBitcoinConnector extends DynamicWaasBitcoinConne
295
330
  */
296
331
  getBalance(address: string): Promise<string | undefined>;
297
332
  /**
298
- * Finds the credential that owns the active address, either as its primary
299
- * (mainnet) address or as one of its network-tagged additional addresses.
333
+ * Finds the credential that owns `address`, either as its primary (mainnet)
334
+ * address or as one of its network-tagged additional addresses.
300
335
  *
301
336
  * This connector is shared across every Bitcoin wallet of the user —
302
337
  * `verifiedCredentials` holds them all — so the owning credential must be
303
- * looked up by the active address. Reading `verifiedCredential` instead
304
- * would always answer with the user's *first* wallet.
338
+ * looked up by address. Reading `verifiedCredential` instead would always
339
+ * answer with the user's *first* wallet.
305
340
  */
341
+ private findCredentialForAddress;
342
+ /** @see findCredentialForAddress */
306
343
  private findCredentialForActiveAddress;
344
+ /**
345
+ * True when `accountAddress` belongs to the same wallet as the currently
346
+ * active address — including when the active address is a *different*
347
+ * network-tagged encoding of that same wallet (e.g. the testnet address
348
+ * set by switchNetwork, compared against the wallet's primary mainnet
349
+ * address).
350
+ *
351
+ * Used by getWalletClientByAddress to avoid clobbering an explicit
352
+ * switchNetwork: callers like ensureActiveAccount re-assert the wallet's
353
+ * primary address before every send/sign, and setting activeAccountAddress
354
+ * back to that primary address would silently undo the network switch —
355
+ * UTXO/PSBT/broadcast calls would then run against the wrong (likely
356
+ * unfunded) encoding while getNetwork still reports the switched network.
357
+ *
358
+ * Compares the two resolved credential *objects* by reference rather than
359
+ * by id: both lookups run against the same verifiedCredentials array in
360
+ * the same synchronous call, so `.find()` returns the identical element
361
+ * for both addresses whenever they belong to the same wallet. Comparing
362
+ * `.id` instead would be wrong whenever accountAddress doesn't resolve to
363
+ * any known credential yet (e.g. a brand-new wallet not yet synced) —
364
+ * `undefined === undefined` would falsely read as "same wallet".
365
+ */
366
+ private isSameWalletAddress;
307
367
  /**
308
368
  * Wallet identity: keyshares/wallet records are keyed by the wallet's
309
369
  * primary (mainnet) address. On testnet the active address is the same
@@ -354,7 +414,11 @@ export declare class DynamicWaasBitcoinConnector extends DynamicWaasBitcoinConne
354
414
  * Signs a Partially Signed Bitcoin Transaction (PSBT) for embedded wallets
355
415
  *
356
416
  * Embedded wallets only support PSBT format and automatically sign all inputs
357
- * that belong to the wallet address. Always uses SIGHASH_ALL (0x01).
417
+ * that belong to the wallet address, unless `signingIndexes` narrows that set.
418
+ *
419
+ * The sighash type is read from the PSBT itself, falling back to
420
+ * SIGHASH_DEFAULT (0x00) for Taproot and SIGHASH_ALL (0x01) otherwise.
421
+ * `allowedSighash` restricts which declared types are accepted.
358
422
  *
359
423
  * @param request - The PSBT signing request. Only unsignedPsbtBase64 is required.
360
424
  * @returns The signed (but not finalized) PSBT response
@@ -414,7 +478,11 @@ export declare class DynamicWaasBitcoinConnector extends DynamicWaasBitcoinConne
414
478
  legacyWalletId?: string;
415
479
  }): Promise<void>;
416
480
  /**
417
- * Gets the wallet client for a specific account address and sets it as active
481
+ * Gets the wallet client for a specific account address and sets it as
482
+ * active — unless `accountAddress` is just a different network-tagged
483
+ * encoding of the wallet that's already active (see isSameWalletAddress),
484
+ * in which case the current (possibly switchNetwork'd) active address is
485
+ * left untouched.
418
486
  * @param accountAddress - The Bitcoin account address
419
487
  * @returns The WaaS wallet client instance
420
488
  */
@@ -480,6 +548,12 @@ export declare class DynamicWaasBitcoinConnector extends DynamicWaasBitcoinConne
480
548
  * @returns Parsed transaction data with inputs and outputs, or null if parsing fails
481
549
  */
482
550
  parseBase64Psbt(psbtBase64: string): ParsedTransaction | null;
551
+ /**
552
+ * bitcoinjs network for the address encoding the parsers should emit.
553
+ * Derived from the active address rather than activeNetworkId, which is
554
+ * in-memory only and can report mainnet while a testnet address is active.
555
+ */
556
+ private getPsbtNetwork;
483
557
  /**
484
558
  * Gets the public key for the active account
485
559
  * Falls back to the account address if public key retrieval fails
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
  import { __awaiter } from '../../../_virtual/_tslib.js';
3
- import { networks, Psbt } from 'bitcoinjs-lib';
3
+ import { address, networks, Psbt } from 'bitcoinjs-lib';
4
4
  import { Logger } from '@dynamic-labs/logger';
5
5
  import { MFAAction, TokenScope } from '@dynamic-labs/sdk-api-core';
6
6
  import { DynamicError } from '@dynamic-labs/utils';
@@ -13,6 +13,7 @@ import { BitcoinWalletConnector } from '../BitcoinWalletConnector.js';
13
13
  import '@wallet-standard/app';
14
14
  import 'jsontokens';
15
15
  import { btcToSatoshis } from '../../utils/btcToSatoshis/btcToSatoshis.js';
16
+ import { getBitcoinNetwork } from '../../utils/getBitcoinNetwork/getBitcoinNetwork.js';
16
17
  import { PsbtParser } from '../../utils/psbtParser/PsbtParser.js';
17
18
  import { BitcoinUiTransaction } from '../../utils/BitcoinUiTransaction/BitcoinUiTransaction.js';
18
19
 
@@ -131,6 +132,32 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
131
132
  : 'mainnet');
132
133
  });
133
134
  }
135
+ /**
136
+ * The address type the wallet's key was derived under, read back from the
137
+ * address itself.
138
+ *
139
+ * `bitcoinConfig.addressType` is the only thing that selects a BTC
140
+ * derivation path downstream (`getMPCChainConfig` throws without it), and a
141
+ * wallet created by an import carries no server-side derivation metadata to
142
+ * fall back on — so signing an imported wallet failed while a
143
+ * Dynamic-created one signed fine. The address already encodes the answer:
144
+ * witness v0 is P2WPKH (native segwit), v1 is P2TR (taproot), which are
145
+ * exactly the two types `importPrivateKey` accepts.
146
+ *
147
+ * Falls back to native segwit for an address that is not bech32 (a legacy
148
+ * P2PKH/P2SH encoding cannot come from this connector's own import or
149
+ * create paths) so a decode failure can never make signing impossible.
150
+ */
151
+ getAddressTypeForSigning(walletAddress) {
152
+ try {
153
+ const { version } = address.fromBech32(walletAddress);
154
+ return (version === 1 ? 'taproot' : 'native_segwit');
155
+ }
156
+ catch (error) {
157
+ this.logger.warn('Could not read the address type from the wallet address; assuming native segwit', { error, walletAddress });
158
+ return 'native_segwit';
159
+ }
160
+ }
134
161
  supportsNetworkSwitching() {
135
162
  return true;
136
163
  }
@@ -157,26 +184,65 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
157
184
  });
158
185
  }
159
186
  /**
160
- * Finds the credential that owns the active address, either as its primary
161
- * (mainnet) address or as one of its network-tagged additional addresses.
187
+ * Finds the credential that owns `address`, either as its primary (mainnet)
188
+ * address or as one of its network-tagged additional addresses.
162
189
  *
163
190
  * This connector is shared across every Bitcoin wallet of the user —
164
191
  * `verifiedCredentials` holds them all — so the owning credential must be
165
- * looked up by the active address. Reading `verifiedCredential` instead
166
- * would always answer with the user's *first* wallet.
192
+ * looked up by address. Reading `verifiedCredential` instead would always
193
+ * answer with the user's *first* wallet.
167
194
  */
168
- findCredentialForActiveAddress() {
195
+ findCredentialForAddress(address) {
169
196
  var _a;
170
- const { activeAccountAddress } = this;
171
- if (!activeAccountAddress) {
197
+ if (!address) {
172
198
  return undefined;
173
199
  }
174
200
  return ((_a = this.verifiedCredentials) !== null && _a !== void 0 ? _a : []).find((credential) => {
175
201
  var _a;
176
- return (credential === null || credential === void 0 ? void 0 : credential.address) === activeAccountAddress ||
177
- ((_a = credential === null || credential === void 0 ? void 0 : credential.walletAdditionalAddresses) !== null && _a !== void 0 ? _a : []).some((additionalAddress) => additionalAddress.address === activeAccountAddress);
202
+ return (credential === null || credential === void 0 ? void 0 : credential.address) === address ||
203
+ ((_a = credential === null || credential === void 0 ? void 0 : credential.walletAdditionalAddresses) !== null && _a !== void 0 ? _a : []).some((additionalAddress) => additionalAddress.address === address);
178
204
  });
179
205
  }
206
+ /** @see findCredentialForAddress */
207
+ findCredentialForActiveAddress() {
208
+ return this.findCredentialForAddress(this.activeAccountAddress);
209
+ }
210
+ /**
211
+ * True when `accountAddress` belongs to the same wallet as the currently
212
+ * active address — including when the active address is a *different*
213
+ * network-tagged encoding of that same wallet (e.g. the testnet address
214
+ * set by switchNetwork, compared against the wallet's primary mainnet
215
+ * address).
216
+ *
217
+ * Used by getWalletClientByAddress to avoid clobbering an explicit
218
+ * switchNetwork: callers like ensureActiveAccount re-assert the wallet's
219
+ * primary address before every send/sign, and setting activeAccountAddress
220
+ * back to that primary address would silently undo the network switch —
221
+ * UTXO/PSBT/broadcast calls would then run against the wrong (likely
222
+ * unfunded) encoding while getNetwork still reports the switched network.
223
+ *
224
+ * Compares the two resolved credential *objects* by reference rather than
225
+ * by id: both lookups run against the same verifiedCredentials array in
226
+ * the same synchronous call, so `.find()` returns the identical element
227
+ * for both addresses whenever they belong to the same wallet. Comparing
228
+ * `.id` instead would be wrong whenever accountAddress doesn't resolve to
229
+ * any known credential yet (e.g. a brand-new wallet not yet synced) —
230
+ * `undefined === undefined` would falsely read as "same wallet".
231
+ */
232
+ isSameWalletAddress(accountAddress) {
233
+ if (!this.activeAccountAddress) {
234
+ return false;
235
+ }
236
+ if (accountAddress === this.activeAccountAddress) {
237
+ return true;
238
+ }
239
+ const activeCredential = this.findCredentialForActiveAddress();
240
+ if (!activeCredential) {
241
+ return false;
242
+ }
243
+ const candidateCredential = this.findCredentialForAddress(accountAddress);
244
+ return (Boolean(candidateCredential) && candidateCredential === activeCredential);
245
+ }
180
246
  /**
181
247
  * Wallet identity: keyshares/wallet records are keyed by the wallet's
182
248
  * primary (mainnet) address. On testnet the active address is the same
@@ -298,7 +364,11 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
298
364
  * Signs a Partially Signed Bitcoin Transaction (PSBT) for embedded wallets
299
365
  *
300
366
  * Embedded wallets only support PSBT format and automatically sign all inputs
301
- * that belong to the wallet address. Always uses SIGHASH_ALL (0x01).
367
+ * that belong to the wallet address, unless `signingIndexes` narrows that set.
368
+ *
369
+ * The sighash type is read from the PSBT itself, falling back to
370
+ * SIGHASH_DEFAULT (0x00) for Taproot and SIGHASH_ALL (0x01) otherwise.
371
+ * `allowedSighash` restricts which declared types are accepted.
302
372
  *
303
373
  * @param request - The PSBT signing request. Only unsignedPsbtBase64 is required.
304
374
  * @returns The signed (but not finalized) PSBT response
@@ -327,12 +397,14 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
327
397
  accountAddress: walletIdentityAddress,
328
398
  });
329
399
  const signedTransaction = yield walletClient.signTransaction({
400
+ allowedSighash: request.allowedSighash,
330
401
  authToken: (_d = this.getAuthToken) === null || _d === void 0 ? void 0 : _d.call(this),
331
402
  elevatedAccessToken,
332
403
  mfaToken,
333
404
  password,
334
405
  senderAddress: walletIdentityAddress,
335
406
  signedSessionId,
407
+ signingIndexes: request.signingIndexes,
336
408
  transaction: request.unsignedPsbtBase64,
337
409
  });
338
410
  return {
@@ -468,12 +540,18 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
468
540
  });
469
541
  }
470
542
  /**
471
- * Gets the wallet client for a specific account address and sets it as active
543
+ * Gets the wallet client for a specific account address and sets it as
544
+ * active — unless `accountAddress` is just a different network-tagged
545
+ * encoding of the wallet that's already active (see isSameWalletAddress),
546
+ * in which case the current (possibly switchNetwork'd) active address is
547
+ * left untouched.
472
548
  * @param accountAddress - The Bitcoin account address
473
549
  * @returns The WaaS wallet client instance
474
550
  */
475
551
  getWalletClientByAddress({ accountAddress }) {
476
- this.setActiveAccountAddress(accountAddress);
552
+ if (!this.isSameWalletAddress(accountAddress)) {
553
+ this.setActiveAccountAddress(accountAddress);
554
+ }
477
555
  return this.getWaasWalletClient();
478
556
  }
479
557
  /**
@@ -493,7 +571,7 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
493
571
  */
494
572
  validateActiveWallet(expectedAddress) {
495
573
  return __awaiter(this, void 0, void 0, function* () {
496
- var _a, _b, _c, _d;
574
+ var _a;
497
575
  const walletClient = yield this.getWaasWalletClient();
498
576
  const targetWallet = yield walletClient.getWallet({
499
577
  accountAddress: expectedAddress,
@@ -507,8 +585,12 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
507
585
  const isWalletActive = isSameAddress(targetWallet.accountAddress, this.activeAccountAddress || '', this.connectedChain);
508
586
  // The active address may be a per-network encoding of this same wallet
509
587
  // (one key, network-tagged additional addresses). Resetting it here would
510
- // silently un-switch the selected network.
511
- const isPerNetworkVariantOfWallet = (_d = (_c = (_b = this.verifiedCredential) === null || _b === void 0 ? void 0 : _b.walletAdditionalAddresses) === null || _c === void 0 ? void 0 : _c.some((addr) => addr.address === this.activeAccountAddress)) !== null && _d !== void 0 ? _d : false;
588
+ // silently un-switch the selected network. Resolved via isSameWalletAddress
589
+ // (which looks up the credential that actually owns the active address)
590
+ // rather than `verifiedCredential`, which is always the user's *first*
591
+ // wallet — using it here would wrongly reset the address for any other
592
+ // wallet the user validates.
593
+ const isPerNetworkVariantOfWallet = this.isSameWalletAddress(targetWallet.accountAddress);
512
594
  if (!isWalletActive && !isPerNetworkVariantOfWallet) {
513
595
  this.activeAccountAddress = targetWallet.accountAddress;
514
596
  }
@@ -543,10 +625,14 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
543
625
  accountAddress: this.getWalletIdentityAddress(),
544
626
  });
545
627
  const messageString = typeof message === 'string' ? message : message.raw;
628
+ const walletIdentityAddress = this.getWalletIdentityAddress();
546
629
  return walletClient.signMessage({
547
- accountAddress: this.getWalletIdentityAddress(),
630
+ accountAddress: walletIdentityAddress,
548
631
  authToken: (_e = this.getAuthToken) === null || _e === void 0 ? void 0 : _e.call(this),
549
- bitcoinConfig: { network: yield this.getBitcoinNetworkEnum() },
632
+ bitcoinConfig: {
633
+ addressType: this.getAddressTypeForSigning(walletIdentityAddress),
634
+ network: yield this.getBitcoinNetworkEnum(),
635
+ },
550
636
  context,
551
637
  elevatedAccessToken,
552
638
  message: messageString,
@@ -661,7 +747,7 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
661
747
  * @returns Parsed transaction data with inputs and outputs, or null if parsing fails
662
748
  */
663
749
  parsePsbt(psbtInput) {
664
- return PsbtParser.parsePsbt(psbtInput);
750
+ return PsbtParser.parsePsbt(psbtInput, this.getPsbtNetwork());
665
751
  }
666
752
  /**
667
753
  * Parse a PSBT or finalized transaction from hex format
@@ -669,7 +755,7 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
669
755
  * @returns Parsed transaction data with inputs and outputs, or null if parsing fails
670
756
  */
671
757
  parseHexPsbt(psbtHex) {
672
- return PsbtParser.parseHexPsbt(psbtHex);
758
+ return PsbtParser.parseHexPsbt(psbtHex, this.getPsbtNetwork());
673
759
  }
674
760
  /**
675
761
  * Parse a PSBT or finalized transaction from base64 format
@@ -677,7 +763,17 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
677
763
  * @returns Parsed transaction data with inputs and outputs, or null if parsing fails
678
764
  */
679
765
  parseBase64Psbt(psbtBase64) {
680
- return PsbtParser.parseBase64Psbt(psbtBase64);
766
+ return PsbtParser.parseBase64Psbt(psbtBase64, this.getPsbtNetwork());
767
+ }
768
+ /**
769
+ * bitcoinjs network for the address encoding the parsers should emit.
770
+ * Derived from the active address rather than activeNetworkId, which is
771
+ * in-memory only and can report mainnet while a testnet address is active.
772
+ */
773
+ getPsbtNetwork() {
774
+ return this.activeAccountAddress
775
+ ? getBitcoinNetwork(this.activeAccountAddress)
776
+ : networks.bitcoin;
681
777
  }
682
778
  /**
683
779
  * Gets the public key for the active account
@@ -768,6 +864,7 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
768
864
  accountAddress: this.getWalletIdentityAddress(),
769
865
  authToken: (_d = this.getAuthToken) === null || _d === void 0 ? void 0 : _d.call(this),
770
866
  bitcoinConfig: {
867
+ addressType: this.getAddressTypeForSigning(this.getWalletIdentityAddress()),
771
868
  network: yield this.getBitcoinNetworkEnum(),
772
869
  },
773
870
  elevatedAccessToken,
package/src/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { OkxConnector } from './connectors';
1
+ import { OkxConnector, DynamicWaasBitcoinConnector } from './connectors';
2
2
  export { BitcoinWalletConnector, type BitcoinWalletConnectorOpts, } from './connectors';
3
3
  export type { BitcoinTransaction, BitcoinSignProtocol, BitcoinSignPsbtRequest, BitcoinSignPsbtResponse, BitcoinWalletStandardMethods, SignPsbtOptions, EmbeddedWalletSignPsbtRequest, } from './types';
4
4
  export * from './utils';
5
5
  export * from './wallet';
6
6
  export { UnisatConnector, BitcoinSatsConnectConnector, DynamicWaasBitcoinConnector, } from './connectors';
7
- export declare const BitcoinWalletConnectors: (props: any) => (import("dist/packages/wallet-connector-core/src").WalletConnectorConstructor | typeof OkxConnector)[];
7
+ export declare const BitcoinWalletConnectors: (props: any) => (import("dist/packages/wallet-connector-core/src").WalletConnectorConstructor | typeof OkxConnector | typeof DynamicWaasBitcoinConnector)[];
8
8
  export { BitcoinProviderHelper } from './bitcoinProviderHelper';
9
9
  export { DUST_LIMIT, HTTP_STATUS_NOT_FOUND, HTTP_STATUS_TOO_MANY_REQUESTS, } from './const';
package/src/types.d.ts CHANGED
@@ -67,6 +67,19 @@ export type EmbeddedWalletSignPsbtRequest = {
67
67
  * Embedded wallets only support PSBT format, not raw transaction hex.
68
68
  */
69
69
  unsignedPsbtBase64: string;
70
+ /**
71
+ * Optional subset of PSBT input indexes to sign. When provided, only these
72
+ * indexes are signed (they must still belong to the wallet's own address).
73
+ * When omitted, behavior is unchanged: all wallet-owned inputs are signed.
74
+ */
75
+ signingIndexes?: number[];
76
+ /**
77
+ * Optional allow-list of sighash types. The sighash type itself is read from
78
+ * the PSBT (BIP-174 `PSBT_IN_SIGHASH_TYPE`) — this only restricts which
79
+ * declared types are accepted, and signing throws if an input declares one
80
+ * outside the list. When omitted, any declared type is signed.
81
+ */
82
+ allowedSighash?: number[];
70
83
  };
71
84
  export type SatsConnectSignTransactionInput = {
72
85
  message?: string;
@@ -13,16 +13,19 @@ class PsbtParser {
13
13
  * Parse a PSBT or finalized transaction and extract transaction information
14
14
  * Auto-detects format (hex/base64) and type (PSBT/finalized transaction)
15
15
  * @param psbtInput - The PSBT or finalized transaction in Base64 or hex format
16
+ * @param network - Network used to encode the extracted addresses. Scripts are
17
+ * network-agnostic, so passing the wrong one silently renders a valid address
18
+ * under the wrong prefix (a testnet output shown as `bc1…`).
16
19
  * @returns Parsed transaction data with inputs and outputs
17
20
  * @throws Error if parsing fails
18
21
  */
19
- static parsePsbt(psbtInput) {
22
+ static parsePsbt(psbtInput, network = bitcoinjsLib.networks.bitcoin) {
20
23
  const isHex = /^[0-9a-fA-F]+$/.test(psbtInput) && psbtInput.length > 100;
21
24
  if (isHex) {
22
- return PsbtParser.parseHexPsbt(psbtInput);
25
+ return PsbtParser.parseHexPsbt(psbtInput, network);
23
26
  }
24
27
  else {
25
- return PsbtParser.parseBase64Psbt(psbtInput);
28
+ return PsbtParser.parseBase64Psbt(psbtInput, network);
26
29
  }
27
30
  }
28
31
  /**
@@ -31,14 +34,14 @@ class PsbtParser {
31
34
  * @returns Parsed transaction data with inputs and outputs
32
35
  * @throws Error if parsing fails
33
36
  */
34
- static parseHexPsbt(psbtHex) {
37
+ static parseHexPsbt(psbtHex, network = bitcoinjsLib.networks.bitcoin) {
35
38
  try {
36
39
  try {
37
- const psbt = bitcoinjsLib.Psbt.fromHex(psbtHex, { network: bitcoinjsLib.networks.bitcoin });
38
- return PsbtParser.extractPsbtData(psbt);
40
+ const psbt = bitcoinjsLib.Psbt.fromHex(psbtHex, { network });
41
+ return PsbtParser.extractPsbtData(psbt, network);
39
42
  }
40
43
  catch (psbtError) {
41
- return PsbtParser.parseFinalizedTransactionHex(psbtHex);
44
+ return PsbtParser.parseFinalizedTransactionHex(psbtHex, network);
42
45
  }
43
46
  }
44
47
  catch (error) {
@@ -51,14 +54,14 @@ class PsbtParser {
51
54
  * @returns Parsed transaction data with inputs and outputs
52
55
  * @throws Error if parsing fails
53
56
  */
54
- static parseBase64Psbt(psbtBase64) {
57
+ static parseBase64Psbt(psbtBase64, network = bitcoinjsLib.networks.bitcoin) {
55
58
  try {
56
59
  try {
57
- const psbt = bitcoinjsLib.Psbt.fromBase64(psbtBase64, { network: bitcoinjsLib.networks.bitcoin });
58
- return PsbtParser.extractPsbtData(psbt);
60
+ const psbt = bitcoinjsLib.Psbt.fromBase64(psbtBase64, { network });
61
+ return PsbtParser.extractPsbtData(psbt, network);
59
62
  }
60
63
  catch (psbtError) {
61
- return PsbtParser.parseFinalizedTransactionBase64(psbtBase64);
64
+ return PsbtParser.parseFinalizedTransactionBase64(psbtBase64, network);
62
65
  }
63
66
  }
64
67
  catch (error) {
@@ -71,7 +74,7 @@ class PsbtParser {
71
74
  * @param psbt - The parsed PSBT object
72
75
  * @returns Parsed transaction data with inputs and outputs
73
76
  */
74
- static extractPsbtData(psbt) {
77
+ static extractPsbtData(psbt, network) {
75
78
  const inputs = [];
76
79
  const outputs = [];
77
80
  psbt.txInputs.forEach((txInput, index) => {
@@ -83,7 +86,7 @@ class PsbtParser {
83
86
  if (input.witnessUtxo) {
84
87
  inputData.value = input.witnessUtxo.value;
85
88
  try {
86
- inputData.address = bitcoinjsLib.address.fromOutputScript(input.witnessUtxo.script, bitcoinjsLib.networks.bitcoin);
89
+ inputData.address = bitcoinjsLib.address.fromOutputScript(input.witnessUtxo.script, network);
87
90
  }
88
91
  catch (e) {
89
92
  // Address extraction failed
@@ -95,7 +98,7 @@ class PsbtParser {
95
98
  const prevOut = prevTx.outs[txInput.index];
96
99
  inputData.value = prevOut.value;
97
100
  try {
98
- inputData.address = bitcoinjsLib.address.fromOutputScript(prevOut.script, bitcoinjsLib.networks.bitcoin);
101
+ inputData.address = bitcoinjsLib.address.fromOutputScript(prevOut.script, network);
99
102
  }
100
103
  catch (e) {
101
104
  // Address extraction failed
@@ -107,7 +110,7 @@ class PsbtParser {
107
110
  }
108
111
  inputs.push(inputData);
109
112
  });
110
- PsbtParser.extractOutputs(psbt.txOutputs, outputs);
113
+ PsbtParser.extractOutputs(psbt.txOutputs, outputs, network);
111
114
  return { inputs, outputs };
112
115
  }
113
116
  /**
@@ -116,13 +119,13 @@ class PsbtParser {
116
119
  * @param txOutputs - Array of transaction outputs (from PSBT or Transaction)
117
120
  * @param outputs - Array to populate with parsed output data
118
121
  */
119
- static extractOutputs(txOutputs, outputs) {
122
+ static extractOutputs(txOutputs, outputs, network) {
120
123
  txOutputs.forEach((txOutput) => {
121
124
  const outputData = {
122
125
  value: txOutput.value,
123
126
  };
124
127
  try {
125
- outputData.address = bitcoinjsLib.address.fromOutputScript(txOutput.script, bitcoinjsLib.networks.bitcoin);
128
+ outputData.address = bitcoinjsLib.address.fromOutputScript(txOutput.script, network);
126
129
  }
127
130
  catch (e) {
128
131
  // Address extraction failed - continue without address
@@ -136,10 +139,10 @@ class PsbtParser {
136
139
  * @returns Parsed transaction data with inputs and outputs
137
140
  * @throws Error if parsing fails
138
141
  */
139
- static parseFinalizedTransactionHex(txHex) {
142
+ static parseFinalizedTransactionHex(txHex, network) {
140
143
  try {
141
144
  const tx = bitcoinjsLib.Transaction.fromHex(txHex);
142
- return PsbtParser.extractTransactionData(tx);
145
+ return PsbtParser.extractTransactionData(tx, network);
143
146
  }
144
147
  catch (error) {
145
148
  throw new Error(`Failed to parse finalized transaction hex: ${error instanceof Error ? error.message : String(error)}`);
@@ -151,11 +154,11 @@ class PsbtParser {
151
154
  * @returns Parsed transaction data with inputs and outputs
152
155
  * @throws Error if parsing fails
153
156
  */
154
- static parseFinalizedTransactionBase64(txBase64) {
157
+ static parseFinalizedTransactionBase64(txBase64, network) {
155
158
  try {
156
159
  const buffer = Buffer.from(txBase64, 'base64');
157
160
  const tx = bitcoinjsLib.Transaction.fromBuffer(buffer);
158
- return PsbtParser.extractTransactionData(tx);
161
+ return PsbtParser.extractTransactionData(tx, network);
159
162
  }
160
163
  catch (error) {
161
164
  throw new Error(`Failed to parse finalized transaction base64: ${error instanceof Error ? error.message : String(error)}`);
@@ -167,7 +170,7 @@ class PsbtParser {
167
170
  * @param tx - The parsed Transaction object
168
171
  * @returns Parsed transaction data with inputs and outputs
169
172
  */
170
- static extractTransactionData(tx) {
173
+ static extractTransactionData(tx, network) {
171
174
  const inputs = [];
172
175
  const outputs = [];
173
176
  tx.ins.forEach((input) => {
@@ -177,7 +180,7 @@ class PsbtParser {
177
180
  };
178
181
  inputs.push(inputData);
179
182
  });
180
- PsbtParser.extractOutputs(tx.outs, outputs);
183
+ PsbtParser.extractOutputs(tx.outs, outputs, network);
181
184
  return { inputs, outputs };
182
185
  }
183
186
  }
@@ -1,3 +1,4 @@
1
+ import { type Network } from 'bitcoinjs-lib';
1
2
  import type { ParsedTransaction } from '../../types';
2
3
  /**
3
4
  * Utility class for parsing PSBTs and finalized Bitcoin transactions
@@ -7,24 +8,27 @@ export declare class PsbtParser {
7
8
  * Parse a PSBT or finalized transaction and extract transaction information
8
9
  * Auto-detects format (hex/base64) and type (PSBT/finalized transaction)
9
10
  * @param psbtInput - The PSBT or finalized transaction in Base64 or hex format
11
+ * @param network - Network used to encode the extracted addresses. Scripts are
12
+ * network-agnostic, so passing the wrong one silently renders a valid address
13
+ * under the wrong prefix (a testnet output shown as `bc1…`).
10
14
  * @returns Parsed transaction data with inputs and outputs
11
15
  * @throws Error if parsing fails
12
16
  */
13
- static parsePsbt(psbtInput: string): ParsedTransaction;
17
+ static parsePsbt(psbtInput: string, network?: Network): ParsedTransaction;
14
18
  /**
15
19
  * Parse a PSBT or finalized transaction from hex format
16
20
  * @param psbtHex - The PSBT or finalized transaction in hex format
17
21
  * @returns Parsed transaction data with inputs and outputs
18
22
  * @throws Error if parsing fails
19
23
  */
20
- static parseHexPsbt(psbtHex: string): ParsedTransaction;
24
+ static parseHexPsbt(psbtHex: string, network?: Network): ParsedTransaction;
21
25
  /**
22
26
  * Parse a PSBT or finalized transaction from base64 format
23
27
  * @param psbtBase64 - The PSBT or finalized transaction in base64 format
24
28
  * @returns Parsed transaction data with inputs and outputs
25
29
  * @throws Error if parsing fails
26
30
  */
27
- static parseBase64Psbt(psbtBase64: string): ParsedTransaction;
31
+ static parseBase64Psbt(psbtBase64: string, network?: Network): ParsedTransaction;
28
32
  /**
29
33
  * Extract transaction data from a PSBT
30
34
  * Shared logic used by both parseHexPsbt and parseBase64Psbt
@@ -1,5 +1,5 @@
1
1
  'use client'
2
- import { Psbt, networks, address, Transaction } from 'bitcoinjs-lib';
2
+ import { networks, Psbt, address, Transaction } from 'bitcoinjs-lib';
3
3
 
4
4
  /**
5
5
  * Utility class for parsing PSBTs and finalized Bitcoin transactions
@@ -9,16 +9,19 @@ class PsbtParser {
9
9
  * Parse a PSBT or finalized transaction and extract transaction information
10
10
  * Auto-detects format (hex/base64) and type (PSBT/finalized transaction)
11
11
  * @param psbtInput - The PSBT or finalized transaction in Base64 or hex format
12
+ * @param network - Network used to encode the extracted addresses. Scripts are
13
+ * network-agnostic, so passing the wrong one silently renders a valid address
14
+ * under the wrong prefix (a testnet output shown as `bc1…`).
12
15
  * @returns Parsed transaction data with inputs and outputs
13
16
  * @throws Error if parsing fails
14
17
  */
15
- static parsePsbt(psbtInput) {
18
+ static parsePsbt(psbtInput, network = networks.bitcoin) {
16
19
  const isHex = /^[0-9a-fA-F]+$/.test(psbtInput) && psbtInput.length > 100;
17
20
  if (isHex) {
18
- return PsbtParser.parseHexPsbt(psbtInput);
21
+ return PsbtParser.parseHexPsbt(psbtInput, network);
19
22
  }
20
23
  else {
21
- return PsbtParser.parseBase64Psbt(psbtInput);
24
+ return PsbtParser.parseBase64Psbt(psbtInput, network);
22
25
  }
23
26
  }
24
27
  /**
@@ -27,14 +30,14 @@ class PsbtParser {
27
30
  * @returns Parsed transaction data with inputs and outputs
28
31
  * @throws Error if parsing fails
29
32
  */
30
- static parseHexPsbt(psbtHex) {
33
+ static parseHexPsbt(psbtHex, network = networks.bitcoin) {
31
34
  try {
32
35
  try {
33
- const psbt = Psbt.fromHex(psbtHex, { network: networks.bitcoin });
34
- return PsbtParser.extractPsbtData(psbt);
36
+ const psbt = Psbt.fromHex(psbtHex, { network });
37
+ return PsbtParser.extractPsbtData(psbt, network);
35
38
  }
36
39
  catch (psbtError) {
37
- return PsbtParser.parseFinalizedTransactionHex(psbtHex);
40
+ return PsbtParser.parseFinalizedTransactionHex(psbtHex, network);
38
41
  }
39
42
  }
40
43
  catch (error) {
@@ -47,14 +50,14 @@ class PsbtParser {
47
50
  * @returns Parsed transaction data with inputs and outputs
48
51
  * @throws Error if parsing fails
49
52
  */
50
- static parseBase64Psbt(psbtBase64) {
53
+ static parseBase64Psbt(psbtBase64, network = networks.bitcoin) {
51
54
  try {
52
55
  try {
53
- const psbt = Psbt.fromBase64(psbtBase64, { network: networks.bitcoin });
54
- return PsbtParser.extractPsbtData(psbt);
56
+ const psbt = Psbt.fromBase64(psbtBase64, { network });
57
+ return PsbtParser.extractPsbtData(psbt, network);
55
58
  }
56
59
  catch (psbtError) {
57
- return PsbtParser.parseFinalizedTransactionBase64(psbtBase64);
60
+ return PsbtParser.parseFinalizedTransactionBase64(psbtBase64, network);
58
61
  }
59
62
  }
60
63
  catch (error) {
@@ -67,7 +70,7 @@ class PsbtParser {
67
70
  * @param psbt - The parsed PSBT object
68
71
  * @returns Parsed transaction data with inputs and outputs
69
72
  */
70
- static extractPsbtData(psbt) {
73
+ static extractPsbtData(psbt, network) {
71
74
  const inputs = [];
72
75
  const outputs = [];
73
76
  psbt.txInputs.forEach((txInput, index) => {
@@ -79,7 +82,7 @@ class PsbtParser {
79
82
  if (input.witnessUtxo) {
80
83
  inputData.value = input.witnessUtxo.value;
81
84
  try {
82
- inputData.address = address.fromOutputScript(input.witnessUtxo.script, networks.bitcoin);
85
+ inputData.address = address.fromOutputScript(input.witnessUtxo.script, network);
83
86
  }
84
87
  catch (e) {
85
88
  // Address extraction failed
@@ -91,7 +94,7 @@ class PsbtParser {
91
94
  const prevOut = prevTx.outs[txInput.index];
92
95
  inputData.value = prevOut.value;
93
96
  try {
94
- inputData.address = address.fromOutputScript(prevOut.script, networks.bitcoin);
97
+ inputData.address = address.fromOutputScript(prevOut.script, network);
95
98
  }
96
99
  catch (e) {
97
100
  // Address extraction failed
@@ -103,7 +106,7 @@ class PsbtParser {
103
106
  }
104
107
  inputs.push(inputData);
105
108
  });
106
- PsbtParser.extractOutputs(psbt.txOutputs, outputs);
109
+ PsbtParser.extractOutputs(psbt.txOutputs, outputs, network);
107
110
  return { inputs, outputs };
108
111
  }
109
112
  /**
@@ -112,13 +115,13 @@ class PsbtParser {
112
115
  * @param txOutputs - Array of transaction outputs (from PSBT or Transaction)
113
116
  * @param outputs - Array to populate with parsed output data
114
117
  */
115
- static extractOutputs(txOutputs, outputs) {
118
+ static extractOutputs(txOutputs, outputs, network) {
116
119
  txOutputs.forEach((txOutput) => {
117
120
  const outputData = {
118
121
  value: txOutput.value,
119
122
  };
120
123
  try {
121
- outputData.address = address.fromOutputScript(txOutput.script, networks.bitcoin);
124
+ outputData.address = address.fromOutputScript(txOutput.script, network);
122
125
  }
123
126
  catch (e) {
124
127
  // Address extraction failed - continue without address
@@ -132,10 +135,10 @@ class PsbtParser {
132
135
  * @returns Parsed transaction data with inputs and outputs
133
136
  * @throws Error if parsing fails
134
137
  */
135
- static parseFinalizedTransactionHex(txHex) {
138
+ static parseFinalizedTransactionHex(txHex, network) {
136
139
  try {
137
140
  const tx = Transaction.fromHex(txHex);
138
- return PsbtParser.extractTransactionData(tx);
141
+ return PsbtParser.extractTransactionData(tx, network);
139
142
  }
140
143
  catch (error) {
141
144
  throw new Error(`Failed to parse finalized transaction hex: ${error instanceof Error ? error.message : String(error)}`);
@@ -147,11 +150,11 @@ class PsbtParser {
147
150
  * @returns Parsed transaction data with inputs and outputs
148
151
  * @throws Error if parsing fails
149
152
  */
150
- static parseFinalizedTransactionBase64(txBase64) {
153
+ static parseFinalizedTransactionBase64(txBase64, network) {
151
154
  try {
152
155
  const buffer = Buffer.from(txBase64, 'base64');
153
156
  const tx = Transaction.fromBuffer(buffer);
154
- return PsbtParser.extractTransactionData(tx);
157
+ return PsbtParser.extractTransactionData(tx, network);
155
158
  }
156
159
  catch (error) {
157
160
  throw new Error(`Failed to parse finalized transaction base64: ${error instanceof Error ? error.message : String(error)}`);
@@ -163,7 +166,7 @@ class PsbtParser {
163
166
  * @param tx - The parsed Transaction object
164
167
  * @returns Parsed transaction data with inputs and outputs
165
168
  */
166
- static extractTransactionData(tx) {
169
+ static extractTransactionData(tx, network) {
167
170
  const inputs = [];
168
171
  const outputs = [];
169
172
  tx.ins.forEach((input) => {
@@ -173,7 +176,7 @@ class PsbtParser {
173
176
  };
174
177
  inputs.push(inputData);
175
178
  });
176
- PsbtParser.extractOutputs(tx.outs, outputs);
179
+ PsbtParser.extractOutputs(tx.outs, outputs, network);
177
180
  return { inputs, outputs };
178
181
  }
179
182
  }