@dynamic-labs-sdk/bitcoin 0.23.2 → 0.23.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.
Files changed (37) hide show
  1. package/dist/{addBitcoinInjectedWalletsExtension-Blf31mFB.esm.js → addBitcoinInjectedWalletsExtension-BIxPSyXV.esm.js} +17 -217
  2. package/dist/addBitcoinInjectedWalletsExtension-BIxPSyXV.esm.js.map +1 -0
  3. package/dist/{addBitcoinInjectedWalletsExtension-DoK6Bz_8.cjs.js → addBitcoinInjectedWalletsExtension-BcX1Ks1d.cjs.js} +36 -296
  4. package/dist/addBitcoinInjectedWalletsExtension-BcX1Ks1d.cjs.js.map +1 -0
  5. package/dist/addWaasBitcoinExtension-8da7zX1k.esm.js +707 -0
  6. package/dist/addWaasBitcoinExtension-8da7zX1k.esm.js.map +1 -0
  7. package/dist/addWaasBitcoinExtension-CXG18ahP.cjs.js +758 -0
  8. package/dist/addWaasBitcoinExtension-CXG18ahP.cjs.js.map +1 -0
  9. package/dist/bitcoinTransferAmount-B2rBAbvM.esm.js +222 -0
  10. package/dist/bitcoinTransferAmount-B2rBAbvM.esm.js.map +1 -0
  11. package/dist/bitcoinTransferAmount-q7TCsKtK.cjs.js +288 -0
  12. package/dist/bitcoinTransferAmount-q7TCsKtK.cjs.js.map +1 -0
  13. package/dist/index.cjs.js +13 -741
  14. package/dist/index.cjs.js.map +1 -1
  15. package/dist/index.esm.js +5 -704
  16. package/dist/index.esm.js.map +1 -1
  17. package/dist/injected.cjs.js +4 -3
  18. package/dist/injected.cjs.js.map +1 -1
  19. package/dist/injected.esm.js +2 -1
  20. package/dist/injected.esm.js.map +1 -1
  21. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  22. package/dist/utils/bitcoinExecuteSwapTransaction/bitcoinExecuteSwapTransaction.d.ts.map +1 -1
  23. package/dist/utils/convertPsbtToBase64/convertPsbtToBase64.d.ts +3 -0
  24. package/dist/utils/convertPsbtToBase64/convertPsbtToBase64.d.ts.map +1 -0
  25. package/dist/utils/convertPsbtToBase64/index.d.ts +2 -0
  26. package/dist/utils/convertPsbtToBase64/index.d.ts.map +1 -0
  27. package/dist/utils/isHexEncodedPsbt/index.d.ts +2 -0
  28. package/dist/utils/isHexEncodedPsbt/index.d.ts.map +1 -0
  29. package/dist/utils/isHexEncodedPsbt/isHexEncodedPsbt.d.ts +3 -0
  30. package/dist/utils/isHexEncodedPsbt/isHexEncodedPsbt.d.ts.map +1 -0
  31. package/dist/waas.cjs.js +41 -0
  32. package/dist/waas.cjs.js.map +1 -0
  33. package/dist/waas.esm.js +40 -0
  34. package/dist/waas.esm.js.map +1 -0
  35. package/package.json +4 -4
  36. package/dist/addBitcoinInjectedWalletsExtension-Blf31mFB.esm.js.map +0 -1
  37. package/dist/addBitcoinInjectedWalletsExtension-DoK6Bz_8.cjs.js.map +0 -1
@@ -0,0 +1,707 @@
1
+ import { c as registerBitcoinNetworkProviderBuilder, i as bitcoinExecuteSwapTransaction, t as bitcoinTransferAmount } from "./bitcoinTransferAmount-B2rBAbvM.esm.js";
2
+ import { WalletProviderPriority, assertDefined, consumeMfaTokenIfRequiredForAction, formatWalletProviderGroupKey, formatWalletProviderKey, getActiveNetworkIdFromLastKnownRegistry, getBuffer, getCore, getDefaultClient, getSignedSessionId, getWalletProviderRegistry, hasExtension, registerExtension, switchActiveNetworkInLastKnownRegistry } from "@dynamic-labs-sdk/client/core";
3
+ import { BaseError } from "@dynamic-labs-sdk/client";
4
+ import { MFAAction, WalletProviderEnum } from "@dynamic-labs/sdk-api-core";
5
+ import { Psbt, address, networks, payments } from "bitcoinjs-lib";
6
+ import { DYNAMIC_WAAS_METADATA, createWaasProvider, getAllUserWaasAddressesForChain } from "@dynamic-labs-sdk/client/waas/core";
7
+ import ecc from "@bitcoinerlab/secp256k1";
8
+ import { ECPairFactory } from "ecpair";
9
+
10
+ //#region src/errors/TransactionBroadcastFailedError.ts
11
+ var TransactionBroadcastFailedError = class extends BaseError {
12
+ response;
13
+ constructor({ response }) {
14
+ super({
15
+ cause: null,
16
+ code: "transaction_broadcast_failed_error",
17
+ details: `Status: ${response.status} ${response.statusText}`,
18
+ docsUrl: null,
19
+ name: "TransactionBroadcastFailedError",
20
+ shortMessage: "Failed to broadcast transaction to mempool"
21
+ });
22
+ this.response = response;
23
+ }
24
+ };
25
+
26
+ //#endregion
27
+ //#region src/errors/TransactionRequiredError.ts
28
+ var TransactionRequiredError = class extends BaseError {
29
+ constructor() {
30
+ super({
31
+ cause: null,
32
+ code: "transaction_required_error",
33
+ docsUrl: null,
34
+ name: "TransactionRequiredError",
35
+ shortMessage: "No transaction specified for broadcast"
36
+ });
37
+ }
38
+ };
39
+
40
+ //#endregion
41
+ //#region src/waas/constants.ts
42
+ /**
43
+ * Mempool.space API URL for mainnet
44
+ */
45
+ const MEMPOOL_API_URL = "https://mempool.space/api";
46
+ /**
47
+ * Number of satoshis per Bitcoin
48
+ */
49
+ const SATOSHIS_PER_BTC = 1e8;
50
+ /**
51
+ * Bitcoin's dust limit in satoshis
52
+ * Outputs below this value are considered "dust" and will be rejected by nodes
53
+ */
54
+ const DUST_LIMIT = 546;
55
+ /**
56
+ * Accurate vSize constants for Native SegWit (P2WPKH) transactions
57
+ * Used for precise fee estimation
58
+ */
59
+ const VSIZE_OVERHEAD = 10.5;
60
+ const VSIZE_INPUT_P2WPKH = 68;
61
+ const VSIZE_OUTPUT_P2WPKH = 31;
62
+ /**
63
+ * Minimum relay fee in satoshis
64
+ * Added to fee estimate to ensure transaction propagation
65
+ */
66
+ const MIN_RELAY_FEE = 111;
67
+ /**
68
+ * Conservative default fee estimate in satoshis
69
+ * Used as fallback when fee estimation fails
70
+ */
71
+ const DEFAULT_FEE_ESTIMATE = 1e3;
72
+ /**
73
+ * RBF (Replace-By-Fee) sequence number
74
+ * 0xfffffffd = 4294967293 (enables RBF, not final)
75
+ */
76
+ const RBF_SEQUENCE = 4294967293;
77
+
78
+ //#endregion
79
+ //#region src/waas/utils/broadcastTransaction/broadcastTransaction.ts
80
+ /**
81
+ * Sends a raw Bitcoin transaction to the mempool
82
+ *
83
+ * @param rawTransaction - The raw transaction in hex format
84
+ * @returns The transaction ID
85
+ * @throws {TransactionRequiredError} if no transaction is specified
86
+ * @throws {TransactionBroadcastFailedError} if broadcasting fails
87
+ * @not-instrumented
88
+ */
89
+ const broadcastTransaction = async (rawTransaction) => {
90
+ if (!rawTransaction) throw new TransactionRequiredError();
91
+ const response = await fetch(`${MEMPOOL_API_URL}/tx`, {
92
+ body: rawTransaction,
93
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
94
+ method: "POST"
95
+ });
96
+ if (!response.ok) throw new TransactionBroadcastFailedError({ response });
97
+ return response.text();
98
+ };
99
+
100
+ //#endregion
101
+ //#region src/errors/InsufficientFundsError.ts
102
+ var InsufficientFundsError = class extends BaseError {
103
+ availableSatoshis;
104
+ requiredSatoshis;
105
+ constructor({ availableSatoshis, requiredSatoshis }) {
106
+ const availableBtc = availableSatoshis / SATOSHIS_PER_BTC;
107
+ const requiredBtc = requiredSatoshis / SATOSHIS_PER_BTC;
108
+ super({
109
+ cause: null,
110
+ code: "insufficient_funds_error",
111
+ details: `Available: ${availableBtc} BTC (${availableSatoshis} satoshis), Required: ${requiredBtc} BTC (${requiredSatoshis} satoshis)`,
112
+ docsUrl: null,
113
+ name: "InsufficientFundsError",
114
+ shortMessage: "Insufficient funds for transaction"
115
+ });
116
+ this.availableSatoshis = availableSatoshis;
117
+ this.requiredSatoshis = requiredSatoshis;
118
+ }
119
+ };
120
+
121
+ //#endregion
122
+ //#region src/errors/InvalidAmountError.ts
123
+ var InvalidAmountError = class extends BaseError {
124
+ amountInSatoshis;
125
+ constructor({ amountInSatoshis, reason }) {
126
+ super({
127
+ cause: null,
128
+ code: "invalid_amount_error",
129
+ details: `Amount: ${amountInSatoshis} satoshis`,
130
+ docsUrl: null,
131
+ name: "InvalidAmountError",
132
+ shortMessage: reason
133
+ });
134
+ this.amountInSatoshis = amountInSatoshis;
135
+ }
136
+ };
137
+
138
+ //#endregion
139
+ //#region src/errors/NoUTXOsFoundError.ts
140
+ var NoUTXOsFoundError = class extends BaseError {
141
+ address;
142
+ constructor({ address: address$1 }) {
143
+ super({
144
+ cause: null,
145
+ code: "no_utxos_found_error",
146
+ details: `Address: ${address$1}`,
147
+ docsUrl: null,
148
+ name: "NoUTXOsFoundError",
149
+ shortMessage: "No UTXOs found for this address"
150
+ });
151
+ this.address = address$1;
152
+ }
153
+ };
154
+
155
+ //#endregion
156
+ //#region src/errors/SegwitOutputScriptError.ts
157
+ var SegwitOutputScriptError = class extends BaseError {
158
+ constructor() {
159
+ super({
160
+ cause: null,
161
+ code: "segwit_output_script_error",
162
+ docsUrl: null,
163
+ name: "SegwitOutputScriptError",
164
+ shortMessage: "Failed to create segwit output script"
165
+ });
166
+ }
167
+ };
168
+
169
+ //#endregion
170
+ //#region src/waas/utils/addInputsToPsbt/addInputsToPsbt.ts
171
+ /**
172
+ * Adds inputs to PSBT from selected UTXOs
173
+ *
174
+ * @param options.network - Bitcoin network configuration (mainnet or testnet)
175
+ * @param options.psbt - The PSBT instance to add inputs to
176
+ * @param options.publicKeyPair - Key pair containing the public key for witness script
177
+ * @param options.selectedUTXOs - Array of UTXOs to add as inputs
178
+ * @not-instrumented
179
+ */
180
+ const addInputsToPsbt = (options) => {
181
+ const { network, psbt, publicKeyPair, selectedUTXOs } = options;
182
+ for (const utxo of selectedUTXOs) {
183
+ const outputScript = payments.p2wpkh({
184
+ network,
185
+ pubkey: publicKeyPair.publicKey
186
+ }).output;
187
+ if (!outputScript) throw new SegwitOutputScriptError();
188
+ const txidBuffer = new Uint8Array(getBuffer().from(utxo.txid, "hex").reverse());
189
+ psbt.addInput({
190
+ hash: txidBuffer,
191
+ index: utxo.vout,
192
+ sequence: RBF_SEQUENCE,
193
+ witnessUtxo: {
194
+ script: outputScript,
195
+ value: BigInt(utxo.value)
196
+ }
197
+ });
198
+ }
199
+ };
200
+
201
+ //#endregion
202
+ //#region src/waas/utils/addOutputsToPsbt/addOutputsToPsbt.ts
203
+ /**
204
+ * Adds outputs to PSBT (recipient and optionally change)
205
+ *
206
+ * @param options.accountAddress - The sender's address for receiving change
207
+ * @param options.amountInSatoshis - Amount to send to the recipient in satoshis
208
+ * @param options.changeAmount - Amount to return as change in satoshis
209
+ * @param options.hasChangeOutput - Whether to include a change output
210
+ * @param options.network - Bitcoin network configuration (mainnet or testnet)
211
+ * @param options.psbt - The PSBT instance to add outputs to
212
+ * @param options.recipientAddress - The recipient's Bitcoin address
213
+ * @not-instrumented
214
+ */
215
+ const addOutputsToPsbt = (options) => {
216
+ const { accountAddress, amountInSatoshis, changeAmount, hasChangeOutput, network, psbt, recipientAddress } = options;
217
+ if (amountInSatoshis < DUST_LIMIT) throw new InvalidAmountError({
218
+ amountInSatoshis,
219
+ reason: `Amount is below dust limit of ${DUST_LIMIT} satoshis (${DUST_LIMIT / SATOSHIS_PER_BTC} BTC)`
220
+ });
221
+ psbt.addOutput({
222
+ script: address.toOutputScript(recipientAddress, network),
223
+ value: BigInt(amountInSatoshis)
224
+ });
225
+ if (hasChangeOutput) psbt.addOutput({
226
+ script: address.toOutputScript(accountAddress, network),
227
+ value: BigInt(changeAmount)
228
+ });
229
+ };
230
+
231
+ //#endregion
232
+ //#region src/errors/FeeRecommendationsFetchError.ts
233
+ var FeeRecommendationsFetchError = class extends BaseError {
234
+ response;
235
+ constructor({ response }) {
236
+ super({
237
+ cause: null,
238
+ code: "fee_recommendations_fetch_error",
239
+ details: `Status: ${response.status} ${response.statusText}`,
240
+ docsUrl: null,
241
+ name: "FeeRecommendationsFetchError",
242
+ shortMessage: "Failed to fetch fee recommendations from mempool"
243
+ });
244
+ this.response = response;
245
+ }
246
+ };
247
+
248
+ //#endregion
249
+ //#region src/waas/utils/getFeeRecommendations/getFeeRecommendations.ts
250
+ /**
251
+ * Gets fee recommendations from mempool.space API
252
+ *
253
+ * @returns Fee recommendation data with rates in sat/vB
254
+ * @throws FeeRecommendationsFetchError if fetching fee recommendations fails
255
+ * @not-instrumented
256
+ */
257
+ const getFeeRecommendations = async () => {
258
+ const response = await fetch(`${MEMPOOL_API_URL}/v1/fees/recommended`);
259
+ if (!response.ok) throw new FeeRecommendationsFetchError({ response });
260
+ return response.json();
261
+ };
262
+
263
+ //#endregion
264
+ //#region src/waas/utils/estimateTransactionFee/estimateTransactionFee.ts
265
+ /**
266
+ * Estimates transaction fees based on number of inputs and outputs using accurate vSize
267
+ *
268
+ * @param options.feePriority - Priority level for fee estimation ('low', 'medium', or 'high')
269
+ * @param options.numInputs - Number of transaction inputs (UTXOs being spent)
270
+ * @param options.numOutputs - Number of transaction outputs
271
+ * @returns Estimated fee in satoshis
272
+ * @not-instrumented
273
+ */
274
+ const estimateTransactionFee = async ({ feePriority = "medium", numInputs, numOutputs }) => {
275
+ try {
276
+ const feeData = await getFeeRecommendations();
277
+ let feePerByte;
278
+ if (feePriority === "high") feePerByte = feeData.fastestFee ?? feeData.halfHourFee ?? 1;
279
+ else if (feePriority === "low") feePerByte = feeData.economyFee ?? feeData.hourFee ?? 1;
280
+ else feePerByte = feeData.halfHourFee ?? feeData.hourFee ?? feeData.economyFee ?? 1;
281
+ const vSize = VSIZE_OVERHEAD + numInputs * VSIZE_INPUT_P2WPKH + numOutputs * VSIZE_OUTPUT_P2WPKH;
282
+ return Math.ceil(feePerByte * vSize) + MIN_RELAY_FEE;
283
+ } catch {
284
+ return DEFAULT_FEE_ESTIMATE;
285
+ }
286
+ };
287
+
288
+ //#endregion
289
+ //#region src/waas/utils/calculateFeeAndChange/calculateFeeAndChange.ts
290
+ /**
291
+ * Calculates fee estimate and change amount, handling dust limit
292
+ *
293
+ * @param options.amountInSatoshis - Amount to send in satoshis
294
+ * @param options.feePriority - Priority level for fee estimation ('low', 'medium', or 'high')
295
+ * @param options.selectedTotalValue - Total value of selected UTXOs in satoshis
296
+ * @param options.selectedUTXOs - Array of selected UTXOs for the transaction
297
+ * @returns Object with feeEstimate, changeAmountNumber, and hasChangeOutput
298
+ * @not-instrumented
299
+ */
300
+ const calculateFeeAndChange = async ({ amountInSatoshis, feePriority, selectedTotalValue, selectedUTXOs }) => {
301
+ let feeEstimate = await estimateTransactionFee({
302
+ feePriority,
303
+ numInputs: selectedUTXOs.length,
304
+ numOutputs: 1
305
+ });
306
+ let maxToSpend = selectedTotalValue - feeEstimate;
307
+ let changeAmount = BigInt(maxToSpend) - amountInSatoshis;
308
+ if (changeAmount > 0 && Number(changeAmount) >= DUST_LIMIT) {
309
+ feeEstimate = await estimateTransactionFee({
310
+ feePriority,
311
+ numInputs: selectedUTXOs.length,
312
+ numOutputs: 2
313
+ });
314
+ maxToSpend = selectedTotalValue - feeEstimate;
315
+ changeAmount = BigInt(maxToSpend) - amountInSatoshis;
316
+ }
317
+ const finalChangeAmountNumber = Number(changeAmount);
318
+ const hasChangeOutput = changeAmount > 0 && finalChangeAmountNumber >= DUST_LIMIT;
319
+ if (changeAmount > 0 && finalChangeAmountNumber < DUST_LIMIT) feeEstimate += finalChangeAmountNumber;
320
+ return {
321
+ changeAmountNumber: finalChangeAmountNumber,
322
+ feeEstimate,
323
+ hasChangeOutput
324
+ };
325
+ };
326
+
327
+ //#endregion
328
+ //#region src/waas/utils/calculateUTXOTotal/calculateUTXOTotal.ts
329
+ /**
330
+ * Calculates the total value of UTXOs
331
+ *
332
+ * @param utxos - Array of UTXOs
333
+ * @returns Total value in satoshis
334
+ * @not-instrumented
335
+ */
336
+ const calculateUTXOTotal = (utxos) => utxos.reduce((total, utxo) => total + utxo.value, 0);
337
+
338
+ //#endregion
339
+ //#region src/errors/UTXOsFetchError.ts
340
+ var UTXOsFetchError = class extends BaseError {
341
+ address;
342
+ response;
343
+ constructor({ address: address$1, response }) {
344
+ super({
345
+ cause: null,
346
+ code: "utxos_fetch_error",
347
+ details: `Address: ${address$1}, Status: ${response.status} ${response.statusText}`,
348
+ docsUrl: null,
349
+ name: "UTXOsFetchError",
350
+ shortMessage: "Failed to fetch UTXOs from mempool"
351
+ });
352
+ this.address = address$1;
353
+ this.response = response;
354
+ }
355
+ };
356
+
357
+ //#endregion
358
+ //#region src/waas/utils/getUTXOs/getUTXOs.ts
359
+ /**
360
+ * Gets UTXOs for a Bitcoin address from mempool.space API
361
+ *
362
+ * @param address - The Bitcoin address to get UTXOs for
363
+ * @returns Array of UTXOs
364
+ * @throws UTXOsFetchError if fetching UTXOs fails
365
+ * @not-instrumented
366
+ */
367
+ const getUTXOs = async (address$1) => {
368
+ const response = await fetch(`${MEMPOOL_API_URL}/address/${address$1}/utxo`);
369
+ if (!response.ok) throw new UTXOsFetchError({
370
+ address: address$1,
371
+ response
372
+ });
373
+ return response.json();
374
+ };
375
+
376
+ //#endregion
377
+ //#region src/waas/utils/selectUTXOsLargestFirst/selectUTXOsLargestFirst.ts
378
+ /**
379
+ * Selects UTXOs using Largest-First (Accumulator) strategy
380
+ * Sorts UTXOs by value (descending) and selects until we have enough to cover amount + fees
381
+ *
382
+ * @param options.targetAmount - Target amount in satoshis (amount + fees + dust limit)
383
+ * @param options.utxos - Array of available UTXOs to select from
384
+ * @returns Selected UTXOs
385
+ * @not-instrumented
386
+ */
387
+ const selectUTXOsLargestFirst = (options) => {
388
+ const { targetAmount, utxos } = options;
389
+ const sortedUTXOs = [...utxos].sort((a, b) => b.value - a.value);
390
+ const selected = [];
391
+ let total = 0;
392
+ for (const utxo of sortedUTXOs) {
393
+ selected.push(utxo);
394
+ total += utxo.value;
395
+ if (total >= targetAmount) break;
396
+ }
397
+ return selected;
398
+ };
399
+
400
+ //#endregion
401
+ //#region src/waas/utils/validateAndSelectUTXOs/validateAndSelectUTXOs.ts
402
+ /**
403
+ * Validates and ensures sufficient funds for the transaction
404
+ *
405
+ * @param options.allUTXOs - Complete array of available UTXOs for the address
406
+ * @param options.amountInSatoshis - Amount to send in satoshis
407
+ * @param options.feeEstimate - Estimated transaction fee in satoshis
408
+ * @param options.selectedTotal - Total value of initially selected UTXOs in satoshis
409
+ * @param options.selectedUTXOs - Array of initially selected UTXOs
410
+ * @returns Validated selected UTXOs
411
+ * @throws InsufficientFundsError if insufficient funds
412
+ * @not-instrumented
413
+ */
414
+ const validateAndSelectUTXOs = (options) => {
415
+ const { allUTXOs, amountInSatoshis, feeEstimate, selectedTotal, selectedUTXOs } = options;
416
+ const requiredAmount = amountInSatoshis + feeEstimate;
417
+ if (selectedTotal >= requiredAmount) return selectedUTXOs;
418
+ if (selectedUTXOs.length < allUTXOs.length) {
419
+ const allTotal = calculateUTXOTotal(allUTXOs);
420
+ if (allTotal < requiredAmount) throw new InsufficientFundsError({
421
+ availableSatoshis: allTotal,
422
+ requiredSatoshis: amountInSatoshis
423
+ });
424
+ return allUTXOs;
425
+ }
426
+ throw new InsufficientFundsError({
427
+ availableSatoshis: selectedTotal,
428
+ requiredSatoshis: amountInSatoshis
429
+ });
430
+ };
431
+
432
+ //#endregion
433
+ //#region src/errors/TaprootAddressNotSupportedError.ts
434
+ var TaprootAddressNotSupportedError = class extends BaseError {
435
+ constructor() {
436
+ super({
437
+ cause: null,
438
+ code: "taproot_address_not_supported",
439
+ docsUrl: null,
440
+ name: "TaprootAddressNotSupportedError",
441
+ shortMessage: "Taproot addresses are not supported for PSBT building. Only Native SegWit (P2WPKH) addresses are allowed."
442
+ });
443
+ }
444
+ };
445
+
446
+ //#endregion
447
+ //#region src/waas/utils/validateNotTaproot/validateNotTaproot.ts
448
+ /**
449
+ * Validates that the address is not a Taproot address
450
+ * Only Native SegWit (P2WPKH) is supported for PSBT building
451
+ *
452
+ * @param accountAddress - The account address to check
453
+ * @throws TaprootAddressNotSupportedError if address is Taproot
454
+ * @not-instrumented
455
+ */
456
+ const validateNotTaproot = (accountAddress) => {
457
+ if (accountAddress.toLowerCase().startsWith("bc1p") || accountAddress.toLowerCase().startsWith("tb1p")) throw new TaprootAddressNotSupportedError();
458
+ };
459
+
460
+ //#endregion
461
+ //#region src/waas/utils/buildPsbt/buildPsbt.ts
462
+ /**
463
+ * Builds a PSBT for a Bitcoin transaction with real UTXOs
464
+ * Uses Largest-First UTXO selection strategy with accurate vSize fee estimation
465
+ *
466
+ * @param options.accountAddress - The sender's Bitcoin address
467
+ * @param options.amountInSatoshis - Amount to send in satoshis
468
+ * @param options.feePriority - Priority level for fee estimation ('low', 'medium', or 'high')
469
+ * @param options.network - Bitcoin network configuration (mainnet or testnet)
470
+ * @param options.publicKeyHex - The sender's public key in hexadecimal format
471
+ * @param options.recipientAddress - The recipient's Bitcoin address
472
+ * @returns A PSBT in Base64 format
473
+ * @throws Error if insufficient funds, no UTXOs, or other errors
474
+ * @not-instrumented
475
+ */
476
+ const buildPsbt = async ({ accountAddress, amountInSatoshis, feePriority = "medium", network, publicKeyHex, recipientAddress }) => {
477
+ if (amountInSatoshis <= BigInt(0)) throw new InvalidAmountError({
478
+ amountInSatoshis: Number(amountInSatoshis),
479
+ reason: "Amount must be greater than 0"
480
+ });
481
+ validateNotTaproot(accountAddress);
482
+ const allUTXOs = await getUTXOs(accountAddress);
483
+ if (allUTXOs.length === 0) throw new NoUTXOsFoundError({ address: accountAddress });
484
+ const publicKeyBuffer = new Uint8Array(getBuffer().from(publicKeyHex, "hex"));
485
+ const publicKeyPair = ECPairFactory(ecc).fromPublicKey(publicKeyBuffer, { compressed: true });
486
+ const amountInSatoshisNumber = Number(amountInSatoshis);
487
+ const initialFeeEstimate = await estimateTransactionFee({
488
+ feePriority,
489
+ numInputs: 1,
490
+ numOutputs: 1
491
+ });
492
+ let selectedUTXOs = selectUTXOsLargestFirst({
493
+ targetAmount: amountInSatoshisNumber + initialFeeEstimate + DUST_LIMIT,
494
+ utxos: allUTXOs
495
+ });
496
+ selectedUTXOs = validateAndSelectUTXOs({
497
+ allUTXOs,
498
+ amountInSatoshis: amountInSatoshisNumber,
499
+ feeEstimate: initialFeeEstimate,
500
+ selectedTotal: calculateUTXOTotal(selectedUTXOs),
501
+ selectedUTXOs
502
+ });
503
+ const selectedTotalValue = calculateUTXOTotal(selectedUTXOs);
504
+ const { changeAmountNumber, feeEstimate, hasChangeOutput } = await calculateFeeAndChange({
505
+ amountInSatoshis,
506
+ feePriority,
507
+ selectedTotalValue,
508
+ selectedUTXOs
509
+ });
510
+ const maxToSpend = selectedTotalValue - feeEstimate;
511
+ if (maxToSpend < amountInSatoshisNumber) throw new InsufficientFundsError({
512
+ availableSatoshis: maxToSpend,
513
+ requiredSatoshis: amountInSatoshisNumber
514
+ });
515
+ const psbt = new Psbt({ network });
516
+ addInputsToPsbt({
517
+ network,
518
+ psbt,
519
+ publicKeyPair,
520
+ selectedUTXOs
521
+ });
522
+ addOutputsToPsbt({
523
+ accountAddress,
524
+ amountInSatoshis: amountInSatoshisNumber,
525
+ changeAmount: changeAmountNumber,
526
+ hasChangeOutput,
527
+ network,
528
+ psbt,
529
+ recipientAddress
530
+ });
531
+ return psbt.toBase64();
532
+ };
533
+
534
+ //#endregion
535
+ //#region src/errors/PublicKeyNotFoundError.ts
536
+ var PublicKeyNotFoundError = class extends BaseError {
537
+ constructor(address$1) {
538
+ super({
539
+ cause: null,
540
+ code: "public_key_not_found_error",
541
+ docsUrl: null,
542
+ name: "PublicKeyNotFoundError",
543
+ shortMessage: `No public key found for address ${address$1}`
544
+ });
545
+ }
546
+ };
547
+
548
+ //#endregion
549
+ //#region src/waas/utils/getPublicKeyForWalletAccount/getPublicKeyForWalletAccount.ts
550
+ /**
551
+ * Gets the public key for a wallet account from the user's verified credentials
552
+ * @not-instrumented
553
+ */
554
+ const getPublicKeyForWalletAccount = (walletAccount, client) => {
555
+ const user = getCore(client).state.get().user;
556
+ for (const credential of user?.verifiedCredentials ?? []) {
557
+ const additionalAddress = credential.walletAdditionalAddresses?.find((addr) => addr.address === walletAccount.address);
558
+ if (additionalAddress?.publicKey) return additionalAddress.publicKey;
559
+ }
560
+ throw new PublicKeyNotFoundError(walletAccount.address);
561
+ };
562
+
563
+ //#endregion
564
+ //#region src/waas/utils/createWalletProviderForWaasBitcoin/createWalletProviderForWaasBitcoin.ts
565
+ /** @not-instrumented */
566
+ const createWalletProviderForWaasBitcoin = (sdkClient) => {
567
+ const chain = "BTC";
568
+ const walletProviderType = WalletProviderEnum.EmbeddedWallet;
569
+ const key = formatWalletProviderKey({
570
+ chain,
571
+ displayName: DYNAMIC_WAAS_METADATA.displayName,
572
+ walletProviderType
573
+ });
574
+ const waasProvider = createWaasProvider({
575
+ chain,
576
+ sdkClient
577
+ });
578
+ const getActiveNetworkId = async () => getActiveNetworkIdFromLastKnownRegistry({
579
+ client: sdkClient,
580
+ walletProviderKey: key
581
+ });
582
+ const switchActiveNetwork = async ({ networkId }) => switchActiveNetworkInLastKnownRegistry({
583
+ client: sdkClient,
584
+ networkId,
585
+ walletProviderKey: key
586
+ });
587
+ const getConnectedAddresses = async () => {
588
+ return { addresses: getAllUserWaasAddressesForChain({ chain }, sdkClient) };
589
+ };
590
+ const signPsbt = async ({ request, walletAccount }) => {
591
+ assertDefined(walletAccount, "Wallet account is required");
592
+ const waasClient = await waasProvider.getWaasClient();
593
+ const { signature: signedSessionId } = await getSignedSessionId(sdkClient);
594
+ const mfaToken = await consumeMfaTokenIfRequiredForAction({ mfaAction: MFAAction.WalletWaasSign }, sdkClient);
595
+ return { signedPsbt: await waasClient.signTransaction({
596
+ authToken: sdkClient.token ?? void 0,
597
+ mfaToken,
598
+ senderAddress: walletAccount.address,
599
+ signedSessionId,
600
+ transaction: request.unsignedPsbtBase64
601
+ }) };
602
+ };
603
+ const signPsbts = async ({ requests, walletAccount }) => {
604
+ assertDefined(walletAccount, "Wallet account is required");
605
+ return { signedPsbts: await Promise.all(requests.map(async (request) => {
606
+ const { signedPsbt } = await signPsbt({
607
+ request,
608
+ walletAccount
609
+ });
610
+ return signedPsbt;
611
+ })) };
612
+ };
613
+ const signMessage = async ({ message, walletAccount }) => {
614
+ assertDefined(walletAccount, "Wallet account is required");
615
+ const waasClient = await waasProvider.getWaasClient();
616
+ const { signature: signedSessionId } = await getSignedSessionId(sdkClient);
617
+ const mfaToken = await consumeMfaTokenIfRequiredForAction({ mfaAction: MFAAction.WalletWaasSign }, sdkClient);
618
+ return { signature: await waasClient.signMessage({
619
+ accountAddress: walletAccount.address,
620
+ authToken: sdkClient.token ?? void 0,
621
+ bitcoinConfig: { network: "mainnet" },
622
+ message,
623
+ mfaToken,
624
+ signedSessionId
625
+ }) };
626
+ };
627
+ const buildPsbt$1 = async ({ transaction, walletAccount }) => {
628
+ assertDefined(walletAccount, "Wallet account is required");
629
+ const publicKeyHex = getPublicKeyForWalletAccount(walletAccount, sdkClient);
630
+ return buildPsbt({
631
+ accountAddress: walletAccount.address,
632
+ amountInSatoshis: transaction.amount,
633
+ feePriority: transaction.feePriority ?? "medium",
634
+ network: networks.bitcoin,
635
+ publicKeyHex,
636
+ recipientAddress: transaction.recipientAddress
637
+ });
638
+ };
639
+ const sendBitcoin = async ({ transaction, walletAccount }) => {
640
+ assertDefined(walletAccount, "Wallet account is required");
641
+ const { signedPsbt } = await signPsbt({
642
+ request: {
643
+ allowedSighash: [],
644
+ unsignedPsbtBase64: await buildPsbt$1({
645
+ transaction: {
646
+ amount: transaction.amount,
647
+ recipientAddress: transaction.recipientAddress
648
+ },
649
+ walletAccount
650
+ })
651
+ },
652
+ walletAccount
653
+ });
654
+ const psbt = Psbt.fromBase64(signedPsbt, { network: networks.bitcoin });
655
+ psbt.finalizeAllInputs();
656
+ return { transactionId: await broadcastTransaction(psbt.extractTransaction().toHex()) };
657
+ };
658
+ return {
659
+ ...waasProvider,
660
+ buildPsbt: buildPsbt$1,
661
+ chain,
662
+ executeSwapTransaction: (args) => bitcoinExecuteSwapTransaction(args, sdkClient),
663
+ getActiveNetworkId,
664
+ getConnectedAddresses,
665
+ groupKey: formatWalletProviderGroupKey(DYNAMIC_WAAS_METADATA.displayName),
666
+ key,
667
+ metadata: {
668
+ displayName: DYNAMIC_WAAS_METADATA.displayName,
669
+ icon: DYNAMIC_WAAS_METADATA.icon
670
+ },
671
+ sendBitcoin,
672
+ signMessage,
673
+ signPsbt,
674
+ signPsbts,
675
+ switchActiveNetwork,
676
+ transferAmount: (args) => bitcoinTransferAmount(args, sdkClient),
677
+ walletProviderType
678
+ };
679
+ };
680
+
681
+ //#endregion
682
+ //#region src/waas/addWaasBitcoinExtension/addWaasBitcoinExtension.ts
683
+ const WAAS_BITCOIN_EXTENSION_KEY = "waasBitcoin";
684
+ /**
685
+ * Adds the Dynamic WaaS (Wallet as a Service) Bitcoin extension to the Dynamic client.
686
+ *
687
+ * This extension enables embedded wallet functionality for Bitcoin blockchain,
688
+ * allowing users to have wallets managed by Dynamic's infrastructure.
689
+ *
690
+ * @param [client] - The Dynamic client instance. Only required when using multiple Dynamic clients.
691
+ * @not-instrumented
692
+ */
693
+ const addWaasBitcoinExtension = (client = getDefaultClient()) => {
694
+ if (hasExtension({ extensionKey: WAAS_BITCOIN_EXTENSION_KEY }, client)) return;
695
+ registerExtension({ extensionKey: WAAS_BITCOIN_EXTENSION_KEY }, client);
696
+ registerBitcoinNetworkProviderBuilder(client);
697
+ const walletProviderRegistry = getWalletProviderRegistry(client);
698
+ const walletProvider = createWalletProviderForWaasBitcoin(client);
699
+ walletProviderRegistry.register({
700
+ priority: WalletProviderPriority.WALLET_SDK,
701
+ walletProvider
702
+ });
703
+ };
704
+
705
+ //#endregion
706
+ export { TransactionRequiredError as n, TransactionBroadcastFailedError as r, addWaasBitcoinExtension as t };
707
+ //# sourceMappingURL=addWaasBitcoinExtension-8da7zX1k.esm.js.map