Integrate EtherScan API for fetching address transactions

Generate Ethereum specific secrets in Ethereum package
This commit is contained in:
OmarHatem 2023-07-21 04:08:45 +03:00
parent fade15d65b
commit 09fd6a8241
14 changed files with 343 additions and 131 deletions

View file

@ -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
View file

@ -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/

View file

@ -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");

View file

@ -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;
}
}

View 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"] ?? ""),
);
}

View file

@ -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');

View file

@ -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;

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
}

View file

@ -33,6 +33,10 @@ class SecretKey {
SecretKey('payfuraApiKey', () => ''),
];
static final ethereumSecrets = [
SecretKey('etherScanApiKey', () => ''),
];
final String name;
final String Function() generate;
}