show Bitcoin address derivation path (#3177)
* show Bitcoin address derivation path * Revert "show Bitcoin address derivation path" * Update cw_bitcoin/lib/bitcoin_address_record.dart [skip ci] * Update cw_bitcoin/lib/bitcoin_address_record.dart [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Serhii committed
Apr 13, 2026 at 23:05 UTC
547fae891a7646821886ae9cef0ff5949c28762c
10 files changed
+148
-6
assets/images/info_icon.svg
new
+21
@@ -0,0 +1,21 @@
1
+<?xml version="1.0" encoding="iso-8859-1"?>
2
+<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
5
+ width="800px" height="800px" viewBox="0 0 490.318 490.318"
6
+ xml:space="preserve">
7
+<g>
8
+ <g>
9
+ <g>
10
+ <path d="M245.148,0C109.967,0,0.009,109.98,0.009,245.162c0,135.182,109.958,245.156,245.139,245.156
11
+ c135.186,0,245.162-109.978,245.162-245.156C490.31,109.98,380.333,0,245.148,0z M245.148,438.415
12
+ c-106.555,0-193.234-86.698-193.234-193.253c0-106.555,86.68-193.258,193.234-193.258c106.559,0,193.258,86.703,193.258,193.258
13
+ C438.406,351.717,351.706,438.415,245.148,438.415z"/>
14
+ <path d="M270.036,221.352h-49.771c-8.351,0-15.131,6.78-15.131,15.118v147.566c0,8.352,6.78,15.119,15.131,15.119h49.771
15
+ c8.351,0,15.131-6.77,15.131-15.119V236.471C285.167,228.133,278.387,221.352,270.036,221.352z"/>
16
+ <path d="M245.148,91.168c-24.48,0-44.336,19.855-44.336,44.336c0,24.484,19.855,44.34,44.336,44.34
17
+ c24.485,0,44.342-19.855,44.342-44.34C289.489,111.023,269.634,91.168,245.148,91.168z"/>
18
+ </g>
19
+ </g>
20
+</g>
21
+</svg>
\ No newline at end of file
cw_bitcoin/lib/bitcoin_address_record.dart
+45
@@ -54,6 +54,8 @@ abstract class BaseBitcoinAddressRecord {
54
55
BitcoinAddressType type;
56
57
+ String get derivationPath;
58
+
59
String toJSON();
60
}
61
@@ -118,6 +120,31 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
120
121
String? scriptHash;
122
123
+ static int _purposeForType(BitcoinAddressType type) {
124
+ if (type == P2pkhAddressType.p2pkh) return 44;
125
+ if (type == P2shAddressType.p2wpkhInP2sh) return 49;
126
+ if (type == SegwitAddresType.p2wsh) return 48;
127
+ if (type == SegwitAddresType.p2tr) return 86;
128
+ return 84;
129
+ }
130
+
131
+ int _coinTypeForNetwork() {
132
+ if (!(network?.isMainnet ?? true)) return 1;
133
+
134
+ switch (network) {
135
+ case BitcoinNetwork.mainnet:
136
+ return 0;
137
+ case LitecoinNetwork.mainnet:
138
+ return 2;
139
+ case BitcoinCashNetwork.mainnet:
140
+ return 145;
141
+ case DogecoinNetwork.mainnet:
142
+ return 3;
143
+ default:
144
+ return 0;
145
+ }
146
+ }
147
+
148
String getScriptHash(BasedUtxoNetwork network) {
149
if (scriptHash != null) return scriptHash!;
150
try {
@@ -127,6 +154,21 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
154
}
155
return scriptHash!;
156
}
157
+ @override
158
+ String get derivationPath {
159
+ if (type == SegwitAddresType.mweb) {
160
+ return "m/1000'/$index";
161
+ }
162
+
163
+ final coinType = _coinTypeForNetwork();
164
+ final purpose = _purposeForType(type);
165
+ final accountPath = isLegacyDerivation
166
+ ? electrum_path
167
+ : "m/$purpose'/$coinType'/0'";
168
+
169
+ final chain = isHidden ? 1 : 0;
170
+ return "$accountPath/$chain/$index";
171
+ }
172
173
@override
174
String toJSON() => json.encode({
@@ -186,6 +228,9 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
228
final String? silentPaymentTweak;
229
final String spendDerivationPath;
230
231
+ @override
232
+ String get derivationPath => spendDerivationPath;
233
+
234
@override
235
String toJSON() => json.encode({
236
'address': address,
lib/bitcoin/cw_bitcoin.dart
+6
-3
@@ -185,7 +185,8 @@ class CWBitcoin extends Bitcoin {
185
txCount: addr.txCount,
186
balance: addr.balance,
187
isChange: addr.isHidden,
188
- isLegacyDerivation: addr.isLegacyDerivation))
188
+ isLegacyDerivation: addr.isLegacyDerivation,
189
+ derivationPath: addr.derivationPath))
190
.toList();
191
}
192
@@ -574,7 +575,8 @@ class CWBitcoin extends Bitcoin {
575
address: addr.address,
576
txCount: addr.txCount,
577
balance: addr.balance,
577
- isChange: addr.isHidden))
578
+ isChange: addr.isHidden,
579
+ derivationPath: addr.derivationPath))
580
.toList();
581
}
582
@@ -589,7 +591,8 @@ class CWBitcoin extends Bitcoin {
591
address: addr.address,
592
txCount: addr.txCount,
593
balance: addr.balance,
592
- isChange: addr.isHidden))
594
+ isChange: addr.isHidden,
595
+ derivationPath: addr.derivationPath))
596
.toList();
597
}
598
lib/di.dart
+6
@@ -310,6 +310,7 @@ import 'buy/kryptonim/kryptonim.dart';
310
import 'buy/meld/meld_buy_provider.dart';
311
import 'dogecoin/dogecoin.dart';
312
import 'new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
313
+import 'new-ui/widgets/addresses_page/address_info.dart';
314
import 'src/screens/buy/buy_sell_page.dart';
315
316
final getIt = GetIt.instance;
@@ -880,6 +881,11 @@ Future<void> setup({
881
walletAddressEditOrCreateViewModel:
882
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
883
884
+ getIt.registerFactoryParam<AddressInfoPopup, dynamic, void>((dynamic item, _) =>
885
+ AddressInfoPopup(
886
+ walletAddressEditOrCreateViewModel:
887
+ getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
888
+
889
getIt.registerFactoryParam<ReceiveLabelModal, dynamic, void>((dynamic item, _) =>
890
ReceiveLabelModal(
891
walletAddressEditOrCreateViewModel:
lib/new-ui/pages/addresses_page.dart
+10
@@ -4,6 +4,7 @@ import 'package:cake_wallet/di.dart';
4
import 'package:cake_wallet/generated/i18n.dart';
5
import 'package:cake_wallet/monero/monero.dart';
6
import 'package:cake_wallet/new-ui/long_press_popup.dart';
7
+import 'package:cake_wallet/new-ui/widgets/addresses_page/address_info.dart';
8
import 'package:cake_wallet/new-ui/widgets/addresses_page/address_label_input.dart';
9
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart';
10
import 'package:cake_wallet/new-ui/widgets/long_press_menu.dart';
@@ -355,6 +356,15 @@ class AddressRow extends StatelessWidget {
356
onAddressHidden();
357
},
358
color: Theme.of(context).colorScheme.error),
359
+ LongPressMenuItem(
360
+ label: 'Info',
361
+ iconPath: "assets/images/info_icon.svg",
362
+ onSelected: () async {
363
+ Navigator.of(context, rootNavigator: true).pop();
364
+ await showPopUp(
365
+ context: context,
366
+ builder: (context) => getIt.get<AddressInfoPopup>(param1: item));
367
+ }),
368
],
369
),
370
child: AnimatedContainer(
lib/new-ui/widgets/addresses_page/address_info.dart
new
+46
@@ -0,0 +1,46 @@
1
+import 'dart:ui';
2
+
3
+import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
4
+import 'package:flutter/material.dart';
5
+
6
+class AddressInfoPopup extends StatelessWidget {
7
+ const AddressInfoPopup({
8
+ super.key,
9
+ required this.walletAddressEditOrCreateViewModel,
10
+ });
11
+
12
+ final WalletAddressEditOrCreateViewModel walletAddressEditOrCreateViewModel;
13
+
14
+ @override
15
+ Widget build(BuildContext context) {
16
+ return BackdropFilter(
17
+ filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
18
+ child: Column(
19
+ mainAxisSize: MainAxisSize.max,
20
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
21
+ children: [
22
+ Container(
23
+ decoration: BoxDecoration(
24
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
25
+ borderRadius: BorderRadiusGeometry.lerp(
26
+ BorderRadius.circular(12),
27
+ BorderRadius.circular(24),
28
+ 0.5,
29
+ ),
30
+ ),
31
+ child: Padding(
32
+ padding: const EdgeInsets.all(8.0),
33
+ child: Column(
34
+ children: [
35
+ Text('Index: ${walletAddressEditOrCreateViewModel.index}'),
36
+ SizedBox(height: 16),
37
+ Text(
38
+ 'Derivation Path: ${walletAddressEditOrCreateViewModel.derivationPath}'),
39
+ ],
40
+ ),
41
+ ),
42
+ ),
43
+ ]),
44
+ );
45
+ }
46
+}
lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart
+3
@@ -51,6 +51,9 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
51
_wallet.type == WalletType.litecoin ||
52
_wallet.type == WalletType.dogecoin;
53
54
+ String get derivationPath => _item?.derivationPath ?? '';
55
+ String get index => _item?.id.toString() ?? '';
56
+
57
Future<void> save() async {
58
try {
59
state = AddressIsSaving();
lib/view_model/wallet_address_list/wallet_address_list_item.dart
+2
@@ -14,6 +14,7 @@ class WalletAddressListItem extends ListItem {
14
this.isHidden = false,
15
this.isManual = false,
16
this.isLegacyDerivation = false,
17
+ this.derivationPath
18
}) : super();
19
20
final int? id;
@@ -26,6 +27,7 @@ class WalletAddressListItem extends ListItem {
27
bool isHidden;
28
bool isManual;
29
bool isLegacyDerivation;
30
+ String? derivationPath;
31
final bool? isOneTimeReceiveAddress;
32
33
@override
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+4
-1
@@ -258,6 +258,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
258
balance: _appStore.amountParsingProxy
259
.getDisplayCryptoString(address.balance, walletTypeToCryptoCurrency(type)),
260
isChange: address.isChange,
261
+ derivationPath: address.derivationPath,
262
);
263
});
264
addressList.addAll(addressItems);
@@ -275,6 +276,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
276
.getDisplayCryptoString(address.balance, walletTypeToCryptoCurrency(type)),
277
isChange: address.isChange,
278
isOneTimeReceiveAddress: true,
279
+ derivationPath: address.derivationPath,
280
);
281
});
282
addressList.addAll(receivedAddressItems);
@@ -291,7 +293,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
293
balance: _appStore.amountParsingProxy
294
.getDisplayCryptoString(subaddress.balance, walletTypeToCryptoCurrency(type)),
295
isChange: subaddress.isChange,
294
- isLegacyDerivation: subaddress.isLegacyDerivation);
296
+ isLegacyDerivation: subaddress.isLegacyDerivation,
297
+ derivationPath: subaddress.derivationPath);
298
});
299
300
// don't show all 1000+ mweb addresses:
tool/configure.dart
+5
-2
@@ -161,7 +161,7 @@ import "package:breez_sdk_spark_flutter/src/rust/errors.dart";
161
const bitcoinCwPart = "part 'cw_bitcoin.dart';";
162
const bitcoinContent = """
163
164
- class ElectrumSubAddress {
164
+class ElectrumSubAddress {
165
ElectrumSubAddress({
166
required this.id,
167
required this.name,
@@ -169,13 +169,16 @@ import "package:breez_sdk_spark_flutter/src/rust/errors.dart";
169
required this.txCount,
170
required this.balance,
171
required this.isChange,
172
- this.isLegacyDerivation = false});
172
+ this.derivationPath,
173
+ this.isLegacyDerivation = false
174
+ });
175
final int id;
176
final String name;
177
final String address;
178
final int txCount;
179
final int balance;
180
final bool isChange;
181
+ final String? derivationPath;
182
final bool isLegacyDerivation;
183
}
184