import 'dart:convert'
import 'package:decimal/decimal.dart'
import 'package:flutter/services.dart'
import 'package:get/get.dart'
import 'package:http/http.dart'
import 'package:web3dart/json_rpc.dart'
import 'package:web3dart/web3dart.dart'
import '../../../../../global_configs/global_configs.dart' as global
import '../global_configs/contract_configs.dart' as global_contract
import '../global_configs/main_service.dart'
import '../public_utils/toast.dart'
class Web3ClientSingleton {
static Web3Client? _client
static Web3Client getInstance() {
if (_client == null) {
final rpcUrl = MainService.s.mainRpcValue
final httpClient = Client()
_client = Web3Client(rpcUrl, httpClient)
}
return _client!
}
}
class Web3Service {
//原生代币小数位数
static const nativeDecimals = '18'
///获取钱包余额
Future<Map> getWalletBalance(
List tokenList, String tokenAddress) async {
Map tokens = {}
final client = Web3ClientSingleton.getInstance()
for (String element in tokenList) {
switch (element) {
case 'BNB':
String bscBalance =
await nativeTokenBalance(client: client)
tokens['BNB'] = bscBalance
break
case 'USDT':
String usdtBalance = await erc20TokenBalance(
client: client,
symbol: 'USDT',
contractAddress: global_contract.tokenContractAddress['USDT'])
tokens['USDT'] = usdtBalance
break
default:
String usdtBalance = await erc20TokenBalance(
client: client,
symbol: element,
contractAddress: tokenAddress)
tokens[element] = usdtBalance
break
}
}
return Future.value(tokens)
}
///原生币查询
Future<String> nativeTokenBalance(
{Web3Client? client}) async {
client ??= Web3ClientSingleton.getInstance()
final balance = await client
.getBalance(EthereumAddress.fromHex(MainService.s.walletAddressValue))
return _getInWeiForNumber(balance.getInWei, nativeDecimals)
}
///查询ERC-20代币余额
Future<String> erc20TokenBalance(
{Web3Client? client,
String symbol = '',
required String contractAddress}) async {
client ??= Web3ClientSingleton.getInstance()
//获取代币符号
if(symbol.isEmpty) {
symbol = await getSymbol(contractAddress, client: client)
}
final contract = await initDeployedContract(contractAddress, symbol: symbol, client: client)
final tokenBalanceFunction = contract.function('balanceOf')
final callResult = await client.call(
contract: contract,
function: tokenBalanceFunction,
params: [EthereumAddress.fromHex(MainService.s.walletAddressValue)],
)
final tokenBalance = callResult.first as BigInt
// 获取小数位数
String decimals = await getErc20Decimals(contractAddress, client: client, contract: contract, symbol: symbol)
return _getInWeiForNumber(tokenBalance, decimals)
}
///转账
//原生
Future<String> nativeTransaction(
{Web3Client? client,
required String toAddress,
required Decimal amount,
String erc20ContractAddress = ''}) async {
client ??= Web3ClientSingleton.getInstance()
// 获取当前以太坊网络上的 gas 价格
final gasPrice = await client.getGasPrice()
// 网络id
final networkId = await client.getNetworkId()
//发送方地址
final credentials = EthPrivateKey.fromHex(MainService.s.privateKeyValue)
final senderAddress = credentials.address
//接收方地址
final recipientAddress = EthereumAddress.fromHex(toAddress)
//值
final value =
EtherAmount.inWei((amount * Decimal.parse('1e$nativeDecimals')).toBigInt())
// 根据需求进行调整
final adjustedGasPrice =
EtherAmount.inWei(gasPrice.getInWei * BigInt.from(1))
try {
// 估算执行交易所需的 gas 量
// final estimateGas = await client.estimateGas(
// sender: senderAddress, to: recipientAddress, value: value, gasPrice: adjustedGasPrice)
final transaction = Transaction(
from: senderAddress,
to: recipientAddress,
// maxGas: estimateGas.toInt(),
gasPrice: adjustedGasPrice,
value: value,
)
// 使用私钥对交易进行签名
final transactionHash = await client.sendTransaction(credentials, transaction, chainId: networkId)
if(isSHA256Hash(transactionHash)) {
return transactionHash
}else {
toastError('error_BlockConfirmationFailed'.tr)
return ''
}
} catch (e) {
// 处理异常情况
getErrorInfo(e as RPCError)
}
return ''
}
//erc20转账
Future<String> erc20Transaction(
{Web3Client? client,
required String toAddress,
required Decimal amount,
required String contractAddress,
String symbol = ''
}) async{
client ??= Web3ClientSingleton.getInstance()
// 获取当前以太坊网络上的 gas 价格
final gasPrice = await client.getGasPrice()
// 网络id
final networkId = await client.getNetworkId()
//发送方地址
final credentials = EthPrivateKey.fromHex(MainService.s.privateKeyValue)
final senderAddress = credentials.address
//接收方地址
final recipientAddress = EthereumAddress.fromHex(toAddress)
// 根据需求进行调整
final adjustedGasPrice = EtherAmount.inWei(gasPrice.getInWei * BigInt.from(1))
//初始化合约对象
DeployedContract contract = await initDeployedContract(contractAddress, client:client, symbol:symbol)
//值
//获取小数位数
String decimals = await getErc20Decimals(contractAddress, client: client, contract:contract, symbol:symbol)
final value = BigInt.parse((amount * Decimal.parse('1e$decimals')).toString())
try {
// 估算执行交易所需的 gas 量
// final estimateGas = await client.estimateGas(
// sender: senderAddress,to: recipientAddress, gasPrice: adjustedGasPrice)
// 构造转账交易
final transferFunction = contract.function('transfer')
final transaction = Transaction.callContract(
contract: contract,
function: transferFunction,
parameters: [recipientAddress, value],
from: senderAddress,
gasPrice: adjustedGasPrice,
// maxGas: estimateGas.toInt()*2
)
final transactionHash = await client.sendTransaction(
credentials,
transaction,
chainId: networkId,
)
if (isSHA256Hash(transactionHash)) {
return transactionHash
} else {
toastError('Transaction failed.')
return ''
}
}catch (e) {
// 处理异常情况
getErrorInfo(e as RPCError)
}
return ''
}
//初始化合约对象
Future<DeployedContract> initDeployedContract(String contractAddress, {Web3Client? client, String symbol = ''}) async {
client ??= Web3ClientSingleton.getInstance()
if(symbol.isEmpty) {
symbol = await getSymbol(contractAddress, client:client)
}
final contractJson = jsonDecode(
await rootBundle.loadString('assets/token_contract/erc20.json'))
final contract = DeployedContract(
ContractAbi.fromJson(jsonEncode(contractJson), symbol),
EthereumAddress.fromHex(contractAddress),
)
return contract
}
///获取代币小数位数
getErc20Decimals(String contractAddress, {Web3Client? client, DeployedContract? contract, String symbol = ''}) async {
client ??= Web3ClientSingleton.getInstance()
contract ??= await initDeployedContract(contractAddress, client:client, symbol: symbol)
// 代币小数位数
final decimalsFunction = contract.function('decimals')
final decimalsResult = await client.call(
contract: contract,
function: decimalsFunction,
params: [],
)
final tokenDecimals = decimalsResult[0].toInt()
return tokenDecimals.toString()
}
///获取代币符号
getSymbol(String symbolAddress, {Web3Client? client}) async {
client ??= Web3ClientSingleton.getInstance()
final contractAddress = EthereumAddress.fromHex(symbolAddress)
// 代币合约实例
final contractJson = jsonDecode(
await rootBundle.loadString('assets/token_contract/erc20.json'))
final contract = DeployedContract(
ContractAbi.fromJson(jsonEncode(contractJson), 'symbol'),
contractAddress)
// 代币符号
final symbolFunction = contract.function('symbol')
final symbolResult = await client.call(
contract: contract,
function: symbolFunction,
params: [],
)
final symbol = symbolResult[0]
return symbol
}
//错误提示总览
getErrorInfo(RPCError error) {
if(error.message.contains('insufficient funds for transfer')){
toastError('error_insufficientFundsForTransfer'.tr)
}
else if(error.message.contains('gas required exceeds allowance')) {
toastError('error_gasRequiredExceedsAllowance'.tr)
}
else if(error.message.contains('intrinsic gas too low')) {
toastError('intrinsicGasTooLow'.tr)
}
else if(error.message.contains('nonce too low')) {
toastError('nonce too low')
}
else {
toastError(error.message)
}
}
//计算精度
String _getInWeiForNumber(BigInt inWei, String decimals) {
Decimal decimal = Decimal.fromBigInt(inWei)
final result = decimal / Decimal.parse('1e$decimals')
final limitResult =
result.toDouble().toStringAsFixed(global.decimalLimit).toString()
return limitResult
}
//校验hash值
bool isSHA256Hash(String value) {
final RegExp hashPattern = RegExp(r'^0x[a-fA-F0-9]{64}$')
return hashPattern.hasMatch(value)
}
}