dev
dart 174 lines 5.96 KB
Raw
1 import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:blockchain_utils/bip/bip/bip32/bip32.dart';
3 import 'package:cw_bitcoin/bitcoin_receive_page_option.dart';
4 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
5 import 'package:cw_bitcoin/lightning/lightning_addres_type.dart';
6 import 'package:cw_bitcoin/payjoin/manager.dart';
7 import 'package:cw_bitcoin/utils.dart';
8 import 'package:cw_core/parse_fixed.dart';
9 import 'package:cw_core/payment_uris.dart';
10 import 'package:cw_core/receive_page_option.dart';
11 import 'package:cw_core/unspent_coin_type.dart';
12 import 'package:cw_core/utils/print_verbose.dart';
13 import 'package:cw_core/wallet_info.dart';
14 import 'package:mobx/mobx.dart';
15 import 'package:payjoin_flutter/receive.dart' as payjoin;
16
17 import 'lightning/utils.dart';
18
19 part 'bitcoin_wallet_addresses.g.dart';
20
21 class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
22
23 abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
24 BitcoinWalletAddressesBase(
25 WalletInfo walletInfo, {
26 required super.mainHdByType,
27 required super.sideHdByType,
28 required super.legacySideHd,
29 required super.legacyMainHd,
30 required super.network,
31 required super.isHardwareWallet,
32 required this.payjoinManager,
33 super.initialAddresses,
34 super.initialRegularAddressIndex,
35 super.initialChangeAddressIndex,
36 super.initialSilentAddresses,
37 super.initialSilentAddressIndex = 0,
38 super.masterHd,
39 super.lightningWallet,
40 }) : super(walletInfo);
41
42 final PayjoinManager payjoinManager;
43
44 payjoin.Receiver? currentPayjoinReceiver;
45
46 @observable
47 String? payjoinEndpoint = null;
48
49 @override
50 String getAddress(
51 {required int index,
52 required Bip32Slip10Secp256k1 hd,
53 BitcoinAddressType? addressType,
54 UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) {
55 if (addressType == P2pkhAddressType.p2pkh)
56 return generateP2PKHAddress(hd: hd, index: index, network: network);
57
58 if (addressType == SegwitAddresType.p2tr)
59 return generateP2TRAddress(hd: hd, index: index, network: network);
60
61 if (addressType == SegwitAddresType.p2wsh)
62 return generateP2WSHAddress(hd: hd, index: index, network: network);
63
64 if (addressType == P2shAddressType.p2wpkhInP2sh)
65 return generateP2SHAddress(hd: hd, index: index, network: network);
66
67 return generateP2WPKHAddress(hd: hd, index: index, network: network);
68 }
69
70 bool _isPayjoinConnectivityError(String error) =>
71 ["error sending request for url", "Instance of 'FfiIoError'"].any((e) => error.contains(e));
72
73 @action
74 Future<void> initPayjoin() async {
75 try {
76 await payjoinManager.initPayjoin();
77 currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(payjoinCompatibleAddress);
78 payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
79
80 payjoinManager.resumeSessions();
81 } catch (e) {
82 printV(e);
83 // Ignore Connectivity errors
84 if (!_isPayjoinConnectivityError(e.toString())) rethrow;
85 }
86 }
87
88 @action
89 Future<void> newPayjoinReceiver() async {
90 try {
91 currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(payjoinCompatibleAddress);
92 payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
93
94 payjoinManager.spawnReceiver(receiver: currentPayjoinReceiver!);
95 } catch (e) {
96 printV(e);
97 // Ignore Connectivity errors
98 if (!_isPayjoinConnectivityError(e.toString())) rethrow;
99 }
100 }
101
102 @override
103 List<ReceivePageOption> get receivePageOptions {
104 if (isHardwareWallet) {
105 return [
106 ...BitcoinReceivePageOption.allViewOnly,
107 ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
108 ];
109 }
110 return [
111 ...BitcoinReceivePageOption.all,
112 ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
113 ];
114 }
115
116 @override
117 PaymentURI getPaymentUri(String amount) {
118 if (addressPageType is LightningAddressType && lightningWallet != null) {
119 final lnUrl = getLnurlOfLightningAddress(address);
120 return LightningPaymentRequest(address: address, lnURL: lnUrl, amount: amount);
121 }
122 return BitcoinURI(address: address, amount: amount, pjUri: payjoinEndpoint ?? '');
123 }
124
125 Future<PaymentURI> getPaymentRequestUri(String amount) async {
126 if (addressPageType is LightningAddressType && lightningWallet != null) {
127 final amountSats = amount.isNotEmpty ? tryParseFixed(amount, 8) : null;
128 final lnUrl = getLnurlOfLightningAddress(address);
129 if (amountSats == null) {
130 return LightningPaymentRequest(address: address, lnURL: lnUrl, amount: amount);
131 }
132 final invoice = await lightningWallet!.getBolt11Invoice(amountSats, "Send to Cake Wallet");
133 return LightningPaymentRequest(
134 address: address, lnURL: lnUrl, amount: amount, bolt11Invoice: invoice);
135 }
136 return getPaymentUri(amount);
137 }
138
139 @override
140 bool containsAddress(String address) =>
141 super.containsAddress(address) || address == lightningAddress;
142
143 @override
144 String get addressForBuy {
145 try {
146 return super
147 .allAddresses
148 .firstWhere((e) => e.type == SegwitAddresType.p2wpkh && !e.isHidden && !e.isUsed)
149 .address;
150 } catch (_) {
151 return super.addressForBuy;
152 }
153 }
154
155 @override
156 String get addressForExchange {
157 final current = getFreshAddress();
158 final availableReceiveAddresses = receiveAddresses.where((element) =>
159 !element.isUsed && !element.isHidden && !hiddenAddresses.contains(element.address));
160
161 final bool isSilentPaymentsPage = addressPageType == SilentPaymentsAddresType.p2sp;
162 final bool isLightningPage = addressPageType == LightningAddressType.p2l;
163
164 if (isSilentPaymentsPage || isLightningPage) {
165 final segwit = availableReceiveAddresses
166 .where((e) => e.type == SegwitAddresType.p2wpkh)
167 .map((e) => e.address)
168 .toList();
169
170 return segwit.isNotEmpty ? segwit.first : current;
171 }
172 return current;
173 }
174 }