dev
dart 296 lines 9.01 KB
Raw
1 import 'package:cw_core/payment_uris.dart';
2 import 'package:cw_core/receive_page_option.dart';
3 import 'package:cw_core/utils/print_verbose.dart';
4 import 'package:cw_core/wallet_addresses.dart';
5 import 'package:cw_core/wallet_info.dart';
6 import 'package:cw_zcash/cw_zcash.dart';
7 import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
8 import 'package:mobx/mobx.dart';
9 import 'package:zkool/src/rust/api/account.dart' as zkool_account;
10
11 part 'zcash_wallet_addresses.g.dart';
12
13 class ZcashWalletAddresses = ZcashWalletAddressesBase with _$ZcashWalletAddresses;
14
15 abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
16 ZcashWalletAddressesBase(this.accountId, final WalletInfo walletInfo) : super(walletInfo) {
17 addressPageType = ZcashReceivePageOption.typeFromString(walletInfo.addressPageType ?? "");
18 }
19
20 int accountId;
21
22 static const int transparentType = 0;
23 static const int shieldedSaplingType = 1;
24 static const int shieldedOrchardType = 2;
25 static const int unifiedType = 3;
26
27 @observable
28 String? transparentAddress;
29
30 @observable
31 String? _transparentObservableAddress;
32
33 @observable
34 String? saplingAddress;
35
36 @observable
37 String? orchardAddress;
38
39 @observable
40 String? unifiedAddress;
41
42 @observable
43 bool ironwoodActive = false;
44
45 @action
46 void setIronwoodActive(final bool active) {
47 ironwoodActive = active;
48 }
49
50 @override
51 @computed
52 List<ReceivePageOption> get receivePageOptions {
53 return [
54 ...ZcashReceivePageOption.allOptionsFor(ironwood: ironwoodActive),
55 ...ReceivePageOptions.where((final element) => element != ReceivePageOption.mainnet),
56 ];
57 }
58
59 @override
60 String get latestAddress {
61 switch (addressPageType) {
62 case ZcashAddressType.transparent:
63 return transparentAddress ?? "unknown transparentAddress";
64 case ZcashAddressType.transparentRotated:
65 // just to display something, after the wallet is synced the address
66 // will start rotating, and since we can't rotate address until the wallet syncs
67 // we can't know if some T address already got used, so we would still default to
68 // the same address.
69 // So.. we can
70 // 1) display placeholder text (looks ugly)
71 // 2) display static address
72 // 3) display first address from rotated pool which is also static until synced
73 // 2 seems like an obvious winner simply because it offers little to no benefit over 3
74 // and is noticably less complex in implementation
75 return _transparentObservableAddress ??
76 transparentAddress ??
77 "unknown transparentAddressRotated";
78 case ZcashAddressType.shieldedSapling:
79 return saplingAddress ?? "unknown saplingAddress";
80 case ZcashAddressType.shieldedOrchard:
81 return orchardAddress ?? "unknown orchardAddress";
82 case ZcashAddressType.unifiedType:
83 return unifiedAddress ?? "unknown unifiedAddress";
84 }
85 }
86
87 @observable
88 ZcashAddressType? _addressPageType;
89
90 @computed
91 ZcashAddressType get addressPageType => _addressPageType ?? ZcashAddressType.shieldedOrchard;
92
93 @computed
94 set addressPageType(final ZcashAddressType newZat) {
95 _addressPageType = newZat;
96 init();
97 address = latestAddress;
98 }
99
100 @action
101 Future<void> setAddressType(final ZcashAddressType type) async {
102 addressPageType = ZcashReceivePageOption.typeFromString(type.toString());
103 walletInfo.addressPageType = type.toString();
104 await walletInfo.save();
105 }
106
107 @override
108 @computed
109 String get addressForExchange =>
110 _transparentObservableAddress ?? transparentAddress ?? "unknown addressForExchange";
111
112 @override
113 @computed
114 String get addressForBuy =>
115 _transparentObservableAddress ?? transparentAddress ?? "unknown addressForBuy";
116
117 @override
118 bool containsAddress(final String address) {
119 return this.address == address || addressesMap.values.contains(address);
120 }
121
122 @override
123 @observable
124 Set<String> hiddenAddresses = {};
125
126 Future<void> _initAddresses() async {
127 addressesMap = {};
128 addressInfos = await walletInfo.getAddressInfos();
129 usedAddresses = await walletInfo.getUsedAddresses();
130 manualAddresses = await walletInfo.getManualAddresses();
131 hiddenAddresses = await walletInfo.getHiddenAddresses();
132 address = latestAddress;
133 }
134
135 Future<void> _syncRotationHiddenAddresses() async {
136 final all = (await ZcashTaddressRotation.allAddressesForAccount(accountId)).toSet();
137 final used = (await ZcashTaddressRotation.usedAddressesForAccount(accountId)).toSet();
138 hiddenAddresses.removeWhere(all.contains);
139 hiddenAddresses.addAll(used);
140 }
141
142 @override
143 Future<void> init() async {
144 try {
145 await _init();
146 } catch (e) {
147 printV("init failed, retrying in 2 seconds");
148 printV(e);
149 await Future.delayed(Duration(seconds: 2));
150 await init();
151 }
152 }
153
154 static bool isPlaceholderAddress(final String? address) =>
155 address == null || address.isEmpty || address.startsWith('unknown ');
156
157 String _resolvedWalletInfoAddress() {
158 for (final candidate in [unifiedAddress, orchardAddress, saplingAddress, transparentAddress]) {
159 if (!isPlaceholderAddress(candidate)) {
160 return candidate!;
161 }
162 }
163 return latestAddress;
164 }
165
166 Future<void> _init() async {
167 await _initAddresses();
168
169 final addr = await ZcashWalletBase.runWithCoin(
170 accountId: accountId,
171 func: (final coin) => zkool_account.getAddresses(c: coin, uaPools: 7),
172 );
173
174 transparentAddress = addr.taddr ?? 'unknown addr.taddr';
175 saplingAddress = addr.saddr ?? 'unknown addr.saddr';
176 orchardAddress = addr.oaddr ?? 'unknown addr.oaddr';
177 unifiedAddress = addr.ua ?? 'unknown addr.ua';
178 _transparentObservableAddress = await ZcashTaddressRotation.addressForAccount(accountId);
179 final rotationAddrs = await ZcashTaddressRotation.allAddressesForAccount(accountId);
180 addressInfos = {
181 0: [
182 for (int i = 0; i < rotationAddrs.length; i++)
183 WalletInfoAddressInfo(
184 walletInfoId: walletInfo.internalId,
185 mapKey: i + 1,
186 accountIndex: 0,
187 address: rotationAddrs[i],
188 label: "",
189 ),
190 ],
191 };
192 await _syncRotationHiddenAddresses();
193 await _applyAddressForCurrentType();
194
195 if (isPlaceholderAddress(walletInfo.address)) {
196 final resolved = _resolvedWalletInfoAddress();
197 if (!isPlaceholderAddress(resolved)) {
198 walletInfo.address = resolved;
199 await walletInfo.save();
200 }
201 }
202
203 await saveAddressesInBox();
204 }
205
206 Future<void> refreshRotationAddresses() async {
207 _transparentObservableAddress = await ZcashTaddressRotation.addressForAccount(accountId);
208 final rotationAddrs = await ZcashTaddressRotation.allAddressesForAccount(accountId);
209 addressInfos = {
210 0: [
211 for (int i = 0; i < rotationAddrs.length; i++)
212 WalletInfoAddressInfo(
213 walletInfoId: walletInfo.internalId,
214 mapKey: i + 1,
215 accountIndex: 0,
216 address: rotationAddrs[i],
217 label: "",
218 ),
219 ],
220 };
221 await _syncRotationHiddenAddresses();
222 await _applyAddressForCurrentType();
223 await _persistAddressForExchange();
224 }
225
226 Future<void> _persistAddressForExchange() async {
227 final addr = addressForExchange;
228 if (isPlaceholderAddress(addr)) return;
229 await walletInfo.setAddresses({addr: 'transparent'});
230 }
231
232 Future<void> _applyAddressForCurrentType() async {
233 if (_addressPageType == ZcashAddressType.transparentRotated) {
234 final addr = await ZcashTaddressRotation.addressForAccount(accountId);
235 if (addr != null) {
236 address = addr;
237 }
238 return;
239 }
240 address = latestAddress;
241 }
242
243 @observable
244 late String _address = latestAddress;
245
246 @computed
247 String get address {
248 return _address;
249 }
250
251 void set address(final String _$address) => _address = _$address;
252
253 @override
254 String get primaryAddress => address;
255
256 @override
257 Future<void> saveAddressesInBox() async {
258 try {
259 if (!isPlaceholderAddress(address)) {
260 walletInfo.address = address;
261 }
262 await walletInfo.setAddresses({});
263 await _persistAddressForExchange();
264 await walletInfo.setAddressInfos(addressInfos);
265 await walletInfo.setUsedAddresses(usedAddresses.toList());
266 await walletInfo.setHiddenAddresses(hiddenAddresses.toList());
267 await walletInfo.setManualAddresses(manualAddresses.toList());
268 await walletInfo.save();
269 await _initAddresses();
270 await _syncRotationHiddenAddresses();
271 } catch (e) {
272 printV("Error saving addresses: $e");
273 }
274 }
275
276 @override
277 Future<void> updateAddressesInBox() async {
278 await _initAddresses();
279 }
280
281 @override
282 Map<String, String> get usableAddressesMap => addressesMap;
283
284 @override
285 Map<String, String> get usableAllAddressesMap => allAddressesMap;
286
287 List<WalletInfoAddressInfo> getAddressInfos() {
288 if (addressPageType != ZcashAddressType.transparentRotated) {
289 return [];
290 }
291 return addressInfos[0] ?? [];
292 }
293
294 @override
295 PaymentURI getPaymentUri(final String amount) => ZcashURI(amount: amount, address: address);
296 }