| 1 | import 'dart:convert'; |
| 2 | import 'package:cw_core/payment_uris.dart'; |
| 3 | import 'package:cw_core/receive_page_option.dart'; |
| 4 | import 'package:cw_core/utils/print_verbose.dart'; |
| 5 | import 'package:mobx/mobx.dart'; |
| 6 | |
| 7 | import 'package:cw_core/wallet_addresses.dart'; |
| 8 | import 'package:cw_core/wallet_info.dart'; |
| 9 | import 'package:cw_decred/api/libdcrwallet.dart'; |
| 10 | |
| 11 | part 'wallet_addresses.g.dart'; |
| 12 | |
| 13 | class DecredWalletAddresses = DecredWalletAddressesBase with _$DecredWalletAddresses; |
| 14 | |
| 15 | abstract class DecredWalletAddressesBase extends WalletAddresses with Store { |
| 16 | DecredWalletAddressesBase(super.walletInfo, this._libwallet, super.isTestnet); |
| 17 | |
| 18 | final Libwallet _libwallet; |
| 19 | String _currentAddr = ''; |
| 20 | |
| 21 | @observable |
| 22 | bool isEnabledAutoGenerateSubaddress = true; |
| 23 | |
| 24 | @observable |
| 25 | String selectedAddr = ''; |
| 26 | |
| 27 | @override |
| 28 | @computed |
| 29 | String get address => selectedAddr; |
| 30 | |
| 31 | @override |
| 32 | set address(value) => selectedAddr = value; |
| 33 | |
| 34 | @override |
| 35 | Future<void> init() async { |
| 36 | addressesMap = await walletInfo.getAddresses(); |
| 37 | addressInfos = await walletInfo.getAddressInfos(); |
| 38 | usedAddresses = await walletInfo.getUsedAddresses(); |
| 39 | manualAddresses = await walletInfo.getManualAddresses(); |
| 40 | hiddenAddresses = await walletInfo.getHiddenAddresses(); |
| 41 | await updateAddressesInBox(); |
| 42 | } |
| 43 | |
| 44 | @override |
| 45 | Future<void> updateAddressesInBox() async { |
| 46 | final addrs = await _libAddresses(); |
| 47 | final allAddrs = List.from(addrs.usedAddrs)..addAll(addrs.unusedAddrs); |
| 48 | |
| 49 | // Add all addresses. |
| 50 | allAddrs.forEach((addr) { |
| 51 | if (addressesMap.containsKey(addr)) return; |
| 52 | |
| 53 | addressesMap[addr] = ""; |
| 54 | addressInfos[0] ??= []; |
| 55 | addressInfos[0]?.add( |
| 56 | WalletInfoAddressInfo( |
| 57 | walletInfoId: walletInfo.internalId, |
| 58 | mapKey: 0, |
| 59 | address: addr, |
| 60 | label: "", |
| 61 | accountIndex: 0, |
| 62 | ), |
| 63 | ); |
| 64 | }); |
| 65 | |
| 66 | // Add used addresses. |
| 67 | addrs.usedAddrs.forEach((addr) { |
| 68 | if (!usedAddresses.contains(addr)) usedAddresses.add(addr); |
| 69 | }); |
| 70 | |
| 71 | if (addrs.unusedAddrs.length > 0 && addrs.unusedAddrs[0] != _currentAddr) { |
| 72 | _currentAddr = addrs.unusedAddrs[0]; |
| 73 | selectedAddr = _currentAddr; |
| 74 | } |
| 75 | |
| 76 | await saveAddressesInBox(); |
| 77 | } |
| 78 | |
| 79 | List<WalletInfoAddressInfo> getAddressInfos() { |
| 80 | if (addressInfos.containsKey(0)) return addressInfos[0]!; |
| 81 | |
| 82 | return <WalletInfoAddressInfo>[]; |
| 83 | } |
| 84 | |
| 85 | Future<void> updateAddress(String address, String label) async { |
| 86 | if (!addressInfos.containsKey(0)) return; |
| 87 | |
| 88 | addressInfos[0]!.forEach((info) { |
| 89 | if (info.address == address) info.label = label; |
| 90 | }); |
| 91 | await saveAddressesInBox(); |
| 92 | } |
| 93 | |
| 94 | Future<_LibAddresses> _libAddresses() async { |
| 95 | final nUsed = "10"; |
| 96 | var nUnused = "1"; |
| 97 | if (this.isEnabledAutoGenerateSubaddress) nUnused = "3"; |
| 98 | |
| 99 | try { |
| 100 | final res = await _libwallet.addresses(walletInfo.name, nUsed, nUnused); |
| 101 | final decoded = json.decode(res); |
| 102 | final usedAddrs = List<String>.from(decoded["used"] ?? []); |
| 103 | final unusedAddrs = List<String>.from(decoded["unused"] ?? []); |
| 104 | // index is the index of the first unused address. |
| 105 | final index = decoded["index"] ?? 0; |
| 106 | return _LibAddresses(usedAddrs, unusedAddrs, index); |
| 107 | } catch (e) { |
| 108 | printV(e); |
| 109 | return _LibAddresses([], [], 0); |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | Future<void> generateNewAddress(String label) async { |
| 114 | // NOTE: This will ignore the gap limit and may cause problems when restoring from seed if too |
| 115 | // many addresses are taken and not used. |
| 116 | final addr = await _libwallet.newExternalAddress(walletInfo.name) ?? ''; |
| 117 | if (addr == "") return; |
| 118 | |
| 119 | if (!addressesMap.containsKey(addr)) { |
| 120 | addressesMap[addr] = ""; |
| 121 | addressInfos[0] ??= []; |
| 122 | addressInfos[0]?.add( |
| 123 | WalletInfoAddressInfo( |
| 124 | walletInfoId: walletInfo.internalId, |
| 125 | mapKey: 0, |
| 126 | address: addr, |
| 127 | label: label, |
| 128 | accountIndex: 0, |
| 129 | ), |
| 130 | ); |
| 131 | } |
| 132 | selectedAddr = addr; |
| 133 | await saveAddressesInBox(); |
| 134 | } |
| 135 | |
| 136 | @override |
| 137 | List<ReceivePageOption> get receivePageOptions => isTestnet |
| 138 | ? [ |
| 139 | ReceivePageOption.testnet, |
| 140 | ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet) |
| 141 | ] |
| 142 | : ReceivePageOptions; |
| 143 | |
| 144 | @override |
| 145 | PaymentURI getPaymentUri(String amount) => DecredURI(address: address, amount: amount); |
| 146 | } |
| 147 | |
| 148 | class _LibAddresses { |
| 149 | final List<String> usedAddrs, unusedAddrs; |
| 150 | final int firstUnusedAddrIndex; |
| 151 | |
| 152 | _LibAddresses(this.usedAddrs, this.unusedAddrs, this.firstUnusedAddrIndex); |
| 153 | } |