WalletAddressRepository.generateNewReceiveAddress(...) is currently used as the source of change addresses across the codebase. For LWK wallets it derives from the external receive chain at lastUnusedIndex + 1:
// lib/core/wallet/data/repositories/wallet_address_repository.dart:91-122
final lastUnusedAddressInfo = await _lwkWallet.getLastUnusedAddress(...);
final addressInfo = await _lwkWallet.getAddressByIndex(
lastUnusedAddressInfo.index + 1,
wallet: walletModel,
);
So change outputs land at m/84'/<coin_type>'/<account>'/0/<i+1> instead of the BIP-44 standard m/.../1/<j>.
Why this matters
- Privacy. Change and inbound payments cluster on a single derivation lineage. Chain-analysis tooling that assumes BIP-44 chain separation can correlate self-spends with receives more easily than it should.
- Gap-limit pressure. External-chain indices are consumed at roughly double the natural rate. Default gap-limit restores (20) can miss balance after a relatively small number of back-to-back self-spends.
- BIP-44 non-conformance. A restored seed in any other compliant wallet will not classify these outputs as change — they'll appear as additional external receives.
Not a fund-safety issue; funds at m/.../0/N+1 are always recoverable. The cost is privacy and interoperability.
Why the workaround exists
The Dart Wallet API exposed by lwk-dart currently has no change-chain primitive — only external-chain address(index:) and addressLastUnused() are surfaced. Tracking issue: SatoshiPortal/lwk-dart#70.
Proposed change
Blocked on lwk-dart exposing changeAddressLastUnused() / changeAddress(index:). Once those land:
- Add
getChangeAddressByIndex(...) and getLastUnusedChangeAddress(...) to LwkWalletDatasource, calling the new lwk-dart methods.
- Add
generateNewChangeAddress({required String walletId}) and getLastUnusedChangeAddress({required String walletId}) to WalletAddressRepository, mirroring the existing receive-address methods but routing to the change chain.
- Migrate any call site that currently uses
generateNewReceiveAddress as a change address to the new change-specific methods.
- For BDK wallets the equivalent primitives (
peek_change_address / next_change_address) are already in bdk_flutter; thread those through the same repository methods so behavior is uniform across BDK and LWK wallets.
Test plan
- Generated change addresses derive at
m/.../1/<i> (verified via descriptor inspection).
- Existing transaction-building flows still pass after migration.
- Restore-from-seed scenario: wallet restored in an external BIP-44 tool sees external receives on chain
0 and change on chain 1, with tx history correctly classified.
- Existing integration tests covering wallet-creates-transaction flows continue to pass.
WalletAddressRepository.generateNewReceiveAddress(...)is currently used as the source of change addresses across the codebase. For LWK wallets it derives from the external receive chain atlastUnusedIndex + 1:So change outputs land at
m/84'/<coin_type>'/<account>'/0/<i+1>instead of the BIP-44 standardm/.../1/<j>.Why this matters
Not a fund-safety issue; funds at
m/.../0/N+1are always recoverable. The cost is privacy and interoperability.Why the workaround exists
The Dart
WalletAPI exposed by lwk-dart currently has no change-chain primitive — only external-chainaddress(index:)andaddressLastUnused()are surfaced. Tracking issue: SatoshiPortal/lwk-dart#70.Proposed change
Blocked on lwk-dart exposing
changeAddressLastUnused()/changeAddress(index:). Once those land:getChangeAddressByIndex(...)andgetLastUnusedChangeAddress(...)toLwkWalletDatasource, calling the new lwk-dart methods.generateNewChangeAddress({required String walletId})andgetLastUnusedChangeAddress({required String walletId})toWalletAddressRepository, mirroring the existing receive-address methods but routing to the change chain.generateNewReceiveAddressas a change address to the new change-specific methods.peek_change_address/next_change_address) are already in bdk_flutter; thread those through the same repository methods so behavior is uniform across BDK and LWK wallets.Test plan
m/.../1/<i>(verified via descriptor inspection).0and change on chain1, with tx history correctly classified.