dev
dart 369 lines 12.1 KB
Raw
1 import 'dart:ffi';
2 import 'dart:isolate';
3
4 import 'package:cw_wownero/api/account_list.dart';
5 import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart';
6 import 'package:cw_wownero/api/wallet.dart';
7 import 'package:cw_wownero/api/wownero_output.dart';
8 import 'package:cw_wownero/api/structs/pending_transaction.dart';
9 import 'package:cw_wownero/exceptions/wownero_transaction_creation_exception.dart';
10 import 'package:ffi/ffi.dart';
11 import 'package:monero/wownero.dart' as wownero;
12 import 'package:monero/src/generated_bindings_wownero.g.dart' as wownero_gen;
13 import 'package:mutex/mutex.dart';
14
15 String getTxKey(String txId) {
16 final ret = wownero.Wallet_getTxKey(wptr!, txid: txId);
17 wownero.Wallet_status(wptr!);
18 return ret;
19 }
20
21 final txHistoryMutex = Mutex();
22 wownero.TransactionHistory? txhistory;
23
24 bool isRefreshingTx = false;
25 Future<void> refreshTransactions() async {
26 if (isRefreshingTx == true) return;
27 isRefreshingTx = true;
28 txhistory ??= wownero.Wallet_history(wptr!);
29 final ptr = txhistory!.address;
30 await txHistoryMutex.acquire();
31 await Isolate.run(() {
32 wownero.TransactionHistory_refresh(Pointer.fromAddress(ptr));
33 });
34 txHistoryMutex.release();
35 isRefreshingTx = false;
36 }
37
38 int countOfTransactions() => wownero.TransactionHistory_count(txhistory!);
39
40 Future<List<Transaction>> getAllTransactions() async {
41 List<Transaction> dummyTxs = [];
42
43 await txHistoryMutex.acquire();
44 txhistory ??= wownero.Wallet_history(wptr!);
45 int size = countOfTransactions();
46 final list = List.generate(
47 size,
48 (index) =>
49 Transaction(txInfo: wownero.TransactionHistory_transaction(txhistory!, index: index)));
50 txHistoryMutex.release();
51
52 final accts = wownero.Wallet_numSubaddressAccounts(wptr!);
53 for (var i = 0; i < accts; i++) {
54 final fullBalance = wownero.Wallet_balance(wptr!, accountIndex: i);
55 final availBalance = wownero.Wallet_unlockedBalance(wptr!, accountIndex: i);
56 if (fullBalance > availBalance) {
57 if (list
58 .where((element) => element.accountIndex == i && element.isConfirmed == false)
59 .isEmpty) {
60 dummyTxs.add(Transaction.dummy(
61 displayLabel: "",
62 description: "",
63 fee: 0,
64 confirmations: 0,
65 blockheight: 0,
66 accountIndex: i,
67 addressIndex: 0,
68 addressIndexList: [0],
69 paymentId: "",
70 amount: fullBalance - availBalance,
71 isSpend: false,
72 hash: "pending",
73 key: "pending",
74 txInfo: Pointer.fromAddress(0),
75 )..timeStamp = DateTime.now());
76 }
77 }
78 }
79 list.addAll(dummyTxs);
80 return list;
81 }
82
83 // TODO(mrcyjanek): ...
84 Transaction getTransaction(String txId) {
85 return Transaction(txInfo: wownero.TransactionHistory_transactionById(txhistory!, txid: txId));
86 }
87
88 Future<PendingTransactionDescription> createTransactionSync(
89 {required String address,
90 required String paymentId,
91 required int priorityRaw,
92 String? amount,
93 int accountIndex = 0,
94 List<String> preferredInputs = const []}) async {
95 final amt = amount == null ? 0 : wownero.Wallet_amountFromString(amount);
96
97 final address_ = address.toNativeUtf8();
98 final paymentId_ = paymentId.toNativeUtf8();
99 if (preferredInputs.isEmpty) {
100 throw WowneroTransactionCreationException(
101 "No inputs provided, transaction cannot be constructed");
102 }
103 final preferredInputs_ = preferredInputs.join(wownero.defaultSeparatorStr).toNativeUtf8();
104
105 final waddr = wptr!.address;
106 final addraddr = address_.address;
107 final paymentIdAddr = paymentId_.address;
108 final preferredInputsAddr = preferredInputs_.address;
109 final spaddr = wownero.defaultSeparator.address;
110 final pendingTx = Pointer<Void>.fromAddress(await Isolate.run(() {
111 final tx =
112 wownero_gen.WowneroC(DynamicLibrary.open(wownero.libPath)).WOWNERO_Wallet_createTransaction(
113 Pointer.fromAddress(waddr),
114 Pointer.fromAddress(addraddr).cast(),
115 Pointer.fromAddress(paymentIdAddr).cast(),
116 amt,
117 1,
118 priorityRaw,
119 accountIndex,
120 Pointer.fromAddress(preferredInputsAddr).cast(),
121 Pointer.fromAddress(spaddr),
122 );
123 return tx.address;
124 }));
125 calloc.free(address_);
126 calloc.free(paymentId_);
127 calloc.free(preferredInputs_);
128 final String? error = (() {
129 final status = wownero.PendingTransaction_status(pendingTx);
130 if (status == 0) {
131 return null;
132 }
133 return wownero.PendingTransaction_errorString(pendingTx);
134 })();
135
136 if (error != null) {
137 final message = error;
138 throw CreationTransactionException(message: message);
139 }
140
141 final rAmt = wownero.PendingTransaction_amount(pendingTx);
142 final rFee = wownero.PendingTransaction_fee(pendingTx);
143 final rHash = wownero.PendingTransaction_txid(pendingTx, '');
144 final rTxKey = rHash;
145
146 return PendingTransactionDescription(
147 amount: rAmt,
148 fee: rFee,
149 hash: rHash,
150 hex: '',
151 txKey: rTxKey,
152 pointerAddress: pendingTx.address,
153 );
154 }
155
156 PendingTransactionDescription createTransactionMultDestSync(
157 {required List<WowneroOutput> outputs,
158 required String paymentId,
159 required int priorityRaw,
160 int accountIndex = 0,
161 List<String> preferredInputs = const []}) {
162 final txptr = wownero.Wallet_createTransactionMultDest(
163 wptr!,
164 dstAddr: outputs.map((e) => e.address).toList(),
165 isSweepAll: false,
166 amounts: outputs.map((e) => wownero.Wallet_amountFromString(e.amount)).toList(),
167 mixinCount: 0,
168 pendingTransactionPriority: priorityRaw,
169 subaddr_account: accountIndex,
170 );
171 if (wownero.PendingTransaction_status(txptr) != 0) {
172 throw CreationTransactionException(message: wownero.PendingTransaction_errorString(txptr));
173 }
174 return PendingTransactionDescription(
175 amount: wownero.PendingTransaction_amount(txptr),
176 fee: wownero.PendingTransaction_fee(txptr),
177 hash: wownero.PendingTransaction_txid(txptr, ''),
178 hex: wownero.PendingTransaction_txid(txptr, ''),
179 txKey: wownero.PendingTransaction_txid(txptr, ''),
180 pointerAddress: txptr.address,
181 );
182 }
183
184 void commitTransactionFromPointerAddress({required int address}) =>
185 commitTransaction(transactionPointer: wownero.PendingTransaction.fromAddress(address));
186
187 void commitTransaction({required wownero.PendingTransaction transactionPointer}) {
188 final txCommit =
189 wownero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
190
191 String? error = (() {
192 final status = wownero.PendingTransaction_status(transactionPointer.cast());
193 if (status == 0) {
194 return null;
195 }
196 return wownero.PendingTransaction_errorString(transactionPointer.cast());
197 })();
198 if (error == null) {
199 error = (() {
200 final status = wownero.Wallet_status(wptr!);
201 if (status == 0) {
202 return null;
203 }
204 return wownero.Wallet_errorString(wptr!);
205 })();
206 }
207
208 if (error != null) {
209 throw CreationTransactionException(message: error);
210 }
211 }
212
213 Future<PendingTransactionDescription> _createTransactionSync(Map args) async {
214 final address = args['address'] as String;
215 final paymentId = args['paymentId'] as String;
216 final amount = args['amount'] as String?;
217 final priorityRaw = args['priorityRaw'] as int;
218 final accountIndex = args['accountIndex'] as int;
219 final preferredInputs = args['preferredInputs'] as List<String>;
220
221 return createTransactionSync(
222 address: address,
223 paymentId: paymentId,
224 amount: amount,
225 priorityRaw: priorityRaw,
226 accountIndex: accountIndex,
227 preferredInputs: preferredInputs);
228 }
229
230 PendingTransactionDescription _createTransactionMultDestSync(Map args) {
231 final outputs = args['outputs'] as List<WowneroOutput>;
232 final paymentId = args['paymentId'] as String;
233 final priorityRaw = args['priorityRaw'] as int;
234 final accountIndex = args['accountIndex'] as int;
235 final preferredInputs = args['preferredInputs'] as List<String>;
236
237 return createTransactionMultDestSync(
238 outputs: outputs,
239 paymentId: paymentId,
240 priorityRaw: priorityRaw,
241 accountIndex: accountIndex,
242 preferredInputs: preferredInputs);
243 }
244
245 Future<PendingTransactionDescription> createTransaction(
246 {required String address,
247 required int priorityRaw,
248 String? amount,
249 String paymentId = '',
250 int accountIndex = 0,
251 List<String> preferredInputs = const []}) async =>
252 _createTransactionSync({
253 'address': address,
254 'paymentId': paymentId,
255 'amount': amount,
256 'priorityRaw': priorityRaw,
257 'accountIndex': accountIndex,
258 'preferredInputs': preferredInputs
259 });
260
261 Future<PendingTransactionDescription> createTransactionMultDest(
262 {required List<WowneroOutput> outputs,
263 required int priorityRaw,
264 String paymentId = '',
265 int accountIndex = 0,
266 List<String> preferredInputs = const []}) async =>
267 _createTransactionMultDestSync({
268 'outputs': outputs,
269 'paymentId': paymentId,
270 'priorityRaw': priorityRaw,
271 'accountIndex': accountIndex,
272 'preferredInputs': preferredInputs
273 });
274
275 class Transaction {
276 final String displayLabel;
277 late final String subaddressLabel = wownero.Wallet_getSubaddressLabel(wptr!,
278 accountIndex: accountIndex, addressIndex: addressIndex);
279 late final String address = getAddress(
280 accountIndex: accountIndex,
281 addressIndex: addressIndex,
282 );
283 late final List<String> addressList = List.generate(
284 addressIndexList.length,
285 (index) => getAddress(
286 accountIndex: accountIndex,
287 addressIndex: addressIndexList[index],
288 ));
289 final String description;
290 final int fee;
291 final int confirmations;
292 late final bool isPending = confirmations < 3;
293 final int blockheight;
294 final int addressIndex;
295 final int accountIndex;
296 final List<int> addressIndexList;
297 final String paymentId;
298 final int amount;
299 final bool isSpend;
300 late final DateTime timeStamp;
301 late final bool isConfirmed = !isPending;
302 final String hash;
303 final String key;
304
305 Map<String, dynamic> toJson() {
306 return {
307 "displayLabel": displayLabel,
308 "subaddressLabel": subaddressLabel,
309 "address": address,
310 "description": description,
311 "fee": fee,
312 "confirmations": confirmations,
313 "isPending": isPending,
314 "blockheight": blockheight,
315 "accountIndex": accountIndex,
316 "addressIndex": addressIndex,
317 "paymentId": paymentId,
318 "amount": amount,
319 "isSpend": isSpend,
320 "timeStamp": timeStamp.toIso8601String(),
321 "isConfirmed": isConfirmed,
322 "hash": hash,
323 };
324 }
325
326 // final SubAddress? subAddress;
327 // List<Transfer> transfers = [];
328 // final int txIndex;
329 final wownero.TransactionInfo txInfo;
330 Transaction({
331 required this.txInfo,
332 }) : displayLabel = wownero.TransactionInfo_label(txInfo),
333 hash = wownero.TransactionInfo_hash(txInfo),
334 timeStamp = DateTime.fromMillisecondsSinceEpoch(
335 wownero.TransactionInfo_timestamp(txInfo) * 1000,
336 ),
337 isSpend =
338 wownero.TransactionInfo_direction(txInfo) == wownero.TransactionInfo_Direction.Out,
339 amount = wownero.TransactionInfo_amount(txInfo),
340 paymentId = wownero.TransactionInfo_paymentId(txInfo),
341 accountIndex = wownero.TransactionInfo_subaddrAccount(txInfo),
342 addressIndex =
343 int.tryParse(wownero.TransactionInfo_subaddrIndex(txInfo).split(", ")[0]) ?? 0,
344 addressIndexList = wownero.TransactionInfo_subaddrIndex(txInfo)
345 .split(", ")
346 .map((e) => int.tryParse(e) ?? 0)
347 .toList(),
348 blockheight = wownero.TransactionInfo_blockHeight(txInfo),
349 confirmations = wownero.TransactionInfo_confirmations(txInfo),
350 fee = wownero.TransactionInfo_fee(txInfo),
351 description = wownero.TransactionInfo_description(txInfo),
352 key = wownero.Wallet_getTxKey(wptr!, txid: wownero.TransactionInfo_hash(txInfo));
353
354 Transaction.dummy(
355 {required this.displayLabel,
356 required this.description,
357 required this.fee,
358 required this.confirmations,
359 required this.blockheight,
360 required this.accountIndex,
361 required this.addressIndex,
362 required this.addressIndexList,
363 required this.paymentId,
364 required this.amount,
365 required this.isSpend,
366 required this.hash,
367 required this.key,
368 required this.txInfo});
369 }