import {utils} from '@womasoft/js-utils'; import {ethers} from 'ethers'; import {BigNumber} from "@ethersproject/bignumber"; import { IApi, IEthereum, IEthereumSignTransactionParams, IEthereumTxDetails, IFeeParams, IFeeResult, IRpc, ISwap, TokenData } from "./ethereum"; import {error, IReturnResult, Return, txListStat, txListType} from '../../config'; import {mnemonicTools} from '../../mnemonic' import {defaultParams, erc20, erc721} from "./config"; import { IBalance, IChainsParams, IChainsVerify, IContractDetails, IFromMnemonicParams, IGetBalanceParams, ISignParams, IToWallet, ITxList, ITxListParams, ITxListToList, IVerifyBalanceParams } from "../chains"; import {joinSignature} from "ethers/lib/utils"; const Web3 = require('web3'); class Ethereum implements IEthereum { // 接收访问参数 private readonly _params: IChainsParams constructor(params: IChainsParams) { this._params = params ?? defaultParams; if (this._params.api && utils.tools.isStrLast(this._params.api, '/')) this._params.api = utils.tools.toStrFirst(this._params.api, this._params.api.length - 1); if (!this._params.keys && this._params.key) this._params.keys = [this._params.key]; } bigInteger = (strHex) => { if (strHex.length > 2) { if (strHex.charAt(0) === '0' && (strHex.charAt(1) === 'X' || strHex.charAt(1) === 'x')) { strHex = strHex.substring(2); } // return new utils.math.bignumber(strHex, 16); let result = '0x00'; let search = ''; for (let i = 0, len = 50; i < len; i++) { search += '0'; const index = strHex.indexOf(search); if (index < 0) { result = `0x${strHex.substring(search.length - 1, strHex.length)}`; break; } } return result; // return strHex; } return undefined } readonly verify: IChainsVerify = { address: function (address: string): boolean { return ethers.utils.isAddress(address) }, privateKey: function (privateKey: string): boolean { try { return !!ethers.utils.computeAddress(privateKey) } catch (e) { return false } }, publicKey: function (publicKey: string): boolean { return this.privateKey(publicKey) }, balance: async (address: string, amount: string, params?: IVerifyBalanceParams): Promise> => { params = params || {}; const ReturnClass = new Return(); if (!this.verify.address(address)) return error.address; if (!params.fee) params.fee = '0'; if (!utils.tools.isAmount(amount)) return error.amountNotZero; const balanceData = await this.getAllBalance(address, [params.contract]); // 主币金额 let amountMain = balanceData.code > 0 ? 0 : Number(balanceData.rs[this._params.symbol.toUpperCase()]); // 对比金额 const amountValue = Number(amount); // 手续费 const feeValue = Number(params.fee); if (params.contract && params.contract.indexOf('0x') === 0) { const amountToken = balanceData.code > 0 ? 0 : Number(balanceData.rs[this.getNormalAddress(params.contract)]); if (amountMain < feeValue) return error.feeNotEnough; if (amountToken < amountValue) return error.amountNotEnough; } else { if (amountMain < amountValue) return error.amountNotEnough; if (feeValue > 0) amountMain = utils.math.minus(amountMain, feeValue); if (amountMain < amountValue) return error.feeNotEnough; } return ReturnClass.end(); } }; getPath(index?: number): string { let path = "m/44'/60'/0'/0/{index}"; if (!utils.tools.isInteger(index)) index = 0; return utils.tools.replaceByJson(path, {index}); } async fromMnemonicToWallet(mnemonic: string, params?: IFromMnemonicParams | undefined): Promise> { params = params || {}; if (!params.path) params.path = this.getPath(); const data = await mnemonicTools.toSeed(mnemonic, params.path); if (data.code > 0) return data; return this.fromPrivateKeyToWallet(data.privateKey) } fromPrivateKeyToWallet(privateKey: string): IReturnResult { const ReturnClass = new Return(); privateKey = privateKey.indexOf('0x') === 0 ? privateKey : `0x${privateKey}`; if (!this.verify.privateKey(privateKey)) return error.privateKey const publicKey = this.getPublicKey(privateKey); const address = this.getAddress(publicKey); const data: IToWallet = { address, publicKey, privateKey, } ReturnClass.setPrivateKey(privateKey) ReturnClass.setPublicKey(this.getPublicKey(privateKey)) ReturnClass.setAddress(this.getAddress(ReturnClass.result.publicKey)) ReturnClass.setResult(data); return ReturnClass.end() } async fromRandomToWallet(): Promise> { const data = ethers.Wallet.createRandom(); return this.fromPrivateKeyToWallet(data.privateKey); } getNormalAddress(address: string): string { return ethers.utils.getAddress(address) } getAddress(value: string): string { return ethers.utils.computeAddress(value) } getPublicKey(privateKey: string): string { return ethers.utils.computePublicKey(this.getWallet(privateKey).publicKey, true); } async getAllBalance(address: string, params?: string[]): Promise> { params = params ?? []; const ReturnClass = new Return<{ [key: string]: string }>(); if (!this.verify.address(address)) return error.address; // let {result} = await this.api.getAllToken(address); const mainBalance = await this.getBalance(address); const list = {}; if (mainBalance.code === 0) list[this._params.symbol] = mainBalance.rs.usable; // if (utils.types.string(result) && params.length > 0 && result.indexOf('Missing Or invalid Action name') >= 0 || !result.length) { if (params.length) { const promiseList = []; params.map((item) => { if (utils.types.undefined(item)) return; promiseList.push(this.getBalance(address, {contract: this.getNormalAddress(item)})) }); const all = await Promise.all(promiseList); if (all.length > 0) all.map((item) => { if (item.code > 0) return; list[item.contracts.address] = item.rs.usable; }) } else { // params.map((item) => { // if (utils.types.undefined(item)) return; // const contractAddress = this.getNormalAddress(item); // const data = result.find(m => this.getNormalAddress(m.contractAddress) === contractAddress); // if (data) list[contractAddress] = this.formatUnits(data.balance, data.decimals); // }) } ReturnClass.setResult(list); return ReturnClass.end(); } async getBalance(address: string, params?: IGetBalanceParams | undefined): Promise> { params = params ?? {}; const ReturnClass = new Return(); if (!this.verify.address(address)) return error.address; // 合约交易 if (params.contract && params.contract.indexOf('0x') === 0) { if (!this.verify.address(params.contract)) return error.contract; if (typeof params.decimals !== "number") { const contractClass = this.getContract(params.contract, erc20); const decimalsResult = await contractClass.decimals().catch(() => 0); if (decimalsResult.code > 0) return decimalsResult; params.decimals = decimalsResult; } const dataTop = '0x70a08231000000000000000000000000'; const callParams = { from: address, to: params.contract, data: `${dataTop}${address.substring(2, address.length)}` } const data = await this.rpc.getCall(callParams); let value = data.result; if (value === '0x') value = '0x00'; const amount = this.formatUnits(value, params.decimals); ReturnClass.setContracts({address: this.getNormalAddress(params.contract), decimals: params.decimals}); ReturnClass.setResult({ balance: amount.toString(), usable: amount.toString(), lock: '0', unconfirmed: '0', }) } else { // 主币交易 const data = await this.rpc.getBalance(address); if (data.error) { ReturnClass.setResult({balance: '0', usable: '0', lock: '0', unconfirmed: '0'}); } else { const amount = this.formatUnits(data.result, 18); ReturnClass.setResult({ balance: amount.toString(), usable: amount.toString(), lock: '0', unconfirmed: '0', }); } } return ReturnClass.end(); } async getBlockHeight(): Promise> { const ReturnClass = new Return(); const data = await this.rpc.getBlockNumber(); ReturnClass.setResult(parseInt(data.result, 16)) return ReturnClass.end() } async getContractDetails(address: string): Promise> { const ReturnClass = new Return(); if (!this.verify.address(address)) return error.contract; address = this.getNormalAddress(address); const contractClass = this.getContract(address, erc20); try { const name = await contractClass.name().catch(err => { const data = err.toString(); if (data.indexOf('CALL_EXCEPTION') >= 0 || data.indexOf('missing revert data in call exception') >= 0) return error.contract; if (data.indexOf('NETWORK_ERROR') >= 0) return error.networksTimeout; }); if (name.code > 0) return name; const symbol = await contractClass.symbol(); const decimals = await contractClass.decimals().catch(() => 0); ReturnClass.setResult({name, symbol, decimals, address}); } catch (err) { console.log('err', err.toString()) } return ReturnClass.end(); } // 格式化 txList 数据 private _getTxListToData(address: string, item, params: ITxListParams): ITxList { const from = item.from && item.from.length ? this.getNormalAddress(item.from) : ''; const to = item.to && item.to.length ? this.getNormalAddress(item.to) : ''; const value = this.formatUnits(item.value, params.decimals ?? 18); const toList: ITxListToList[] = [{address: to, amount: value}]; const resultParams: ITxList = { type: txListType.init, stat: txListStat.unconfirmed, hash: item.hash, from: from, to: toList, value, timestamp: item.timeStamp, time: item.time, fee: utils.math.dividedBy(item.gasUsed, 1000000000).toString(), contractAddress: item.contractAddress, contractDecimals: item.contractDecimals, contractSymbol: item.contractSymbol, contractName: item.contractName, } if (item.hasInternal) resultParams.stat = txListStat.success; if (item.hasData) resultParams.stat = txListStat.success; if (item.isError === '1') resultParams.stat = txListStat.fail; else if (item.isError === '0') resultParams.stat = txListStat.success; if (item.hasPending) resultParams.stat = txListStat.confirming; if (from === address) resultParams.type = txListType.out; else if (to === address) resultParams.type = txListType.in; return resultParams; } async getPendingTxList(address: string, params?: ITxListParams | undefined): Promise> { params = params || {} const ReturnClass = new Return(); if (!this.verify.address(address)) return error.address; address = this.getNormalAddress(address); const data = await this.api.getPendingTxList(address).catch(err => { console.log('getTxPendingList', err.toString()) return {result: []} }); let result: ITxList[] = []; if (!utils.types.array(data.result)) { if (data.result.indexOf('Missing Or invalid Action name') >= 0) return error.functionErr; if (data.result.indexOf('Max rate limit reached') >= 0) return error.networksTimeout; } else { data.result.map((item) => { if (params.contract) { if (this.getNormalAddress(params.contract) !== this.getNormalAddress(item.to)) return; item.contractAddress = item.to; // 判断类型是否是token交易 if (item.input.indexOf('0xa9059cbb') >= 0) { item.to = `0x${item.input.substring(34, 74)}`; const inputValue = item.input.substring(74); console.log(this.bigInteger(inputValue).toString()) item.value = this.bigInteger(inputValue).toString(); // item.value = this.bigInteger(inputValue); } } console.log(item.gas, item.gasPrice); item.fee = this.getFee(item.gas, item.gasPrice); item.hasPending = true; item.timeStamp = parseInt((new Date().getTime() / 1000).toString()); result.push(this._getTxListToData(address, item, params)); }) } ReturnClass.setResult(result); return ReturnClass.end(); } async getTxList(address: string, params?: ITxListParams | undefined): Promise> { params = params || {}; const ReturnClass = new Return(); if (!this.verify.address(address)) return error.address; address = this.getNormalAddress(address); params.type = params.type || 'all'; params.size = params.size || 30 params.page = params.page || 1; const hasToken = (params.contract && params.contract.indexOf('0x') === 0); const all = await Promise.all([ this.api.getPendingTxList(address), hasToken ? {result: []} : this.api.getInternalList(address, params.size, params.page), hasToken ? this.api.getContractTxList(address, params.contract, params.size, params.page) : this.api.getTxList(address, params.size, params.page), hasToken ? this.getContractDetails(params.contract) : undefined ]) const pendingList = all[0], internalList = all[1], dataList = all[2], tokenDetails = all[3]; let resultList: ITxList[] = []; // token 详细 if (tokenDetails && !params.decimals) params.decimals = tokenDetails.rs.decimals; // 缓存中的交易 if (pendingList.status === '1' && pendingList.result.length > 0) { pendingList.result.map((item) => { if (params.contract) { if (this.getNormalAddress(params.contract) !== this.getNormalAddress(item.to)) return; item.contractAddress = item.to; // 判断类型是否是token交易 if (item.input.indexOf('0xa9059cbb') >= 0) { item.to = `0x${item.input.substring(34, 74)}`; const inputValue = item.input.substring(74); console.log(inputValue); console.log(this.bigInteger(inputValue).toString()) item.value = this.bigInteger(inputValue).toString(); } } item.timeStamp = parseInt((new Date().getTime() / 1000).toString()); item.hasPending = true; resultList.push(this._getTxListToData(address, item, params)) }) } // 交易数据 if (dataList.result.length > 0) dataList.result.map((item) => { item.hasData = true; resultList.push(this._getTxListToData(address, item, params)) }) // 内部交易 if (internalList.result.length > 0) internalList.result.map((item) => { if (item.transactionHash) item.hash = item.transactionHash; item.hasInternal = true; const data = this._getTxListToData(address, item, params); const index = resultList.findIndex(m => m.hash === data.hash) if (index >= 0) { data.fee = resultList.find(m => m.hash === data.hash).fee; resultList.splice(index, 1); } resultList.push(data) }); resultList = resultList.sort((a, b) => parseInt(b.timestamp) - parseInt(a.timestamp)); if (params.type === txListType.in) resultList = resultList.filter(m => m.type === txListType.in); if (params.type === txListType.out) resultList = resultList.filter(m => m.type === txListType.out); resultList = resultList.map((item) => { if (hasToken) { item.contractAddress = tokenDetails.rs.address; item.contractSymbol = tokenDetails.rs.symbol; item.contractDecimals = tokenDetails.rs.decimals; item.contractName = tokenDetails.rs.name; } item.time = utils.tools.formatDate(utils.math.multipliedBy(item.timestamp, 1000)) return item; }); ReturnClass.setResult(resultList); return ReturnClass.end(); } async getTxDetailsByHash(hash: string): Promise> { const ReturnClass = new Return(); const provider = this.getProvider(); const getTransaction = await provider.getTransaction(hash); if (getTransaction === null) return error.hash const getBlock = await provider.getBlock(getTransaction.blockNumber); const getTxReceiptData = await this.rpc.getTransactionReceipt(hash); const getTxReceipt = getTxReceiptData.result; let paramsJson: IEthereumTxDetails = { type: txListType.init, stat: txListStat.init, hash: getTransaction.hash, blockHash: getTransaction.blockHash, blockNumber: Number(getTransaction.blockNumber).toString(), from: getTransaction.from, to: getTransaction.to, fee: this.getFee(getTransaction.gasLimit.toNumber().toString(), getTransaction.gasPrice.toNumber().toString()), nonce: getTransaction.nonce.toString(), value: utils.math.plus(this.formatUnits(getTransaction.value, 18), 0).toString(), time: utils.tools.formatDate(getBlock.timestamp * 1000), timestamp: (getBlock.timestamp * 1000).toString(), token: [], } if (getTxReceipt) { if (getTxReceipt.logs && getTxReceipt.logs.length) { const contract = this.getContract(paramsJson.to, erc20); const getContractDetails = await this.getContractDetails(paramsJson.to) as any; if (getContractDetails.code === 0) { paramsJson['token'] = []; getTxReceipt.logs.map(async (item) => { if (item.topics.length <= 1) return; const logs = contract.interface.parseLog(item); if (logs.name === 'Transfer') { paramsJson.value = utils.math.plus(this.formatUnits(logs.args.value, getContractDetails.rs.decimals), 0).toString(); } paramsJson['token'].push({ name: logs.name, from: logs.args.from, to: logs.args.to, value: utils.math.plus(this.formatUnits(logs.args.value, getContractDetails.rs.decimals), 0).toString(), symbol: getContractDetails.rs.symbol, }) }) } } if (Number(getTxReceipt.status) === 1) paramsJson.stat = txListStat.success; if (Number(getTxReceipt.status) === 0) paramsJson.stat = txListStat.fail; } ReturnClass.setResult(paramsJson); return ReturnClass.end(); } /** * 获取手续费列表 * @param from 转账地址 * @param to 收款地址 * @param params */ async getFeeList(from: string, to: string, params?: IFeeParams): Promise> { params = params || {}; const ReturnClass = new Return(); const gasPriceResult = await this.rpc.getGasPrice(); let baseData: IFeeResult = { gasLimit: `0x${Number(21000).toString(16)}`, gasPrice: gasPriceResult.result, fee: '', }; if (typeof params.contract !== "undefined" && params.contract.indexOf('0x') === 0) { const contractClass = typeof params.tokenId === "number" ? this.getErc721Contract(params.contract) : this.getContract(params.contract, erc20); if (typeof params.decimals !== "number") params.decimals = params.tokenId ? 0 : await contractClass.decimals().catch(() => 0); let getGasLimitParams; if (typeof params.tokenId === "number") { getGasLimitParams = await contractClass.populateTransaction.transferFrom(from, to, 1) } else { const balanceRes = await this.getBalance(from, {contract: params.contract, decimals: params.decimals}); const value = this.parseUnits(balanceRes.rs.usable, params.decimals)._hex; getGasLimitParams = await contractClass.populateTransaction.transfer(to, value); } getGasLimitParams.from = from; const gasLimit = await this.rpc.getGasLimit(getGasLimitParams); baseData.gasLimit = `0x${Math.ceil(utils.math.multipliedBy(gasLimit.result, 1.5)).toString(16)}`; } baseData.fee = this.getFee(baseData.gasLimit, baseData.gasPrice); ReturnClass.setResult([ this._getCountFee(baseData.gasLimit, baseData.gasPrice, 1.3), this._getCountFee(baseData.gasLimit, baseData.gasPrice, 1.2), baseData, this._getCountFee(baseData.gasLimit, baseData.gasPrice, 0.95), ]); return ReturnClass.end() } // 升级删除 替换成为 getFeeList async getCountFee(from: string, to: string, contract?: string | undefined, tokenId?: string): Promise> { const ReturnClass = new Return(); const gasPriceResult = await this.rpc.getGasPrice(); let baseData: IFeeResult = { gasLimit: `0x${Number(21000).toString(16)}`, gasPrice: gasPriceResult.result, fee: '', }; if (typeof contract !== "undefined" && contract.indexOf('0x') === 0) { const contractClass = tokenId ? this.getErc721Contract(contract) : this.getContract(contract, erc20); const decimals = tokenId ? 0 : await contractClass.decimals().catch(() => 0); const balanceRes = await this.getBalance(from, {contract: contract, decimals}); const value = tokenId ? '0x00' : this.parseUnits(balanceRes.rs.usable, decimals)._hex; let getGasLimitParams = tokenId ? await contractClass.populateTransaction.transferFrom(from, to, tokenId) : await contractClass.populateTransaction.transfer(to, value); getGasLimitParams.from = from; const gasLimit = await this.rpc.getGasLimit(getGasLimitParams); baseData.gasLimit = `0x${Math.ceil(utils.math.multipliedBy(gasLimit.result, 1.5)).toString(16)}`; } baseData.fee = this.getFee(baseData.gasLimit, baseData.gasPrice); ReturnClass.setResult([ this._getCountFee(baseData.gasLimit, baseData.gasPrice, 1.2), this._getCountFee(baseData.gasLimit, baseData.gasPrice, 1.1), baseData, this._getCountFee(baseData.gasLimit, baseData.gasPrice, 0.9), ]); return ReturnClass.end() } private _getCountFee(gasLimit: string, gasPrice: string, proportion: number) { const gasPriceResult = utils.math.multipliedBy(gasPrice, proportion); let result: IFeeResult = { gasLimit: gasLimit, gasPrice: this.parseUnits(Math.ceil(gasPriceResult).toString(), 0)._hex, fee: '' }; result.fee = this.getFee(result.gasLimit, result.gasPrice); return result; } async sendTx(value: string): Promise> { const ReturnClass = new Return(); const data = await this.rpc.sendTransaction(value); if (data.error) { console.error(data); if (data.error.message.indexOf('insufficient funds for gas * price + value') === 0) return error.amountNotEnough; if (data.error.message.indexOf('intrinsic gas too low') === 0) return error.feeNotEnough; } ReturnClass.setResult(data.result); return ReturnClass.end(); } async transfer(from: string, to: string, amount: string, params?: ISignParams) { if (!this.verify.privateKey(params.privateKey)) return error.privateKey; const data = await this.sign(from, to, amount, params); if (data.code) return data; return await this.sendTx(data.rs); } async sign(from: string, to: string, amount: string, params?: ISignParams): Promise> { params = params || {}; if (params.privateKey) { if (!this.verify.privateKey(params.privateKey)) return error.privateKey; const walletData = this.fromPrivateKeyToWallet(params.privateKey); if (this.getNormalAddress(walletData.address) !== this.getNormalAddress(from)) return error.privateKey; } if (params.contract && params.contract.indexOf('0x') === 0) { params.contractType = params.contractType ?? 'erc-20'; if (!params.abi) { if (params.contractType === 'erc-20') params.abi = erc20; if (params.contractType === 'erc-721') params.abi = erc721; } if (params.contractType === 'erc-20') { if (typeof params.decimals !== "number") { const contractClass = this.getContract(params.contract, params.abi, params.privateKey ? this.getWallet(params.privateKey) : undefined); const decimals = await contractClass.decimals().catch(err => { if (err.toString().indexOf('NETWORK_ERROR') >= 0) return error.networksTimeout else return 0 }) if (decimals.code > 0) return decimals; params.decimals = utils.types.number(decimals) ? decimals : decimals.toNumber(); } } if (typeof params.decimals !== "number") params.decimals = 0; } else params.decimals = 18; const value = this.parseUnits(amount, params.decimals)._hex; const transactionParams = { from: this.getNormalAddress(from), to: this.getNormalAddress(to), gasLimit: `0x${Number(31000).toString(16)}`, gasPrice: undefined, data: '0x', value, } let resultData; if (params.contract && params.contract.indexOf('0x') === 0) { let txParams; if (params.contractType === 'erc-20') { const contractClass = this.getContract(params.contract, params.abi, params.privateKey ? this.getWallet(params.privateKey) : undefined); txParams = await contractClass.populateTransaction.transfer(transactionParams.to, transactionParams.value); } else { const contractClass = this.getErc721Contract(params.contract, params.privateKey ? this.getWallet(params.privateKey) : undefined) txParams = await contractClass.populateTransaction.transferFrom(transactionParams.from, transactionParams.to, transactionParams.value); } if (!txParams.from) txParams.from = transactionParams.from; if (!params.gasLimit) { const getGasLimit = await this.rpc.getGasLimit(txParams).catch(err => { console.log('err', err.toString()) return {result: undefined} }); // console.log('getGasLimit', getGasLimit, utils.math.multipliedBy(getGasLimit.result, 1.5)) if (getGasLimit.result && Number(getGasLimit.result) > Number(params.gasLimit ?? 0)) params.gasLimit = `0x${Math.ceil(utils.math.multipliedBy(getGasLimit.result, 1.5)).toString(16)}`; } resultData = txParams; } else { resultData = transactionParams; } // console.log('params.gasLimit', params.gasLimit) if (params.gasPrice) resultData.gasPrice = params.gasPrice; if (params.gasLimit) resultData.gasLimit = params.gasLimit; // console.log('resultData', resultData.gasPrice) if (!resultData.gasPrice) { const gasPrice = await this.rpc.getGasPrice(); resultData.gasPrice = gasPrice.result.toString(); } // console.log(resultData.gasLimit, resultData.gasPrice) const fee = this.getFee(resultData.gasLimit ?? 0, resultData.gasPrice ?? 0); const verifyFee = await this.verify.balance(transactionParams.from, amount, { fee, contract: params.contract, decimals: params.decimals, }); if (verifyFee.code > 0 && params.contractType !== 'erc-721') return verifyFee; return await this.signTransaction(params.privateKey, resultData); } formatUnits(value: BigNumber, decimals: number): string { return this.getUtils().formatUnits(value, decimals); } async getAllowance(address: string, exchangeAddress: string, contractAddress: string, abi: string): Promise> { const ReturnClass = new Return(); const contractClass = await this.getContract(contractAddress, abi); const data = await contractClass.allowance(address, exchangeAddress); ReturnClass.setResult(data); return ReturnClass.end(); } getContract(address: string, abi: string, provider?: any): any { return new ethers.Contract(address, abi, provider ?? this.getProvider()); } getFee(gasLimit: string, gasPrice: string): string { let fee = utils.math.multipliedBy(gasLimit, gasPrice); return utils.math.dividedBy(fee, 1e18).toString(); } getProvider(): any { return ethers.getDefaultProvider(this._params.rpcUrl) } getWallet(privateKey: string): any { return new ethers.Wallet(privateKey, this.getProvider()) } getUtils(): any { return ethers.utils } parseUnits(value: string, decimals: number): BigNumber { return this.getUtils().parseUnits(value, decimals); } async signApprove(privateKey: string, exchangeAddress: string, contractAddress: string, abi: string, value?: string | undefined): Promise> { if (!this.verify.privateKey(privateKey)) return error.privateKey; if (!this.verify.address(exchangeAddress)) return error.contract; if (!this.verify.address(contractAddress)) return error.contract; if (!abi) return error.contractEthAbiError; const walletData = this.fromPrivateKeyToWallet(privateKey); const contractClass = this.getContract(contractAddress, abi); if (!value) value = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; const data = await contractClass.populateTransaction.approve(exchangeAddress, value); const params: IEthereumSignTransactionParams = { from: walletData.address, to: data.to, gasLimit: this.parseUnits('100000', 0)._hex, value: this.parseUnits('0', 0)._hex, data: data.data, } return await this.signTransaction(privateKey, params); } async signTransaction(privateKey: string | undefined, params: IEthereumSignTransactionParams): Promise> { const ReturnClass = new Return(); if (!this.verify.address(params.from)) return error.address; if (!this.verify.address(params.to)) return error.address; if (!params.nonce) { const nonce = await this.rpc.getPendingNonce(params.from); params.nonce = Number(nonce.result); } if (!params.gasPrice) { const gasPrice = await this.rpc.getGasPrice(); params.gasPrice = gasPrice.result.toString(); } if (!params.gasLimit) { const getGasLimit = await this.rpc.getGasLimit({ from: params.from, to: params.to, data: params.data }).catch(err => { console.log('err', err.toString()) return {result: undefined} }); if (getGasLimit.error && getGasLimit.error.message) { if (getGasLimit.error.message.indexOf('execution reverted: ERC20: transfer to the zero address') === 0) return error.addressNotZero; } if (getGasLimit.result) params.gasLimit = getGasLimit.result; } if (typeof params.gasPrice !== "undefined" && params.gasPrice.indexOf('0x') !== 0) params.gasPrice = `0x${Number(params.gasPrice).toString(16)}`; if (typeof params.gasLimit !== "undefined" && params.gasLimit.indexOf('0x') !== 0) params.gasLimit = `0x${Number(params.gasLimit).toString(16)}`; const resultData: any = { from: params.from, to: params.to, nonce: params.nonce, gasLimit: params.gasLimit, gasPrice: params.gasPrice, data: params.data, chainId: this._params.chainId, } if (params.value) resultData.value = params.value; const fee = this.getFee(resultData.gasLimit ?? 0, resultData.gasPrice ?? 0); // const verifyFee = await this.verify.balance(params.from, fee); // if (verifyFee.code > 0) return verifyFee; if (privateKey) { if (!this.verify.privateKey(privateKey)) return error.privateKey; const raw = await this.getWallet(privateKey).signTransaction(resultData); const hash = this.getUtils().keccak256(raw); ReturnClass.setHash(hash); ReturnClass.setResult(raw); } else { ReturnClass.setResult(fee); } ReturnClass.setFee(fee); return ReturnClass.end(); } async signMessage(privateKey: string, msgData: string): Promise> { msgData = msgData || ''; msgData = msgData.toString(); const ReturnClass = new Return(); const data = await this.getWallet(privateKey).signMessage(msgData); ReturnClass.setResult(data); return ReturnClass.end(); } signTypedMessage(privateKey: string, object: { data: string, raw: string }): IReturnResult { const ReturnClass = new Return(); const signature = joinSignature(this.getWallet(privateKey)._signingKey().signDigest(object.data)) ReturnClass.setResult(signature); return ReturnClass.end(); } async signPersonalMessage(privateKey: string, msgData: string): Promise> { const ReturnClass = new Return(); if (!this.verify.privateKey(privateKey)) return error.privateKey; const web3 = new Web3(new Web3.providers.HttpProvider(this._params.rpcUrl)); const data = await web3.eth.accounts.sign(msgData, privateKey); ReturnClass.setResult(data.signature); // const _hashMessage = ethers.utils.hashMessage(msgData) // const rs = await this.getWallet(privateKey).signMessage(_hashMessage); // ReturnClass.setResult(rs); return ReturnClass.end() } getErc721Contract(contractAddress: string, provider?: any): any { return this.getContract(contractAddress, erc721, provider); } async getErc721DataByAccount(contractAddress: string, address: string): Promise> { const ReturnClass = new Return(); const contract = this.getErc721Contract(contractAddress); const addr = this.getNormalAddress(address); const totalSupply = await contract.totalSupply(); return new Promise(resolve => { const count = 100; const totalLength = totalSupply.toNumber(); // const totalLength = 18; // console.log('totalLength', totalLength); const length = Math.ceil(totalLength / count) - 1; const result: number[] = []; let page = 1; const getOwnerOf = () => { let promiseList = []; const lenJ = page <= length ? count : totalLength - ((page - 1) * count); for (let j = 0; j <= lenJ; j++) { const tokenId = ((page - 1) * count) + j; // console.log(tokenId); promiseList.push(contract.ownerOf(tokenId).catch(() => undefined)); } Promise.all(promiseList).then(data => { const has = data.findIndex(m => m === addr); // console.log(page, has); if (has >= 0) { data.map((item, key) => { if (item !== addr) return; const tokenId = ((page - 1) * count) + key; result.push(tokenId); }); } if (length >= page) { page++; getOwnerOf(); } else { ReturnClass.setResult(result); return resolve(ReturnClass.end()) } }) } getOwnerOf(); }) } async getErc721TokenURI(contractAddress: string, tokenId: number): Promise> { const ReturnClass = new Return(); const contract = this.getErc721Contract(contractAddress); ReturnClass.setResult(await contract.tokenURI(tokenId)); return ReturnClass.end() } async getErc721Details(contractAddress: string): Promise> { const ReturnClass = new Return(); const contract = this.getErc721Contract(contractAddress); ReturnClass.setResult({ name: await contract.name(), symbol: await contract.symbol(), address: this.getNormalAddress(contractAddress) }) return ReturnClass.end() } async getTokenList(address: string): Promise> { const ReturnClass = new Return(); const data = await this.api.getAllToken(address); ReturnClass.setResult(data.result); return ReturnClass.end() } readonly swap: ISwap = { getAmountsIn: async (routerAddress: string, routerAbi: string, fromTokenAddress: string, toTokenAddress: string, toValue: string, fromDecimals: number, toDecimals: number): Promise => { const contractClass = this.getContract(routerAddress, routerAbi); const valueData = this.parseUnits(toValue, toDecimals); const data = await contractClass.getAmountsIn(valueData, [fromTokenAddress, toTokenAddress]); return this.formatUnits(data[0], fromDecimals); }, getAmountsOut: async (routerAddress: string, routerAbi: string, fromTokenAddress: string, toTokenAddress: string, fromValue: string, fromDecimals: number, toDecimals: number): Promise => { const contractClass = this.getContract(routerAddress, routerAbi); const valueData = this.parseUnits(fromValue, fromDecimals); const data = await contractClass.getAmountsIn(valueData, [fromTokenAddress, toTokenAddress]); return this.formatUnits(data[0], toDecimals); }, hasPair: async (factoryAddress: string, factoryAbi: string, fromTokenAddress: string, toTokenAddress: string): Promise => { const contractClass = this.getContract(factoryAddress, factoryAbi); return await contractClass.getPair(fromTokenAddress, toTokenAddress); }, // signApprove: async (privateKey: string, value: string, decimals: number, exchangeAddress: string, exchangeAbi: string, fromToken: string, toToken: string): Promise => { // return Promise.resolve(undefined); // }, }; readonly api: IApi = { getAllToken: (address: string): Promise => { let key = ''; if (this._params.keys) key = utils.tools.toShuffle(this._params.keys)[0]; const url = `${this._params.api}/api?module=account&action=tokenlist&address={address}&apikey=${key}`; return new utils.Http().get(url, {address}); }, getContractTxList: (address: string, contract: string, size: number, page: number): Promise => { let key = ''; if (this._params.keys) key = utils.tools.toShuffle(this._params.keys)[0]; const url = `${this._params.api}/api?module=account&action=tokentx&contractaddress={contract}&address={address}&page={page}&offset={size}&sort=desc&apikey=${key}`; return new utils.Http().get(url, {address, contract, size, page}); }, getInternalList: (address: string, size: number, page: number): Promise => { let key = ''; if (this._params.keys) key = utils.tools.toShuffle(this._params.keys)[0]; const url = `${this._params.api}/api?module=account&action=txlistinternal&address={address}&startblock=0&endblock=99999999&page={page}&offset={size}&sort=desc&apikey=${key}`; return new utils.Http().get(url, {address, size, page}); }, getPendingTxList: (address: string): Promise => { const url = `${this._params.api}/api?module=account&action=pendingtxlist&address={address}`; return new utils.Http().get(url, {address}); }, getTxList: (address: string, size: number, page: number): Promise => { let key = ''; if (this._params.keys) key = utils.tools.toShuffle(this._params.keys)[0]; const url = `${this._params.api}/api?module=account&action=txlist&address={address}&startblock=0&endblock=99999999&page={page}&offset={size}&sort=desc&apikey=${key}`; return new utils.Http().get(url, {address, size, page}); } }; readonly rpc: IRpc = { getBalance: (address): Promise => { const rpcParams = GetJsonRpcParams('eth_getBalance', [address, 'latest']); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, getBlockNumber: (): Promise => { const rpcParams = GetJsonRpcParams('eth_blockNumber', []); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, getCall: (params: { [p: string]: string }): Promise => { const rpcParams = GetJsonRpcParams('eth_call', [params, "latest"]); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, getChainId: (): Promise => { const rpcParams = GetJsonRpcParams('eth_chainId', []); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, getGasLimit: (params: { [p: string]: string }): Promise => { const rpcParams = GetJsonRpcParams('eth_estimateGas', [params]); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, getGasPrice: (): Promise => { const rpcParams = GetJsonRpcParams('eth_gasPrice', []); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, getNonce: (address: string): Promise => { const rpcParams = GetJsonRpcParams('eth_getTransactionCount', [address, 'pending']); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, getPendingNonce: (address: string): Promise => { const rpcParams = GetJsonRpcParams('eth_getTransactionCount', [address, 'pending']); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, getTransactionByHash: (hash: string): Promise => { const rpcParams = GetJsonRpcParams('eth_getTransactionByHash', [hash]); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, getTransactionReceipt: (hash: string): Promise => { const rpcParams = GetJsonRpcParams('eth_getTransactionReceipt', [hash]); return new utils.Http().post(this._params.rpcUrl, rpcParams); }, sendTransaction: (value: string): Promise => { const rpcParams = GetJsonRpcParams('eth_sendRawTransaction', [value]); return new utils.Http().post(this._params.rpcUrl, rpcParams); } }; } function GetJsonRpcParams(method, params = []) { return { "jsonrpc": "2.0", "method": method, "params": params, "id": 1 } } export { Ethereum, } export type { IEthereum }