2023-05-26 21:21:16 +00:00
|
|
|
/*
|
|
|
|
* This file is part of Stack Wallet.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2023 Cypher Stack
|
|
|
|
* All Rights Reserved.
|
|
|
|
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
|
|
|
* Generated by Cypher Stack on 2023-05-26
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2023-02-23 22:59:58 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
import 'package:http/http.dart';
|
2023-03-02 21:07:25 +00:00
|
|
|
import 'package:stackwallet/dto/ethereum/eth_token_tx_dto.dart';
|
2023-03-23 18:19:51 +00:00
|
|
|
import 'package:stackwallet/dto/ethereum/eth_token_tx_extra_dto.dart';
|
2023-03-02 21:07:25 +00:00
|
|
|
import 'package:stackwallet/dto/ethereum/eth_tx_dto.dart';
|
2023-03-31 21:38:22 +00:00
|
|
|
import 'package:stackwallet/dto/ethereum/pending_eth_tx_dto.dart';
|
2023-03-03 00:40:12 +00:00
|
|
|
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
|
2023-02-23 22:59:58 +00:00
|
|
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
2023-09-08 22:53:09 +00:00
|
|
|
import 'package:stackwallet/networking/http.dart';
|
|
|
|
import 'package:stackwallet/services/tor_service.dart';
|
2023-04-07 22:02:28 +00:00
|
|
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
2023-03-02 19:28:51 +00:00
|
|
|
import 'package:stackwallet/utilities/default_nodes.dart';
|
2023-02-23 22:59:58 +00:00
|
|
|
import 'package:stackwallet/utilities/eth_commons.dart';
|
2023-04-03 17:25:56 +00:00
|
|
|
import 'package:stackwallet/utilities/extensions/extensions.dart';
|
2023-02-23 22:59:58 +00:00
|
|
|
import 'package:stackwallet/utilities/logger.dart';
|
2023-09-08 22:53:09 +00:00
|
|
|
import 'package:stackwallet/utilities/prefs.dart';
|
2023-03-31 16:15:42 +00:00
|
|
|
import 'package:tuple/tuple.dart';
|
2023-02-23 22:59:58 +00:00
|
|
|
|
2023-05-30 16:41:27 +00:00
|
|
|
class EthApiException implements Exception {
|
2023-02-28 22:03:36 +00:00
|
|
|
EthApiException(this.message);
|
|
|
|
|
|
|
|
final String message;
|
|
|
|
|
|
|
|
@override
|
|
|
|
String toString() => "$runtimeType: $message";
|
|
|
|
}
|
|
|
|
|
2023-02-23 22:59:58 +00:00
|
|
|
class EthereumResponse<T> {
|
2023-02-27 17:42:22 +00:00
|
|
|
EthereumResponse(this.value, this.exception);
|
|
|
|
|
2023-02-23 22:59:58 +00:00
|
|
|
final T? value;
|
2023-02-28 22:03:36 +00:00
|
|
|
final EthApiException? exception;
|
2023-02-23 22:59:58 +00:00
|
|
|
|
2023-02-27 17:42:22 +00:00
|
|
|
@override
|
2023-03-02 19:28:51 +00:00
|
|
|
toString() => "EthereumResponse: { value: $value, exception: $exception }";
|
2023-02-23 22:59:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
abstract class EthereumAPI {
|
2023-03-02 19:28:51 +00:00
|
|
|
static String get stackBaseServer => DefaultNodes.ethereum.host;
|
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
static HTTP client = HTTP();
|
|
|
|
|
2023-06-15 22:39:42 +00:00
|
|
|
static Future<EthereumResponse<List<EthTxDTO>>> getEthTransactions({
|
|
|
|
required String address,
|
|
|
|
int firstBlock = 0,
|
2023-07-20 17:34:31 +00:00
|
|
|
bool includeTokens = false,
|
2023-06-15 22:39:42 +00:00
|
|
|
}) async {
|
2023-02-23 22:59:58 +00:00
|
|
|
try {
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: Uri.parse(
|
2023-06-15 22:39:42 +00:00
|
|
|
"$stackBaseServer/export?addrs=$address&firstBlock=$firstBlock",
|
2023-03-02 19:28:51 +00:00
|
|
|
),
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-03-02 19:28:51 +00:00
|
|
|
);
|
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
if (response.code == 200) {
|
2023-03-02 21:07:25 +00:00
|
|
|
if (response.body.isNotEmpty) {
|
|
|
|
final json = jsonDecode(response.body) as Map;
|
|
|
|
final list = json["data"] as List?;
|
|
|
|
|
|
|
|
final List<EthTxDTO> txns = [];
|
|
|
|
for (final map in list!) {
|
2023-03-03 00:40:12 +00:00
|
|
|
final txn = EthTxDTO.fromMap(Map<String, dynamic>.from(map as Map));
|
2023-03-03 17:36:40 +00:00
|
|
|
|
2023-10-28 16:20:17 +00:00
|
|
|
if (!txn.hasToken || includeTokens) {
|
2023-03-03 00:40:12 +00:00
|
|
|
txns.add(txn);
|
|
|
|
}
|
2023-03-02 21:07:25 +00:00
|
|
|
}
|
|
|
|
return EthereumResponse(
|
|
|
|
txns,
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
} else {
|
2023-07-20 17:34:31 +00:00
|
|
|
// nice that the api returns an empty body instead of being
|
|
|
|
// consistent and returning a json object with no transactions
|
|
|
|
return EthereumResponse(
|
|
|
|
[],
|
|
|
|
null,
|
2023-03-02 21:07:25 +00:00
|
|
|
);
|
|
|
|
}
|
2023-02-23 22:59:58 +00:00
|
|
|
} else {
|
2023-03-02 21:07:25 +00:00
|
|
|
throw EthApiException(
|
|
|
|
"getEthTransactions($address) failed with status code: "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-03-02 21:07:25 +00:00
|
|
|
);
|
2023-02-23 22:59:58 +00:00
|
|
|
}
|
2023-03-02 21:07:25 +00:00
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
2023-02-23 22:59:58 +00:00
|
|
|
} catch (e, s) {
|
2023-03-02 21:07:25 +00:00
|
|
|
Logging.instance.log(
|
2023-03-23 18:19:51 +00:00
|
|
|
"getEthTransactions($address): $e\n$s",
|
2023-03-02 21:07:25 +00:00
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
EthApiException(e.toString()),
|
|
|
|
);
|
2023-02-23 22:59:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-31 21:38:22 +00:00
|
|
|
static Future<EthereumResponse<PendingEthTxDto>> getEthTransactionByHash(
|
|
|
|
String txid) async {
|
|
|
|
try {
|
|
|
|
final response = await post(
|
|
|
|
Uri.parse(
|
|
|
|
"$stackBaseServer/v1/mainnet",
|
|
|
|
),
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
body: json.encode({
|
|
|
|
"jsonrpc": "2.0",
|
|
|
|
"method": "eth_getTransactionByHash",
|
|
|
|
"params": [
|
|
|
|
txid,
|
|
|
|
],
|
|
|
|
"id": DateTime.now().millisecondsSinceEpoch,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
if (response.statusCode == 200) {
|
|
|
|
if (response.body.isNotEmpty) {
|
|
|
|
try {
|
|
|
|
final json = jsonDecode(response.body) as Map;
|
|
|
|
final result = json["result"] as Map;
|
|
|
|
return EthereumResponse(
|
|
|
|
PendingEthTxDto.fromMap(Map<String, dynamic>.from(result)),
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
} catch (_) {
|
|
|
|
throw EthApiException(
|
|
|
|
"getEthTransactionByHash($txid) failed with response: "
|
|
|
|
"${response.body}",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
|
|
|
"getEthTransactionByHash($txid) response is empty but status code is "
|
|
|
|
"${response.statusCode}",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
|
|
|
"getEthTransactionByHash($txid) failed with status code: "
|
|
|
|
"${response.statusCode}",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
|
|
|
"getEthTransactionByHash($txid): $e\n$s",
|
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
EthApiException(e.toString()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-31 16:15:42 +00:00
|
|
|
static Future<EthereumResponse<List<Tuple2<EthTxDTO, int?>>>>
|
|
|
|
getEthTransactionNonces(
|
|
|
|
List<EthTxDTO> txns,
|
|
|
|
) async {
|
|
|
|
try {
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: Uri.parse(
|
2023-04-03 17:25:56 +00:00
|
|
|
"$stackBaseServer/transactions?transactions=${txns.map((e) => e.hash).join(" ")}&raw=true",
|
2023-03-31 16:15:42 +00:00
|
|
|
),
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-03-31 16:15:42 +00:00
|
|
|
);
|
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
if (response.code == 200) {
|
2023-03-31 16:15:42 +00:00
|
|
|
if (response.body.isNotEmpty) {
|
|
|
|
final json = jsonDecode(response.body) as Map;
|
|
|
|
final list = List<Map<String, dynamic>>.from(json["data"] as List);
|
|
|
|
|
|
|
|
final List<Tuple2<EthTxDTO, int?>> result = [];
|
|
|
|
|
|
|
|
for (final dto in txns) {
|
|
|
|
final data =
|
|
|
|
list.firstWhere((e) => e["hash"] == dto.hash, orElse: () => {});
|
|
|
|
|
2023-04-03 17:25:56 +00:00
|
|
|
final nonce = (data["nonce"] as String?)?.toBigIntFromHex.toInt();
|
2023-03-31 16:15:42 +00:00
|
|
|
result.add(Tuple2(dto, nonce));
|
|
|
|
}
|
|
|
|
return EthereumResponse(
|
|
|
|
result,
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
} else {
|
2023-07-20 17:34:31 +00:00
|
|
|
// nice that the api returns an empty body instead of being
|
|
|
|
// consistent and returning a json object with no transactions
|
|
|
|
return EthereumResponse(
|
|
|
|
[],
|
|
|
|
null,
|
2023-03-31 16:15:42 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
|
|
|
"getEthTransactionNonces($txns) failed with status code: "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-03-31 16:15:42 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
|
|
|
"getEthTransactionNonces($txns): $e\n$s",
|
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
EthApiException(e.toString()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-23 18:19:51 +00:00
|
|
|
static Future<EthereumResponse<List<EthTokenTxExtraDTO>>>
|
|
|
|
getEthTokenTransactionsByTxids(List<String> txids) async {
|
|
|
|
try {
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: Uri.parse(
|
2023-03-23 18:19:51 +00:00
|
|
|
"$stackBaseServer/transactions?transactions=${txids.join(" ")}",
|
|
|
|
),
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-03-23 18:19:51 +00:00
|
|
|
);
|
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
if (response.code == 200) {
|
2023-03-23 18:19:51 +00:00
|
|
|
if (response.body.isNotEmpty) {
|
|
|
|
final json = jsonDecode(response.body) as Map;
|
|
|
|
final list = json["data"] as List?;
|
|
|
|
|
|
|
|
final List<EthTokenTxExtraDTO> txns = [];
|
|
|
|
for (final map in list!) {
|
|
|
|
final txn = EthTokenTxExtraDTO.fromMap(
|
|
|
|
Map<String, dynamic>.from(map as Map),
|
|
|
|
);
|
|
|
|
|
|
|
|
txns.add(txn);
|
|
|
|
}
|
|
|
|
return EthereumResponse(
|
|
|
|
txns,
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
2023-07-20 17:34:31 +00:00
|
|
|
"getEthTokenTransactionsByTxids($txids) response is empty but status code is "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-03-23 18:19:51 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
2023-07-20 17:34:31 +00:00
|
|
|
"getEthTokenTransactionsByTxids($txids) failed with status code: "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-03-23 18:19:51 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
2023-07-20 17:34:31 +00:00
|
|
|
"getEthTokenTransactionsByTxids($txids): $e\n$s",
|
2023-03-23 18:19:51 +00:00
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
EthApiException(e.toString()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<EthereumResponse<List<EthTokenTxDto>>> getTokenTransactions({
|
2023-02-24 16:22:25 +00:00
|
|
|
required String address,
|
2023-03-23 18:19:51 +00:00
|
|
|
required String tokenContractAddress,
|
2023-02-24 16:22:25 +00:00
|
|
|
}) async {
|
|
|
|
try {
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: Uri.parse(
|
2023-03-23 18:19:51 +00:00
|
|
|
"$stackBaseServer/export?addrs=$address&emitter=$tokenContractAddress&logs=true",
|
|
|
|
),
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-03-23 18:19:51 +00:00
|
|
|
);
|
2023-02-24 16:22:25 +00:00
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
if (response.code == 200) {
|
2023-03-23 18:19:51 +00:00
|
|
|
if (response.body.isNotEmpty) {
|
|
|
|
final json = jsonDecode(response.body) as Map;
|
|
|
|
final list = json["data"] as List?;
|
|
|
|
|
|
|
|
final List<EthTokenTxDto> txns = [];
|
|
|
|
for (final map in list!) {
|
|
|
|
final txn =
|
|
|
|
EthTokenTxDto.fromMap(Map<String, dynamic>.from(map as Map));
|
2023-02-24 16:22:25 +00:00
|
|
|
|
2023-03-23 18:19:51 +00:00
|
|
|
txns.add(txn);
|
|
|
|
}
|
2023-02-24 16:22:25 +00:00
|
|
|
return EthereumResponse(
|
2023-03-23 18:19:51 +00:00
|
|
|
txns,
|
2023-02-24 16:22:25 +00:00
|
|
|
null,
|
|
|
|
);
|
|
|
|
} else {
|
2023-07-20 17:34:31 +00:00
|
|
|
// nice that the api returns an empty body instead of being
|
|
|
|
// consistent and returning a json object with no transactions
|
|
|
|
return EthereumResponse(
|
|
|
|
[],
|
|
|
|
null,
|
2023-03-23 18:19:51 +00:00
|
|
|
);
|
2023-02-24 16:22:25 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-02-28 22:03:36 +00:00
|
|
|
throw EthApiException(
|
2023-03-23 18:19:51 +00:00
|
|
|
"getTokenTransactions($address, $tokenContractAddress) failed with status code: "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-02-24 16:22:25 +00:00
|
|
|
);
|
|
|
|
}
|
2023-02-28 22:03:36 +00:00
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
2023-02-24 16:22:25 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
2023-03-23 18:19:51 +00:00
|
|
|
"getTokenTransactions($address, $tokenContractAddress): $e\n$s",
|
2023-02-24 16:22:25 +00:00
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
2023-02-28 22:03:36 +00:00
|
|
|
EthApiException(e.toString()),
|
2023-02-24 16:22:25 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-01 00:36:54 +00:00
|
|
|
// ONLY FETCHES WALLET TOKENS WITH A NON ZERO BALANCE
|
|
|
|
// static Future<EthereumResponse<List<EthToken>>> getWalletTokens({
|
|
|
|
// required String address,
|
|
|
|
// }) async {
|
|
|
|
// try {
|
|
|
|
// final uri = Uri.parse(
|
|
|
|
// "$blockExplorer?module=account&action=tokenlist&address=$address",
|
|
|
|
// );
|
|
|
|
// final response = await get(uri);
|
|
|
|
//
|
|
|
|
// if (response.statusCode == 200) {
|
|
|
|
// final json = jsonDecode(response.body);
|
|
|
|
// if (json["message"] == "OK") {
|
|
|
|
// final result =
|
|
|
|
// List<Map<String, dynamic>>.from(json["result"] as List);
|
|
|
|
// final List<EthToken> tokens = [];
|
|
|
|
// for (final map in result) {
|
|
|
|
// if (map["type"] == "ERC-20") {
|
|
|
|
// tokens.add(
|
|
|
|
// Erc20Token(
|
|
|
|
// balance: int.parse(map["balance"] as String),
|
|
|
|
// contractAddress: map["contractAddress"] as String,
|
|
|
|
// decimals: int.parse(map["decimals"] as String),
|
|
|
|
// name: map["name"] as String,
|
|
|
|
// symbol: map["symbol"] as String,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// } else if (map["type"] == "ERC-721") {
|
|
|
|
// tokens.add(
|
|
|
|
// Erc721Token(
|
|
|
|
// balance: int.parse(map["balance"] as String),
|
|
|
|
// contractAddress: map["contractAddress"] as String,
|
|
|
|
// decimals: int.parse(map["decimals"] as String),
|
|
|
|
// name: map["name"] as String,
|
|
|
|
// symbol: map["symbol"] as String,
|
|
|
|
// ),
|
|
|
|
// );
|
|
|
|
// } else {
|
|
|
|
// throw EthApiException(
|
|
|
|
// "Unsupported token type found: ${map["type"]}");
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return EthereumResponse(
|
|
|
|
// tokens,
|
|
|
|
// null,
|
|
|
|
// );
|
|
|
|
// } else {
|
|
|
|
// throw EthApiException(json["message"] as String);
|
|
|
|
// }
|
|
|
|
// } else {
|
|
|
|
// throw EthApiException(
|
|
|
|
// "getWalletTokens($address) failed with status code: "
|
|
|
|
// "${response.statusCode}",
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// } on EthApiException catch (e) {
|
|
|
|
// return EthereumResponse(
|
|
|
|
// null,
|
|
|
|
// e,
|
|
|
|
// );
|
|
|
|
// } catch (e, s) {
|
|
|
|
// Logging.instance.log(
|
|
|
|
// "getWalletTokens(): $e\n$s",
|
|
|
|
// level: LogLevel.Error,
|
|
|
|
// );
|
|
|
|
// return EthereumResponse(
|
|
|
|
// null,
|
|
|
|
// EthApiException(e.toString()),
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// }
|
2023-02-23 22:59:58 +00:00
|
|
|
|
2023-04-07 22:02:28 +00:00
|
|
|
static Future<EthereumResponse<Amount>> getWalletTokenBalance({
|
2023-03-01 20:04:25 +00:00
|
|
|
required String address,
|
|
|
|
required String contractAddress,
|
|
|
|
}) async {
|
|
|
|
try {
|
|
|
|
final uri = Uri.parse(
|
2023-03-02 19:28:51 +00:00
|
|
|
"$stackBaseServer/tokens?addrs=$contractAddress $address",
|
2023-03-01 20:04:25 +00:00
|
|
|
);
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: uri,
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-09-08 22:53:09 +00:00
|
|
|
);
|
2023-03-01 20:04:25 +00:00
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
if (response.code == 200) {
|
2023-03-01 20:04:25 +00:00
|
|
|
final json = jsonDecode(response.body);
|
2023-03-02 19:28:51 +00:00
|
|
|
if (json["data"] is List) {
|
|
|
|
final map = json["data"].first as Map;
|
|
|
|
|
2023-04-07 22:02:28 +00:00
|
|
|
final balance =
|
2023-07-21 19:23:36 +00:00
|
|
|
BigInt.tryParse(map["units"].toString()) ?? BigInt.zero;
|
2023-03-01 20:04:25 +00:00
|
|
|
|
|
|
|
return EthereumResponse(
|
2023-07-21 19:23:36 +00:00
|
|
|
Amount(rawValue: balance, fractionDigits: map["decimals"] as int),
|
2023-03-01 20:04:25 +00:00
|
|
|
null,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
throw EthApiException(json["message"] as String);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
2023-03-02 00:02:53 +00:00
|
|
|
"getWalletTokenBalance($address) failed with status code: "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-03-01 20:04:25 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
2023-03-31 22:31:19 +00:00
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
|
|
|
"getWalletTokenBalance(): $e\n$s",
|
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
EthApiException(e.toString()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<EthereumResponse<int>> getAddressNonce({
|
|
|
|
required String address,
|
|
|
|
}) async {
|
|
|
|
try {
|
|
|
|
final uri = Uri.parse(
|
2023-04-11 20:25:13 +00:00
|
|
|
"$stackBaseServer/state?addrs=$address&parts=all",
|
2023-03-31 22:31:19 +00:00
|
|
|
);
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: uri,
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-09-08 22:53:09 +00:00
|
|
|
);
|
2023-03-31 22:31:19 +00:00
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
if (response.code == 200) {
|
2023-03-31 22:31:19 +00:00
|
|
|
final json = jsonDecode(response.body);
|
|
|
|
if (json["data"] is List) {
|
|
|
|
final map = json["data"].first as Map;
|
|
|
|
|
|
|
|
final nonce = map["nonce"] as int;
|
|
|
|
|
|
|
|
return EthereumResponse(
|
|
|
|
nonce,
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
throw EthApiException(json["message"] as String);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
2023-04-11 20:25:13 +00:00
|
|
|
"getAddressNonce($address) failed with status code: "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-03-31 22:31:19 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
2023-03-01 20:04:25 +00:00
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
2023-04-11 20:25:13 +00:00
|
|
|
"getAddressNonce(): $e\n$s",
|
2023-03-01 20:04:25 +00:00
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
EthApiException(e.toString()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-30 20:50:58 +00:00
|
|
|
static Future<EthereumResponse<GasTracker>> getGasOracle() async {
|
|
|
|
try {
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: Uri.parse(
|
2023-03-30 20:50:58 +00:00
|
|
|
"$stackBaseServer/gas-prices",
|
|
|
|
),
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-03-30 20:50:58 +00:00
|
|
|
);
|
2023-03-29 18:49:12 +00:00
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
if (response.code == 200) {
|
2023-03-30 20:50:58 +00:00
|
|
|
final json = jsonDecode(response.body) as Map;
|
|
|
|
if (json["success"] == true) {
|
2023-04-11 22:09:18 +00:00
|
|
|
try {
|
|
|
|
return EthereumResponse(
|
|
|
|
GasTracker.fromJson(
|
|
|
|
Map<String, dynamic>.from(json["result"]["result"] as Map),
|
|
|
|
),
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
} catch (_) {
|
|
|
|
throw EthApiException(
|
|
|
|
"getGasOracle() failed with response: "
|
|
|
|
"${response.body}",
|
|
|
|
);
|
|
|
|
}
|
2023-03-30 20:50:58 +00:00
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
|
|
|
"getGasOracle() failed with response: "
|
|
|
|
"${response.body}",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
|
|
|
"getGasOracle() failed with status code: "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-03-30 20:50:58 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
|
|
|
"getGasOracle(): $e\n$s",
|
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
EthApiException(e.toString()),
|
|
|
|
);
|
2023-02-23 22:59:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static Future<FeeObject> getFees() async {
|
2023-03-30 20:50:58 +00:00
|
|
|
final fees = (await getGasOracle()).value!;
|
|
|
|
final feesFast = fees.fast.shift(9).toBigInt();
|
|
|
|
final feesStandard = fees.average.shift(9).toBigInt();
|
|
|
|
final feesSlow = fees.slow.shift(9).toBigInt();
|
2023-02-23 22:59:58 +00:00
|
|
|
|
|
|
|
return FeeObject(
|
2023-03-30 20:50:58 +00:00
|
|
|
numberOfBlocksFast: fees.numberOfBlocksFast,
|
|
|
|
numberOfBlocksAverage: fees.numberOfBlocksAverage,
|
|
|
|
numberOfBlocksSlow: fees.numberOfBlocksSlow,
|
2023-02-23 22:59:58 +00:00
|
|
|
fast: feesFast.toInt(),
|
|
|
|
medium: feesStandard.toInt(),
|
|
|
|
slow: feesSlow.toInt());
|
|
|
|
}
|
|
|
|
|
2023-03-23 19:48:38 +00:00
|
|
|
static Future<EthereumResponse<EthContract>> getTokenContractInfoByAddress(
|
2023-02-23 22:59:58 +00:00
|
|
|
String contractAddress) async {
|
|
|
|
try {
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: Uri.parse(
|
2023-12-04 16:46:34 +00:00
|
|
|
// "$stackBaseServer/tokens?addrs=$contractAddress&parts=all",
|
|
|
|
"$stackBaseServer/names?terms=$contractAddress",
|
2023-03-23 19:48:38 +00:00
|
|
|
),
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-03-23 19:48:38 +00:00
|
|
|
);
|
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
if (response.code == 200) {
|
2023-03-24 14:37:47 +00:00
|
|
|
final json = jsonDecode(response.body) as Map;
|
|
|
|
if (json["data"] is List) {
|
2023-12-04 16:46:34 +00:00
|
|
|
if ((json["data"] as List).isEmpty) {
|
|
|
|
throw EthApiException("Unknown token");
|
|
|
|
}
|
|
|
|
|
2023-03-24 14:37:47 +00:00
|
|
|
final map = Map<String, dynamic>.from(json["data"].first as Map);
|
2023-03-03 00:40:12 +00:00
|
|
|
EthContract? token;
|
2023-03-23 19:48:38 +00:00
|
|
|
if (map["isErc20"] == true) {
|
2023-03-03 00:40:12 +00:00
|
|
|
token = EthContract(
|
2023-03-23 19:48:38 +00:00
|
|
|
address: map["address"] as String,
|
|
|
|
decimals: map["decimals"] as int,
|
2023-02-23 22:59:58 +00:00
|
|
|
name: map["name"] as String,
|
|
|
|
symbol: map["symbol"] as String,
|
2023-03-03 00:40:12 +00:00
|
|
|
type: EthContractType.erc20,
|
2023-02-23 22:59:58 +00:00
|
|
|
);
|
2023-03-23 19:48:38 +00:00
|
|
|
} else if (map["isErc721"] == true) {
|
2023-03-03 00:40:12 +00:00
|
|
|
token = EthContract(
|
2023-03-23 19:48:38 +00:00
|
|
|
address: map["address"] as String,
|
|
|
|
decimals: map["decimals"] as int,
|
2023-02-23 22:59:58 +00:00
|
|
|
name: map["name"] as String,
|
|
|
|
symbol: map["symbol"] as String,
|
2023-03-03 00:40:12 +00:00
|
|
|
type: EthContractType.erc721,
|
2023-02-23 22:59:58 +00:00
|
|
|
);
|
|
|
|
} else {
|
2023-02-28 22:03:36 +00:00
|
|
|
throw EthApiException(
|
|
|
|
"Unsupported token type found: ${map["type"]}");
|
2023-02-23 22:59:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return EthereumResponse(
|
|
|
|
token,
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
} else {
|
2023-03-24 14:37:47 +00:00
|
|
|
throw EthApiException(response.body);
|
2023-02-23 22:59:58 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-02-28 22:03:36 +00:00
|
|
|
throw EthApiException(
|
2023-02-23 22:59:58 +00:00
|
|
|
"getTokenByContractAddress($contractAddress) failed with status code: "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-02-23 22:59:58 +00:00
|
|
|
);
|
|
|
|
}
|
2023-02-28 22:03:36 +00:00
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
2023-02-23 22:59:58 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
2023-03-02 00:02:53 +00:00
|
|
|
"getTokenByContractAddress(): $e\n$s",
|
2023-02-23 22:59:58 +00:00
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
2023-02-28 22:03:36 +00:00
|
|
|
EthApiException(e.toString()),
|
2023-02-23 22:59:58 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-24 00:04:47 +00:00
|
|
|
static Future<EthereumResponse<String>> getTokenAbi({
|
|
|
|
required String name,
|
|
|
|
required String contractAddress,
|
|
|
|
}) async {
|
2023-03-02 00:02:53 +00:00
|
|
|
try {
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: Uri.parse(
|
2023-03-24 00:04:47 +00:00
|
|
|
"$stackBaseServer/abis?addrs=$contractAddress&verbose=true",
|
2023-03-02 19:28:51 +00:00
|
|
|
),
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-03-02 19:28:51 +00:00
|
|
|
);
|
|
|
|
|
2023-09-08 22:53:09 +00:00
|
|
|
if (response.code == 200) {
|
2023-03-02 19:28:51 +00:00
|
|
|
final json = jsonDecode(response.body)["data"] as List;
|
|
|
|
|
|
|
|
return EthereumResponse(
|
|
|
|
jsonEncode(json),
|
|
|
|
null,
|
|
|
|
);
|
2023-03-02 00:02:53 +00:00
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
2023-03-24 00:04:47 +00:00
|
|
|
"getTokenAbi($name, $contractAddress) failed with status code: "
|
2023-09-08 22:53:09 +00:00
|
|
|
"${response.code}",
|
2023-03-02 00:02:53 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
2023-03-24 00:04:47 +00:00
|
|
|
"getTokenAbi($name, $contractAddress): $e\n$s",
|
2023-03-02 00:02:53 +00:00
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
EthApiException(e.toString()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-29 21:57:52 +00:00
|
|
|
/// Fetch the underlying contract address that a proxy contract points to
|
|
|
|
static Future<EthereumResponse<String>> getProxyTokenImplementationAddress(
|
|
|
|
String contractAddress,
|
|
|
|
) async {
|
|
|
|
try {
|
2023-09-08 22:53:09 +00:00
|
|
|
final response = await client.get(
|
|
|
|
url: Uri.parse(
|
|
|
|
"$stackBaseServer/state?addrs=$contractAddress&parts=proxy"),
|
2023-09-15 19:51:20 +00:00
|
|
|
proxyInfo: Prefs.instance.useTor
|
|
|
|
? TorService.sharedInstance.getProxyInfo()
|
|
|
|
: null,
|
2023-09-08 22:53:09 +00:00
|
|
|
);
|
|
|
|
if (response.code == 200) {
|
2023-03-29 21:57:52 +00:00
|
|
|
final json = jsonDecode(response.body);
|
|
|
|
final list = json["data"] as List;
|
|
|
|
final map = Map<String, dynamic>.from(list.first as Map);
|
2023-03-02 00:02:53 +00:00
|
|
|
|
2023-03-29 21:57:52 +00:00
|
|
|
return EthereumResponse(
|
|
|
|
map["proxy"] as String,
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
throw EthApiException(
|
|
|
|
"getProxyTokenImplementationAddress($contractAddress) failed with"
|
2023-09-08 22:53:09 +00:00
|
|
|
" status code: ${response.code}",
|
2023-03-29 21:57:52 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} on EthApiException catch (e) {
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
e,
|
|
|
|
);
|
|
|
|
} catch (e, s) {
|
|
|
|
Logging.instance.log(
|
|
|
|
"getProxyTokenImplementationAddress($contractAddress) : $e\n$s",
|
|
|
|
level: LogLevel.Error,
|
|
|
|
);
|
|
|
|
return EthereumResponse(
|
|
|
|
null,
|
|
|
|
EthApiException(e.toString()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2023-02-23 22:59:58 +00:00
|
|
|
}
|