Add initial send transaction flow

This commit is contained in:
OmarHatem 2023-03-16 19:24:21 +02:00
parent 405cf2aaab
commit 268a6c3a05
14 changed files with 219 additions and 23 deletions

View file

@ -202,7 +202,7 @@ packages:
source: hosted
version: "2.0.1"
file:
dependency: transitive
dependency: "direct main"
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"

View file

@ -31,11 +31,27 @@ class EthereumClient {
final result = await Future.wait(EthereumTransactionPriority.all.map(
(priority) => _client!.estimateGas(
maxPriorityFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip),
maxFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, 100),
// maxFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, 100),
),
));
return result.map((e) => e.toInt()).toList();
}
Future<String> sendTransaction(String privateKey, String toAddress, String amount) async {
final credentials = EthPrivateKey.fromHex(privateKey);
final transaction = Transaction(
from: credentials.address,
to: EthereumAddress.fromHex(toAddress),
value: EtherAmount.fromUnitAndValue(EtherUnit.ether, amount),
);
await _client!.signTransaction(credentials, transaction);
return await _client!.sendTransaction(
credentials,
transaction,
);
}
}

View file

@ -0,0 +1,6 @@
class EthereumTransactionCreationException implements Exception {
EthereumTransactionCreationException();
@override
String toString() => 'Wrong balance. Not enough Ether on your balance.';
}

View file

@ -0,0 +1,6 @@
import 'package:web3dart/web3dart.dart';
class EthereumFormatter {
static int parseEthereumAmount(String amount) =>
EtherAmount.fromUnitAndValue(EtherUnit.ether, amount).getInWei.toInt();
}

View file

@ -0,0 +1,10 @@
import 'package:cw_core/output_info.dart';
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
class EthereumTransactionCredentials {
EthereumTransactionCredentials(this.outputs, {required this.priority, this.feeRate});
final List<OutputInfo> outputs;
final EthereumTransactionPriority? priority;
final int? feeRate;
}

View file

@ -12,11 +12,14 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_ethereum/ethereum_balance.dart';
import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_exceptions.dart';
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
import 'package:cw_ethereum/ethereum_transaction_history.dart';
import 'package:cw_ethereum/ethereum_transaction_info.dart';
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
import 'package:cw_ethereum/file.dart';
import 'package:cw_ethereum/pending_ethereum_transaction.dart';
import 'package:mobx/mobx.dart';
import 'package:web3dart/web3dart.dart';
import 'package:ed25519_hd_key/ed25519_hd_key.dart';
@ -50,7 +53,7 @@ abstract class EthereumWalletBase
final String _mnemonic;
final String _password;
late final String privateKey;
late final String _privateKey;
late EthereumClient _client;
@ -69,9 +72,9 @@ abstract class EthereumWalletBase
late ObservableMap<CryptoCurrency, EthereumBalance> balance;
Future<void> init() async {
privateKey = await getPrivateKey(_mnemonic, _password);
_privateKey = await getPrivateKey(_mnemonic, _password);
transactionHistory = EthereumTransactionHistory();
walletAddresses.address = EthPrivateKey.fromHex(privateKey).address.toString();
walletAddresses.address = EthPrivateKey.fromHex(_privateKey).address.toString();
}
@override
@ -116,8 +119,78 @@ abstract class EthereumWalletBase
}
@override
Future<PendingTransaction> createTransaction(Object credentials) {
throw UnimplementedError("createTransaction");
Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as EthereumTransactionCredentials;
final outputs = _credentials.outputs;
final hasMultiDestination = outputs.length > 1;
final balance = await _client.getBalance(_privateKey);
if (hasMultiDestination) {
outputs.any((element) => element.sendAll);
}
// if (hasMultiDestination) {
// if (outputs.any((item) => item.sendAll
// || (item.formattedCryptoAmount ?? 0) <= 0)) {
// throw EthereumTransactionCreationException();
// }
//
// final BigInt totalAmount = outputs.fold(0, (acc, value) =>
// acc + (value.formattedCryptoAmount ?? 0));
//
// if (balance.getInWei < EtherAmount.inWei(totalAmount)) {
// throw MoneroTransactionCreationException('Wrong balance. Not enough XMR on your balance.');
// }
//
// final moneroOutputs = outputs.map((output) {
// final outputAddress = output.isParsedAddress
// ? output.extractedAddress
// : output.address;
//
// return MoneroOutput(
// address: outputAddress!,
// amount: output.cryptoAmount!.replaceAll(',', '.'));
// }).toList();
//
// pendingTransactionDescription =
// await transaction_history.createTransactionMultDest(
// outputs: moneroOutputs,
// priorityRaw: _credentials.priority.serialize(),
// accountIndex: walletAddresses.account!.id);
// } else {
// final output = outputs.first;
// final address = output.isParsedAddress
// ? output.extractedAddress
// : output.address;
// final amount = output.sendAll
// ? null
// : output.cryptoAmount!.replaceAll(',', '.');
// final formattedAmount = output.sendAll
// ? null
// : output.formattedCryptoAmount;
//
// if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
// (formattedAmount == null && unlockedBalance <= 0)) {
// final formattedBalance = moneroAmountToString(amount: unlockedBalance);
//
// throw MoneroTransactionCreationException(
// 'Incorrect unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.');
// }
//
// pendingTransactionDescription =
// await transaction_history.createTransaction(
// address: address!,
// amount: amount,
// priorityRaw: _credentials.priority.serialize(),
// accountIndex: walletAddresses.account!.id);
// }
return PendingEthereumTransaction(
client: _client,
credentials: _credentials,
privateKey: _privateKey,
);
}
@override
@ -211,7 +284,7 @@ abstract class EthereumWalletBase
}
Future<EthereumBalance> _fetchBalances() async {
final balance = await _client.getBalance(privateKey);
final balance = await _client.getBalance(_privateKey);
return EthereumBalance(
available: balance.getInEther.toInt(),

View file

@ -0,0 +1,38 @@
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
class PendingEthereumTransaction with PendingTransaction {
final EthereumClient client;
final EthereumTransactionCredentials credentials;
final String privateKey;
PendingEthereumTransaction({
required this.client,
required this.credentials,
required this.privateKey,
});
@override
// TODO: implement amountFormatted
String get amountFormatted => throw UnimplementedError();
@override
Future<void> commit() async {
for (var output in credentials.outputs) {
await client.sendTransaction(privateKey, output.address, output.cryptoAmount!);
}
}
@override
// TODO: implement feeFormatted
String get feeFormatted => throw UnimplementedError();
@override
// TODO: implement hex
String get hex => throw UnimplementedError();
@override
// TODO: implement id
String get id => throw UnimplementedError();
}

View file

@ -204,10 +204,10 @@ packages:
dependency: "direct main"
description:
name: ffi
sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18"
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "2.0.1"
file:
dependency: transitive
description:
@ -454,10 +454,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905
sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130
url: "https://pub.dev"
source: hosted
version: "2.0.7"
version: "2.1.5"
platform:
dependency: transitive
description:
@ -651,10 +651,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
url: "https://pub.dev"
source: hosted
version: "2.6.1"
version: "3.1.3"
xdg_directories:
dependency: transitive
description:

View file

@ -204,10 +204,10 @@ packages:
dependency: "direct main"
description:
name: ffi
sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18"
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "2.0.1"
file:
dependency: transitive
description:
@ -454,10 +454,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905
sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130
url: "https://pub.dev"
source: hosted
version: "2.0.7"
version: "2.1.5"
platform:
dependency: transitive
description:
@ -651,10 +651,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
url: "https://pub.dev"
source: hosted
version: "2.6.1"
version: "3.1.3"
xdg_directories:
dependency: transitive
description:

View file

@ -80,7 +80,7 @@ class CWBitcoin extends Bitcoin {
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority != null ? priority as BitcoinTransactionPriority : null,
priority: priority as BitcoinTransactionPriority,
feeRate: feeRate);
@override

View file

@ -40,4 +40,33 @@ class CWEthereum extends Ethereum {
final ethereumWallet = wallet as EthereumWallet;
return ethereumWallet.feeRate(priority);
}
Object createEthereumTransactionCredentials(List<Output> outputs,
{required TransactionPriority priority, int? feeRate}) =>
EthereumTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as EthereumTransactionPriority,
feeRate: feeRate,
);
Object createEthereumTransactionCredentialsRaw(List<OutputInfo> outputs,
{TransactionPriority? priority, required int feeRate}) =>
EthereumTransactionCredentials(
outputs,
priority: priority as EthereumTransactionPriority,
feeRate: feeRate,
);
@override
int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
}

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
import 'package:cw_core/crypto_currency.dart';
@ -70,7 +71,7 @@ abstract class OutputBase with Store {
int amount = 0;
try {
if (cryptoAmount?.isNotEmpty ?? false) {
if (cryptoAmount.isNotEmpty) {
final _cryptoAmount = cryptoAmount.replaceAll(',', '.');
int _amount = 0;
switch (walletType) {
@ -88,6 +89,9 @@ abstract class OutputBase with Store {
case WalletType.haven:
_amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
break;
case WalletType.ethereum:
_amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount);
break;
default:
break;
}

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/view_model/send/output.dart';
@ -286,6 +287,14 @@ abstract class SendViewModelBase with Store {
return haven!.createHavenTransactionCreationCredentials(
outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title);
case WalletType.ethereum:
final priority = _settingsStore.priority[_wallet.type];
if (priority == null) {
throw Exception('Priority is null for wallet type: ${_wallet.type}');
}
return ethereum!.createEthereumTransactionCredentials(outputs, priority: priority);
default:
throw Exception('Unexpected wallet type: ${_wallet.type}');
}

View file

@ -477,6 +477,8 @@ Future<void> generateEthereum(bool hasImplementation) async {
const ethereumCommonHeaders = """
""";
const ethereumCWHeaders = """
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
@ -501,6 +503,9 @@ abstract class Ethereum {
List<TransactionPriority> getTransactionPriorities();
TransactionPriority deserializeEthereumTransactionPriority(int raw);
int getEstimatedFee(Object wallet, TransactionPriority priority);
Object createEthereumTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate});
Object createEthereumTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority? priority, required int feeRate});
}
""";