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;
}
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 committed = transaction->transaction->commit();

View file

@ -95,6 +95,16 @@ typedef transaction_create = Int8 Function(
Pointer<Utf8Box> error,
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 secret_view_key = Pointer<Utf8> Function();

View file

@ -26,6 +26,10 @@ final transactionCreateNative = moneroApi
.lookup<NativeFunction<transaction_create>>('transaction_create')
.asFunction<TransactionCreate>();
final transactionCreateMultDestNative = moneroApi
.lookup<NativeFunction<transaction_create_mult_dest>>('transaction_create_mult_dest')
.asFunction<TransactionCreateMultDest>();
final transactionCommitNative = moneroApi
.lookup<NativeFunction<transaction_commit>>('transaction_commit')
.asFunction<TransactionCommit>();
@ -102,6 +106,59 @@ PendingTransactionDescription createTransactionSync(
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(
transactionPointer: Pointer<PendingTransactionRaw>.fromAddress(address));
@ -132,6 +189,23 @@ PendingTransactionDescription _createTransactionSync(Map args) {
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(
{String address,
String paymentId,
@ -145,3 +219,19 @@ Future<PendingTransactionDescription> createTransaction(
'priorityRaw': priorityRaw,
'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<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 SecretViewKey = Pointer<Utf8> Function();

View file

@ -1,13 +1,11 @@
import 'package:cake_wallet/entities/transaction_creation_credentials.dart';
import 'package:cake_wallet/entities/monero_transaction_priority.dart';
import 'package:cake_wallet/view_model/send/send_item.dart';
class MoneroTransactionCreationCredentials
extends TransactionCreationCredentials {
MoneroTransactionCreationCredentials(
{this.address, this.paymentId, this.priority, this.amount});
MoneroTransactionCreationCredentials({this.sendItemList, this.priority});
final String address;
final String paymentId;
final String amount;
final List<SendItem> sendItemList;
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_wallet_addresses.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:mobx/mobx.dart';
import 'package:cw_monero/transaction_history.dart'
@ -149,31 +150,80 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials;
final amount = _credentials.amount != null
? moneroParseAmount(amount: _credentials.amount)
: null;
final sendItemList = _credentials.sendItemList;
final listSize = sendItemList.length;
final unlockedBalance =
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id);
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id);
if ((amount != null && unlockedBalance < amount) ||
(amount == null && unlockedBalance <= 0)) {
final formattedBalance = moneroAmountToString(amount: unlockedBalance);
throw MoneroTransactionCreationException(
'Incorrect unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${_credentials.amount}.');
}
PendingTransactionDescription pendingTransactionDescription;
if (!(syncStatus is SyncedSyncStatus)) {
throw MoneroTransactionCreationException('The wallet is not synced.');
}
final pendingTransactionDescription =
await transaction_history.createTransaction(
address: _credentials.address,
paymentId: _credentials.paymentId,
amount: _credentials.amount,
priorityRaw: _credentials.priority.serialize(),
accountIndex: walletAddresses.account.id);
if (listSize > 1) {
final sendAllItems = sendItemList.where((item) => item.sendAll).toList();
if (sendAllItems?.isNotEmpty ?? false) {
throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.');
}
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);
}

View file

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

View file

@ -1,7 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/electrum_wallet.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/parsed_address.dart';
import 'package:cake_wallet/monero/monero_amount_format.dart';

View file

@ -199,18 +199,11 @@ abstract class SendViewModelBase with Store {
return BitcoinTransactionCredentials(
sendItemList, priority as BitcoinTransactionPriority);
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];
return MoneroTransactionCreationCredentials(
address: address,
paymentId: '',
priority: priority as MoneroTransactionPriority,
amount: amount);
sendItemList: sendItemList,
priority: priority as MoneroTransactionPriority);
default:
return null;
}

View file

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