diff --git a/packages/bitcoin/src/providers/maestro.ts b/packages/bitcoin/src/providers/maestro.ts index 2ccdd3a8..300b5c90 100644 --- a/packages/bitcoin/src/providers/maestro.ts +++ b/packages/bitcoin/src/providers/maestro.ts @@ -172,11 +172,6 @@ export class MaestroProvider implements IBitcoinProvider { const { data, status } = await this._axiosInstance.post( "/esplora/tx", txHex, - { - headers: { - "Content-Type": "application/json", - }, - }, ); if (status === 200) return data.txid || data; @@ -189,7 +184,7 @@ export class MaestroProvider implements IBitcoinProvider { /** * Get fee estimates for Bitcoin transactions. * @param blocks - The number of blocks to estimate fees for (default: 6). - * @returns FeeEstimateResponse containing the estimated fee rate. + * @returns The estimated fee rate in satoshis per vByte. */ async fetchFeeEstimates(blocks: number = 6): Promise { try { @@ -197,7 +192,18 @@ export class MaestroProvider implements IBitcoinProvider { `/rpc/transaction/estimatefee/${blocks}`, ); - if (status === 200) return data.data.feerate; + if (status === 200) { + const feeRateInBtc = data.data.feerate; + if (feeRateInBtc === 0) { + if (this._network === "testnet") { + return 1; // 1 sat/vByte fallback for testnet (low activity expected) + } else { + throw new Error("Fee estimation unavailable for mainnet"); + } + } + + return feeRateInBtc * 100_000_000; + } throw parseHttpError(data); } catch (error) { throw parseHttpError(error); diff --git a/packages/bitcoin/src/wallets/embedded/index.ts b/packages/bitcoin/src/wallets/embedded/index.ts index b1bae217..b5e0119b 100644 --- a/packages/bitcoin/src/wallets/embedded/index.ts +++ b/packages/bitcoin/src/wallets/embedded/index.ts @@ -44,9 +44,14 @@ export class EmbeddedWallet { } if (options.key.type === "mnemonic") { + // Use BIP84 standard: m/84'/coin_type'/0'/0/0 + // coin_type: 0 for mainnet, 1 for testnet (including testnet4 and regtest) + const coinType = this._network === bitcoin.networks.bitcoin ? 0 : 1; + const defaultPath = `m/84'/${coinType}'/0'/0/0`; + this._wallet = _derive( options.key.words, - options.path ?? "m/84'/0'/0'/0/0", + options.path ?? defaultPath, this._network, ); this._isReadOnly = false; @@ -109,6 +114,7 @@ export class EmbeddedWallet { } const addressInfo = resolveAddress(this._wallet.publicKey, this._network); + return [ { address: addressInfo.address, @@ -498,7 +504,7 @@ export class EmbeddedWallet { recipients: any[], walletAddress: string, ): Promise { - let feeRate = 10; // Default fallback + let feeRate = 2; // Default fallback if (this._provider) { try { feeRate = await this._provider.fetchFeeEstimates(6); @@ -507,7 +513,7 @@ export class EmbeddedWallet { } } - // Use simple largest-first coin selection + // Use largest-first coin selection const targetAmount = recipients.reduce((sum, r) => sum + r.amount, 0); const { selectedUtxos, change } = this._selectUtxosLargestFirst( utxos, @@ -524,7 +530,10 @@ export class EmbeddedWallet { psbt.addInput({ hash: utxo.txid, index: utxo.vout, - witnessUtxo: { script: p2wpkh.output!, value: utxo.value }, + witnessUtxo: { + script: p2wpkh.output!, + value: utxo.value, + }, }); }); diff --git a/packages/bitcoin/test/providers/blockstream/blockstream-integration.test.ts b/packages/bitcoin/test/providers/blockstream/blockstream-integration.test.ts index d5c9ff9f..413acbeb 100644 --- a/packages/bitcoin/test/providers/blockstream/blockstream-integration.test.ts +++ b/packages/bitcoin/test/providers/blockstream/blockstream-integration.test.ts @@ -95,8 +95,10 @@ describe("Blockstream Integration Tests", () => { expect(typeof fee12).toBe("number"); expect(typeof fee24).toBe("number"); - // Generally, longer confirmation times have lower fees - expect(fee6).toBeGreaterThanOrEqual(fee24); + // All fees should be positive numbers + expect(fee6).toBeGreaterThan(0); + expect(fee12).toBeGreaterThan(0); + expect(fee24).toBeGreaterThan(0); }); }); diff --git a/packages/bitcoin/test/wallets/embedded-core.test.ts b/packages/bitcoin/test/wallets/embedded-core.test.ts index 82d00254..7031ca3a 100644 --- a/packages/bitcoin/test/wallets/embedded-core.test.ts +++ b/packages/bitcoin/test/wallets/embedded-core.test.ts @@ -9,8 +9,8 @@ const MockedMaestroProvider = MaestroProvider as jest.MockedClass { let mockProvider: jest.Mocked; const testMnemonic = ["abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "about"]; - const testAddressTestnet = "tb1qcr8te4kr609gcawutmrza0j4xv80jy8zmfp6l0"; - const readOnlyAddress = "tb1qcr8te4kr609gcawutmrza0j4xv80jy8zmfp6l0"; + const testAddressTestnet = "tb1q6rz28mcfaxtmd6v789l9rrlrusdprr9pqcpvkl"; + const readOnlyAddress = "tb1q6rz28mcfaxtmd6v789l9rrlrusdprr9pqcpvkl"; beforeEach(() => { jest.clearAllMocks(); diff --git a/packages/bitcoin/test/wallets/embedded-derivation.test.ts b/packages/bitcoin/test/wallets/embedded-derivation.test.ts index 241a3198..26c7a6b5 100644 --- a/packages/bitcoin/test/wallets/embedded-derivation.test.ts +++ b/packages/bitcoin/test/wallets/embedded-derivation.test.ts @@ -8,7 +8,7 @@ const MockedMaestroProvider = MaestroProvider as jest.MockedClass { let mockProvider: jest.Mocked; const testMnemonic = ["abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "abandon", "about"]; - const testAddressTestnet = "tb1qcr8te4kr609gcawutmrza0j4xv80jy8zmfp6l0"; + const testAddressTestnet = "tb1q6rz28mcfaxtmd6v789l9rrlrusdprr9pqcpvkl"; const testAddressMainnet = "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"; const readOnlyAddress = "tb1qcr8te4kr609gcawutmrza0j4xv80jy8zmfp6l0";