CAKE-345 | applied batch sending to monero wallet

This commit is contained in:
OleksandrSobol 2021-08-04 17:38:03 +03:00
parent d582f89c7d
commit d4c0fb6fec
10 changed files with 230 additions and 35 deletions

View file

@ -495,6 +495,48 @@ extern "C"
return true; return true;
} }
bool transaction_create_mult_dest(char **addresses, char *payment_id, char **amounts, uint32_t size,
uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction)
{
nice(19);
std::vector<std::string> _addresses;
std::vector<uint64_t> _amounts;
for (int i = 0; i < size; i++) {
_addresses.push_back(std::string(*addresses));
_amounts.push_back(Monero::Wallet::amountFromString(std::string(*amounts)));
addresses++;
amounts++;
}
auto priority = static_cast<Monero::PendingTransaction::Priority>(priority_raw);
std::string _payment_id;
Monero::PendingTransaction *transaction;
if (payment_id != nullptr)
{
_payment_id = std::string(payment_id);
}
transaction = m_wallet->createTransactionMultDest(_addresses, _payment_id, _amounts, m_wallet->defaultMixin(), priority, subaddr_account);
int status = transaction->status();
if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical)
{
error = Utf8Box(strdup(transaction->errorString().c_str()));
return false;
}
if (m_listener != nullptr) {
m_listener->m_new_transaction = true;
}
pendingTransaction = PendingTransactionRaw(transaction);
return true;
}
bool transaction_commit(PendingTransactionRaw *transaction, Utf8Box &error) bool transaction_commit(PendingTransactionRaw *transaction, Utf8Box &error)
{ {
bool committed = transaction->transaction->commit(); bool committed = transaction->transaction->commit();

View file

@ -95,6 +95,16 @@ typedef transaction_create = Int8 Function(
Pointer<Utf8Box> error, Pointer<Utf8Box> error,
Pointer<PendingTransactionRaw> pendingTransaction); Pointer<PendingTransactionRaw> pendingTransaction);
typedef transaction_create_mult_dest = Int8 Function(
Pointer<Pointer<Utf8>> addresses,
Pointer<Utf8> paymentId,
Pointer<Pointer<Utf8>> amounts,
Int32 size,
Int8 priorityRaw,
Int32 subaddrAccount,
Pointer<Utf8Box> error,
Pointer<PendingTransactionRaw> pendingTransaction);
typedef transaction_commit = Int8 Function(Pointer<PendingTransactionRaw>, Pointer<Utf8Box>); typedef transaction_commit = Int8 Function(Pointer<PendingTransactionRaw>, Pointer<Utf8Box>);
typedef secret_view_key = Pointer<Utf8> Function(); typedef secret_view_key = Pointer<Utf8> Function();

View file

@ -26,6 +26,10 @@ final transactionCreateNative = moneroApi
.lookup<NativeFunction<transaction_create>>('transaction_create') .lookup<NativeFunction<transaction_create>>('transaction_create')
.asFunction<TransactionCreate>(); .asFunction<TransactionCreate>();
final transactionCreateMultDestNative = moneroApi
.lookup<NativeFunction<transaction_create_mult_dest>>('transaction_create_mult_dest')
.asFunction<TransactionCreateMultDest>();
final transactionCommitNative = moneroApi final transactionCommitNative = moneroApi
.lookup<NativeFunction<transaction_commit>>('transaction_commit') .lookup<NativeFunction<transaction_commit>>('transaction_commit')
.asFunction<TransactionCommit>(); .asFunction<TransactionCommit>();
@ -102,6 +106,59 @@ PendingTransactionDescription createTransactionSync(
pointerAddress: pendingTransactionRawPointer.address); pointerAddress: pendingTransactionRawPointer.address);
} }
PendingTransactionDescription createTransactionMultDestSync(
{List<String> addresses,
String paymentId,
List<String> amounts,
int size,
int priorityRaw,
int accountIndex = 0}) {
final List<Pointer<Utf8>> addressesPointers = addresses.map(Utf8.toUtf8).toList();
final Pointer<Pointer<Utf8>> addressesPointerPointer = allocate(count: size);
final List<Pointer<Utf8>> amountsPointers = amounts.map(Utf8.toUtf8).toList();
final Pointer<Pointer<Utf8>> amountsPointerPointer = allocate(count: size);
for (int i = 0; i < size; i++) {
addressesPointerPointer[ i ] = addressesPointers[ i ];
amountsPointerPointer[ i ] = amountsPointers[ i ];
}
final paymentIdPointer = Utf8.toUtf8(paymentId);
final errorMessagePointer = allocate<Utf8Box>();
final pendingTransactionRawPointer = allocate<PendingTransactionRaw>();
final created = transactionCreateMultDestNative(
addressesPointerPointer,
paymentIdPointer,
amountsPointerPointer,
size,
priorityRaw,
accountIndex,
errorMessagePointer,
pendingTransactionRawPointer) !=
0;
free(addressesPointerPointer);
free(amountsPointerPointer);
addressesPointers.forEach((element) => free(element));
amountsPointers.forEach((element) => free(element));
free(paymentIdPointer);
if (!created) {
final message = errorMessagePointer.ref.getValue();
free(errorMessagePointer);
throw CreationTransactionException(message: message);
}
return PendingTransactionDescription(
amount: pendingTransactionRawPointer.ref.amount,
fee: pendingTransactionRawPointer.ref.fee,
hash: pendingTransactionRawPointer.ref.getHash(),
pointerAddress: pendingTransactionRawPointer.address);
}
void commitTransactionFromPointerAddress({int address}) => commitTransaction( void commitTransactionFromPointerAddress({int address}) => commitTransaction(
transactionPointer: Pointer<PendingTransactionRaw>.fromAddress(address)); transactionPointer: Pointer<PendingTransactionRaw>.fromAddress(address));
@ -132,6 +189,23 @@ PendingTransactionDescription _createTransactionSync(Map args) {
accountIndex: accountIndex); accountIndex: accountIndex);
} }
PendingTransactionDescription _createTransactionMultDestSync(Map args) {
final addresses = args['addresses'] as List<String>;
final paymentId = args['paymentId'] as String;
final amounts = args['amounts'] as List<String>;
final size = args['size'] as int;
final priorityRaw = args['priorityRaw'] as int;
final accountIndex = args['accountIndex'] as int;
return createTransactionMultDestSync(
addresses: addresses,
paymentId: paymentId,
amounts: amounts,
size: size,
priorityRaw: priorityRaw,
accountIndex: accountIndex);
}
Future<PendingTransactionDescription> createTransaction( Future<PendingTransactionDescription> createTransaction(
{String address, {String address,
String paymentId, String paymentId,
@ -145,3 +219,19 @@ Future<PendingTransactionDescription> createTransaction(
'priorityRaw': priorityRaw, 'priorityRaw': priorityRaw,
'accountIndex': accountIndex 'accountIndex': accountIndex
}); });
Future<PendingTransactionDescription> createTransactionMultDest(
{List<String> addresses,
String paymentId,
List<String> amounts,
int size,
int priorityRaw,
int accountIndex = 0}) =>
compute(_createTransactionMultDestSync, {
'addresses': addresses,
'paymentId': paymentId,
'amounts': amounts,
'size': size,
'priorityRaw': priorityRaw,
'accountIndex': accountIndex
});

View file

@ -93,6 +93,16 @@ typedef TransactionCreate = int Function(
Pointer<Utf8Box> error, Pointer<Utf8Box> error,
Pointer<PendingTransactionRaw> pendingTransaction); Pointer<PendingTransactionRaw> pendingTransaction);
typedef TransactionCreateMultDest = int Function(
Pointer<Pointer<Utf8>> addresses,
Pointer<Utf8> paymentId,
Pointer<Pointer<Utf8>> amounts,
int size,
int priorityRaw,
int subaddrAccount,
Pointer<Utf8Box> error,
Pointer<PendingTransactionRaw> pendingTransaction);
typedef TransactionCommit = int Function(Pointer<PendingTransactionRaw>, Pointer<Utf8Box>); typedef TransactionCommit = int Function(Pointer<PendingTransactionRaw>, Pointer<Utf8Box>);
typedef SecretViewKey = Pointer<Utf8> Function(); typedef SecretViewKey = Pointer<Utf8> Function();

View file

@ -1,13 +1,11 @@
import 'package:cake_wallet/entities/transaction_creation_credentials.dart'; import 'package:cake_wallet/entities/transaction_creation_credentials.dart';
import 'package:cake_wallet/entities/monero_transaction_priority.dart'; import 'package:cake_wallet/entities/monero_transaction_priority.dart';
import 'package:cake_wallet/view_model/send/send_item.dart';
class MoneroTransactionCreationCredentials class MoneroTransactionCreationCredentials
extends TransactionCreationCredentials { extends TransactionCreationCredentials {
MoneroTransactionCreationCredentials( MoneroTransactionCreationCredentials({this.sendItemList, this.priority});
{this.address, this.paymentId, this.priority, this.amount});
final String address; final List<SendItem> sendItemList;
final String paymentId;
final String amount;
final MoneroTransactionPriority priority; final MoneroTransactionPriority priority;
} }

View file

@ -5,6 +5,7 @@ import 'package:cake_wallet/monero/monero_transaction_creation_exception.dart';
import 'package:cake_wallet/monero/monero_transaction_info.dart'; import 'package:cake_wallet/monero/monero_transaction_info.dart';
import 'package:cake_wallet/monero/monero_wallet_addresses.dart'; import 'package:cake_wallet/monero/monero_wallet_addresses.dart';
import 'package:cake_wallet/monero/monero_wallet_utils.dart'; import 'package:cake_wallet/monero/monero_wallet_utils.dart';
import 'package:cw_monero/structs/pending_transaction.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cw_monero/transaction_history.dart' import 'package:cw_monero/transaction_history.dart'
@ -149,31 +150,80 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
@override @override
Future<PendingTransaction> createTransaction(Object credentials) async { Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials; final _credentials = credentials as MoneroTransactionCreationCredentials;
final amount = _credentials.amount != null final sendItemList = _credentials.sendItemList;
? moneroParseAmount(amount: _credentials.amount) final listSize = sendItemList.length;
: null;
final unlockedBalance = final unlockedBalance =
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id); monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id);
if ((amount != null && unlockedBalance < amount) || PendingTransactionDescription pendingTransactionDescription;
(amount == null && unlockedBalance <= 0)) {
final formattedBalance = moneroAmountToString(amount: unlockedBalance);
throw MoneroTransactionCreationException(
'Incorrect unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${_credentials.amount}.');
}
if (!(syncStatus is SyncedSyncStatus)) { if (!(syncStatus is SyncedSyncStatus)) {
throw MoneroTransactionCreationException('The wallet is not synced.'); throw MoneroTransactionCreationException('The wallet is not synced.');
} }
final pendingTransactionDescription = if (listSize > 1) {
await transaction_history.createTransaction( final sendAllItems = sendItemList.where((item) => item.sendAll).toList();
address: _credentials.address,
paymentId: _credentials.paymentId, if (sendAllItems?.isNotEmpty ?? false) {
amount: _credentials.amount, throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.');
priorityRaw: _credentials.priority.serialize(), }
accountIndex: walletAddresses.account.id);
final nullAmountItems = sendItemList.where((item) =>
moneroParseAmount(amount: item.cryptoAmount.replaceAll(',', '.')) <= 0)
.toList();
if (nullAmountItems?.isNotEmpty ?? false) {
throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.');
}
var credentialsAmount = 0;
credentialsAmount = sendItemList.fold(0, (previousValue, element) =>
previousValue + moneroParseAmount(
amount: element.cryptoAmount.replaceAll(',', '.')));
if (unlockedBalance < credentialsAmount) {
throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.');
}
final addresses = sendItemList.map((e) => e.address).toList();
final amounts = sendItemList.map((e) =>
e.cryptoAmount.replaceAll(',', '.')).toList();
pendingTransactionDescription =
await transaction_history.createTransactionMultDest(
addresses: addresses,
paymentId: '',
amounts: amounts,
size: listSize,
priorityRaw: _credentials.priority.serialize(),
accountIndex: walletAddresses.account.id);
} else {
final item = sendItemList.first;
final address = item.address;
final amount = item.sendAll
? null
: item.cryptoAmount.replaceAll(',', '.');
final formattedAmount = item.sendAll
? null
: moneroParseAmount(amount: amount);
if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
(formattedAmount == null && unlockedBalance <= 0)) {
final formattedBalance = moneroAmountToString(amount: unlockedBalance);
throw MoneroTransactionCreationException(
'Incorrect unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${item.cryptoAmount}.');
}
pendingTransactionDescription =
await transaction_history.createTransaction(
address: address,
paymentId: '',
amount: amount,
priorityRaw: _credentials.priority.serialize(),
accountIndex: walletAddresses.account.id);
}
return PendingMoneroTransaction(pendingTransactionDescription); return PendingMoneroTransaction(pendingTransactionDescription);
} }

View file

@ -259,7 +259,7 @@ class SendPage extends BasePage {
EdgeInsets.only(left: 24, right: 24, bottom: 24), EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column( bottomSection: Column(
children: [ children: [
if (sendViewModel.isElectrumWallet) Padding( Padding(
padding: EdgeInsets.only(bottom: 12), padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton( child: PrimaryButton(
onPressed: () { onPressed: () {

View file

@ -1,7 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/entities/openalias_record.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/monero/monero_amount_format.dart'; import 'package:cake_wallet/monero/monero_amount_format.dart';

View file

@ -199,18 +199,11 @@ abstract class SendViewModelBase with Store {
return BitcoinTransactionCredentials( return BitcoinTransactionCredentials(
sendItemList, priority as BitcoinTransactionPriority); sendItemList, priority as BitcoinTransactionPriority);
case WalletType.monero: case WalletType.monero:
final _item = sendItemList.first;
final address = _item.address;
final amount = _item.sendAll
? null
: _item.cryptoAmount.replaceAll(',', '.');
final priority = _settingsStore.priority[_wallet.type]; final priority = _settingsStore.priority[_wallet.type];
return MoneroTransactionCreationCredentials( return MoneroTransactionCreationCredentials(
address: address, sendItemList: sendItemList,
paymentId: '', priority: priority as MoneroTransactionPriority);
priority: priority as MoneroTransactionPriority,
amount: amount);
default: default:
return null; return null;
} }

View file

@ -30,6 +30,7 @@ abstract class TransactionDetailsViewModelBase with Store {
this.settingsStore}) this.settingsStore})
: items = [] { : items = [] {
showRecipientAddress = settingsStore?.shouldSaveRecipientAddress ?? false; showRecipientAddress = settingsStore?.shouldSaveRecipientAddress ?? false;
isRecipientAddressShown = false;
final dateFormat = DateFormatter.withCurrentLocal(); final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo; final tx = transactionInfo;
@ -64,6 +65,7 @@ abstract class TransactionDetailsViewModelBase with Store {
final address = final address =
_wallet.getTransactionAddress(accountIndex, addressIndex); _wallet.getTransactionAddress(accountIndex, addressIndex);
if (address?.isNotEmpty ?? false) { if (address?.isNotEmpty ?? false) {
isRecipientAddressShown = true;
_items.add( _items.add(
StandartListItem( StandartListItem(
title: S.current.transaction_details_recipient_address, title: S.current.transaction_details_recipient_address,
@ -101,7 +103,7 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items); items.addAll(_items);
} }
if (showRecipientAddress) { if (showRecipientAddress && !isRecipientAddressShown) {
final recipientAddress = transactionDescriptionBox.values final recipientAddress = transactionDescriptionBox.values
.firstWhere((val) => val.id == transactionInfo.id, orElse: () => null) .firstWhere((val) => val.id == transactionInfo.id, orElse: () => null)
?.recipientAddress; ?.recipientAddress;
@ -151,6 +153,7 @@ abstract class TransactionDetailsViewModelBase with Store {
final List<TransactionDetailsListItem> items; final List<TransactionDetailsListItem> items;
bool showRecipientAddress; bool showRecipientAddress;
bool isRecipientAddressShown;
String _explorerUrl(WalletType type, String txId) { String _explorerUrl(WalletType type, String txId) {
switch (type) { switch (type) {