web3dart

718 阅读2分钟
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);
  }

}