@dynamic-labs/bitcoin 4.92.3 → 4.92.4

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,21 @@
1
1
 
2
+ ### [4.92.4](https://github.com/dynamic-labs/dynamic-auth/compare/v4.92.3...v4.92.4) (2026-07-16)
3
+
4
+
5
+ ### Features
6
+
7
+ * **bitcoin:** per-network address switching and dust-limit send validation [v4] ([#11970](https://github.com/dynamic-labs/dynamic-auth/issues/11970)) ([76a4a9a](https://github.com/dynamic-labs/dynamic-auth/commit/76a4a9a5025d78798ebd1beffe6c0af59d2d029a)), closes [#11896](https://github.com/dynamic-labs/dynamic-auth/issues/11896) [#11800](https://github.com/dynamic-labs/dynamic-auth/issues/11800)
8
+ * **midnight:** per-network addresses in deposit and wallet info views [v4] ([#11949](https://github.com/dynamic-labs/dynamic-auth/issues/11949)) ([978446a](https://github.com/dynamic-labs/dynamic-auth/commit/978446aca8546eb038ba9ed3be0503d10594ee15))
9
+ * **midnight:** resolve wallet address per selected network [v4] ([#11948](https://github.com/dynamic-labs/dynamic-auth/issues/11948)) ([d6f858c](https://github.com/dynamic-labs/dynamic-auth/commit/d6f858c5e35defda7ed73cf97b3fdbe5ed9d131d))
10
+ * set useMetamaskSdk to true by default ([#11926](https://github.com/dynamic-labs/dynamic-auth/issues/11926)) ([75b5a4a](https://github.com/dynamic-labs/dynamic-auth/commit/75b5a4a0fe8b1a375afce145c0b8bdc3ce761117))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **midnight:** align per-network code with the final main iteration [v4] ([#11971](https://github.com/dynamic-labs/dynamic-auth/issues/11971)) ([3ef3ba8](https://github.com/dynamic-labs/dynamic-auth/commit/3ef3ba8c090dfd9e4286b4e51bf350ea5e6feac7))
16
+ * settle social sign-in when captcha is backed out or oauth popup never responds (v4) ([#11956](https://github.com/dynamic-labs/dynamic-auth/issues/11956)) ([ad49477](https://github.com/dynamic-labs/dynamic-auth/commit/ad49477f2acf61a154bb79cbe26b24f803cdb0e2))
17
+ * validate that private key is not empty before upgrading to WaaS ([#11952](https://github.com/dynamic-labs/dynamic-auth/issues/11952)) ([21dfb83](https://github.com/dynamic-labs/dynamic-auth/commit/21dfb83cb29e42b335bc94a0cdd2062cf14a262a))
18
+
2
19
  ### [4.92.3](https://github.com/dynamic-labs/dynamic-auth/compare/v4.92.2...v4.92.3) (2026-07-10)
3
20
 
4
21
 
package/package.cjs CHANGED
@@ -3,6 +3,6 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "4.92.3";
6
+ var version = "4.92.4";
7
7
 
8
8
  exports.version = version;
package/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use client'
2
- var version = "4.92.3";
2
+ var version = "4.92.4";
3
3
 
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-labs/bitcoin",
3
- "version": "4.92.3",
3
+ "version": "4.92.4",
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.48",
22
- "@dynamic-labs-wallet/forward-mpc-client": "0.12.0",
21
+ "@dynamic-labs-wallet/browser-wallet-client": "1.0.70",
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.1067",
25
+ "@dynamic-labs/sdk-api-core": "0.0.1073",
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": "4.92.3",
33
- "@dynamic-labs/logger": "4.92.3",
34
- "@dynamic-labs/types": "4.92.3",
35
- "@dynamic-labs/utils": "4.92.3",
36
- "@dynamic-labs/waas": "4.92.3",
37
- "@dynamic-labs/wallet-book": "4.92.3",
38
- "@dynamic-labs/wallet-connector-core": "4.92.3",
32
+ "@dynamic-labs/assert-package-version": "4.92.4",
33
+ "@dynamic-labs/logger": "4.92.4",
34
+ "@dynamic-labs/types": "4.92.4",
35
+ "@dynamic-labs/utils": "4.92.4",
36
+ "@dynamic-labs/waas": "4.92.4",
37
+ "@dynamic-labs/wallet-book": "4.92.4",
38
+ "@dynamic-labs/wallet-connector-core": "4.92.4",
39
39
  "eventemitter3": "5.0.1"
40
40
  },
41
41
  "peerDependencies": {}
@@ -21,6 +21,9 @@ var PsbtParser = require('../../utils/psbtParser/PsbtParser.cjs');
21
21
  var BitcoinUiTransaction = require('../../utils/BitcoinUiTransaction/BitcoinUiTransaction.cjs');
22
22
 
23
23
  const logger = new logger$1.Logger('DynamicWaasConnector');
24
+ /** Dynamic network ids for the Bitcoin networks (mirrors the networks catalog). */
25
+ const BITCOIN_MAINNET_NETWORK_ID = 1;
26
+ const BITCOIN_TESTNET_NETWORK_ID = 2;
24
27
  /**
25
28
  * Bitcoin connector for Dynamic WaaS (Wallet as a Service)
26
29
  * Extends BitcoinWalletConnector with WaaS functionality for Bitcoin transactions
@@ -40,6 +43,12 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
40
43
  this.overrideKey = 'dynamicwaas';
41
44
  this.isEmbeddedWallet = true;
42
45
  this.connectedChain = 'BTC';
46
+ /**
47
+ * The currently selected Bitcoin network id (1 = mainnet, 2 = testnet).
48
+ * One wallet (one key) exposes an address per network via network-tagged
49
+ * additional addresses; switching networks swaps the active address.
50
+ */
51
+ this.activeNetworkId = BITCOIN_MAINNET_NETWORK_ID;
43
52
  this.walletUiUtils = props.walletUiUtils;
44
53
  this.mempoolApiService = new MempoolApiService.MempoolApiService();
45
54
  this.psbtBuilderService = new PsbtBuilderService.PsbtBuilderService(this.mempoolApiService);
@@ -83,16 +92,15 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
83
92
  getAddress() {
84
93
  return _tslib.__awaiter(this, void 0, void 0, function* () {
85
94
  var _a;
86
- const activeAccount = this.getActiveAccount();
87
- if (!activeAccount || !activeAccount.address) {
88
- if ((_a = this.verifiedCredential) === null || _a === void 0 ? void 0 : _a.address) {
89
- const { address } = this.verifiedCredential;
90
- this.setActiveAccount(address);
91
- return address;
92
- }
93
- throw new utils.DynamicError('No active wallet address found', 'no_active_wallet_address_found');
95
+ if (this.activeAccountAddress) {
96
+ return this.activeAccountAddress;
97
+ }
98
+ if ((_a = this.verifiedCredential) === null || _a === void 0 ? void 0 : _a.address) {
99
+ const { address } = this.verifiedCredential;
100
+ this.activeAccountAddress = address;
101
+ return address;
94
102
  }
95
- return activeAccount.address;
103
+ throw new utils.DynamicError('No active wallet address found', 'no_active_wallet_address_found');
96
104
  });
97
105
  }
98
106
  /**
@@ -108,28 +116,113 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
108
116
  });
109
117
  }
110
118
  /**
111
- * Gets the current Bitcoin network
112
- * Always returns Dynamic's networkId (1 for mainnet)
113
- * @returns The Dynamic networkId (1) for Bitcoin mainnet
119
+ * Gets the current Bitcoin network id (1 mainnet, 2 testnet)
114
120
  */
115
121
  getNetwork() {
116
122
  return _tslib.__awaiter(this, void 0, void 0, function* () {
117
- return 1;
123
+ return this.activeNetworkId;
118
124
  });
119
125
  }
120
126
  /**
121
- * Maps Dynamic's networkId to BitcoinNetwork enum for internal WaaS API calls
122
- * Uses the current network from getNetwork() to determine the BitcoinNetwork value
123
- * @returns The corresponding BitcoinNetwork enum value for the current network
127
+ * Maps the selected networkId to the BitcoinNetwork enum for internal WaaS
128
+ * API calls (signing) balance/UTXO/broadcast are address-driven and follow
129
+ * the active address automatically.
124
130
  */
125
131
  getBitcoinNetworkEnum() {
132
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
133
+ return (this.activeNetworkId === BITCOIN_TESTNET_NETWORK_ID
134
+ ? 'testnet'
135
+ : 'mainnet');
136
+ });
137
+ }
138
+ supportsNetworkSwitching() {
139
+ return true;
140
+ }
141
+ /**
142
+ * Testnet when the testnet network (id 2) is selected — drives the widget's
143
+ * native-denominated balance display (testnet coins have no fiat price).
144
+ */
145
+ isTestnet() {
146
+ return Promise.resolve(this.activeNetworkId === BITCOIN_TESTNET_NETWORK_ID);
147
+ }
148
+ /**
149
+ * Balance follows the active (per-network) address: callers pass the
150
+ * wallet's static primary address, which is the mainnet encoding — on
151
+ * testnet the switched address must be queried instead (the mempool API
152
+ * routing is address-driven).
153
+ */
154
+ getBalance(address) {
155
+ const _super = Object.create(null, {
156
+ getBalance: { get: () => super.getBalance }
157
+ });
126
158
  return _tslib.__awaiter(this, void 0, void 0, function* () {
127
159
  var _a;
128
- const networkId = yield this.getNetwork();
129
- const networkIdToBitcoinNetwork = {
130
- 1: 'mainnet',
131
- };
132
- return ((_a = networkIdToBitcoinNetwork[networkId]) !== null && _a !== void 0 ? _a : 'mainnet');
160
+ return _super.getBalance.call(this, (_a = this.activeAccountAddress) !== null && _a !== void 0 ? _a : address);
161
+ });
162
+ }
163
+ /**
164
+ * Wallet identity: keyshares/wallet records are keyed by the wallet's
165
+ * primary (mainnet) address. On testnet the active address is the same
166
+ * wallet's per-network encoding — WaaS lookups (password, signing) must use
167
+ * the primary address (the same key signs both encodings).
168
+ */
169
+ getWalletIdentityAddress() {
170
+ var _a, _b;
171
+ // The active address is a valid fallback only on mainnet, where it is
172
+ // the primary encoding; a switched-network encoding must never be used
173
+ // as the wallet identity.
174
+ const identity = (_b = (_a = this.verifiedCredential) === null || _a === void 0 ? void 0 : _a.address) !== null && _b !== void 0 ? _b : (this.activeNetworkId === BITCOIN_MAINNET_NETWORK_ID
175
+ ? this.activeAccountAddress
176
+ : undefined);
177
+ if (!identity) {
178
+ throw new utils.DynamicError('Active account address is required', 'active_account_address_is_required');
179
+ }
180
+ return identity;
181
+ }
182
+ /**
183
+ * Resolves the wallet's payment address for a network from the credential's
184
+ * network-tagged additional addresses. Mainnet falls back to the primary
185
+ * address (wallets created before per-network registration).
186
+ */
187
+ getPaymentAddressForNetwork(network) {
188
+ var _a, _b, _c;
189
+ const tagged = (_b = (_a = this.verifiedCredential) === null || _a === void 0 ? void 0 : _a.walletAdditionalAddresses) === null || _b === void 0 ? void 0 : _b.find((addr) => addr.type === 'payment' &&
190
+ addr.network === network);
191
+ if (tagged === null || tagged === void 0 ? void 0 : tagged.address) {
192
+ return tagged.address;
193
+ }
194
+ if (network === 'mainnet') {
195
+ return (_c = this.verifiedCredential) === null || _c === void 0 ? void 0 : _c.address;
196
+ }
197
+ return undefined;
198
+ }
199
+ /**
200
+ * Switches between Bitcoin mainnet (1) and testnet (2). Same key — the
201
+ * active address swaps to the network's encoding; mempool/PSBT/broadcast
202
+ * follow the address, signing follows getBitcoinNetworkEnum().
203
+ */
204
+ switchNetwork(_a) {
205
+ return _tslib.__awaiter(this, arguments, void 0, function* ({ networkChainId, networkName, }) {
206
+ let targetId = BITCOIN_MAINNET_NETWORK_ID;
207
+ if (networkChainId !== undefined && networkChainId !== null) {
208
+ targetId = Number(networkChainId);
209
+ }
210
+ else if (networkName === null || networkName === void 0 ? void 0 : networkName.toLowerCase().includes('test')) {
211
+ targetId = BITCOIN_TESTNET_NETWORK_ID;
212
+ }
213
+ if (targetId !== BITCOIN_MAINNET_NETWORK_ID &&
214
+ targetId !== BITCOIN_TESTNET_NETWORK_ID) {
215
+ throw new utils.DynamicError(`Unsupported Bitcoin network chain id: ${String(networkChainId)}`, 'unsupported_network');
216
+ }
217
+ const network = targetId === BITCOIN_TESTNET_NETWORK_ID ? 'testnet' : 'mainnet';
218
+ const address = this.getPaymentAddressForNetwork(network);
219
+ if (!address) {
220
+ logger.error('[DynamicWaasBitcoinConnector] switchNetwork: no address for network', { network, targetId });
221
+ throw new utils.DynamicError(`No ${network} address available for this wallet`, 'no_address_for_network');
222
+ }
223
+ this.activeNetworkId = targetId;
224
+ this.activeAccountAddress = address;
225
+ this.emit('chainChange', { chain: targetId.toString() });
133
226
  });
134
227
  }
135
228
  /**
@@ -147,7 +240,9 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
147
240
  */
148
241
  getBlockExplorerUrlsForCurrentNetwork() {
149
242
  return _tslib.__awaiter(this, void 0, void 0, function* () {
150
- return ['https://mempool.space/'];
243
+ return this.activeNetworkId === BITCOIN_TESTNET_NETWORK_ID
244
+ ? ['https://mempool.space/testnet/']
245
+ : ['https://mempool.space/'];
151
246
  });
152
247
  }
153
248
  /**
@@ -192,15 +287,17 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
192
287
  const elevatedAccessToken = yield ((_c = this.getElevatedAccessToken) === null || _c === void 0 ? void 0 : _c.call(this, {
193
288
  scope: sdkApiCore.TokenScope.Walletsign,
194
289
  }));
290
+ const walletIdentityAddress = this.getWalletIdentityAddress();
195
291
  const password = yield this.getPasswordIfNeeded({
196
- accountAddress: this.activeAccountAddress,
292
+ accountAddress: walletIdentityAddress,
197
293
  });
198
294
  const signedTransaction = yield walletClient.signTransaction({
199
295
  authToken: (_d = this.getAuthToken) === null || _d === void 0 ? void 0 : _d.call(this),
296
+ bitcoinConfig: { network: yield this.getBitcoinNetworkEnum() },
200
297
  elevatedAccessToken,
201
298
  mfaToken,
202
299
  password,
203
- senderAddress: this.activeAccountAddress,
300
+ senderAddress: walletIdentityAddress,
204
301
  signedSessionId,
205
302
  transaction: request.unsignedPsbtBase64,
206
303
  });
@@ -338,7 +435,7 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
338
435
  */
339
436
  validateActiveWallet(expectedAddress) {
340
437
  return _tslib.__awaiter(this, void 0, void 0, function* () {
341
- var _a;
438
+ var _a, _b, _c, _d;
342
439
  const walletClient = yield this.getWaasWalletClient();
343
440
  const targetWallet = yield walletClient.getWallet({
344
441
  accountAddress: expectedAddress,
@@ -350,7 +447,11 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
350
447
  throw new utils.DynamicError('Account not found', 'account_not_found');
351
448
  }
352
449
  const isWalletActive = walletConnectorCore.isSameAddress(targetWallet.accountAddress, this.activeAccountAddress || '', this.connectedChain);
353
- if (!isWalletActive) {
450
+ // The active address may be a per-network encoding of this same wallet
451
+ // (one key, network-tagged additional addresses). Resetting it here would
452
+ // silently un-switch the selected network.
453
+ 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;
454
+ if (!isWalletActive && !isPerNetworkVariantOfWallet) {
354
455
  this.activeAccountAddress = targetWallet.accountAddress;
355
456
  }
356
457
  });
@@ -381,12 +482,13 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
381
482
  scope: sdkApiCore.TokenScope.Walletsign,
382
483
  }));
383
484
  const password = yield this.getPasswordIfNeeded({
384
- accountAddress: this.activeAccountAddress,
485
+ accountAddress: this.getWalletIdentityAddress(),
385
486
  });
386
487
  const messageString = typeof message === 'string' ? message : message.raw;
387
488
  return walletClient.signMessage({
388
- accountAddress: this.activeAccountAddress,
489
+ accountAddress: this.getWalletIdentityAddress(),
389
490
  authToken: (_e = this.getAuthToken) === null || _e === void 0 ? void 0 : _e.call(this),
491
+ bitcoinConfig: { network: yield this.getBitcoinNetworkEnum() },
390
492
  context,
391
493
  elevatedAccessToken,
392
494
  message: messageString,
@@ -536,12 +638,17 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
536
638
  if (allCredentials.length === 0) {
537
639
  throw new utils.DynamicError(`No verified credentials found. The address ${this.activeAccountAddress} may not be properly registered.`, 'no_verified_credentials_found_the_address');
538
640
  }
641
+ // The public key is stored on the wallet's primary (mainnet) address entry
642
+ // and is shared across every per-network encoding, since the same key
643
+ // signs both. Look it up by the wallet identity address rather than the
644
+ // active address, which after a network switch is the testnet encoding.
645
+ const walletIdentityAddress = this.getWalletIdentityAddress();
539
646
  for (const credential of allCredentials) {
540
647
  const { walletAdditionalAddresses } = credential !== null && credential !== void 0 ? credential : {};
541
648
  if (!walletAdditionalAddresses) {
542
649
  continue;
543
650
  }
544
- const additionalAddress = walletAdditionalAddresses.find((address) => address.address === this.activeAccountAddress);
651
+ const additionalAddress = walletAdditionalAddresses.find((address) => address.address === walletIdentityAddress);
545
652
  if (additionalAddress === null || additionalAddress === void 0 ? void 0 : additionalAddress.publicKey) {
546
653
  return additionalAddress.publicKey;
547
654
  }
@@ -550,7 +657,7 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
550
657
  .flatMap((cred) => { var _a; return (_a = cred.walletAdditionalAddresses) !== null && _a !== void 0 ? _a : []; })
551
658
  .map((addr) => addr.address)
552
659
  .join(', ');
553
- throw new utils.DynamicError(`No additional address found for ${this.activeAccountAddress}. Available addresses: ${allAvailableAddresses || 'none'}`, 'no_additional_address_found_for_available');
660
+ throw new utils.DynamicError(`No additional address found for ${walletIdentityAddress}. Available addresses: ${allAvailableAddresses || 'none'}`, 'no_additional_address_found_for_available');
554
661
  });
555
662
  }
556
663
  /**
@@ -594,10 +701,10 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
594
701
  scope: sdkApiCore.TokenScope.Walletsign,
595
702
  }));
596
703
  const password = yield this.getPasswordIfNeeded({
597
- accountAddress: this.activeAccountAddress,
704
+ accountAddress: this.getWalletIdentityAddress(),
598
705
  });
599
706
  return walletClient.signMessage({
600
- accountAddress: this.activeAccountAddress,
707
+ accountAddress: this.getWalletIdentityAddress(),
601
708
  authToken: (_d = this.getAuthToken) === null || _d === void 0 ? void 0 : _d.call(this),
602
709
  bitcoinConfig: {
603
710
  network: yield this.getBitcoinNetworkEnum(),
@@ -22,10 +22,7 @@ declare const DynamicWaasBitcoinConnector_base: (abstract new (...args: any[]) =
22
22
  scope: TokenScope;
23
23
  }) => Promise<string | undefined>) | undefined;
24
24
  onUnauthorized?: (() => void | Promise<void>) | undefined;
25
- /**
26
- * Bitcoin connector for Dynamic WaaS (Wallet as a Service)
27
- * Extends BitcoinWalletConnector with WaaS functionality for Bitcoin transactions
28
- */
25
+ /** Dynamic network ids for the Bitcoin networks (mirrors the networks catalog). */
29
26
  environmentId?: string | undefined;
30
27
  baseApiUrl?: string | undefined;
31
28
  relayUrl?: string | undefined;
@@ -100,11 +97,6 @@ declare const DynamicWaasBitcoinConnector_base: (abstract new (...args: any[]) =
100
97
  thresholdSignatureScheme?: string | undefined;
101
98
  publicAddressCheck?: string | undefined;
102
99
  addressType?: string | undefined;
103
- /**
104
- * Gets the current Bitcoin network
105
- * Always returns Dynamic's networkId (1 for mainnet)
106
- * @returns The Dynamic networkId (1) for Bitcoin mainnet
107
- */
108
100
  legacyWalletId?: string | undefined;
109
101
  password?: string | undefined;
110
102
  }): Promise<void>;
@@ -132,11 +124,7 @@ declare const DynamicWaasBitcoinConnector_base: (abstract new (...args: any[]) =
132
124
  backupKeySharesToGoogleDrive({ accountAddress, password, googleDriveAccessToken, }: {
133
125
  accountAddress: string;
134
126
  password?: string | undefined;
135
- googleDriveAccessToken?: string | undefined; /**
136
- * Gets block explorer URLs for the current network
137
- * Returns mempool.space for Bitcoin transactions
138
- * @returns Array containing mempool.space URL
139
- */
127
+ googleDriveAccessToken?: string | undefined;
140
128
  }): Promise<void>;
141
129
  exportClientKeysharesFromGoogleDrive({ accountAddress, password, }: {
142
130
  accountAddress: string;
@@ -263,17 +251,56 @@ export declare class DynamicWaasBitcoinConnector extends DynamicWaasBitcoinConne
263
251
  */
264
252
  getConnectedAccounts(): Promise<string[]>;
265
253
  /**
266
- * Gets the current Bitcoin network
267
- * Always returns Dynamic's networkId (1 for mainnet)
268
- * @returns The Dynamic networkId (1) for Bitcoin mainnet
254
+ * The currently selected Bitcoin network id (1 = mainnet, 2 = testnet).
255
+ * One wallet (one key) exposes an address per network via network-tagged
256
+ * additional addresses; switching networks swaps the active address.
257
+ */
258
+ private activeNetworkId;
259
+ /**
260
+ * Gets the current Bitcoin network id (1 mainnet, 2 testnet)
269
261
  */
270
262
  getNetwork(): Promise<number>;
271
263
  /**
272
- * Maps Dynamic's networkId to BitcoinNetwork enum for internal WaaS API calls
273
- * Uses the current network from getNetwork() to determine the BitcoinNetwork value
274
- * @returns The corresponding BitcoinNetwork enum value for the current network
264
+ * Maps the selected networkId to the BitcoinNetwork enum for internal WaaS
265
+ * API calls (signing) balance/UTXO/broadcast are address-driven and follow
266
+ * the active address automatically.
275
267
  */
276
268
  private getBitcoinNetworkEnum;
269
+ supportsNetworkSwitching(): boolean;
270
+ /**
271
+ * Testnet when the testnet network (id 2) is selected — drives the widget's
272
+ * native-denominated balance display (testnet coins have no fiat price).
273
+ */
274
+ isTestnet(): Promise<boolean>;
275
+ /**
276
+ * Balance follows the active (per-network) address: callers pass the
277
+ * wallet's static primary address, which is the mainnet encoding — on
278
+ * testnet the switched address must be queried instead (the mempool API
279
+ * routing is address-driven).
280
+ */
281
+ getBalance(address: string): Promise<string | undefined>;
282
+ /**
283
+ * Wallet identity: keyshares/wallet records are keyed by the wallet's
284
+ * primary (mainnet) address. On testnet the active address is the same
285
+ * wallet's per-network encoding — WaaS lookups (password, signing) must use
286
+ * the primary address (the same key signs both encodings).
287
+ */
288
+ private getWalletIdentityAddress;
289
+ /**
290
+ * Resolves the wallet's payment address for a network from the credential's
291
+ * network-tagged additional addresses. Mainnet falls back to the primary
292
+ * address (wallets created before per-network registration).
293
+ */
294
+ private getPaymentAddressForNetwork;
295
+ /**
296
+ * Switches between Bitcoin mainnet (1) and testnet (2). Same key — the
297
+ * active address swaps to the network's encoding; mempool/PSBT/broadcast
298
+ * follow the address, signing follows getBitcoinNetworkEnum().
299
+ */
300
+ switchNetwork({ networkChainId, networkName, }: {
301
+ networkChainId?: number | string;
302
+ networkName?: string;
303
+ }): Promise<void>;
277
304
  /**
278
305
  * Get the enabled networks for Bitcoin
279
306
  * Returns configured networks from props
@@ -17,6 +17,9 @@ import { PsbtParser } from '../../utils/psbtParser/PsbtParser.js';
17
17
  import { BitcoinUiTransaction } from '../../utils/BitcoinUiTransaction/BitcoinUiTransaction.js';
18
18
 
19
19
  const logger = new Logger('DynamicWaasConnector');
20
+ /** Dynamic network ids for the Bitcoin networks (mirrors the networks catalog). */
21
+ const BITCOIN_MAINNET_NETWORK_ID = 1;
22
+ const BITCOIN_TESTNET_NETWORK_ID = 2;
20
23
  /**
21
24
  * Bitcoin connector for Dynamic WaaS (Wallet as a Service)
22
25
  * Extends BitcoinWalletConnector with WaaS functionality for Bitcoin transactions
@@ -36,6 +39,12 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
36
39
  this.overrideKey = 'dynamicwaas';
37
40
  this.isEmbeddedWallet = true;
38
41
  this.connectedChain = 'BTC';
42
+ /**
43
+ * The currently selected Bitcoin network id (1 = mainnet, 2 = testnet).
44
+ * One wallet (one key) exposes an address per network via network-tagged
45
+ * additional addresses; switching networks swaps the active address.
46
+ */
47
+ this.activeNetworkId = BITCOIN_MAINNET_NETWORK_ID;
39
48
  this.walletUiUtils = props.walletUiUtils;
40
49
  this.mempoolApiService = new MempoolApiService();
41
50
  this.psbtBuilderService = new PsbtBuilderService(this.mempoolApiService);
@@ -79,16 +88,15 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
79
88
  getAddress() {
80
89
  return __awaiter(this, void 0, void 0, function* () {
81
90
  var _a;
82
- const activeAccount = this.getActiveAccount();
83
- if (!activeAccount || !activeAccount.address) {
84
- if ((_a = this.verifiedCredential) === null || _a === void 0 ? void 0 : _a.address) {
85
- const { address } = this.verifiedCredential;
86
- this.setActiveAccount(address);
87
- return address;
88
- }
89
- throw new DynamicError('No active wallet address found', 'no_active_wallet_address_found');
91
+ if (this.activeAccountAddress) {
92
+ return this.activeAccountAddress;
93
+ }
94
+ if ((_a = this.verifiedCredential) === null || _a === void 0 ? void 0 : _a.address) {
95
+ const { address } = this.verifiedCredential;
96
+ this.activeAccountAddress = address;
97
+ return address;
90
98
  }
91
- return activeAccount.address;
99
+ throw new DynamicError('No active wallet address found', 'no_active_wallet_address_found');
92
100
  });
93
101
  }
94
102
  /**
@@ -104,28 +112,113 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
104
112
  });
105
113
  }
106
114
  /**
107
- * Gets the current Bitcoin network
108
- * Always returns Dynamic's networkId (1 for mainnet)
109
- * @returns The Dynamic networkId (1) for Bitcoin mainnet
115
+ * Gets the current Bitcoin network id (1 mainnet, 2 testnet)
110
116
  */
111
117
  getNetwork() {
112
118
  return __awaiter(this, void 0, void 0, function* () {
113
- return 1;
119
+ return this.activeNetworkId;
114
120
  });
115
121
  }
116
122
  /**
117
- * Maps Dynamic's networkId to BitcoinNetwork enum for internal WaaS API calls
118
- * Uses the current network from getNetwork() to determine the BitcoinNetwork value
119
- * @returns The corresponding BitcoinNetwork enum value for the current network
123
+ * Maps the selected networkId to the BitcoinNetwork enum for internal WaaS
124
+ * API calls (signing) balance/UTXO/broadcast are address-driven and follow
125
+ * the active address automatically.
120
126
  */
121
127
  getBitcoinNetworkEnum() {
128
+ return __awaiter(this, void 0, void 0, function* () {
129
+ return (this.activeNetworkId === BITCOIN_TESTNET_NETWORK_ID
130
+ ? 'testnet'
131
+ : 'mainnet');
132
+ });
133
+ }
134
+ supportsNetworkSwitching() {
135
+ return true;
136
+ }
137
+ /**
138
+ * Testnet when the testnet network (id 2) is selected — drives the widget's
139
+ * native-denominated balance display (testnet coins have no fiat price).
140
+ */
141
+ isTestnet() {
142
+ return Promise.resolve(this.activeNetworkId === BITCOIN_TESTNET_NETWORK_ID);
143
+ }
144
+ /**
145
+ * Balance follows the active (per-network) address: callers pass the
146
+ * wallet's static primary address, which is the mainnet encoding — on
147
+ * testnet the switched address must be queried instead (the mempool API
148
+ * routing is address-driven).
149
+ */
150
+ getBalance(address) {
151
+ const _super = Object.create(null, {
152
+ getBalance: { get: () => super.getBalance }
153
+ });
122
154
  return __awaiter(this, void 0, void 0, function* () {
123
155
  var _a;
124
- const networkId = yield this.getNetwork();
125
- const networkIdToBitcoinNetwork = {
126
- 1: 'mainnet',
127
- };
128
- return ((_a = networkIdToBitcoinNetwork[networkId]) !== null && _a !== void 0 ? _a : 'mainnet');
156
+ return _super.getBalance.call(this, (_a = this.activeAccountAddress) !== null && _a !== void 0 ? _a : address);
157
+ });
158
+ }
159
+ /**
160
+ * Wallet identity: keyshares/wallet records are keyed by the wallet's
161
+ * primary (mainnet) address. On testnet the active address is the same
162
+ * wallet's per-network encoding — WaaS lookups (password, signing) must use
163
+ * the primary address (the same key signs both encodings).
164
+ */
165
+ getWalletIdentityAddress() {
166
+ var _a, _b;
167
+ // The active address is a valid fallback only on mainnet, where it is
168
+ // the primary encoding; a switched-network encoding must never be used
169
+ // as the wallet identity.
170
+ const identity = (_b = (_a = this.verifiedCredential) === null || _a === void 0 ? void 0 : _a.address) !== null && _b !== void 0 ? _b : (this.activeNetworkId === BITCOIN_MAINNET_NETWORK_ID
171
+ ? this.activeAccountAddress
172
+ : undefined);
173
+ if (!identity) {
174
+ throw new DynamicError('Active account address is required', 'active_account_address_is_required');
175
+ }
176
+ return identity;
177
+ }
178
+ /**
179
+ * Resolves the wallet's payment address for a network from the credential's
180
+ * network-tagged additional addresses. Mainnet falls back to the primary
181
+ * address (wallets created before per-network registration).
182
+ */
183
+ getPaymentAddressForNetwork(network) {
184
+ var _a, _b, _c;
185
+ const tagged = (_b = (_a = this.verifiedCredential) === null || _a === void 0 ? void 0 : _a.walletAdditionalAddresses) === null || _b === void 0 ? void 0 : _b.find((addr) => addr.type === 'payment' &&
186
+ addr.network === network);
187
+ if (tagged === null || tagged === void 0 ? void 0 : tagged.address) {
188
+ return tagged.address;
189
+ }
190
+ if (network === 'mainnet') {
191
+ return (_c = this.verifiedCredential) === null || _c === void 0 ? void 0 : _c.address;
192
+ }
193
+ return undefined;
194
+ }
195
+ /**
196
+ * Switches between Bitcoin mainnet (1) and testnet (2). Same key — the
197
+ * active address swaps to the network's encoding; mempool/PSBT/broadcast
198
+ * follow the address, signing follows getBitcoinNetworkEnum().
199
+ */
200
+ switchNetwork(_a) {
201
+ return __awaiter(this, arguments, void 0, function* ({ networkChainId, networkName, }) {
202
+ let targetId = BITCOIN_MAINNET_NETWORK_ID;
203
+ if (networkChainId !== undefined && networkChainId !== null) {
204
+ targetId = Number(networkChainId);
205
+ }
206
+ else if (networkName === null || networkName === void 0 ? void 0 : networkName.toLowerCase().includes('test')) {
207
+ targetId = BITCOIN_TESTNET_NETWORK_ID;
208
+ }
209
+ if (targetId !== BITCOIN_MAINNET_NETWORK_ID &&
210
+ targetId !== BITCOIN_TESTNET_NETWORK_ID) {
211
+ throw new DynamicError(`Unsupported Bitcoin network chain id: ${String(networkChainId)}`, 'unsupported_network');
212
+ }
213
+ const network = targetId === BITCOIN_TESTNET_NETWORK_ID ? 'testnet' : 'mainnet';
214
+ const address = this.getPaymentAddressForNetwork(network);
215
+ if (!address) {
216
+ logger.error('[DynamicWaasBitcoinConnector] switchNetwork: no address for network', { network, targetId });
217
+ throw new DynamicError(`No ${network} address available for this wallet`, 'no_address_for_network');
218
+ }
219
+ this.activeNetworkId = targetId;
220
+ this.activeAccountAddress = address;
221
+ this.emit('chainChange', { chain: targetId.toString() });
129
222
  });
130
223
  }
131
224
  /**
@@ -143,7 +236,9 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
143
236
  */
144
237
  getBlockExplorerUrlsForCurrentNetwork() {
145
238
  return __awaiter(this, void 0, void 0, function* () {
146
- return ['https://mempool.space/'];
239
+ return this.activeNetworkId === BITCOIN_TESTNET_NETWORK_ID
240
+ ? ['https://mempool.space/testnet/']
241
+ : ['https://mempool.space/'];
147
242
  });
148
243
  }
149
244
  /**
@@ -188,15 +283,17 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
188
283
  const elevatedAccessToken = yield ((_c = this.getElevatedAccessToken) === null || _c === void 0 ? void 0 : _c.call(this, {
189
284
  scope: TokenScope.Walletsign,
190
285
  }));
286
+ const walletIdentityAddress = this.getWalletIdentityAddress();
191
287
  const password = yield this.getPasswordIfNeeded({
192
- accountAddress: this.activeAccountAddress,
288
+ accountAddress: walletIdentityAddress,
193
289
  });
194
290
  const signedTransaction = yield walletClient.signTransaction({
195
291
  authToken: (_d = this.getAuthToken) === null || _d === void 0 ? void 0 : _d.call(this),
292
+ bitcoinConfig: { network: yield this.getBitcoinNetworkEnum() },
196
293
  elevatedAccessToken,
197
294
  mfaToken,
198
295
  password,
199
- senderAddress: this.activeAccountAddress,
296
+ senderAddress: walletIdentityAddress,
200
297
  signedSessionId,
201
298
  transaction: request.unsignedPsbtBase64,
202
299
  });
@@ -334,7 +431,7 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
334
431
  */
335
432
  validateActiveWallet(expectedAddress) {
336
433
  return __awaiter(this, void 0, void 0, function* () {
337
- var _a;
434
+ var _a, _b, _c, _d;
338
435
  const walletClient = yield this.getWaasWalletClient();
339
436
  const targetWallet = yield walletClient.getWallet({
340
437
  accountAddress: expectedAddress,
@@ -346,7 +443,11 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
346
443
  throw new DynamicError('Account not found', 'account_not_found');
347
444
  }
348
445
  const isWalletActive = isSameAddress(targetWallet.accountAddress, this.activeAccountAddress || '', this.connectedChain);
349
- if (!isWalletActive) {
446
+ // The active address may be a per-network encoding of this same wallet
447
+ // (one key, network-tagged additional addresses). Resetting it here would
448
+ // silently un-switch the selected network.
449
+ 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;
450
+ if (!isWalletActive && !isPerNetworkVariantOfWallet) {
350
451
  this.activeAccountAddress = targetWallet.accountAddress;
351
452
  }
352
453
  });
@@ -377,12 +478,13 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
377
478
  scope: TokenScope.Walletsign,
378
479
  }));
379
480
  const password = yield this.getPasswordIfNeeded({
380
- accountAddress: this.activeAccountAddress,
481
+ accountAddress: this.getWalletIdentityAddress(),
381
482
  });
382
483
  const messageString = typeof message === 'string' ? message : message.raw;
383
484
  return walletClient.signMessage({
384
- accountAddress: this.activeAccountAddress,
485
+ accountAddress: this.getWalletIdentityAddress(),
385
486
  authToken: (_e = this.getAuthToken) === null || _e === void 0 ? void 0 : _e.call(this),
487
+ bitcoinConfig: { network: yield this.getBitcoinNetworkEnum() },
386
488
  context,
387
489
  elevatedAccessToken,
388
490
  message: messageString,
@@ -532,12 +634,17 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
532
634
  if (allCredentials.length === 0) {
533
635
  throw new DynamicError(`No verified credentials found. The address ${this.activeAccountAddress} may not be properly registered.`, 'no_verified_credentials_found_the_address');
534
636
  }
637
+ // The public key is stored on the wallet's primary (mainnet) address entry
638
+ // and is shared across every per-network encoding, since the same key
639
+ // signs both. Look it up by the wallet identity address rather than the
640
+ // active address, which after a network switch is the testnet encoding.
641
+ const walletIdentityAddress = this.getWalletIdentityAddress();
535
642
  for (const credential of allCredentials) {
536
643
  const { walletAdditionalAddresses } = credential !== null && credential !== void 0 ? credential : {};
537
644
  if (!walletAdditionalAddresses) {
538
645
  continue;
539
646
  }
540
- const additionalAddress = walletAdditionalAddresses.find((address) => address.address === this.activeAccountAddress);
647
+ const additionalAddress = walletAdditionalAddresses.find((address) => address.address === walletIdentityAddress);
541
648
  if (additionalAddress === null || additionalAddress === void 0 ? void 0 : additionalAddress.publicKey) {
542
649
  return additionalAddress.publicKey;
543
650
  }
@@ -546,7 +653,7 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
546
653
  .flatMap((cred) => { var _a; return (_a = cred.walletAdditionalAddresses) !== null && _a !== void 0 ? _a : []; })
547
654
  .map((addr) => addr.address)
548
655
  .join(', ');
549
- throw new DynamicError(`No additional address found for ${this.activeAccountAddress}. Available addresses: ${allAvailableAddresses || 'none'}`, 'no_additional_address_found_for_available');
656
+ throw new DynamicError(`No additional address found for ${walletIdentityAddress}. Available addresses: ${allAvailableAddresses || 'none'}`, 'no_additional_address_found_for_available');
550
657
  });
551
658
  }
552
659
  /**
@@ -590,10 +697,10 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
590
697
  scope: TokenScope.Walletsign,
591
698
  }));
592
699
  const password = yield this.getPasswordIfNeeded({
593
- accountAddress: this.activeAccountAddress,
700
+ accountAddress: this.getWalletIdentityAddress(),
594
701
  });
595
702
  return walletClient.signMessage({
596
- accountAddress: this.activeAccountAddress,
703
+ accountAddress: this.getWalletIdentityAddress(),
597
704
  authToken: (_d = this.getAuthToken) === null || _d === void 0 ? void 0 : _d.call(this),
598
705
  bitcoinConfig: {
599
706
  network: yield this.getBitcoinNetworkEnum(),
@@ -13,6 +13,9 @@ class BitcoinUiTransaction {
13
13
  this.data = undefined;
14
14
  this.fee = { gas: undefined };
15
15
  this.feePriority = 'medium';
16
+ // Outputs below the dust limit are unrelayable; PsbtBuilderService throws
17
+ // on them, so surface the floor to the send form instead.
18
+ this.minAmount = BigInt(_const.DUST_LIMIT);
16
19
  /**
17
20
  * Formats the value into a string for the UI for non-native tokens
18
21
  * @param value - The value to format
@@ -22,6 +22,7 @@ export declare class BitcoinUiTransaction implements IUITransaction {
22
22
  };
23
23
  nativePrice?: number;
24
24
  feePriority: FeePriority;
25
+ minAmount: bigint;
25
26
  private lastFeePriority?;
26
27
  private onSubmit;
27
28
  private mempoolApiService;
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
  import { __awaiter } from '../../../_virtual/_tslib.js';
3
3
  import { formatNumberText } from '@dynamic-labs/utils';
4
- import { SATOSHIS_PER_BTC } from '../../const.js';
4
+ import { DUST_LIMIT, SATOSHIS_PER_BTC } from '../../const.js';
5
5
 
6
6
  class BitcoinUiTransaction {
7
7
  constructor({ onSubmit, from, mempoolApiService, }) {
@@ -9,6 +9,9 @@ class BitcoinUiTransaction {
9
9
  this.data = undefined;
10
10
  this.fee = { gas: undefined };
11
11
  this.feePriority = 'medium';
12
+ // Outputs below the dust limit are unrelayable; PsbtBuilderService throws
13
+ // on them, so surface the floor to the send form instead.
14
+ this.minAmount = BigInt(DUST_LIMIT);
12
15
  /**
13
16
  * Formats the value into a string for the UI for non-native tokens
14
17
  * @param value - The value to format