mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-23 03:59:23 +00:00
Integrate EtherScan API for fetching address transactions
Generate Ethereum specific secrets in Ethereum package
This commit is contained in:
parent
fade15d65b
commit
09fd6a8241
14 changed files with 343 additions and 131 deletions
1
.github/workflows/pr_test_build.yml
vendored
1
.github/workflows/pr_test_build.yml
vendored
|
@ -118,6 +118,7 @@ jobs:
|
|||
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
|
||||
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
|
||||
|
||||
- name: Rename app
|
||||
run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -89,7 +89,9 @@ android/key.properties
|
|||
**/tool/.secrets-prod.json
|
||||
**/tool/.secrets-test.json
|
||||
**/tool/.secrets-config.json
|
||||
**/tool/.ethereum-secrets-config.json
|
||||
**/lib/.secrets.g.dart
|
||||
**/cw_ethereum/lib/.secrets.g.dart
|
||||
|
||||
vendor/
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_ethereum/ethereum_balance.dart';
|
||||
import 'package:cw_ethereum/erc20_balance.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_ethereum/ethereum_transaction_model.dart';
|
||||
import 'package:cw_ethereum/pending_ethereum_transaction.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
@ -11,6 +13,7 @@ import 'package:web3dart/web3dart.dart';
|
|||
import 'package:web3dart/contracts/erc20.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
|
||||
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
|
||||
|
||||
class EthereumClient {
|
||||
Web3Client? _client;
|
||||
|
@ -53,7 +56,6 @@ class EthereumClient {
|
|||
// final eventFilter = FilterOptions(address: userAddress);
|
||||
//
|
||||
// _client!.events(eventFilter).listen((event) {
|
||||
// print("!!!!!!!!!!!!!!!!!!");
|
||||
// print('Address ${event.address} data ${event.data} tx hash ${event.transactionHash}!');
|
||||
// onNewTransaction(event);
|
||||
// });
|
||||
|
@ -61,7 +63,6 @@ class EthereumClient {
|
|||
// final erc20 = Erc20(client: _client!, address: userAddress);
|
||||
//
|
||||
// subscription = erc20.transferEvents().take(1).listen((event) {
|
||||
// print("!!!!!!!!!!!!!!!!!!");
|
||||
// print('${event.from} sent ${event.value} MetaCoins to ${event.to}!');
|
||||
// onNewTransaction(event);
|
||||
// });
|
||||
|
@ -155,7 +156,6 @@ class EthereumClient {
|
|||
// Wait for the transaction receipt to become available
|
||||
TransactionReceipt? receipt;
|
||||
while (receipt == null) {
|
||||
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
|
||||
receipt = await _client!.getTransactionReceipt(transactionHash);
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
|
@ -240,6 +240,29 @@ I/flutter ( 4474): Gas Used: 53000
|
|||
_client?.dispose();
|
||||
}
|
||||
|
||||
Future<List<EthereumTransactionModel>> fetchTransactions(String address,
|
||||
{String? contractAddress}) async {
|
||||
final client = Client();
|
||||
|
||||
final response = await client.get(Uri.https("api.etherscan.io", "/api", {
|
||||
"module": "account",
|
||||
"action": contractAddress != null ? "tokentx" : "txlist",
|
||||
if (contractAddress != null) "contractaddress": contractAddress,
|
||||
"address": address,
|
||||
"apikey": secrets.etherScanApiKey,
|
||||
}));
|
||||
|
||||
final _jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300 && _jsonResponse['status'] != 0) {
|
||||
return (_jsonResponse['result'] as List)
|
||||
.map((e) => EthereumTransactionModel.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// Future<int> _getDecimalPlacesForContract(DeployedContract contract) async {
|
||||
// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
|
||||
// final contractAbi = ContractAbi.fromJson(abi, "ERC20");
|
||||
|
|
|
@ -1,27 +1,68 @@
|
|||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
|
||||
class EthereumTransactionInfo extends TransactionInfo {
|
||||
@override
|
||||
String amountFormatted() {
|
||||
// TODO: implement amountFormatted
|
||||
throw UnimplementedError();
|
||||
}
|
||||
EthereumTransactionInfo({
|
||||
required this.id,
|
||||
required this.height,
|
||||
required this.amount,
|
||||
required this.fee,
|
||||
this.tokenSymbol = "ETH",
|
||||
this.exponent = 18,
|
||||
required this.direction,
|
||||
required this.isPending,
|
||||
required this.date,
|
||||
required this.confirmations,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final int height;
|
||||
final int amount;
|
||||
final int exponent;
|
||||
final TransactionDirection direction;
|
||||
final DateTime date;
|
||||
final bool isPending;
|
||||
final int fee;
|
||||
final int confirmations;
|
||||
final String tokenSymbol;
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) {
|
||||
// TODO: implement changeFiatAmount
|
||||
}
|
||||
String amountFormatted() =>
|
||||
'${formatAmount((BigInt.from(amount) / BigInt.from(10).pow(exponent)).toString())} $tokenSymbol';
|
||||
|
||||
@override
|
||||
String? feeFormatted() {
|
||||
// TODO: implement feeFormatted
|
||||
throw UnimplementedError();
|
||||
}
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
String fiatAmount() {
|
||||
// TODO: implement fiatAmount
|
||||
throw UnimplementedError();
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
|
||||
@override
|
||||
String feeFormatted() => '${(BigInt.from(fee) / BigInt.from(10).pow(exponent)).toString()} ETH';
|
||||
|
||||
factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||
return EthereumTransactionInfo(
|
||||
id: data['id'] as String,
|
||||
height: data['height'] as int,
|
||||
amount: data['amount'] as int,
|
||||
fee: data['fee'] as int,
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||
isPending: data['isPending'] as bool,
|
||||
confirmations: data['confirmations'] as int);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final m = <String, dynamic>{};
|
||||
m['id'] = id;
|
||||
m['height'] = height;
|
||||
m['amount'] = amount;
|
||||
m['direction'] = direction.index;
|
||||
m['date'] = date.millisecondsSinceEpoch;
|
||||
m['isPending'] = isPending;
|
||||
m['confirmations'] = confirmations;
|
||||
m['fee'] = fee;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
|
44
cw_ethereum/lib/ethereum_transaction_model.dart
Normal file
44
cw_ethereum/lib/ethereum_transaction_model.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
class EthereumTransactionModel {
|
||||
final DateTime date;
|
||||
final String hash;
|
||||
final String from;
|
||||
final String to;
|
||||
final BigInt amount;
|
||||
final int gasUsed;
|
||||
final BigInt gasPrice;
|
||||
final String contractAddress;
|
||||
final int confirmations;
|
||||
final int blockNumber;
|
||||
final String? tokenSymbol;
|
||||
final int? tokenDecimal;
|
||||
|
||||
EthereumTransactionModel({
|
||||
required this.date,
|
||||
required this.hash,
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.amount,
|
||||
required this.gasUsed,
|
||||
required this.gasPrice,
|
||||
required this.contractAddress,
|
||||
required this.confirmations,
|
||||
required this.blockNumber,
|
||||
required this.tokenSymbol,
|
||||
required this.tokenDecimal,
|
||||
});
|
||||
|
||||
factory EthereumTransactionModel.fromJson(Map<String, dynamic> json) => EthereumTransactionModel(
|
||||
date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
|
||||
hash: json["hash"],
|
||||
from: json["from"],
|
||||
to: json["to"],
|
||||
amount: BigInt.parse(json["value"]),
|
||||
gasUsed: int.parse(json["gasUsed"]),
|
||||
gasPrice: BigInt.parse(json["gasPrice"]),
|
||||
contractAddress: json["contractAddress"],
|
||||
confirmations: int.parse(json["confirmations"]),
|
||||
blockNumber: int.parse(json["blockNumber"]),
|
||||
tokenSymbol: json["tokenSymbol"] ?? "ETH",
|
||||
tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
|
||||
);
|
||||
}
|
|
@ -7,12 +7,13 @@ import 'package:cw_core/node.dart';
|
|||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_ethereum/default_erc20_tokens.dart';
|
||||
import 'package:cw_ethereum/ethereum_balance.dart';
|
||||
import 'package:cw_ethereum/erc20_balance.dart';
|
||||
import 'package:cw_ethereum/ethereum_client.dart';
|
||||
import 'package:cw_ethereum/ethereum_exceptions.dart';
|
||||
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
|
||||
|
@ -45,6 +46,7 @@ abstract class EthereumWalletBase
|
|||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
_priorityFees = [],
|
||||
_isTransactionUpdating = false,
|
||||
_client = EthereumClient(),
|
||||
walletAddresses = EthereumWalletAddresses(walletInfo),
|
||||
balance = ObservableMap<CryptoCurrency, ERC20Balance>.of(
|
||||
|
@ -68,6 +70,7 @@ abstract class EthereumWalletBase
|
|||
|
||||
List<int> _priorityFees;
|
||||
int? _gasPrice;
|
||||
bool _isTransactionUpdating;
|
||||
|
||||
@override
|
||||
WalletAddresses walletAddresses;
|
||||
|
@ -174,9 +177,56 @@ abstract class EthereumWalletBase
|
|||
return pendingEthereumTransaction;
|
||||
}
|
||||
|
||||
Future<void> updateTransactions() async {
|
||||
try {
|
||||
if (_isTransactionUpdating) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isTransactionUpdating = true;
|
||||
final transactions = await fetchTransactions();
|
||||
transactionHistory.addMany(transactions);
|
||||
await transactionHistory.save();
|
||||
_isTransactionUpdating = false;
|
||||
} catch (_) {
|
||||
_isTransactionUpdating = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, EthereumTransactionInfo>> fetchTransactions() {
|
||||
throw UnimplementedError("fetchTransactions");
|
||||
Future<Map<String, EthereumTransactionInfo>> fetchTransactions() async {
|
||||
final address = _privateKey.address.hex;
|
||||
final transactions = await _client.fetchTransactions(address);
|
||||
|
||||
for (var token in balance.keys) {
|
||||
if (token is Erc20Token) {
|
||||
transactions.addAll(await _client.fetchTransactions(
|
||||
address,
|
||||
contractAddress: token.contractAddress,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, EthereumTransactionInfo> result = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
result[transactionModel.hash] = EthereumTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
height: transactionModel.blockNumber,
|
||||
amount: transactionModel.amount.toInt(),
|
||||
direction: transactionModel.from == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
isPending: false,
|
||||
date: transactionModel.date,
|
||||
confirmations: transactionModel.confirmations,
|
||||
fee: transactionModel.gasUsed * transactionModel.gasPrice.toInt(),
|
||||
exponent: transactionModel.tokenDecimal ?? 18,
|
||||
tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -204,6 +254,7 @@ abstract class EthereumWalletBase
|
|||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
await _updateBalance();
|
||||
await updateTransactions();
|
||||
_gasPrice = await _client.getGasUnitPrice();
|
||||
_priorityFees = await _client.getEstimatedGasForPriorities();
|
||||
|
||||
|
@ -354,8 +405,7 @@ abstract class EthereumWalletBase
|
|||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||
final currentWalletFile = File(currentWalletPath);
|
||||
|
||||
final currentDirPath =
|
||||
await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
// TODO: un-hash when transactions flow is implemented
|
||||
// final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
|
||||
|
||||
|
|
|
@ -80,6 +80,13 @@ class CWEthereum extends Ethereum {
|
|||
@override
|
||||
int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
|
||||
|
||||
@override
|
||||
double formatterEthereumAmountToDouble({required TransactionInfo transaction}) {
|
||||
transaction as EthereumTransactionInfo;
|
||||
return cryptoAmountToDouble(
|
||||
amount: transaction.amount, divider: BigInt.from(10).pow(transaction.exponent).toInt());
|
||||
}
|
||||
|
||||
@override
|
||||
List<Erc20Token> getERC20Currencies(WalletBase wallet) {
|
||||
final ethereumWallet = wallet as EthereumWallet;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
|
@ -84,6 +85,11 @@ class TransactionListItem extends ActionListItem with Keyable {
|
|||
cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
|
||||
price: price);
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
|
||||
price: price);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:cw_core/transaction_direction.dart';
|
|||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/src/intl/date_format.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -27,105 +28,27 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
required this.wallet,
|
||||
required this.settingsStore})
|
||||
: items = [],
|
||||
isRecipientAddressShown = false,
|
||||
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
|
||||
isRecipientAddressShown = false,
|
||||
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
|
||||
final dateFormat = DateFormatter.withCurrentLocal();
|
||||
final tx = transactionInfo;
|
||||
|
||||
if (wallet.type == WalletType.monero) {
|
||||
final key = tx.additionalInfo['key'] as String?;
|
||||
final accountIndex = tx.additionalInfo['accountIndex'] as int;
|
||||
final addressIndex = tx.additionalInfo['addressIndex'] as int;
|
||||
final feeFormatted = tx.feeFormatted();
|
||||
final _items = [
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date,
|
||||
value: dateFormat.format(tx.date)),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_amount,
|
||||
value: tx.amountFormatted()),
|
||||
if (feeFormatted != null)
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_fee, value: feeFormatted),
|
||||
if (key?.isNotEmpty ?? false)
|
||||
StandartListItem(title: S.current.transaction_key, value: key!)
|
||||
];
|
||||
|
||||
if (tx.direction == TransactionDirection.incoming &&
|
||||
accountIndex != null &&
|
||||
addressIndex != null) {
|
||||
try {
|
||||
final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
|
||||
final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
|
||||
|
||||
if (address?.isNotEmpty ?? false) {
|
||||
isRecipientAddressShown = true;
|
||||
_items.add(
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: address));
|
||||
}
|
||||
|
||||
if (label?.isNotEmpty ?? false) {
|
||||
_items.add(
|
||||
StandartListItem(
|
||||
title: S.current.address_label,
|
||||
value: label)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.bitcoin
|
||||
|| wallet.type == WalletType.litecoin) {
|
||||
final _items = [
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date,
|
||||
value: dateFormat.format(tx.date)),
|
||||
StandartListItem(
|
||||
title: S.current.confirmations,
|
||||
value: tx.confirmations.toString()),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_amount,
|
||||
value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_fee,
|
||||
value: tx.feeFormatted()!),
|
||||
];
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.haven) {
|
||||
items.addAll([
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date,
|
||||
value: dateFormat.format(tx.date)),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_amount,
|
||||
value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||
]);
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
_addMoneroListItems(tx, dateFormat);
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
_addElectrumListItems(tx, dateFormat);
|
||||
break;
|
||||
case WalletType.haven:
|
||||
_addHavenListItems(tx, dateFormat);
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
_addEthereumListItems(tx, dateFormat);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (showRecipientAddress && !isRecipientAddressShown) {
|
||||
|
@ -136,10 +59,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
|
||||
if (recipientAddress?.isNotEmpty ?? false) {
|
||||
items.add(StandartListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: recipientAddress!));
|
||||
title: S.current.transaction_details_recipient_address, value: recipientAddress!));
|
||||
}
|
||||
} catch(_) {
|
||||
} catch (_) {
|
||||
// FIX-ME: Unhandled exception
|
||||
}
|
||||
}
|
||||
|
@ -211,4 +133,86 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
void _addMoneroListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
final key = tx.additionalInfo['key'] as String?;
|
||||
final accountIndex = tx.additionalInfo['accountIndex'] as int;
|
||||
final addressIndex = tx.additionalInfo['addressIndex'] as int;
|
||||
final feeFormatted = tx.feeFormatted();
|
||||
final _items = [
|
||||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
if (feeFormatted != null)
|
||||
StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted),
|
||||
if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!),
|
||||
];
|
||||
|
||||
if (tx.direction == TransactionDirection.incoming) {
|
||||
try {
|
||||
final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
|
||||
final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
|
||||
|
||||
if (address.isNotEmpty) {
|
||||
isRecipientAddressShown = true;
|
||||
_items.add(StandartListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: address,
|
||||
));
|
||||
}
|
||||
|
||||
if (label.isNotEmpty) {
|
||||
_items.add(StandartListItem(title: S.current.address_label, value: label));
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
void _addElectrumListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
final _items = [
|
||||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
|
||||
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||
];
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
items.addAll([
|
||||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||
]);
|
||||
}
|
||||
|
||||
void _addEthereumListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
final _items = [
|
||||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
|
||||
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||
];
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -480,9 +480,11 @@ Future<void> generateEthereum(bool hasImplementation) async {
|
|||
""";
|
||||
const ethereumCWHeaders = """
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
|
@ -491,6 +493,7 @@ import 'package:cw_core/wallet_service.dart';
|
|||
import 'package:cw_ethereum/ethereum_formatter.dart';
|
||||
import 'package:cw_ethereum/ethereum_mnemonics.dart';
|
||||
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
|
||||
import 'package:cw_ethereum/ethereum_transaction_info.dart';
|
||||
import 'package:cw_ethereum/ethereum_wallet.dart';
|
||||
import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
|
||||
import 'package:cw_ethereum/ethereum_wallet_service.dart';
|
||||
|
@ -525,6 +528,7 @@ abstract class Ethereum {
|
|||
});
|
||||
|
||||
int formatterEthereumParseAmount(String amount);
|
||||
double formatterEthereumAmountToDouble({required TransactionInfo transaction});
|
||||
List<Erc20Token> getERC20Currencies(WalletBase wallet);
|
||||
Future<void> addErc20Token(WalletBase wallet, Erc20Token token);
|
||||
Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token);
|
||||
|
|
|
@ -4,12 +4,12 @@ import 'utils/secret_key.dart';
|
|||
import 'utils/utils.dart';
|
||||
|
||||
const configPath = 'tool/.secrets-config.json';
|
||||
const ethereumConfigPath = 'tool/.ethereum-secrets-config.json';
|
||||
|
||||
Future<void> main(List<String> args) async => generateSecretsConfig(args);
|
||||
|
||||
Future<void> generateSecretsConfig(List<String> args) async {
|
||||
final extraInfo =
|
||||
args.fold(<String, dynamic>{}, (Map<String, dynamic> acc, String arg) {
|
||||
final extraInfo = args.fold(<String, dynamic>{}, (Map<String, dynamic> acc, String arg) {
|
||||
final parts = arg.split('=');
|
||||
final key = normalizeKeyName(parts[0]);
|
||||
acc[key] = acc[key] = parts.length > 1 ? parts[1] : 1;
|
||||
|
@ -17,6 +17,7 @@ Future<void> generateSecretsConfig(List<String> args) async {
|
|||
});
|
||||
|
||||
final configFile = File(configPath);
|
||||
final ethereumConfigFile = File(ethereumConfigPath);
|
||||
final secrets = <String, dynamic>{};
|
||||
|
||||
secrets.addAll(extraInfo);
|
||||
|
@ -44,6 +45,19 @@ Future<void> generateSecretsConfig(List<String> args) async {
|
|||
secrets[sec.name] = sec.generate();
|
||||
});
|
||||
|
||||
final secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
|
||||
var secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
|
||||
await configFile.writeAsString(secretsJson);
|
||||
|
||||
secrets.clear();
|
||||
SecretKey.ethereumSecrets.forEach((sec) {
|
||||
if (secrets[sec.name] != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
secrets[sec.name] = sec.generate();
|
||||
});
|
||||
|
||||
secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
|
||||
|
||||
await ethereumConfigFile.writeAsString(secretsJson);
|
||||
}
|
||||
|
|
|
@ -5,19 +5,31 @@ import 'utils/utils.dart';
|
|||
const configPath = 'tool/.secrets-config.json';
|
||||
const outputPath = 'lib/.secrets.g.dart';
|
||||
|
||||
const ethereumConfigPath = 'tool/.ethereum-secrets-config.json';
|
||||
const ethereumOutputPath = 'cw_ethereum/lib/.secrets.g.dart';
|
||||
|
||||
Future<void> main(List<String> args) async => importSecretsConfig();
|
||||
|
||||
Future<void> importSecretsConfig() async {
|
||||
final outputFile = File(outputPath);
|
||||
final input = json.decode(File(configPath).readAsStringSync())
|
||||
as Map<String, dynamic> ??
|
||||
<String, dynamic>{};
|
||||
final output = input.keys
|
||||
.fold('', (String acc, String val) => acc + generateConst(val, input));
|
||||
final input = json.decode(File(configPath).readAsStringSync()) as Map<String, dynamic>;
|
||||
final output = input.keys.fold('', (String acc, String val) => acc + generateConst(val, input));
|
||||
|
||||
final ethereumOutputFile = File(ethereumOutputPath);
|
||||
final ethereumInput =
|
||||
json.decode(File(ethereumConfigPath).readAsStringSync()) as Map<String, dynamic>;
|
||||
final ethereumOutput = ethereumInput.keys
|
||||
.fold('', (String acc, String val) => acc + generateConst(val, ethereumInput));
|
||||
|
||||
if (outputFile.existsSync()) {
|
||||
await outputFile.delete();
|
||||
}
|
||||
|
||||
await outputFile.writeAsString(output);
|
||||
|
||||
if (ethereumOutputFile.existsSync()) {
|
||||
await ethereumOutputFile.delete();
|
||||
}
|
||||
|
||||
await ethereumOutputFile.writeAsString(ethereumOutput);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,10 @@ class SecretKey {
|
|||
SecretKey('payfuraApiKey', () => ''),
|
||||
];
|
||||
|
||||
static final ethereumSecrets = [
|
||||
SecretKey('etherScanApiKey', () => ''),
|
||||
];
|
||||
|
||||
final String name;
|
||||
final String Function() generate;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue