mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +00:00
CAKE-345 | applied batch sending to monero wallet
This commit is contained in:
parent
d582f89c7d
commit
d4c0fb6fec
10 changed files with 230 additions and 35 deletions
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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: () {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue