Skip to content

Commit 16736f0

Browse files
authored
test(wallet): cover address lifecycle and fee introspection (#47)
1 parent 6d90d45 commit 16736f0

3 files changed

Lines changed: 93 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
- `TxBuilder::set_exact_sequence` for fine-grained nSequence control
3636
- `TxIn::sequence` getter for reading the nSequence value of transaction inputs
3737

38+
### Changed
39+
40+
- Expand Node and regtest integration coverage for wallet address lifecycle, output introspection, and fee calculation APIs ([#22](https://github.com/bitcoindevkit/bdk-wasm/issues/22))
41+
3842
## [0.3.0] - 2026-03-16
3943

4044
### Added

tests/node/integration/esplora.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,26 @@ describe(`Esplora client (${network})`, () => {
139139
expect(wallet.latest_checkpoint.height).toBeGreaterThan(0);
140140
}, 30000);
141141

142+
it("lists scanned outputs and resolves known UTXOs", () => {
143+
const utxos = wallet.list_unspent();
144+
145+
expect(utxos.length).toBeGreaterThan(0);
146+
147+
const firstUtxo = utxos[0];
148+
const resolved = wallet.get_utxo(firstUtxo.outpoint);
149+
150+
expect(resolved).toBeDefined();
151+
expect(resolved!.outpoint.toString()).toBe(firstUtxo.outpoint.toString());
152+
expect(resolved!.derivation_index).toBe(firstUtxo.derivation_index);
153+
expect(resolved!.txout.value.to_sat()).toBe(firstUtxo.txout.value.to_sat());
154+
expect(wallet.is_mine(firstUtxo.txout.script_pubkey)).toBe(true);
155+
expect(
156+
wallet
157+
.list_output()
158+
.some((output) => output.outpoint.toString() === firstUtxo.outpoint.toString())
159+
).toBe(true);
160+
});
161+
142162
it("fetches fee estimates", async () => {
143163
const confirmationTarget = 2;
144164
const feeEstimates = await esploraClient.get_fee_estimates();
@@ -232,6 +252,35 @@ describe(`Esplora client (${network})`, () => {
232252
);
233253
});
234254

255+
it("calculates fee and fee rate for signed wallet transactions", () => {
256+
const recipientAddress = wallet.peek_address("external", 15);
257+
const sendAmount = Amount.from_sat(BigInt(800));
258+
259+
const psbt = wallet
260+
.build_tx()
261+
.fee_rate(new FeeRate(BigInt(1)))
262+
.add_recipient(
263+
new Recipient(recipientAddress.address.script_pubkey, sendAmount)
264+
)
265+
.finish();
266+
267+
const expectedFee = psbt.fee().to_sat();
268+
269+
expect(wallet.sign(psbt, new SignOptions())).toBe(true);
270+
271+
const expectedFeeRate = psbt.fee_rate();
272+
expect(expectedFeeRate).toBeDefined();
273+
274+
const tx = psbt.extract_tx();
275+
const calculatedFee = wallet.calculate_fee(tx);
276+
const calculatedFeeRate = wallet.calculate_fee_rate(tx);
277+
278+
expect(calculatedFee.to_sat()).toBe(expectedFee);
279+
expect(calculatedFeeRate.to_sat_per_vb_ceil()).toBe(
280+
expectedFeeRate!.to_sat_per_vb_ceil()
281+
);
282+
});
283+
235284
describe("TxBuilder advanced options (funded wallet)", () => {
236285
// Note: FeeRate is consumed by wasm-bindgen when passed to a builder method,
237286
// so we create fresh instances for each test instead of using the shared feeRate.

tests/node/integration/wallet.test.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,50 @@ describe("Wallet", () => {
7373

7474
it("marks and unmarks addresses as used", () => {
7575
const freshWallet = Wallet.create(network, externalDesc, internalDesc);
76+
freshWallet.reveal_addresses_to("external", 2);
77+
78+
expect(
79+
freshWallet.list_unused_addresses("external").map((address) => address.index)
80+
).toEqual([0, 1, 2]);
7681

7782
// mark_used returns whether the index was present in unused set
78-
const marked = freshWallet.mark_used("external", 0);
79-
// The first address should have been in the unused set
80-
expect(typeof marked).toBe("boolean");
83+
const marked = freshWallet.mark_used("external", 1);
84+
expect(marked).toBe(true);
85+
expect(
86+
freshWallet.list_unused_addresses("external").map((address) => address.index)
87+
).toEqual([0, 2]);
8188

8289
// unmark_used returns whether the index was inserted back
83-
const unmarked = freshWallet.unmark_used("external", 0);
84-
expect(typeof unmarked).toBe("boolean");
90+
const unmarked = freshWallet.unmark_used("external", 1);
91+
expect(unmarked).toBe(true);
92+
expect(
93+
freshWallet.list_unused_addresses("external").map((address) => address.index)
94+
).toEqual([0, 1, 2]);
95+
});
96+
97+
it("reveals address ranges and advances the next derivation index", () => {
98+
const freshWallet = Wallet.create(network, externalDesc, internalDesc);
99+
100+
const revealed = freshWallet.reveal_addresses_to("external", 2);
101+
102+
expect(revealed.map((address) => address.index)).toEqual([0, 1, 2]);
103+
expect(revealed.map((address) => address.keychain)).toEqual([
104+
"external",
105+
"external",
106+
"external",
107+
]);
108+
expect(freshWallet.next_derivation_index("external")).toBe(3);
109+
expect(
110+
freshWallet.list_unused_addresses("external").map((address) => address.index)
111+
).toEqual([0, 1, 2]);
112+
});
113+
114+
it("detects wallet-owned scripts", () => {
115+
const freshWallet = Wallet.create(network, externalDesc, internalDesc);
116+
const ownAddress = freshWallet.reveal_next_address("external");
117+
118+
expect(freshWallet.is_mine(ownAddress.address.script_pubkey)).toBe(true);
119+
expect(freshWallet.is_mine(recipientAddress.script_pubkey)).toBe(false);
85120
});
86121

87122
describe("TxBuilder options", () => {

0 commit comments

Comments
 (0)