@dynamic-labs/bitcoin 4.53.1 → 4.53.2

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,20 @@
1
1
 
2
+ ### [4.53.2](https://github.com/dynamic-labs/dynamic-auth/compare/v4.53.1...v4.53.2) (2026-01-16)
3
+
4
+
5
+ ### Features
6
+
7
+ * add iCloud backup functionality ([#10219](https://github.com/dynamic-labs/dynamic-auth/issues/10219)) ([44e95e5](https://github.com/dynamic-labs/dynamic-auth/commit/44e95e5a5dc99f83918a382ab1c69d452359c346))
8
+ * update PSBT building with Largest-First UTXO selection and fee priorities (high/medium/low) ([#10227](https://github.com/dynamic-labs/dynamic-auth/issues/10227)) ([94c5f5c](https://github.com/dynamic-labs/dynamic-auth/commit/94c5f5cb97432bb97374b754cb95bc23290dd184))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * allow late registered solana wallets to appear in the wallet list ([#10224](https://github.com/dynamic-labs/dynamic-auth/issues/10224)) ([2f418d4](https://github.com/dynamic-labs/dynamic-auth/commit/2f418d4f3e0543bdd829a45807329f89da1e41a7))
14
+ * correctly show app name in wallet app for wallet connect evm connections ([#10218](https://github.com/dynamic-labs/dynamic-auth/issues/10218)) ([fec0009](https://github.com/dynamic-labs/dynamic-auth/commit/fec0009177439baa67015050b4ad799119615f4f))
15
+ * destructure wallets from getWalletStandardWallets before calling find ([#10234](https://github.com/dynamic-labs/dynamic-auth/issues/10234)) ([b59617a](https://github.com/dynamic-labs/dynamic-auth/commit/b59617a8579b9c7d8a5f744a2ea9363ccb4aee58))
16
+ * **react-native:** filter connectors by enabled chains and add chain parameter to connectWallet ([#10230](https://github.com/dynamic-labs/dynamic-auth/issues/10230)) ([a2bbd03](https://github.com/dynamic-labs/dynamic-auth/commit/a2bbd03ece52950711d2eda18cb2345df15710dd))
17
+
2
18
  ### [4.53.1](https://github.com/dynamic-labs/dynamic-auth/compare/v4.53.0...v4.53.1) (2026-01-14)
3
19
 
4
20
  ## [4.53.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.52.5...v4.53.0) (2026-01-13)
package/package.cjs CHANGED
@@ -3,6 +3,6 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "4.53.1";
6
+ var version = "4.53.2";
7
7
 
8
8
  exports.version = version;
package/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use client'
2
- var version = "4.53.1";
2
+ var version = "4.53.2";
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.53.1",
3
+ "version": "4.53.2",
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,23 +18,23 @@
18
18
  },
19
19
  "homepage": "https://www.dynamic.xyz/",
20
20
  "dependencies": {
21
- "@dynamic-labs-wallet/browser-wallet-client": "0.0.237",
21
+ "@dynamic-labs-wallet/browser-wallet-client": "0.0.245",
22
22
  "@bitcoinerlab/secp256k1": "1.1.1",
23
23
  "@btckit/types": "0.0.19",
24
- "@dynamic-labs/sdk-api-core": "0.0.843",
24
+ "@dynamic-labs/sdk-api-core": "0.0.855",
25
25
  "@wallet-standard/app": "1.0.1",
26
26
  "@wallet-standard/base": "1.0.1",
27
27
  "bitcoinjs-lib": "6.1.5",
28
28
  "ecpair": "2.1.0",
29
29
  "sats-connect": "4.2.0",
30
30
  "jsontokens": "4.0.1",
31
- "@dynamic-labs/assert-package-version": "4.53.1",
32
- "@dynamic-labs/logger": "4.53.1",
33
- "@dynamic-labs/types": "4.53.1",
34
- "@dynamic-labs/utils": "4.53.1",
35
- "@dynamic-labs/waas": "4.53.1",
36
- "@dynamic-labs/wallet-book": "4.53.1",
37
- "@dynamic-labs/wallet-connector-core": "4.53.1",
31
+ "@dynamic-labs/assert-package-version": "4.53.2",
32
+ "@dynamic-labs/logger": "4.53.2",
33
+ "@dynamic-labs/types": "4.53.2",
34
+ "@dynamic-labs/utils": "4.53.2",
35
+ "@dynamic-labs/waas": "4.53.2",
36
+ "@dynamic-labs/wallet-book": "4.53.2",
37
+ "@dynamic-labs/wallet-connector-core": "4.53.2",
38
38
  "eventemitter3": "5.0.1"
39
39
  },
40
40
  "peerDependencies": {}
@@ -128,11 +128,15 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
128
128
  });
129
129
  }
130
130
  /**
131
- * Signs a Partially Signed Bitcoin Transaction (PSBT)
132
- * @param request - The PSBT signing request containing the unsigned PSBT
133
- * @returns The signed PSBT response
131
+ * Signs a Partially Signed Bitcoin Transaction (PSBT) for embedded wallets
132
+ *
133
+ * Embedded wallets only support PSBT format and automatically sign all inputs
134
+ * that belong to the wallet address. Always uses SIGHASH_ALL (0x01).
135
+ *
136
+ * @param request - The PSBT signing request. Only unsignedPsbtBase64 is required.
137
+ * @returns The signed (but not finalized) PSBT response
134
138
  * @throws {DynamicError} If active account address is not set
135
- * @throws {DynamicError} If signed session ID is not available or signature is provided
139
+ * @throws {DynamicError} If signed session ID is not available
136
140
  */
137
141
  signPsbt(request) {
138
142
  return _tslib.__awaiter(this, void 0, void 0, function* () {
@@ -148,9 +152,6 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
148
152
  const mfaToken = yield ((_b = this.getMfaToken) === null || _b === void 0 ? void 0 : _b.call(this, {
149
153
  mfaAction: sdkApiCore.MFAAction.WalletWaasSign,
150
154
  }));
151
- if (request.signature && request.signature.length > 0) {
152
- throw new utils.DynamicError('Signature is not supported for waas at the moment');
153
- }
154
155
  const signedTransaction = yield walletClient.signTransaction({
155
156
  authToken: (_c = this.getAuthToken) === null || _c === void 0 ? void 0 : _c.call(this),
156
157
  mfaToken,
@@ -192,9 +193,7 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
192
193
  // Step 1: Build the PSBT
193
194
  const unsignedPsbt = yield this.buildPsbt(transaction);
194
195
  // Step 2: Sign the PSBT
195
- // SIGHASH_ALL (0x01) is the most common sighash type for Bitcoin transactions (Eventually can be configurable)
196
196
  const signedPsbtResponse = yield this.signPsbt({
197
- allowedSighash: [0x01],
198
197
  unsignedPsbtBase64: unsignedPsbt,
199
198
  });
200
199
  if (!signedPsbtResponse) {
@@ -337,7 +336,7 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
337
336
  }
338
337
  /**
339
338
  * Builds a PSBT for a Bitcoin transaction with real UTXOs
340
- * @param transaction - The Bitcoin transaction containing recipient address and amount in satoshis to send
339
+ * @param transaction - Bitcoin transaction with recipient address, amount in satoshis, and optional fee priority
341
340
  * @returns A PSBT in Base64 format
342
341
  * @throws {DynamicError} If no active account address, insufficient funds, or other errors
343
342
  */
@@ -347,7 +346,7 @@ class DynamicWaasBitcoinConnector extends waas.withDynamicWaas(BitcoinWalletConn
347
346
  throw new utils.DynamicError('Active account address is required');
348
347
  }
349
348
  const publicKeyHex = yield this.getPublicKey();
350
- const buildOptions = PsbtBuilderService.PsbtBuilderService.createBuildOptions(this.activeAccountAddress, transaction, publicKeyHex);
349
+ const buildOptions = PsbtBuilderService.PsbtBuilderService.createBuildOptions(this.activeAccountAddress, transaction, publicKeyHex, transaction.feePriority || 'medium');
351
350
  return this.psbtBuilderService.buildPsbt(buildOptions);
352
351
  });
353
352
  }
@@ -2,8 +2,8 @@ import { BitcoinConfig, BitcoinNetwork } from '@dynamic-labs-wallet/browser-wall
2
2
  import { JwtVerifiedCredential, MFAAction, SignMessageContext } from '@dynamic-labs/sdk-api-core';
3
3
  import { Logger } from '@dynamic-labs/logger';
4
4
  import { WalletUiUtils } from '@dynamic-labs/types';
5
- import { IDynamicWaasConnector, InternalWalletConnector, Chain, BitcoinSignPsbtRequest, BitcoinSignPsbtResponse } from '@dynamic-labs/wallet-connector-core';
6
- import { BitcoinTransaction } from '../../types';
5
+ import { IDynamicWaasConnector, InternalWalletConnector, Chain, BitcoinSignPsbtResponse } from '@dynamic-labs/wallet-connector-core';
6
+ import { BitcoinTransaction, EmbeddedWalletSignPsbtRequest } from '../../types';
7
7
  import { BitcoinWalletConnector } from '../BitcoinWalletConnector';
8
8
  import type { ParsedTransaction, DynamicWaasBitcoinConnectorProps } from '../../types';
9
9
  declare const DynamicWaasBitcoinConnector_base: (abstract new (...args: any[]) => {
@@ -35,7 +35,10 @@ declare const DynamicWaasBitcoinConnector_base: (abstract new (...args: any[]) =
35
35
  setBaseApiUrl(baseApiUrl: string): void;
36
36
  setBaseClientKeysharesRelayApiUrl(baseClientKeysharesRelayApiUrl?: string | undefined): void;
37
37
  setRelayUrl(relayUrl: string): void;
38
- setGetSignedSessionIdFunction(getSignedSessionId: () => Promise<string>): void;
38
+ setGetSignedSessionIdFunction(getSignedSessionId: () => Promise<string>): void; /**
39
+ * The primary/active verified credential (first from the filtered array)
40
+ * This is used for the active account address
41
+ */
39
42
  delegateKeyShares({ accountAddress, password, }: {
40
43
  accountAddress: string;
41
44
  password?: string | undefined;
@@ -56,7 +59,11 @@ declare const DynamicWaasBitcoinConnector_base: (abstract new (...args: any[]) =
56
59
  privateKey: string;
57
60
  thresholdSignatureScheme?: string | undefined;
58
61
  publicAddressCheck?: string | undefined;
59
- addressType?: string | undefined;
62
+ addressType?: string | undefined; /**
63
+ * Override setVerifiedCredentials to filter and set Bitcoin WaaS credentials
64
+ * Filters for credentials with walletName === 'dynamicwaas' and chain === 'bip122'
65
+ * The base class already has verifiedCredentials property, so we just filter and set it
66
+ */
60
67
  }): Promise<void>;
61
68
  exportPrivateKey({ accountAddress, displayContainer, password, }?: {
62
69
  accountAddress?: string | undefined;
@@ -78,6 +85,15 @@ declare const DynamicWaasBitcoinConnector_base: (abstract new (...args: any[]) =
78
85
  accountAddress: string;
79
86
  password?: string | undefined;
80
87
  }): Promise<void>;
88
+ backupKeySharesToICloud({ accountAddress, password, }: {
89
+ accountAddress: string;
90
+ password?: string | undefined;
91
+ }): Promise<void>;
92
+ displayICloudSignIn({ displayContainer, }: {
93
+ displayContainer: HTMLElement;
94
+ }): Promise<void>;
95
+ hideICloudSignIn(): Promise<void>;
96
+ isICloudAuthenticated(): Promise<boolean>;
81
97
  refreshWalletAccountShares({ accountAddress, password, }: {
82
98
  accountAddress: string;
83
99
  password?: string | undefined;
@@ -169,13 +185,17 @@ export declare class DynamicWaasBitcoinConnector extends DynamicWaasBitcoinConne
169
185
  */
170
186
  signMessage(message: string): Promise<string>;
171
187
  /**
172
- * Signs a Partially Signed Bitcoin Transaction (PSBT)
173
- * @param request - The PSBT signing request containing the unsigned PSBT
174
- * @returns The signed PSBT response
188
+ * Signs a Partially Signed Bitcoin Transaction (PSBT) for embedded wallets
189
+ *
190
+ * Embedded wallets only support PSBT format and automatically sign all inputs
191
+ * that belong to the wallet address. Always uses SIGHASH_ALL (0x01).
192
+ *
193
+ * @param request - The PSBT signing request. Only unsignedPsbtBase64 is required.
194
+ * @returns The signed (but not finalized) PSBT response
175
195
  * @throws {DynamicError} If active account address is not set
176
- * @throws {DynamicError} If signed session ID is not available or signature is provided
196
+ * @throws {DynamicError} If signed session ID is not available
177
197
  */
178
- signPsbt(request: BitcoinSignPsbtRequest): Promise<BitcoinSignPsbtResponse>;
198
+ signPsbt(request: EmbeddedWalletSignPsbtRequest): Promise<BitcoinSignPsbtResponse>;
179
199
  /**
180
200
  * Sends a raw Bitcoin transaction to the mempool
181
201
  * @param rawTransaction - The raw transaction in hex format
@@ -258,7 +278,7 @@ export declare class DynamicWaasBitcoinConnector extends DynamicWaasBitcoinConne
258
278
  }): Promise<string>;
259
279
  /**
260
280
  * Builds a PSBT for a Bitcoin transaction with real UTXOs
261
- * @param transaction - The Bitcoin transaction containing recipient address and amount in satoshis to send
281
+ * @param transaction - Bitcoin transaction with recipient address, amount in satoshis, and optional fee priority
262
282
  * @returns A PSBT in Base64 format
263
283
  * @throws {DynamicError} If no active account address, insufficient funds, or other errors
264
284
  */
@@ -124,11 +124,15 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
124
124
  });
125
125
  }
126
126
  /**
127
- * Signs a Partially Signed Bitcoin Transaction (PSBT)
128
- * @param request - The PSBT signing request containing the unsigned PSBT
129
- * @returns The signed PSBT response
127
+ * Signs a Partially Signed Bitcoin Transaction (PSBT) for embedded wallets
128
+ *
129
+ * Embedded wallets only support PSBT format and automatically sign all inputs
130
+ * that belong to the wallet address. Always uses SIGHASH_ALL (0x01).
131
+ *
132
+ * @param request - The PSBT signing request. Only unsignedPsbtBase64 is required.
133
+ * @returns The signed (but not finalized) PSBT response
130
134
  * @throws {DynamicError} If active account address is not set
131
- * @throws {DynamicError} If signed session ID is not available or signature is provided
135
+ * @throws {DynamicError} If signed session ID is not available
132
136
  */
133
137
  signPsbt(request) {
134
138
  return __awaiter(this, void 0, void 0, function* () {
@@ -144,9 +148,6 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
144
148
  const mfaToken = yield ((_b = this.getMfaToken) === null || _b === void 0 ? void 0 : _b.call(this, {
145
149
  mfaAction: MFAAction.WalletWaasSign,
146
150
  }));
147
- if (request.signature && request.signature.length > 0) {
148
- throw new DynamicError('Signature is not supported for waas at the moment');
149
- }
150
151
  const signedTransaction = yield walletClient.signTransaction({
151
152
  authToken: (_c = this.getAuthToken) === null || _c === void 0 ? void 0 : _c.call(this),
152
153
  mfaToken,
@@ -188,9 +189,7 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
188
189
  // Step 1: Build the PSBT
189
190
  const unsignedPsbt = yield this.buildPsbt(transaction);
190
191
  // Step 2: Sign the PSBT
191
- // SIGHASH_ALL (0x01) is the most common sighash type for Bitcoin transactions (Eventually can be configurable)
192
192
  const signedPsbtResponse = yield this.signPsbt({
193
- allowedSighash: [0x01],
194
193
  unsignedPsbtBase64: unsignedPsbt,
195
194
  });
196
195
  if (!signedPsbtResponse) {
@@ -333,7 +332,7 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
333
332
  }
334
333
  /**
335
334
  * Builds a PSBT for a Bitcoin transaction with real UTXOs
336
- * @param transaction - The Bitcoin transaction containing recipient address and amount in satoshis to send
335
+ * @param transaction - Bitcoin transaction with recipient address, amount in satoshis, and optional fee priority
337
336
  * @returns A PSBT in Base64 format
338
337
  * @throws {DynamicError} If no active account address, insufficient funds, or other errors
339
338
  */
@@ -343,7 +342,7 @@ class DynamicWaasBitcoinConnector extends withDynamicWaas(BitcoinWalletConnector
343
342
  throw new DynamicError('Active account address is required');
344
343
  }
345
344
  const publicKeyHex = yield this.getPublicKey();
346
- const buildOptions = PsbtBuilderService.createBuildOptions(this.activeAccountAddress, transaction, publicKeyHex);
345
+ const buildOptions = PsbtBuilderService.createBuildOptions(this.activeAccountAddress, transaction, publicKeyHex, transaction.feePriority || 'medium');
347
346
  return this.psbtBuilderService.buildPsbt(buildOptions);
348
347
  });
349
348
  }
package/src/const.cjs CHANGED
@@ -12,20 +12,28 @@ const MEMPOOL_API_URL_TESTNET = 'https://mempool.space/testnet/api';
12
12
  // WaaS Bitcoin constants
13
13
  const SATOSHIS_PER_BTC = 100000000;
14
14
  const DUST_LIMIT = 546; // Bitcoin's dust limit in satoshis
15
- const INPUT_BYTE_SIZE_UPPER_BOUND = 90;
16
- const OUTPUT_BYTE_SIZE_UPPER_BOUND = 45;
15
+ // Accurate vSize constants for Native SegWit (P2WPKH) transactions
16
+ // These are used for precise fee estimation
17
+ const VSIZE_OVERHEAD = 10.5; // Base transaction overhead in vBytes
18
+ const VSIZE_INPUT_P2WPKH = 68; // Each P2WPKH input in vBytes
19
+ const VSIZE_OUTPUT_P2WPKH = 31; // Each P2WPKH output in vBytes
17
20
  const MIN_RELAY_FEE = 111;
18
21
  const DEFAULT_FEE_ESTIMATE = 1000; // Conservative default fee estimate in satoshis
22
+ // RBF (Replace-By-Fee) sequence number
23
+ // 0xfffffffd = 4294967293 (enables RBF, not final)
24
+ const RBF_SEQUENCE = 0xfffffffd;
19
25
 
20
26
  exports.BTCKIT_INTERFACE = BTCKIT_INTERFACE;
21
27
  exports.DEFAULT_FEE_ESTIMATE = DEFAULT_FEE_ESTIMATE;
22
28
  exports.DUST_LIMIT = DUST_LIMIT;
23
29
  exports.HTTP_STATUS_NOT_FOUND = HTTP_STATUS_NOT_FOUND;
24
30
  exports.HTTP_STATUS_TOO_MANY_REQUESTS = HTTP_STATUS_TOO_MANY_REQUESTS;
25
- exports.INPUT_BYTE_SIZE_UPPER_BOUND = INPUT_BYTE_SIZE_UPPER_BOUND;
26
31
  exports.MEMPOOL_API_URL = MEMPOOL_API_URL;
27
32
  exports.MEMPOOL_API_URL_TESTNET = MEMPOOL_API_URL_TESTNET;
28
33
  exports.MIN_RELAY_FEE = MIN_RELAY_FEE;
29
- exports.OUTPUT_BYTE_SIZE_UPPER_BOUND = OUTPUT_BYTE_SIZE_UPPER_BOUND;
34
+ exports.RBF_SEQUENCE = RBF_SEQUENCE;
30
35
  exports.SATOSHIS_PER_BTC = SATOSHIS_PER_BTC;
31
36
  exports.SATSCONNECT_FEATURE = SATSCONNECT_FEATURE;
37
+ exports.VSIZE_INPUT_P2WPKH = VSIZE_INPUT_P2WPKH;
38
+ exports.VSIZE_OUTPUT_P2WPKH = VSIZE_OUTPUT_P2WPKH;
39
+ exports.VSIZE_OVERHEAD = VSIZE_OVERHEAD;
package/src/const.d.ts CHANGED
@@ -7,7 +7,9 @@ export declare const MEMPOOL_API_URL = "https://mempool.space/api";
7
7
  export declare const MEMPOOL_API_URL_TESTNET = "https://mempool.space/testnet/api";
8
8
  export declare const SATOSHIS_PER_BTC = 100000000;
9
9
  export declare const DUST_LIMIT = 546;
10
- export declare const INPUT_BYTE_SIZE_UPPER_BOUND = 90;
11
- export declare const OUTPUT_BYTE_SIZE_UPPER_BOUND = 45;
10
+ export declare const VSIZE_OVERHEAD = 10.5;
11
+ export declare const VSIZE_INPUT_P2WPKH = 68;
12
+ export declare const VSIZE_OUTPUT_P2WPKH = 31;
12
13
  export declare const MIN_RELAY_FEE = 111;
13
14
  export declare const DEFAULT_FEE_ESTIMATE = 1000;
15
+ export declare const RBF_SEQUENCE = 4294967293;
package/src/const.js CHANGED
@@ -8,9 +8,15 @@ const MEMPOOL_API_URL_TESTNET = 'https://mempool.space/testnet/api';
8
8
  // WaaS Bitcoin constants
9
9
  const SATOSHIS_PER_BTC = 100000000;
10
10
  const DUST_LIMIT = 546; // Bitcoin's dust limit in satoshis
11
- const INPUT_BYTE_SIZE_UPPER_BOUND = 90;
12
- const OUTPUT_BYTE_SIZE_UPPER_BOUND = 45;
11
+ // Accurate vSize constants for Native SegWit (P2WPKH) transactions
12
+ // These are used for precise fee estimation
13
+ const VSIZE_OVERHEAD = 10.5; // Base transaction overhead in vBytes
14
+ const VSIZE_INPUT_P2WPKH = 68; // Each P2WPKH input in vBytes
15
+ const VSIZE_OUTPUT_P2WPKH = 31; // Each P2WPKH output in vBytes
13
16
  const MIN_RELAY_FEE = 111;
14
17
  const DEFAULT_FEE_ESTIMATE = 1000; // Conservative default fee estimate in satoshis
18
+ // RBF (Replace-By-Fee) sequence number
19
+ // 0xfffffffd = 4294967293 (enables RBF, not final)
20
+ const RBF_SEQUENCE = 0xfffffffd;
15
21
 
16
- export { BTCKIT_INTERFACE, DEFAULT_FEE_ESTIMATE, DUST_LIMIT, HTTP_STATUS_NOT_FOUND, HTTP_STATUS_TOO_MANY_REQUESTS, INPUT_BYTE_SIZE_UPPER_BOUND, MEMPOOL_API_URL, MEMPOOL_API_URL_TESTNET, MIN_RELAY_FEE, OUTPUT_BYTE_SIZE_UPPER_BOUND, SATOSHIS_PER_BTC, SATSCONNECT_FEATURE };
22
+ export { BTCKIT_INTERFACE, DEFAULT_FEE_ESTIMATE, DUST_LIMIT, HTTP_STATUS_NOT_FOUND, HTTP_STATUS_TOO_MANY_REQUESTS, MEMPOOL_API_URL, MEMPOOL_API_URL_TESTNET, MIN_RELAY_FEE, RBF_SEQUENCE, SATOSHIS_PER_BTC, SATSCONNECT_FEATURE, VSIZE_INPUT_P2WPKH, VSIZE_OUTPUT_P2WPKH, VSIZE_OVERHEAD };
package/src/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { OkxConnector } from './connectors';
2
2
  export { BitcoinWalletConnector, type BitcoinWalletConnectorOpts, } from './connectors';
3
- export type { BitcoinTransaction, BitcoinSignProtocol, BitcoinSignPsbtRequest, BitcoinSignPsbtResponse, BitcoinWalletStandardMethods, SignPsbtOptions, } from './types';
3
+ export type { BitcoinTransaction, BitcoinSignProtocol, BitcoinSignPsbtRequest, BitcoinSignPsbtResponse, BitcoinWalletStandardMethods, SignPsbtOptions, EmbeddedWalletSignPsbtRequest, } from './types';
4
4
  export * from './utils';
5
5
  export * from './wallet';
6
6
  export { UnisatConnector, BitcoinSatsConnectConnector, DynamicWaasBitcoinConnector, } from './connectors';
@@ -70,21 +70,44 @@ class MempoolApiService {
70
70
  });
71
71
  }
72
72
  /**
73
- * Estimates transaction fees based on number of inputs and outputs
73
+ * Estimates transaction fees based on number of inputs and outputs using accurate vSize
74
74
  * @param address - The Bitcoin address to determine the network from
75
75
  * @param numInputs - Number of transaction inputs
76
76
  * @param numOutputs - Number of transaction outputs
77
+ * @param feePriority - Fee priority level (high/medium/low)
77
78
  * @returns Estimated fee in satoshis
78
79
  */
79
- estimateTransactionFee(address, numInputs, numOutputs) {
80
- return _tslib.__awaiter(this, void 0, void 0, function* () {
80
+ estimateTransactionFee(address_1, numInputs_1, numOutputs_1) {
81
+ return _tslib.__awaiter(this, arguments, void 0, function* (address, numInputs, numOutputs, feePriority = 'medium') {
81
82
  try {
82
83
  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;
84
+ // High (Fastest): fastestFee -> Block 1
85
+ // Medium (Fast): halfHourFee -> Blocks 2-3
86
+ // Low (Eco): economyFee (preferred) or hourFee -> Blocks 6+
87
+ let feePerByte;
88
+ switch (feePriority) {
89
+ case 'high':
90
+ feePerByte =
91
+ feeData.fastestFee || feeData.halfHourFee || feeData.hourFee || 1;
92
+ break;
93
+ case 'low':
94
+ feePerByte = feeData.economyFee || feeData.hourFee || 1;
95
+ break;
96
+ case 'medium':
97
+ default:
98
+ feePerByte =
99
+ feeData.halfHourFee || feeData.hourFee || feeData.economyFee || 1;
100
+ break;
101
+ }
102
+ logger.debug(`Fee estimation - Priority: ${feePriority}, fastestFee: ${feeData.fastestFee}, halfHourFee: ${feeData.halfHourFee}, hourFee: ${feeData.hourFee}, economyFee: ${feeData.economyFee}, selected: ${feePerByte} sat/vB`);
103
+ // Use accurate vSize calculation for Native SegWit (P2WPKH)
104
+ // Formula: overhead + (inputs × 68) + (outputs × 31)
105
+ const vSize = _const.VSIZE_OVERHEAD +
106
+ numInputs * _const.VSIZE_INPUT_P2WPKH +
107
+ numOutputs * _const.VSIZE_OUTPUT_P2WPKH;
108
+ const estimatedFee = Math.ceil(feePerByte * vSize) + _const.MIN_RELAY_FEE;
109
+ logger.debug(`Fee calculation - Priority: ${feePriority}, vSize: ${vSize}, feePerByte: ${feePerByte} sat/vB, estimatedFee: ${estimatedFee} satoshis`);
110
+ logger.debug(`[MempoolApiService] Fee Priority: ${feePriority}, Selected Rate: ${feePerByte} sat/vB, vSize: ${vSize}, Estimated Fee: ${estimatedFee} satoshis`);
88
111
  return estimatedFee;
89
112
  }
90
113
  catch (error) {
@@ -24,13 +24,14 @@ export declare class MempoolApiService {
24
24
  */
25
25
  getFeeRecommendations(address: string): Promise<FeeRecommendations>;
26
26
  /**
27
- * Estimates transaction fees based on number of inputs and outputs
27
+ * Estimates transaction fees based on number of inputs and outputs using accurate vSize
28
28
  * @param address - The Bitcoin address to determine the network from
29
29
  * @param numInputs - Number of transaction inputs
30
30
  * @param numOutputs - Number of transaction outputs
31
+ * @param feePriority - Fee priority level (high/medium/low)
31
32
  * @returns Estimated fee in satoshis
32
33
  */
33
- estimateTransactionFee(address: string, numInputs: number, numOutputs: number): Promise<number>;
34
+ estimateTransactionFee(address: string, numInputs: number, numOutputs: number, feePriority?: 'high' | 'medium' | 'low'): Promise<number>;
34
35
  /**
35
36
  * Sends a raw Bitcoin transaction to the mempool
36
37
  * @param address - The Bitcoin address to determine the network from
@@ -9,7 +9,7 @@ import '@dynamic-labs/wallet-book';
9
9
  import '@dynamic-labs/sdk-api-core';
10
10
  import '@wallet-standard/app';
11
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';
12
+ import { VSIZE_OVERHEAD, VSIZE_INPUT_P2WPKH, VSIZE_OUTPUT_P2WPKH, MIN_RELAY_FEE, DEFAULT_FEE_ESTIMATE } from '../const.js';
13
13
  import 'jsontokens';
14
14
  import '../connectors/DynamicWaasBitcoinConnector/DynamicWaasBitcoinConnector.js';
15
15
 
@@ -66,21 +66,44 @@ class MempoolApiService {
66
66
  });
67
67
  }
68
68
  /**
69
- * Estimates transaction fees based on number of inputs and outputs
69
+ * Estimates transaction fees based on number of inputs and outputs using accurate vSize
70
70
  * @param address - The Bitcoin address to determine the network from
71
71
  * @param numInputs - Number of transaction inputs
72
72
  * @param numOutputs - Number of transaction outputs
73
+ * @param feePriority - Fee priority level (high/medium/low)
73
74
  * @returns Estimated fee in satoshis
74
75
  */
75
- estimateTransactionFee(address, numInputs, numOutputs) {
76
- return __awaiter(this, void 0, void 0, function* () {
76
+ estimateTransactionFee(address_1, numInputs_1, numOutputs_1) {
77
+ return __awaiter(this, arguments, void 0, function* (address, numInputs, numOutputs, feePriority = 'medium') {
77
78
  try {
78
79
  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;
80
+ // High (Fastest): fastestFee -> Block 1
81
+ // Medium (Fast): halfHourFee -> Blocks 2-3
82
+ // Low (Eco): economyFee (preferred) or hourFee -> Blocks 6+
83
+ let feePerByte;
84
+ switch (feePriority) {
85
+ case 'high':
86
+ feePerByte =
87
+ feeData.fastestFee || feeData.halfHourFee || feeData.hourFee || 1;
88
+ break;
89
+ case 'low':
90
+ feePerByte = feeData.economyFee || feeData.hourFee || 1;
91
+ break;
92
+ case 'medium':
93
+ default:
94
+ feePerByte =
95
+ feeData.halfHourFee || feeData.hourFee || feeData.economyFee || 1;
96
+ break;
97
+ }
98
+ logger.debug(`Fee estimation - Priority: ${feePriority}, fastestFee: ${feeData.fastestFee}, halfHourFee: ${feeData.halfHourFee}, hourFee: ${feeData.hourFee}, economyFee: ${feeData.economyFee}, selected: ${feePerByte} sat/vB`);
99
+ // Use accurate vSize calculation for Native SegWit (P2WPKH)
100
+ // Formula: overhead + (inputs × 68) + (outputs × 31)
101
+ const vSize = VSIZE_OVERHEAD +
102
+ numInputs * VSIZE_INPUT_P2WPKH +
103
+ numOutputs * VSIZE_OUTPUT_P2WPKH;
104
+ const estimatedFee = Math.ceil(feePerByte * vSize) + MIN_RELAY_FEE;
105
+ logger.debug(`Fee calculation - Priority: ${feePriority}, vSize: ${vSize}, feePerByte: ${feePerByte} sat/vB, estimatedFee: ${estimatedFee} satoshis`);
106
+ logger.debug(`[MempoolApiService] Fee Priority: ${feePriority}, Selected Rate: ${feePerByte} sat/vB, vSize: ${vSize}, Estimated Fee: ${estimatedFee} satoshis`);
84
107
  return estimatedFee;
85
108
  }
86
109
  catch (error) {
@@ -24,104 +24,234 @@ class PsbtBuilderService {
24
24
  constructor(mempoolApiService) {
25
25
  this.mempoolApiService = mempoolApiService;
26
26
  }
27
+ /**
28
+ * Filters out Taproot (P2TR) UTXOs to prevent accidental spending of Ordinals/Runes
29
+ * Since we only support Native SegWit (P2WPKH), we ensure the account address is not Taproot
30
+ * @param accountAddress - The account address to check
31
+ * @param utxos - Array of UTXOs to filter
32
+ * @returns Filtered array of UTXOs (only P2WPKH compatible)
33
+ */
34
+ filterTaprootUTXOs(accountAddress, utxos) {
35
+ // Safety check: Ensure account address is not Taproot (bc1p...)
36
+ if (accountAddress.toLowerCase().startsWith('bc1p') ||
37
+ accountAddress.toLowerCase().startsWith('tb1p')) {
38
+ logger.warn(`Account address ${accountAddress} appears to be Taproot. Only Native SegWit (P2WPKH) is supported.`);
39
+ throw new utils.DynamicError('Taproot addresses are not supported. Only Native SegWit (P2WPKH) addresses are allowed.');
40
+ }
41
+ return utxos;
42
+ }
43
+ /**
44
+ * Selects UTXOs using Largest-First (Accumulator) strategy
45
+ * Sorts UTXOs by value (descending) and selects until we have enough to cover amount + fees
46
+ * @param utxos - Available UTXOs
47
+ * @param targetAmount - Target amount including fees and dust limit
48
+ * @returns Selected UTXOs
49
+ */
50
+ selectUTXOsLargestFirst(utxos, targetAmount) {
51
+ // Sort UTXOs by value (largest first)
52
+ const sortedUTXOs = [...utxos].sort((a, b) => b.value - a.value);
53
+ // Accumulate UTXOs until we have enough
54
+ const selected = [];
55
+ let total = 0;
56
+ for (const utxo of sortedUTXOs) {
57
+ selected.push(utxo);
58
+ total += utxo.value;
59
+ // Stop when we have enough to cover the target amount
60
+ if (total >= targetAmount) {
61
+ break;
62
+ }
63
+ }
64
+ return selected;
65
+ }
66
+ /**
67
+ * Calculates the total value of UTXOs
68
+ * @param utxos - Array of UTXOs
69
+ * @returns Total value in satoshis
70
+ */
71
+ calculateUTXOTotal(utxos) {
72
+ return utxos.reduce((total, utxo) => total + utxo.value, 0);
73
+ }
74
+ /**
75
+ * Validates and ensures sufficient funds for the transaction
76
+ * @param selectedUTXOs - Initially selected UTXOs
77
+ * @param allUTXOs - All available UTXOs
78
+ * @param selectedTotal - Total value of selected UTXOs
79
+ * @param amountInSatoshisNumber - Transaction amount
80
+ * @param feeEstimate - Estimated fee
81
+ * @returns Validated selected UTXOs
82
+ * @throws {DynamicError} If insufficient funds
83
+ */
84
+ validateAndSelectUTXOs(selectedUTXOs, allUTXOs, selectedTotal, amountInSatoshisNumber, feeEstimate) {
85
+ const requiredAmount = amountInSatoshisNumber + feeEstimate;
86
+ if (selectedTotal >= requiredAmount) {
87
+ return selectedUTXOs;
88
+ }
89
+ // Try with all UTXOs if selection wasn't enough
90
+ if (selectedUTXOs.length < allUTXOs.length) {
91
+ const allTotal = this.calculateUTXOTotal(allUTXOs);
92
+ if (allTotal < requiredAmount) {
93
+ throw new utils.DynamicError(`Insufficient funds. Available: ${allTotal / _const.SATOSHIS_PER_BTC} BTC (${allTotal} satoshis), Required: ${amountInSatoshisNumber / _const.SATOSHIS_PER_BTC} BTC (${amountInSatoshisNumber} satoshis) + fees`);
94
+ }
95
+ return allUTXOs;
96
+ }
97
+ throw new utils.DynamicError(`Insufficient funds. Available: ${selectedTotal / _const.SATOSHIS_PER_BTC} BTC (${selectedTotal} satoshis), Required: ${amountInSatoshisNumber / _const.SATOSHIS_PER_BTC} BTC (${amountInSatoshisNumber} satoshis) + fees`);
98
+ }
99
+ /**
100
+ * Calculates fee estimate and change amount, handling dust limit
101
+ * @param accountAddress - Account address for fee estimation
102
+ * @param selectedUTXOs - Selected UTXOs
103
+ * @param selectedTotalValue - Total value of selected UTXOs
104
+ * @param amountInSatoshis - Transaction amount
105
+ * @param feePriority - Fee priority level
106
+ * @returns Object with feeEstimate, changeAmount, changeAmountNumber, and hasChangeOutput
107
+ */
108
+ calculateFeeAndChange(accountAddress, selectedUTXOs, selectedTotalValue, amountInSatoshis, feePriority) {
109
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
110
+ // Re-estimate fee with actual number of inputs
111
+ let feeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, selectedUTXOs.length, 1, // Start with 1 output (recipient only)
112
+ feePriority);
113
+ let maxToSpend = selectedTotalValue - feeEstimate;
114
+ let changeAmount = BigInt(maxToSpend) - amountInSatoshis;
115
+ // If change will be above dust limit, re-estimate fees for 2 outputs
116
+ const changeAmountNumber = Number(changeAmount);
117
+ if (changeAmount > 0 && changeAmountNumber >= _const.DUST_LIMIT) {
118
+ feeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, selectedUTXOs.length, 2, // recipient + change output
119
+ feePriority);
120
+ maxToSpend = selectedTotalValue - feeEstimate;
121
+ changeAmount = BigInt(maxToSpend) - amountInSatoshis;
122
+ }
123
+ const finalChangeAmountNumber = Number(changeAmount);
124
+ const hasChangeOutput = changeAmount > 0 && finalChangeAmountNumber >= _const.DUST_LIMIT;
125
+ // Final fee adjustment: if change < dust limit, add it to fee
126
+ if (changeAmount > 0 && finalChangeAmountNumber < _const.DUST_LIMIT) {
127
+ feeEstimate += finalChangeAmountNumber;
128
+ }
129
+ return {
130
+ changeAmount,
131
+ changeAmountNumber: finalChangeAmountNumber,
132
+ feeEstimate,
133
+ hasChangeOutput,
134
+ };
135
+ });
136
+ }
137
+ /**
138
+ * Adds inputs to PSBT from selected UTXOs
139
+ * @param psbt - PSBT to add inputs to
140
+ * @param selectedUTXOs - Selected UTXOs
141
+ * @param publicKeyPair - ECPair public key pair
142
+ * @param network - Bitcoin network
143
+ */
144
+ addInputsToPsbt(psbt, selectedUTXOs, publicKeyPair, network) {
145
+ for (const utxo of selectedUTXOs) {
146
+ const outputScript = bitcoinjsLib.payments.p2wpkh({
147
+ network,
148
+ pubkey: publicKeyPair.publicKey,
149
+ }).output;
150
+ if (!outputScript) {
151
+ throw new utils.DynamicError('Failed to create segwit output script');
152
+ }
153
+ // Convert txid from hex string to Buffer and reverse it (Bitcoin uses little-endian)
154
+ // The txid from the API is in big-endian format, but bitcoinjs-lib expects little-endian
155
+ const txidBuffer = Buffer.from(utxo.txid, 'hex').reverse();
156
+ psbt.addInput({
157
+ hash: txidBuffer,
158
+ index: utxo.vout,
159
+ sequence: _const.RBF_SEQUENCE, // Enable RBF (Replace-By-Fee)
160
+ witnessUtxo: {
161
+ script: outputScript,
162
+ value: utxo.value,
163
+ },
164
+ });
165
+ }
166
+ }
167
+ /**
168
+ * Adds outputs to PSBT (recipient and optionally change)
169
+ * @param psbt - PSBT to add outputs to
170
+ * @param recipientAddress - Recipient address
171
+ * @param accountAddress - Account address for change
172
+ * @param amountInSatoshisNumber - Transaction amount
173
+ * @param changeAmountNumber - Change amount
174
+ * @param hasChangeOutput - Whether to include change output
175
+ * @param network - Bitcoin network
176
+ */
177
+ addOutputsToPsbt(psbt, recipientAddress, accountAddress, amountInSatoshisNumber, changeAmountNumber, hasChangeOutput, network) {
178
+ if (amountInSatoshisNumber < _const.DUST_LIMIT) {
179
+ throw new utils.DynamicError(`Amount is below dust limit of ${_const.DUST_LIMIT} satoshis (${_const.DUST_LIMIT / _const.SATOSHIS_PER_BTC} BTC)`);
180
+ }
181
+ psbt.addOutput({
182
+ script: bitcoinjsLib.address.toOutputScript(recipientAddress, network),
183
+ value: amountInSatoshisNumber,
184
+ });
185
+ if (hasChangeOutput) {
186
+ psbt.addOutput({
187
+ script: bitcoinjsLib.address.toOutputScript(accountAddress, network),
188
+ value: changeAmountNumber,
189
+ });
190
+ }
191
+ }
27
192
  /**
28
193
  * Builds a PSBT for a Bitcoin transaction with real UTXOs
194
+ * Uses Largest-First UTXO selection strategy with accurate vSize fee estimation
29
195
  * @param options - Options for building the PSBT
30
196
  * @returns A PSBT in Base64 format
31
197
  * @throws {DynamicError} If insufficient funds, no UTXOs, or other errors
32
198
  */
33
199
  buildPsbt(options) {
34
200
  return _tslib.__awaiter(this, void 0, void 0, function* () {
35
- const { accountAddress, recipientAddress, amountInSatoshis, publicKeyHex, network, } = options;
201
+ const { accountAddress, recipientAddress, amountInSatoshis, publicKeyHex, network, feePriority = 'medium', } = options;
202
+ logger.debug(`buildPsbt called with feePriority: ${feePriority}, amount: ${amountInSatoshis} satoshis`);
36
203
  if (amountInSatoshis <= BigInt(0)) {
37
204
  throw new utils.DynamicError('Amount must be greater than 0');
38
205
  }
39
- // Get UTXOs for the account address
40
- const utxos = yield this.mempoolApiService.getUTXOs(accountAddress);
41
- if (utxos.length === 0) {
206
+ const allUTXOs = yield this.mempoolApiService.getUTXOs(accountAddress);
207
+ if (allUTXOs.length === 0) {
42
208
  throw new utils.DynamicError('No UTXOs found for this address');
43
209
  }
44
- // Get public key for creating output scripts
210
+ const utxos = this.filterTaprootUTXOs(accountAddress, allUTXOs);
45
211
  const publicKeyBuffer = Buffer.from(publicKeyHex, 'hex');
46
- // Use ECPair to ensure the public key is properly formatted for bitcoinjs-lib
47
212
  const ECPair = ecpair.ECPairFactory(ecc__default["default"]);
48
213
  const publicKeyPair = ECPair.fromPublicKey(publicKeyBuffer, {
49
214
  compressed: true,
50
215
  });
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);
216
+ const amountInSatoshisNumber = Number(amountInSatoshis);
217
+ // Initial fee estimate with 1 input and 1 output
218
+ const initialFeeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, 1, 1, feePriority);
219
+ // Target amount: transaction amount + fee + dust limit (for change output safety)
220
+ const targetAmount = amountInSatoshisNumber + initialFeeEstimate + _const.DUST_LIMIT;
221
+ // Select UTXOs using Largest-First strategy
222
+ let selectedUTXOs = this.selectUTXOsLargestFirst(utxos, targetAmount);
223
+ const selectedTotal = this.calculateUTXOTotal(selectedUTXOs);
224
+ // Validate and ensure sufficient funds
225
+ selectedUTXOs = this.validateAndSelectUTXOs(selectedUTXOs, utxos, selectedTotal, amountInSatoshisNumber, initialFeeEstimate);
226
+ const selectedTotalValue = this.calculateUTXOTotal(selectedUTXOs);
227
+ // Calculate fee and change with proper dust limit handling
228
+ const { feeEstimate, changeAmountNumber, hasChangeOutput } = yield this.calculateFeeAndChange(accountAddress, selectedUTXOs, selectedTotalValue, amountInSatoshis, feePriority);
229
+ // Final check: ensure we have enough after final fee calculation
230
+ const maxToSpend = selectedTotalValue - feeEstimate;
231
+ if (maxToSpend < amountInSatoshisNumber) {
66
232
  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
233
  }
68
- // Create PSBT
234
+ // Create PSBT and add inputs/outputs
69
235
  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`);
236
+ this.addInputsToPsbt(psbt, selectedUTXOs, publicKeyPair, network);
237
+ this.addOutputsToPsbt(psbt, recipientAddress, accountAddress, amountInSatoshisNumber, changeAmountNumber, hasChangeOutput, network);
238
+ logger.debug(`buildPsbt created PSBT for recipientAddress: ${recipientAddress}, amount: ${amountInSatoshisNumber} satoshis (${amountInSatoshisNumber / _const.SATOSHIS_PER_BTC} BTC), change: ${changeAmountNumber} satoshis, estimated fee: ${feeEstimate} satoshis, feePriority: ${feePriority}, inputs: ${selectedUTXOs.length}`);
111
239
  return psbt.toBase64();
112
240
  });
113
241
  }
114
242
  /**
115
243
  * Helper method to create BuildPsbtOptions from transaction and account details
116
244
  * @param accountAddress - The account address
117
- * @param transaction - The Bitcoin transaction
245
+ * @param transaction - The Bitcoin transaction (must have amount and recipientAddress)
118
246
  * @param publicKeyHex - The public key in hex format
247
+ * @param feePriority - Optional fee priority (defaults to 'medium')
119
248
  * @returns BuildPsbtOptions
120
249
  */
121
- static createBuildOptions(accountAddress, transaction, publicKeyHex) {
250
+ static createBuildOptions(accountAddress, transaction, publicKeyHex, feePriority = 'medium') {
122
251
  return {
123
252
  accountAddress,
124
253
  amountInSatoshis: transaction.amount,
254
+ feePriority,
125
255
  network: getBitcoinNetwork.getBitcoinNetwork(accountAddress),
126
256
  publicKeyHex,
127
257
  recipientAddress: transaction.recipientAddress,
@@ -6,8 +6,71 @@ import { MempoolApiService } from './MempoolApiService';
6
6
  export declare class PsbtBuilderService {
7
7
  private mempoolApiService;
8
8
  constructor(mempoolApiService: MempoolApiService);
9
+ /**
10
+ * Filters out Taproot (P2TR) UTXOs to prevent accidental spending of Ordinals/Runes
11
+ * Since we only support Native SegWit (P2WPKH), we ensure the account address is not Taproot
12
+ * @param accountAddress - The account address to check
13
+ * @param utxos - Array of UTXOs to filter
14
+ * @returns Filtered array of UTXOs (only P2WPKH compatible)
15
+ */
16
+ private filterTaprootUTXOs;
17
+ /**
18
+ * Selects UTXOs using Largest-First (Accumulator) strategy
19
+ * Sorts UTXOs by value (descending) and selects until we have enough to cover amount + fees
20
+ * @param utxos - Available UTXOs
21
+ * @param targetAmount - Target amount including fees and dust limit
22
+ * @returns Selected UTXOs
23
+ */
24
+ private selectUTXOsLargestFirst;
25
+ /**
26
+ * Calculates the total value of UTXOs
27
+ * @param utxos - Array of UTXOs
28
+ * @returns Total value in satoshis
29
+ */
30
+ private calculateUTXOTotal;
31
+ /**
32
+ * Validates and ensures sufficient funds for the transaction
33
+ * @param selectedUTXOs - Initially selected UTXOs
34
+ * @param allUTXOs - All available UTXOs
35
+ * @param selectedTotal - Total value of selected UTXOs
36
+ * @param amountInSatoshisNumber - Transaction amount
37
+ * @param feeEstimate - Estimated fee
38
+ * @returns Validated selected UTXOs
39
+ * @throws {DynamicError} If insufficient funds
40
+ */
41
+ private validateAndSelectUTXOs;
42
+ /**
43
+ * Calculates fee estimate and change amount, handling dust limit
44
+ * @param accountAddress - Account address for fee estimation
45
+ * @param selectedUTXOs - Selected UTXOs
46
+ * @param selectedTotalValue - Total value of selected UTXOs
47
+ * @param amountInSatoshis - Transaction amount
48
+ * @param feePriority - Fee priority level
49
+ * @returns Object with feeEstimate, changeAmount, changeAmountNumber, and hasChangeOutput
50
+ */
51
+ private calculateFeeAndChange;
52
+ /**
53
+ * Adds inputs to PSBT from selected UTXOs
54
+ * @param psbt - PSBT to add inputs to
55
+ * @param selectedUTXOs - Selected UTXOs
56
+ * @param publicKeyPair - ECPair public key pair
57
+ * @param network - Bitcoin network
58
+ */
59
+ private addInputsToPsbt;
60
+ /**
61
+ * Adds outputs to PSBT (recipient and optionally change)
62
+ * @param psbt - PSBT to add outputs to
63
+ * @param recipientAddress - Recipient address
64
+ * @param accountAddress - Account address for change
65
+ * @param amountInSatoshisNumber - Transaction amount
66
+ * @param changeAmountNumber - Change amount
67
+ * @param hasChangeOutput - Whether to include change output
68
+ * @param network - Bitcoin network
69
+ */
70
+ private addOutputsToPsbt;
9
71
  /**
10
72
  * Builds a PSBT for a Bitcoin transaction with real UTXOs
73
+ * Uses Largest-First UTXO selection strategy with accurate vSize fee estimation
11
74
  * @param options - Options for building the PSBT
12
75
  * @returns A PSBT in Base64 format
13
76
  * @throws {DynamicError} If insufficient funds, no UTXOs, or other errors
@@ -16,12 +79,13 @@ export declare class PsbtBuilderService {
16
79
  /**
17
80
  * Helper method to create BuildPsbtOptions from transaction and account details
18
81
  * @param accountAddress - The account address
19
- * @param transaction - The Bitcoin transaction
82
+ * @param transaction - The Bitcoin transaction (must have amount and recipientAddress)
20
83
  * @param publicKeyHex - The public key in hex format
84
+ * @param feePriority - Optional fee priority (defaults to 'medium')
21
85
  * @returns BuildPsbtOptions
22
86
  */
23
87
  static createBuildOptions(accountAddress: string, transaction: {
24
88
  amount: bigint;
25
89
  recipientAddress: string;
26
- }, publicKeyHex: string): BuildPsbtOptions;
90
+ }, publicKeyHex: string, feePriority?: 'high' | 'medium' | 'low'): BuildPsbtOptions;
27
91
  }
@@ -2,10 +2,10 @@
2
2
  import { __awaiter } from '../../_virtual/_tslib.js';
3
3
  import ecc from '@bitcoinerlab/secp256k1';
4
4
  import { ECPairFactory } from 'ecpair';
5
- import { Psbt, payments, address } from 'bitcoinjs-lib';
5
+ import { payments, address, Psbt } from 'bitcoinjs-lib';
6
6
  import { Logger } from '@dynamic-labs/logger';
7
7
  import { DynamicError } from '@dynamic-labs/utils';
8
- import { DUST_LIMIT, SATOSHIS_PER_BTC } from '../const.js';
8
+ import { SATOSHIS_PER_BTC, DUST_LIMIT, RBF_SEQUENCE } from '../const.js';
9
9
  import { getBitcoinNetwork } from '../utils/getBitcoinNetwork/getBitcoinNetwork.js';
10
10
 
11
11
  const logger = new Logger('PsbtBuilderService');
@@ -16,104 +16,234 @@ class PsbtBuilderService {
16
16
  constructor(mempoolApiService) {
17
17
  this.mempoolApiService = mempoolApiService;
18
18
  }
19
+ /**
20
+ * Filters out Taproot (P2TR) UTXOs to prevent accidental spending of Ordinals/Runes
21
+ * Since we only support Native SegWit (P2WPKH), we ensure the account address is not Taproot
22
+ * @param accountAddress - The account address to check
23
+ * @param utxos - Array of UTXOs to filter
24
+ * @returns Filtered array of UTXOs (only P2WPKH compatible)
25
+ */
26
+ filterTaprootUTXOs(accountAddress, utxos) {
27
+ // Safety check: Ensure account address is not Taproot (bc1p...)
28
+ if (accountAddress.toLowerCase().startsWith('bc1p') ||
29
+ accountAddress.toLowerCase().startsWith('tb1p')) {
30
+ logger.warn(`Account address ${accountAddress} appears to be Taproot. Only Native SegWit (P2WPKH) is supported.`);
31
+ throw new DynamicError('Taproot addresses are not supported. Only Native SegWit (P2WPKH) addresses are allowed.');
32
+ }
33
+ return utxos;
34
+ }
35
+ /**
36
+ * Selects UTXOs using Largest-First (Accumulator) strategy
37
+ * Sorts UTXOs by value (descending) and selects until we have enough to cover amount + fees
38
+ * @param utxos - Available UTXOs
39
+ * @param targetAmount - Target amount including fees and dust limit
40
+ * @returns Selected UTXOs
41
+ */
42
+ selectUTXOsLargestFirst(utxos, targetAmount) {
43
+ // Sort UTXOs by value (largest first)
44
+ const sortedUTXOs = [...utxos].sort((a, b) => b.value - a.value);
45
+ // Accumulate UTXOs until we have enough
46
+ const selected = [];
47
+ let total = 0;
48
+ for (const utxo of sortedUTXOs) {
49
+ selected.push(utxo);
50
+ total += utxo.value;
51
+ // Stop when we have enough to cover the target amount
52
+ if (total >= targetAmount) {
53
+ break;
54
+ }
55
+ }
56
+ return selected;
57
+ }
58
+ /**
59
+ * Calculates the total value of UTXOs
60
+ * @param utxos - Array of UTXOs
61
+ * @returns Total value in satoshis
62
+ */
63
+ calculateUTXOTotal(utxos) {
64
+ return utxos.reduce((total, utxo) => total + utxo.value, 0);
65
+ }
66
+ /**
67
+ * Validates and ensures sufficient funds for the transaction
68
+ * @param selectedUTXOs - Initially selected UTXOs
69
+ * @param allUTXOs - All available UTXOs
70
+ * @param selectedTotal - Total value of selected UTXOs
71
+ * @param amountInSatoshisNumber - Transaction amount
72
+ * @param feeEstimate - Estimated fee
73
+ * @returns Validated selected UTXOs
74
+ * @throws {DynamicError} If insufficient funds
75
+ */
76
+ validateAndSelectUTXOs(selectedUTXOs, allUTXOs, selectedTotal, amountInSatoshisNumber, feeEstimate) {
77
+ const requiredAmount = amountInSatoshisNumber + feeEstimate;
78
+ if (selectedTotal >= requiredAmount) {
79
+ return selectedUTXOs;
80
+ }
81
+ // Try with all UTXOs if selection wasn't enough
82
+ if (selectedUTXOs.length < allUTXOs.length) {
83
+ const allTotal = this.calculateUTXOTotal(allUTXOs);
84
+ if (allTotal < requiredAmount) {
85
+ throw new DynamicError(`Insufficient funds. Available: ${allTotal / SATOSHIS_PER_BTC} BTC (${allTotal} satoshis), Required: ${amountInSatoshisNumber / SATOSHIS_PER_BTC} BTC (${amountInSatoshisNumber} satoshis) + fees`);
86
+ }
87
+ return allUTXOs;
88
+ }
89
+ throw new DynamicError(`Insufficient funds. Available: ${selectedTotal / SATOSHIS_PER_BTC} BTC (${selectedTotal} satoshis), Required: ${amountInSatoshisNumber / SATOSHIS_PER_BTC} BTC (${amountInSatoshisNumber} satoshis) + fees`);
90
+ }
91
+ /**
92
+ * Calculates fee estimate and change amount, handling dust limit
93
+ * @param accountAddress - Account address for fee estimation
94
+ * @param selectedUTXOs - Selected UTXOs
95
+ * @param selectedTotalValue - Total value of selected UTXOs
96
+ * @param amountInSatoshis - Transaction amount
97
+ * @param feePriority - Fee priority level
98
+ * @returns Object with feeEstimate, changeAmount, changeAmountNumber, and hasChangeOutput
99
+ */
100
+ calculateFeeAndChange(accountAddress, selectedUTXOs, selectedTotalValue, amountInSatoshis, feePriority) {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ // Re-estimate fee with actual number of inputs
103
+ let feeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, selectedUTXOs.length, 1, // Start with 1 output (recipient only)
104
+ feePriority);
105
+ let maxToSpend = selectedTotalValue - feeEstimate;
106
+ let changeAmount = BigInt(maxToSpend) - amountInSatoshis;
107
+ // If change will be above dust limit, re-estimate fees for 2 outputs
108
+ const changeAmountNumber = Number(changeAmount);
109
+ if (changeAmount > 0 && changeAmountNumber >= DUST_LIMIT) {
110
+ feeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, selectedUTXOs.length, 2, // recipient + change output
111
+ feePriority);
112
+ maxToSpend = selectedTotalValue - feeEstimate;
113
+ changeAmount = BigInt(maxToSpend) - amountInSatoshis;
114
+ }
115
+ const finalChangeAmountNumber = Number(changeAmount);
116
+ const hasChangeOutput = changeAmount > 0 && finalChangeAmountNumber >= DUST_LIMIT;
117
+ // Final fee adjustment: if change < dust limit, add it to fee
118
+ if (changeAmount > 0 && finalChangeAmountNumber < DUST_LIMIT) {
119
+ feeEstimate += finalChangeAmountNumber;
120
+ }
121
+ return {
122
+ changeAmount,
123
+ changeAmountNumber: finalChangeAmountNumber,
124
+ feeEstimate,
125
+ hasChangeOutput,
126
+ };
127
+ });
128
+ }
129
+ /**
130
+ * Adds inputs to PSBT from selected UTXOs
131
+ * @param psbt - PSBT to add inputs to
132
+ * @param selectedUTXOs - Selected UTXOs
133
+ * @param publicKeyPair - ECPair public key pair
134
+ * @param network - Bitcoin network
135
+ */
136
+ addInputsToPsbt(psbt, selectedUTXOs, publicKeyPair, network) {
137
+ for (const utxo of selectedUTXOs) {
138
+ const outputScript = payments.p2wpkh({
139
+ network,
140
+ pubkey: publicKeyPair.publicKey,
141
+ }).output;
142
+ if (!outputScript) {
143
+ throw new DynamicError('Failed to create segwit output script');
144
+ }
145
+ // Convert txid from hex string to Buffer and reverse it (Bitcoin uses little-endian)
146
+ // The txid from the API is in big-endian format, but bitcoinjs-lib expects little-endian
147
+ const txidBuffer = Buffer.from(utxo.txid, 'hex').reverse();
148
+ psbt.addInput({
149
+ hash: txidBuffer,
150
+ index: utxo.vout,
151
+ sequence: RBF_SEQUENCE, // Enable RBF (Replace-By-Fee)
152
+ witnessUtxo: {
153
+ script: outputScript,
154
+ value: utxo.value,
155
+ },
156
+ });
157
+ }
158
+ }
159
+ /**
160
+ * Adds outputs to PSBT (recipient and optionally change)
161
+ * @param psbt - PSBT to add outputs to
162
+ * @param recipientAddress - Recipient address
163
+ * @param accountAddress - Account address for change
164
+ * @param amountInSatoshisNumber - Transaction amount
165
+ * @param changeAmountNumber - Change amount
166
+ * @param hasChangeOutput - Whether to include change output
167
+ * @param network - Bitcoin network
168
+ */
169
+ addOutputsToPsbt(psbt, recipientAddress, accountAddress, amountInSatoshisNumber, changeAmountNumber, hasChangeOutput, network) {
170
+ if (amountInSatoshisNumber < DUST_LIMIT) {
171
+ throw new DynamicError(`Amount is below dust limit of ${DUST_LIMIT} satoshis (${DUST_LIMIT / SATOSHIS_PER_BTC} BTC)`);
172
+ }
173
+ psbt.addOutput({
174
+ script: address.toOutputScript(recipientAddress, network),
175
+ value: amountInSatoshisNumber,
176
+ });
177
+ if (hasChangeOutput) {
178
+ psbt.addOutput({
179
+ script: address.toOutputScript(accountAddress, network),
180
+ value: changeAmountNumber,
181
+ });
182
+ }
183
+ }
19
184
  /**
20
185
  * Builds a PSBT for a Bitcoin transaction with real UTXOs
186
+ * Uses Largest-First UTXO selection strategy with accurate vSize fee estimation
21
187
  * @param options - Options for building the PSBT
22
188
  * @returns A PSBT in Base64 format
23
189
  * @throws {DynamicError} If insufficient funds, no UTXOs, or other errors
24
190
  */
25
191
  buildPsbt(options) {
26
192
  return __awaiter(this, void 0, void 0, function* () {
27
- const { accountAddress, recipientAddress, amountInSatoshis, publicKeyHex, network, } = options;
193
+ const { accountAddress, recipientAddress, amountInSatoshis, publicKeyHex, network, feePriority = 'medium', } = options;
194
+ logger.debug(`buildPsbt called with feePriority: ${feePriority}, amount: ${amountInSatoshis} satoshis`);
28
195
  if (amountInSatoshis <= BigInt(0)) {
29
196
  throw new DynamicError('Amount must be greater than 0');
30
197
  }
31
- // Get UTXOs for the account address
32
- const utxos = yield this.mempoolApiService.getUTXOs(accountAddress);
33
- if (utxos.length === 0) {
198
+ const allUTXOs = yield this.mempoolApiService.getUTXOs(accountAddress);
199
+ if (allUTXOs.length === 0) {
34
200
  throw new DynamicError('No UTXOs found for this address');
35
201
  }
36
- // Get public key for creating output scripts
202
+ const utxos = this.filterTaprootUTXOs(accountAddress, allUTXOs);
37
203
  const publicKeyBuffer = Buffer.from(publicKeyHex, 'hex');
38
- // Use ECPair to ensure the public key is properly formatted for bitcoinjs-lib
39
204
  const ECPair = ECPairFactory(ecc);
40
205
  const publicKeyPair = ECPair.fromPublicKey(publicKeyBuffer, {
41
206
  compressed: true,
42
207
  });
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);
208
+ const amountInSatoshisNumber = Number(amountInSatoshis);
209
+ // Initial fee estimate with 1 input and 1 output
210
+ const initialFeeEstimate = yield this.mempoolApiService.estimateTransactionFee(accountAddress, 1, 1, feePriority);
211
+ // Target amount: transaction amount + fee + dust limit (for change output safety)
212
+ const targetAmount = amountInSatoshisNumber + initialFeeEstimate + DUST_LIMIT;
213
+ // Select UTXOs using Largest-First strategy
214
+ let selectedUTXOs = this.selectUTXOsLargestFirst(utxos, targetAmount);
215
+ const selectedTotal = this.calculateUTXOTotal(selectedUTXOs);
216
+ // Validate and ensure sufficient funds
217
+ selectedUTXOs = this.validateAndSelectUTXOs(selectedUTXOs, utxos, selectedTotal, amountInSatoshisNumber, initialFeeEstimate);
218
+ const selectedTotalValue = this.calculateUTXOTotal(selectedUTXOs);
219
+ // Calculate fee and change with proper dust limit handling
220
+ const { feeEstimate, changeAmountNumber, hasChangeOutput } = yield this.calculateFeeAndChange(accountAddress, selectedUTXOs, selectedTotalValue, amountInSatoshis, feePriority);
221
+ // Final check: ensure we have enough after final fee calculation
222
+ const maxToSpend = selectedTotalValue - feeEstimate;
223
+ if (maxToSpend < amountInSatoshisNumber) {
58
224
  throw new DynamicError(`Insufficient funds. Available: ${maxToSpend / SATOSHIS_PER_BTC} BTC (${maxToSpend} satoshis), Required: ${amountInSatoshisNumber / SATOSHIS_PER_BTC} BTC (${amountInSatoshisNumber} satoshis)`);
59
225
  }
60
- // Create PSBT
226
+ // Create PSBT and add inputs/outputs
61
227
  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`);
228
+ this.addInputsToPsbt(psbt, selectedUTXOs, publicKeyPair, network);
229
+ this.addOutputsToPsbt(psbt, recipientAddress, accountAddress, amountInSatoshisNumber, changeAmountNumber, hasChangeOutput, network);
230
+ logger.debug(`buildPsbt created PSBT for recipientAddress: ${recipientAddress}, amount: ${amountInSatoshisNumber} satoshis (${amountInSatoshisNumber / SATOSHIS_PER_BTC} BTC), change: ${changeAmountNumber} satoshis, estimated fee: ${feeEstimate} satoshis, feePriority: ${feePriority}, inputs: ${selectedUTXOs.length}`);
103
231
  return psbt.toBase64();
104
232
  });
105
233
  }
106
234
  /**
107
235
  * Helper method to create BuildPsbtOptions from transaction and account details
108
236
  * @param accountAddress - The account address
109
- * @param transaction - The Bitcoin transaction
237
+ * @param transaction - The Bitcoin transaction (must have amount and recipientAddress)
110
238
  * @param publicKeyHex - The public key in hex format
239
+ * @param feePriority - Optional fee priority (defaults to 'medium')
111
240
  * @returns BuildPsbtOptions
112
241
  */
113
- static createBuildOptions(accountAddress, transaction, publicKeyHex) {
242
+ static createBuildOptions(accountAddress, transaction, publicKeyHex, feePriority = 'medium') {
114
243
  return {
115
244
  accountAddress,
116
245
  amountInSatoshis: transaction.amount,
246
+ feePriority,
117
247
  network: getBitcoinNetwork(accountAddress),
118
248
  publicKeyHex,
119
249
  recipientAddress: transaction.recipientAddress,
package/src/types.d.ts CHANGED
@@ -32,6 +32,7 @@ export type BitcoinConnectedAccount = {
32
32
  export type BitcoinTransaction = {
33
33
  amount: bigint;
34
34
  recipientAddress: string;
35
+ feePriority?: FeePriority;
35
36
  };
36
37
  export type SignPsbtOptions = {
37
38
  autoFinalized: boolean;
@@ -52,6 +53,21 @@ export type BitcoinSignPsbtRequestSignature = {
52
53
  signingIndexes: number[] | undefined;
53
54
  disableAddressValidation?: boolean;
54
55
  };
56
+ /**
57
+ * PSBT signing request specifically for embedded wallets (DynamicWaasBitcoinConnector)
58
+ *
59
+ * Embedded wallets:
60
+ * - Only support PSBT format (Base64)
61
+ * - Automatically sign all inputs that belong to the wallet address
62
+ * - Always use SIGHASH_ALL (0x01)
63
+ */
64
+ export type EmbeddedWalletSignPsbtRequest = {
65
+ /**
66
+ * The unsigned PSBT in Base64 format.
67
+ * Embedded wallets only support PSBT format, not raw transaction hex.
68
+ */
69
+ unsignedPsbtBase64: string;
70
+ };
55
71
  export type SatsConnectSignTransactionInput = {
56
72
  message?: string;
57
73
  psbtBase64: string;
@@ -173,11 +189,13 @@ export interface ParsedTransaction {
173
189
  inputs: ParsedTransactionInput[];
174
190
  outputs: ParsedTransactionOutput[];
175
191
  }
192
+ export type FeePriority = 'high' | 'medium' | 'low';
176
193
  export interface BuildPsbtOptions {
177
194
  accountAddress: string;
178
195
  recipientAddress: string;
179
196
  amountInSatoshis: bigint;
180
197
  publicKeyHex: string;
181
198
  network: Network;
199
+ feePriority?: FeePriority;
182
200
  }
183
201
  export {};