@dynamic-labs/bitcoin 4.52.5 → 4.53.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.cjs +1 -1
  3. package/package.js +1 -1
  4. package/package.json +11 -6
  5. package/src/connectors/BitcoinSatsConnectConnector/BitcoinSatsConnectConnector.js +1 -1
  6. package/src/connectors/BitcoinWalletConnector.cjs +8 -0
  7. package/src/connectors/BitcoinWalletConnector.d.ts +1 -0
  8. package/src/connectors/BitcoinWalletConnector.js +10 -2
  9. package/src/connectors/DynamicWaasBitcoinConnector/DynamicWaasBitcoinConnector.cjs +464 -0
  10. package/src/connectors/DynamicWaasBitcoinConnector/DynamicWaasBitcoinConnector.d.ts +314 -0
  11. package/src/connectors/DynamicWaasBitcoinConnector/DynamicWaasBitcoinConnector.js +460 -0
  12. package/src/connectors/DynamicWaasBitcoinConnector/index.d.ts +1 -0
  13. package/src/connectors/index.d.ts +1 -0
  14. package/src/const.cjs +13 -0
  15. package/src/const.d.ts +6 -0
  16. package/src/const.js +8 -1
  17. package/src/index.cjs +9 -0
  18. package/src/index.d.ts +2 -2
  19. package/src/index.js +6 -0
  20. package/src/services/MempoolApiService.cjs +127 -0
  21. package/src/services/MempoolApiService.d.ts +42 -0
  22. package/src/services/MempoolApiService.js +123 -0
  23. package/src/services/PsbtBuilderService.cjs +132 -0
  24. package/src/services/PsbtBuilderService.d.ts +27 -0
  25. package/src/services/PsbtBuilderService.js +124 -0
  26. package/src/types.d.ts +43 -0
  27. package/src/utils/btcToSatoshis/btcToSatoshis.cjs +24 -0
  28. package/src/utils/btcToSatoshis/btcToSatoshis.d.ts +7 -0
  29. package/src/utils/btcToSatoshis/btcToSatoshis.js +20 -0
  30. package/src/utils/btcToSatoshis/index.d.ts +1 -0
  31. package/src/utils/fetchSatsConnectConnectors/fetchSatsConnectConnectors.cjs +1 -0
  32. package/src/utils/fetchSatsConnectConnectors/fetchSatsConnectConnectors.js +1 -0
  33. package/src/utils/getBitcoinNetwork/getBitcoinNetwork.cjs +15 -0
  34. package/src/utils/getBitcoinNetwork/getBitcoinNetwork.d.ts +7 -0
  35. package/src/utils/getBitcoinNetwork/getBitcoinNetwork.js +11 -0
  36. package/src/utils/getBitcoinNetwork/index.d.ts +1 -0
  37. package/src/utils/index.d.ts +3 -0
  38. package/src/utils/psbtParser/PsbtParser.cjs +185 -0
  39. package/src/utils/psbtParser/PsbtParser.d.ts +63 -0
  40. package/src/utils/psbtParser/PsbtParser.js +181 -0
  41. package/src/utils/psbtParser/index.d.ts +1 -0
  42. package/src/wallet/BitcoinWallet.cjs +11 -0
  43. package/src/wallet/BitcoinWallet.d.ts +6 -0
  44. package/src/wallet/BitcoinWallet.js +11 -0
@@ -0,0 +1,127 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ var _tslib = require('../../_virtual/_tslib.cjs');
7
+ var logger$1 = require('@dynamic-labs/logger');
8
+ var utils = require('@dynamic-labs/utils');
9
+ require('sats-connect');
10
+ require('bitcoinjs-lib');
11
+ require('@dynamic-labs/wallet-connector-core');
12
+ require('@dynamic-labs/wallet-book');
13
+ require('@dynamic-labs/sdk-api-core');
14
+ require('@wallet-standard/app');
15
+ var getMempoolApiUrl = require('../utils/getMempoolApiUrl.cjs');
16
+ var _const = require('../const.cjs');
17
+ require('jsontokens');
18
+ require('../connectors/DynamicWaasBitcoinConnector/DynamicWaasBitcoinConnector.cjs');
19
+
20
+ const logger = new logger$1.Logger('MempoolApiService');
21
+ /**
22
+ * Service for interacting with the mempool.space API
23
+ */
24
+ class MempoolApiService {
25
+ /**
26
+ * Gets the base URL for the mempool API based on the address network
27
+ * @param address - Bitcoin address to determine network
28
+ * @returns The mempool API base URL
29
+ */
30
+ getBaseUrl(address) {
31
+ return getMempoolApiUrl.getMempoolApiUrl(address);
32
+ }
33
+ /**
34
+ * Gets UTXOs for a Bitcoin address from mempool.space API
35
+ * @param address - The Bitcoin address to get UTXOs for
36
+ * @returns Array of UTXOs
37
+ * @throws {DynamicError} If fetching UTXOs fails
38
+ */
39
+ getUTXOs(address) {
40
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
41
+ try {
42
+ const baseUrl = this.getBaseUrl(address);
43
+ const response = yield fetch(`${baseUrl}/address/${address}/utxo`);
44
+ if (!response.ok) {
45
+ throw new utils.DynamicError(`Failed to fetch UTXOs: ${response.statusText}`);
46
+ }
47
+ return response.json();
48
+ }
49
+ catch (error) {
50
+ logger.error('Error fetching UTXOs:', error);
51
+ throw new utils.DynamicError('Failed to fetch UTXOs');
52
+ }
53
+ });
54
+ }
55
+ /**
56
+ * Gets fee recommendations from mempool.space API
57
+ * @param address - The Bitcoin address to determine the network from
58
+ * @returns Fee recommendation data
59
+ * @throws {Error} If fetching fee recommendations fails
60
+ */
61
+ getFeeRecommendations(address) {
62
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
63
+ const baseUrl = this.getBaseUrl(address);
64
+ const url = `${baseUrl}/v1/fees/recommended`;
65
+ const response = yield fetch(url);
66
+ if (!response.ok) {
67
+ throw new Error('Failed to fetch fee estimate');
68
+ }
69
+ return response.json();
70
+ });
71
+ }
72
+ /**
73
+ * Estimates transaction fees based on number of inputs and outputs
74
+ * @param address - The Bitcoin address to determine the network from
75
+ * @param numInputs - Number of transaction inputs
76
+ * @param numOutputs - Number of transaction outputs
77
+ * @returns Estimated fee in satoshis
78
+ */
79
+ estimateTransactionFee(address, numInputs, numOutputs) {
80
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
81
+ try {
82
+ const feeData = yield this.getFeeRecommendations(address);
83
+ const feePerByte = feeData.hourFee || feeData.economyFee || 1;
84
+ const estimatedFee = feePerByte *
85
+ (_const.INPUT_BYTE_SIZE_UPPER_BOUND * numInputs +
86
+ _const.OUTPUT_BYTE_SIZE_UPPER_BOUND * numOutputs) +
87
+ _const.MIN_RELAY_FEE;
88
+ return estimatedFee;
89
+ }
90
+ catch (error) {
91
+ logger.error('Error fetching fee estimate:', error);
92
+ // Return a conservative default fee estimate
93
+ return _const.DEFAULT_FEE_ESTIMATE;
94
+ }
95
+ });
96
+ }
97
+ /**
98
+ * Sends a raw Bitcoin transaction to the mempool
99
+ * @param address - The Bitcoin address to determine the network from
100
+ * @param rawTransaction - The raw transaction in hex format
101
+ * @returns The transaction ID
102
+ * @throws {DynamicError} If no transaction is specified or sending fails
103
+ */
104
+ sendRawTransaction(address, rawTransaction) {
105
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
106
+ if (!rawTransaction) {
107
+ throw new utils.DynamicError('No transaction specified!');
108
+ }
109
+ const baseUrl = this.getBaseUrl(address);
110
+ const response = yield fetch(`${baseUrl}/tx`, {
111
+ body: rawTransaction,
112
+ headers: {
113
+ 'Content-Type': 'application/x-www-form-urlencoded',
114
+ },
115
+ method: 'POST',
116
+ });
117
+ if (!response.ok) {
118
+ const error = yield response.text();
119
+ logger.debug(`sendRawTransaction - response not ok: ${JSON.stringify(error)}`);
120
+ throw new utils.DynamicError('sendRawTransaction - failed to send transaction');
121
+ }
122
+ return response.text();
123
+ });
124
+ }
125
+ }
126
+
127
+ exports.MempoolApiService = MempoolApiService;
@@ -0,0 +1,42 @@
1
+ import type { FeeRecommendations, UTXO } from '../types';
2
+ /**
3
+ * Service for interacting with the mempool.space API
4
+ */
5
+ export declare class MempoolApiService {
6
+ /**
7
+ * Gets the base URL for the mempool API based on the address network
8
+ * @param address - Bitcoin address to determine network
9
+ * @returns The mempool API base URL
10
+ */
11
+ private getBaseUrl;
12
+ /**
13
+ * Gets UTXOs for a Bitcoin address from mempool.space API
14
+ * @param address - The Bitcoin address to get UTXOs for
15
+ * @returns Array of UTXOs
16
+ * @throws {DynamicError} If fetching UTXOs fails
17
+ */
18
+ getUTXOs(address: string): Promise<UTXO[]>;
19
+ /**
20
+ * Gets fee recommendations from mempool.space API
21
+ * @param address - The Bitcoin address to determine the network from
22
+ * @returns Fee recommendation data
23
+ * @throws {Error} If fetching fee recommendations fails
24
+ */
25
+ getFeeRecommendations(address: string): Promise<FeeRecommendations>;
26
+ /**
27
+ * Estimates transaction fees based on number of inputs and outputs
28
+ * @param address - The Bitcoin address to determine the network from
29
+ * @param numInputs - Number of transaction inputs
30
+ * @param numOutputs - Number of transaction outputs
31
+ * @returns Estimated fee in satoshis
32
+ */
33
+ estimateTransactionFee(address: string, numInputs: number, numOutputs: number): Promise<number>;
34
+ /**
35
+ * Sends a raw Bitcoin transaction to the mempool
36
+ * @param address - The Bitcoin address to determine the network from
37
+ * @param rawTransaction - The raw transaction in hex format
38
+ * @returns The transaction ID
39
+ * @throws {DynamicError} If no transaction is specified or sending fails
40
+ */
41
+ sendRawTransaction(address: string, rawTransaction: string): Promise<string>;
42
+ }
@@ -0,0 +1,123 @@
1
+ 'use client'
2
+ import { __awaiter } from '../../_virtual/_tslib.js';
3
+ import { Logger } from '@dynamic-labs/logger';
4
+ import { DynamicError } from '@dynamic-labs/utils';
5
+ import 'sats-connect';
6
+ import 'bitcoinjs-lib';
7
+ import '@dynamic-labs/wallet-connector-core';
8
+ import '@dynamic-labs/wallet-book';
9
+ import '@dynamic-labs/sdk-api-core';
10
+ import '@wallet-standard/app';
11
+ import { getMempoolApiUrl } from '../utils/getMempoolApiUrl.js';
12
+ import { INPUT_BYTE_SIZE_UPPER_BOUND, OUTPUT_BYTE_SIZE_UPPER_BOUND, MIN_RELAY_FEE, DEFAULT_FEE_ESTIMATE } from '../const.js';
13
+ import 'jsontokens';
14
+ import '../connectors/DynamicWaasBitcoinConnector/DynamicWaasBitcoinConnector.js';
15
+
16
+ const logger = new Logger('MempoolApiService');
17
+ /**
18
+ * Service for interacting with the mempool.space API
19
+ */
20
+ class MempoolApiService {
21
+ /**
22
+ * Gets the base URL for the mempool API based on the address network
23
+ * @param address - Bitcoin address to determine network
24
+ * @returns The mempool API base URL
25
+ */
26
+ getBaseUrl(address) {
27
+ return getMempoolApiUrl(address);
28
+ }
29
+ /**
30
+ * Gets UTXOs for a Bitcoin address from mempool.space API
31
+ * @param address - The Bitcoin address to get UTXOs for
32
+ * @returns Array of UTXOs
33
+ * @throws {DynamicError} If fetching UTXOs fails
34
+ */
35
+ getUTXOs(address) {
36
+ return __awaiter(this, void 0, void 0, function* () {
37
+ try {
38
+ const baseUrl = this.getBaseUrl(address);
39
+ const response = yield fetch(`${baseUrl}/address/${address}/utxo`);
40
+ if (!response.ok) {
41
+ throw new DynamicError(`Failed to fetch UTXOs: ${response.statusText}`);
42
+ }
43
+ return response.json();
44
+ }
45
+ catch (error) {
46
+ logger.error('Error fetching UTXOs:', error);
47
+ throw new DynamicError('Failed to fetch UTXOs');
48
+ }
49
+ });
50
+ }
51
+ /**
52
+ * Gets fee recommendations from mempool.space API
53
+ * @param address - The Bitcoin address to determine the network from
54
+ * @returns Fee recommendation data
55
+ * @throws {Error} If fetching fee recommendations fails
56
+ */
57
+ getFeeRecommendations(address) {
58
+ return __awaiter(this, void 0, void 0, function* () {
59
+ const baseUrl = this.getBaseUrl(address);
60
+ const url = `${baseUrl}/v1/fees/recommended`;
61
+ const response = yield fetch(url);
62
+ if (!response.ok) {
63
+ throw new Error('Failed to fetch fee estimate');
64
+ }
65
+ return response.json();
66
+ });
67
+ }
68
+ /**
69
+ * Estimates transaction fees based on number of inputs and outputs
70
+ * @param address - The Bitcoin address to determine the network from
71
+ * @param numInputs - Number of transaction inputs
72
+ * @param numOutputs - Number of transaction outputs
73
+ * @returns Estimated fee in satoshis
74
+ */
75
+ estimateTransactionFee(address, numInputs, numOutputs) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ try {
78
+ const feeData = yield this.getFeeRecommendations(address);
79
+ const feePerByte = feeData.hourFee || feeData.economyFee || 1;
80
+ const estimatedFee = feePerByte *
81
+ (INPUT_BYTE_SIZE_UPPER_BOUND * numInputs +
82
+ OUTPUT_BYTE_SIZE_UPPER_BOUND * numOutputs) +
83
+ MIN_RELAY_FEE;
84
+ return estimatedFee;
85
+ }
86
+ catch (error) {
87
+ logger.error('Error fetching fee estimate:', error);
88
+ // Return a conservative default fee estimate
89
+ return DEFAULT_FEE_ESTIMATE;
90
+ }
91
+ });
92
+ }
93
+ /**
94
+ * Sends a raw Bitcoin transaction to the mempool
95
+ * @param address - The Bitcoin address to determine the network from
96
+ * @param rawTransaction - The raw transaction in hex format
97
+ * @returns The transaction ID
98
+ * @throws {DynamicError} If no transaction is specified or sending fails
99
+ */
100
+ sendRawTransaction(address, rawTransaction) {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ if (!rawTransaction) {
103
+ throw new DynamicError('No transaction specified!');
104
+ }
105
+ const baseUrl = this.getBaseUrl(address);
106
+ const response = yield fetch(`${baseUrl}/tx`, {
107
+ body: rawTransaction,
108
+ headers: {
109
+ 'Content-Type': 'application/x-www-form-urlencoded',
110
+ },
111
+ method: 'POST',
112
+ });
113
+ if (!response.ok) {
114
+ const error = yield response.text();
115
+ logger.debug(`sendRawTransaction - response not ok: ${JSON.stringify(error)}`);
116
+ throw new DynamicError('sendRawTransaction - failed to send transaction');
117
+ }
118
+ return response.text();
119
+ });
120
+ }
121
+ }
122
+
123
+ export { MempoolApiService };
@@ -0,0 +1,132 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ var _tslib = require('../../_virtual/_tslib.cjs');
7
+ var ecc = require('@bitcoinerlab/secp256k1');
8
+ var ecpair = require('ecpair');
9
+ var bitcoinjsLib = require('bitcoinjs-lib');
10
+ var logger$1 = require('@dynamic-labs/logger');
11
+ var utils = require('@dynamic-labs/utils');
12
+ var _const = require('../const.cjs');
13
+ var getBitcoinNetwork = require('../utils/getBitcoinNetwork/getBitcoinNetwork.cjs');
14
+
15
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
16
+
17
+ var ecc__default = /*#__PURE__*/_interopDefaultLegacy(ecc);
18
+
19
+ const logger = new logger$1.Logger('PsbtBuilderService');
20
+ /**
21
+ * Service for building Bitcoin PSBTs
22
+ */
23
+ class PsbtBuilderService {
24
+ constructor(mempoolApiService) {
25
+ this.mempoolApiService = mempoolApiService;
26
+ }
27
+ /**
28
+ * Builds a PSBT for a Bitcoin transaction with real UTXOs
29
+ * @param options - Options for building the PSBT
30
+ * @returns A PSBT in Base64 format
31
+ * @throws {DynamicError} If insufficient funds, no UTXOs, or other errors
32
+ */
33
+ buildPsbt(options) {
34
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
35
+ const { accountAddress, recipientAddress, amountInSatoshis, publicKeyHex, network, } = options;
36
+ if (amountInSatoshis <= BigInt(0)) {
37
+ throw new utils.DynamicError('Amount must be greater than 0');
38
+ }
39
+ // Get UTXOs for the account address
40
+ const utxos = yield this.mempoolApiService.getUTXOs(accountAddress);
41
+ if (utxos.length === 0) {
42
+ throw new utils.DynamicError('No UTXOs found for this address');
43
+ }
44
+ // Get public key for creating output scripts
45
+ const publicKeyBuffer = Buffer.from(publicKeyHex, 'hex');
46
+ // Use ECPair to ensure the public key is properly formatted for bitcoinjs-lib
47
+ const ECPair = ecpair.ECPairFactory(ecc__default["default"]);
48
+ const publicKeyPair = ECPair.fromPublicKey(publicKeyBuffer, {
49
+ compressed: true,
50
+ });
51
+ // Calculate total available
52
+ const totalToSpend = utxos.reduce((total, utxo) => total + utxo.value, 0);
53
+ // Initial fee estimate with 1 output (recipient only)
54
+ let feeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, utxos.length, 1);
55
+ // Calculate change amount with 1-output fee estimate
56
+ let maxToSpend = totalToSpend - feeEstimate;
57
+ let changeAmount = BigInt(maxToSpend) - amountInSatoshis;
58
+ // If change will be above dust limit, re-estimate fees for 2 outputs
59
+ if (changeAmount > 0 && Number(changeAmount) >= _const.DUST_LIMIT) {
60
+ feeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, utxos.length, 2);
61
+ maxToSpend = totalToSpend - feeEstimate;
62
+ changeAmount = BigInt(maxToSpend) - amountInSatoshis;
63
+ }
64
+ if (maxToSpend < Number(amountInSatoshis)) {
65
+ const amountInSatoshisNumber = Number(amountInSatoshis);
66
+ throw new utils.DynamicError(`Insufficient funds. Available: ${maxToSpend / _const.SATOSHIS_PER_BTC} BTC (${maxToSpend} satoshis), Required: ${amountInSatoshisNumber / _const.SATOSHIS_PER_BTC} BTC (${amountInSatoshisNumber} satoshis)`);
67
+ }
68
+ // Create PSBT
69
+ const psbt = new bitcoinjsLib.Psbt({ network });
70
+ // Add inputs from UTXOs (only supporting native SegWit P2WPKH)
71
+ for (const utxo of utxos) {
72
+ // SegWit (P2WPKH) addresses
73
+ // For SegWit, we need the public key to construct the witness output script
74
+ const outputScript = bitcoinjsLib.payments.p2wpkh({
75
+ network,
76
+ pubkey: publicKeyPair.publicKey,
77
+ }).output;
78
+ if (!outputScript) {
79
+ throw new utils.DynamicError('Failed to create segwit output script');
80
+ }
81
+ // Convert txid from hex string to Buffer and reverse it (Bitcoin uses little-endian)
82
+ // The txid from the API is in big-endian format, but bitcoinjs-lib expects little-endian
83
+ const txidBuffer = Buffer.from(utxo.txid, 'hex').reverse();
84
+ psbt.addInput({
85
+ hash: txidBuffer,
86
+ index: utxo.vout,
87
+ witnessUtxo: {
88
+ script: outputScript,
89
+ value: utxo.value,
90
+ },
91
+ });
92
+ }
93
+ // Add recipient output
94
+ const amountInSatoshisNumber = Number(amountInSatoshis);
95
+ if (amountInSatoshisNumber < _const.DUST_LIMIT) {
96
+ throw new utils.DynamicError(`Amount is below dust limit of ${_const.DUST_LIMIT} satoshis (${_const.DUST_LIMIT / _const.SATOSHIS_PER_BTC} BTC)`);
97
+ }
98
+ psbt.addOutput({
99
+ script: bitcoinjsLib.address.toOutputScript(recipientAddress, network),
100
+ value: amountInSatoshisNumber,
101
+ });
102
+ // Add change output if needed
103
+ const changeAmountNumber = Number(changeAmount);
104
+ if (changeAmount > 0 && changeAmountNumber >= _const.DUST_LIMIT) {
105
+ psbt.addOutput({
106
+ script: bitcoinjsLib.address.toOutputScript(accountAddress, network),
107
+ value: changeAmountNumber,
108
+ });
109
+ }
110
+ logger.debug(`buildPsbt created PSBT for recipientAddress: ${recipientAddress}, amount: ${amountInSatoshisNumber} satoshis (${amountInSatoshisNumber / _const.SATOSHIS_PER_BTC} BTC), change: ${changeAmountNumber} satoshis`);
111
+ return psbt.toBase64();
112
+ });
113
+ }
114
+ /**
115
+ * Helper method to create BuildPsbtOptions from transaction and account details
116
+ * @param accountAddress - The account address
117
+ * @param transaction - The Bitcoin transaction
118
+ * @param publicKeyHex - The public key in hex format
119
+ * @returns BuildPsbtOptions
120
+ */
121
+ static createBuildOptions(accountAddress, transaction, publicKeyHex) {
122
+ return {
123
+ accountAddress,
124
+ amountInSatoshis: transaction.amount,
125
+ network: getBitcoinNetwork.getBitcoinNetwork(accountAddress),
126
+ publicKeyHex,
127
+ recipientAddress: transaction.recipientAddress,
128
+ };
129
+ }
130
+ }
131
+
132
+ exports.PsbtBuilderService = PsbtBuilderService;
@@ -0,0 +1,27 @@
1
+ import type { BuildPsbtOptions } from '../types';
2
+ import { MempoolApiService } from './MempoolApiService';
3
+ /**
4
+ * Service for building Bitcoin PSBTs
5
+ */
6
+ export declare class PsbtBuilderService {
7
+ private mempoolApiService;
8
+ constructor(mempoolApiService: MempoolApiService);
9
+ /**
10
+ * Builds a PSBT for a Bitcoin transaction with real UTXOs
11
+ * @param options - Options for building the PSBT
12
+ * @returns A PSBT in Base64 format
13
+ * @throws {DynamicError} If insufficient funds, no UTXOs, or other errors
14
+ */
15
+ buildPsbt(options: BuildPsbtOptions): Promise<string>;
16
+ /**
17
+ * Helper method to create BuildPsbtOptions from transaction and account details
18
+ * @param accountAddress - The account address
19
+ * @param transaction - The Bitcoin transaction
20
+ * @param publicKeyHex - The public key in hex format
21
+ * @returns BuildPsbtOptions
22
+ */
23
+ static createBuildOptions(accountAddress: string, transaction: {
24
+ amount: bigint;
25
+ recipientAddress: string;
26
+ }, publicKeyHex: string): BuildPsbtOptions;
27
+ }
@@ -0,0 +1,124 @@
1
+ 'use client'
2
+ import { __awaiter } from '../../_virtual/_tslib.js';
3
+ import ecc from '@bitcoinerlab/secp256k1';
4
+ import { ECPairFactory } from 'ecpair';
5
+ import { Psbt, payments, address } from 'bitcoinjs-lib';
6
+ import { Logger } from '@dynamic-labs/logger';
7
+ import { DynamicError } from '@dynamic-labs/utils';
8
+ import { DUST_LIMIT, SATOSHIS_PER_BTC } from '../const.js';
9
+ import { getBitcoinNetwork } from '../utils/getBitcoinNetwork/getBitcoinNetwork.js';
10
+
11
+ const logger = new Logger('PsbtBuilderService');
12
+ /**
13
+ * Service for building Bitcoin PSBTs
14
+ */
15
+ class PsbtBuilderService {
16
+ constructor(mempoolApiService) {
17
+ this.mempoolApiService = mempoolApiService;
18
+ }
19
+ /**
20
+ * Builds a PSBT for a Bitcoin transaction with real UTXOs
21
+ * @param options - Options for building the PSBT
22
+ * @returns A PSBT in Base64 format
23
+ * @throws {DynamicError} If insufficient funds, no UTXOs, or other errors
24
+ */
25
+ buildPsbt(options) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ const { accountAddress, recipientAddress, amountInSatoshis, publicKeyHex, network, } = options;
28
+ if (amountInSatoshis <= BigInt(0)) {
29
+ throw new DynamicError('Amount must be greater than 0');
30
+ }
31
+ // Get UTXOs for the account address
32
+ const utxos = yield this.mempoolApiService.getUTXOs(accountAddress);
33
+ if (utxos.length === 0) {
34
+ throw new DynamicError('No UTXOs found for this address');
35
+ }
36
+ // Get public key for creating output scripts
37
+ const publicKeyBuffer = Buffer.from(publicKeyHex, 'hex');
38
+ // Use ECPair to ensure the public key is properly formatted for bitcoinjs-lib
39
+ const ECPair = ECPairFactory(ecc);
40
+ const publicKeyPair = ECPair.fromPublicKey(publicKeyBuffer, {
41
+ compressed: true,
42
+ });
43
+ // Calculate total available
44
+ const totalToSpend = utxos.reduce((total, utxo) => total + utxo.value, 0);
45
+ // Initial fee estimate with 1 output (recipient only)
46
+ let feeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, utxos.length, 1);
47
+ // Calculate change amount with 1-output fee estimate
48
+ let maxToSpend = totalToSpend - feeEstimate;
49
+ let changeAmount = BigInt(maxToSpend) - amountInSatoshis;
50
+ // If change will be above dust limit, re-estimate fees for 2 outputs
51
+ if (changeAmount > 0 && Number(changeAmount) >= DUST_LIMIT) {
52
+ feeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, utxos.length, 2);
53
+ maxToSpend = totalToSpend - feeEstimate;
54
+ changeAmount = BigInt(maxToSpend) - amountInSatoshis;
55
+ }
56
+ if (maxToSpend < Number(amountInSatoshis)) {
57
+ const amountInSatoshisNumber = Number(amountInSatoshis);
58
+ throw new DynamicError(`Insufficient funds. Available: ${maxToSpend / SATOSHIS_PER_BTC} BTC (${maxToSpend} satoshis), Required: ${amountInSatoshisNumber / SATOSHIS_PER_BTC} BTC (${amountInSatoshisNumber} satoshis)`);
59
+ }
60
+ // Create PSBT
61
+ const psbt = new Psbt({ network });
62
+ // Add inputs from UTXOs (only supporting native SegWit P2WPKH)
63
+ for (const utxo of utxos) {
64
+ // SegWit (P2WPKH) addresses
65
+ // For SegWit, we need the public key to construct the witness output script
66
+ const outputScript = payments.p2wpkh({
67
+ network,
68
+ pubkey: publicKeyPair.publicKey,
69
+ }).output;
70
+ if (!outputScript) {
71
+ throw new DynamicError('Failed to create segwit output script');
72
+ }
73
+ // Convert txid from hex string to Buffer and reverse it (Bitcoin uses little-endian)
74
+ // The txid from the API is in big-endian format, but bitcoinjs-lib expects little-endian
75
+ const txidBuffer = Buffer.from(utxo.txid, 'hex').reverse();
76
+ psbt.addInput({
77
+ hash: txidBuffer,
78
+ index: utxo.vout,
79
+ witnessUtxo: {
80
+ script: outputScript,
81
+ value: utxo.value,
82
+ },
83
+ });
84
+ }
85
+ // Add recipient output
86
+ const amountInSatoshisNumber = Number(amountInSatoshis);
87
+ if (amountInSatoshisNumber < DUST_LIMIT) {
88
+ throw new DynamicError(`Amount is below dust limit of ${DUST_LIMIT} satoshis (${DUST_LIMIT / SATOSHIS_PER_BTC} BTC)`);
89
+ }
90
+ psbt.addOutput({
91
+ script: address.toOutputScript(recipientAddress, network),
92
+ value: amountInSatoshisNumber,
93
+ });
94
+ // Add change output if needed
95
+ const changeAmountNumber = Number(changeAmount);
96
+ if (changeAmount > 0 && changeAmountNumber >= DUST_LIMIT) {
97
+ psbt.addOutput({
98
+ script: address.toOutputScript(accountAddress, network),
99
+ value: changeAmountNumber,
100
+ });
101
+ }
102
+ logger.debug(`buildPsbt created PSBT for recipientAddress: ${recipientAddress}, amount: ${amountInSatoshisNumber} satoshis (${amountInSatoshisNumber / SATOSHIS_PER_BTC} BTC), change: ${changeAmountNumber} satoshis`);
103
+ return psbt.toBase64();
104
+ });
105
+ }
106
+ /**
107
+ * Helper method to create BuildPsbtOptions from transaction and account details
108
+ * @param accountAddress - The account address
109
+ * @param transaction - The Bitcoin transaction
110
+ * @param publicKeyHex - The public key in hex format
111
+ * @returns BuildPsbtOptions
112
+ */
113
+ static createBuildOptions(accountAddress, transaction, publicKeyHex) {
114
+ return {
115
+ accountAddress,
116
+ amountInSatoshis: transaction.amount,
117
+ network: getBitcoinNetwork(accountAddress),
118
+ publicKeyHex,
119
+ recipientAddress: transaction.recipientAddress,
120
+ };
121
+ }
122
+ }
123
+
124
+ export { PsbtBuilderService };
package/src/types.d.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { BitcoinProvider, InputToSign as SatsConnectInputToSign } from 'sats-connect';
2
2
  import { SignPsbtResponse as BtcKitSignPsbtResponse } from '@btckit/types';
3
3
  import { IdentifierString, WalletAccount } from '@wallet-standard/base';
4
+ import type { Network } from 'bitcoinjs-lib';
5
+ import type { WalletUiUtils } from '@dynamic-labs/types';
6
+ import type { InternalWalletConnector } from '@dynamic-labs/wallet-connector-core';
4
7
  import type { WalletAdditionalAddress } from '@dynamic-labs/sdk-api-core';
8
+ import type { BitcoinWalletConnectorOpts } from './connectors';
5
9
  import { SATSCONNECT_FEATURE } from './const';
6
10
  export type ErrorWithCode = Error & {
7
11
  code: string;
@@ -137,4 +141,43 @@ interface ProviderBalance {
137
141
  export interface ProviderWithBalance {
138
142
  getBalance: () => Promise<ProviderBalance>;
139
143
  }
144
+ export type DynamicWaasBitcoinConnectorProps = BitcoinWalletConnectorOpts & {
145
+ walletUiUtils: WalletUiUtils<InternalWalletConnector>;
146
+ };
147
+ export interface UTXO {
148
+ txid: string;
149
+ vout: number;
150
+ value: number;
151
+ status?: {
152
+ confirmed: boolean;
153
+ };
154
+ }
155
+ export interface FeeRecommendations {
156
+ fastestFee?: number;
157
+ halfHourFee?: number;
158
+ hourFee?: number;
159
+ economyFee?: number;
160
+ minimumFee?: number;
161
+ }
162
+ export interface ParsedTransactionInput {
163
+ txid: string;
164
+ vout: number;
165
+ address?: string;
166
+ value?: number;
167
+ }
168
+ export interface ParsedTransactionOutput {
169
+ address?: string;
170
+ value: number;
171
+ }
172
+ export interface ParsedTransaction {
173
+ inputs: ParsedTransactionInput[];
174
+ outputs: ParsedTransactionOutput[];
175
+ }
176
+ export interface BuildPsbtOptions {
177
+ accountAddress: string;
178
+ recipientAddress: string;
179
+ amountInSatoshis: bigint;
180
+ publicKeyHex: string;
181
+ network: Network;
182
+ }
140
183
  export {};
@@ -0,0 +1,24 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ var utils = require('@dynamic-labs/utils');
7
+ var _const = require('../../const.cjs');
8
+
9
+ /**
10
+ * Helper function to convert BTC amount to satoshis
11
+ * @param btcAmount - The Bitcoin amount to convert (number)
12
+ * @returns The amount in satoshis as a BigInt
13
+ * @throws {DynamicError} If the amount is negative
14
+ */
15
+ const btcToSatoshis = (btcAmount) => {
16
+ if (btcAmount < 0) {
17
+ throw new utils.DynamicError('BTC amount cannot be negative');
18
+ }
19
+ // Convert to satoshis and return as BigInt
20
+ const satoshis = Math.floor(btcAmount * _const.SATOSHIS_PER_BTC);
21
+ return BigInt(satoshis);
22
+ };
23
+
24
+ exports.btcToSatoshis = btcToSatoshis;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Helper function to convert BTC amount to satoshis
3
+ * @param btcAmount - The Bitcoin amount to convert (number)
4
+ * @returns The amount in satoshis as a BigInt
5
+ * @throws {DynamicError} If the amount is negative
6
+ */
7
+ export declare const btcToSatoshis: (btcAmount: number) => bigint;