web3技术、连接metamask、切换metamask网络、查询metamask余额、计算gas手续费、metamask签名等.........

发布时间 2023-04-27 11:17:16作者: 龙卷风吹毁停车场
在assets下新建web3.js文件
import { Message } from 'element-ui';
import { ethers } from "ethers"; //版本号为 "ethers": "^4.0.47",
import Tabi from './Tabi.json'; //后端给
import { infoChain } from '@/api/require'

let provider = new ethers.providers.Web3Provider(window[sessionStorage.getItem('ethereumType')] || window.ethereum);
function renewProvider() {
    provider = new ethers.providers.Web3Provider(window[sessionStorage.getItem('ethereumType')] || window.ethereum);
}

/**
 * 查询主网络资产余额
 * @param {*} address 钱包地址
 * @param {*} tokenDecimals token精度
 */
export async function getMainNetworkBalance(address ,tokenDecimals=18){
    const balancePromise = provider.getBalance(address)
    return balancePromise.then((balance) => {
        return ethers.utils.formatUnits(balance, tokenDecimals);
    }).catch(e => {
        Message({
            type: 'error',
            showClose: true,
            message: 'Failed to get balance.'
        })
        throw new Error("获取余额失败" + e)
    });
}


// 查询余额,固定不变的
const erc20BalanceAbiFragment = [{
    "constant": true,
    "inputs": [{ "name": "", "type": "address" }],
    "name": "balanceOf",
    "outputs": [{ "name": "", "type": "uint256" }],
    "type": "function"
}]
// token授权,固定不变的
const ERC20_ABI = [
    "function allowance(address owner, address spender) external view returns (uint256)",
    "function approve(address spender, uint256 amount) external returns (bool)"
]

/**
 * 添加metamask网络
 */
async function addChain() {
    try {
        // infoChain 获取要添加的链的 {chainId,chainName,nativeCurrency,rpcUrls,blockExplorerUrls}
        const res = await infoChain()
        if (res.code === 0) {
            await window[sessionStorage.getItem('ethereumType') || 'ethereum'].request({
                method: "wallet_addEthereumChain",
                params: [{
                    chainId: '0x' + res.data.chainId.toString(16),
                    chainName: res.data.chainName,
                    nativeCurrency: {
                        name: res.data.suisse.symbol,
                        symbol: res.data.suisse.symbol,
                        decimals: 18,
                    },
                    rpcUrls: [res.data.rpcUrl],
                    blockExplorerUrls: [res.data.scanUrl],
                }],
            });
        }

    } catch (error) {
        console.log(error)
        throw error
    }
}

/**
 * 切换到指定网络
 * @param {*} chainId 要切换的链id
 */
export async function toSwitch(chainId) {
    try {
        window[sessionStorage.getItem('ethereumType') || 'ethereum'] && await window[sessionStorage.getItem('ethereumType') || 'ethereum'].request({
            method: 'wallet_switchEthereumChain',
            params: [{ chainId: '0x' + chainId.toString(16) }]
        }, (err, result) => {
            console.log(err, '添加失败')
            if (err) {
                Message({
                    message: err.message,
                    type: 'error'
                })
                return false
            }
        });
    } catch (error) {
        if (error.code === 4902) {
            addChain()
        } else {
            throw error
        }
    }
}

/**
 * 检查当前metamask 链接网络是否正确
 * @param {*} chainId 传入链id
 * @returns {Boolean} 
 */
export async function chainIdJudgment(chainId) {
    try {
        let eth_chainId = await window[sessionStorage.getItem('ethereumType') || 'ethereum'].request({ method: 'eth_chainId' }); //16进制
        // 将16进制转为10进制
        eth_chainId = parseInt(eth_chainId, 16)
        if (eth_chainId != chainId) {
            toSwitch(chainId)
        } else {
            return true
        }
    } catch (error) {
        console.log(error)
        return false
    }
}


/**
 * 发起交易
 * @param {multySignAddress} String 后端部署合约地址
 * @param {String} numbers 交易数量
 * @param {Number} decimals token精度
 */
export async function payMoney(multySignAddress,pid,numbers, decimals=18) {
    // new ethers.utils.BigNumber
    // numbers 交易数量
    // decimals token精度
    // functions后面的方法 调什么方法在 Tabi.json找对应的name(问后端用那个)
    // encode 传几个参数在 Tabi 里面inputs数组有几个传几个,类型看internalType,值看internalType值
    /**
     * 使用条件:购买5个面包,一个面包价值6.89元  priceView 当面包的价格
     * ethers.utils.parseEther(new BigNumber(numbers).multipliedBy(new BigNumber(priceView)).toString())
     * 如何使用: 将value: '0x00' 改为 value: ethers.utils.parseEther(new BigNum................
     */
    const numberOfTokens = ethers.utils.parseUnits(numbers.toString(), decimals);
    const pidTokens = ethers.utils.parseUnits(pid.toString(), 0);
    const iface = new ethers.utils.Interface(Tabi);
    const data = iface.functions.deposit.encode([pidTokens,numberOfTokens]);
    const address = window[sessionStorage.getItem('ethereumType') || window.ethereum].selectedAddress
    let transactionParameters = {
        to: multySignAddress,
        from: address, //验证合约调用需要from,必传
        value: '0x00',
        data: data
    };
    const failed = await validate(transactionParameters);
    if (failed) {
        console.error('failed approveERC20' + failed);
        return { success: false, msg: 'failed crossIn' + failed }
    }
    delete transactionParameters.from;   //etherjs 4.0 from参数无效 报错
    return sendTransaction(transactionParameters)
}
//验证交易参数
async function validate(tx) {
    renewProvider()
    try {
        const result = await provider.call(tx);
        return ethers.utils.toUtf8String("0x" + result.substr(138));
    } catch (e) {
        // 金额不够提示
        if(e.code == -32000){
            if(e.message.indexOf('err: insufficient funds for gas') > -1){
                Message({
                    type: 'error',
                    showClose: true,
                    message: 'You do not have enough Balance in your account to pay for transaction fees on network.'
                })
            }else{
                Message({
                    type: 'error',
                    showClose: true,
                    message: e.message
                })
            }
        }else{
            Message({
                type: 'error',
                showClose: true,
                message: e.message
            })
        }
        return false;
    }
}

async function sendTransaction(tx) {
    renewProvider()
    const wallet = provider.getSigner();
    return await wallet.sendTransaction(tx);
}
/**
 * 进行授权
 * @param {*} contractAddress 支付币的合约地址
 * @param {*} multySignAddress 要收到支付的合约地址
 * @param {*} address 钱包地址
 */
export async function approveERC20(contractAddress, multySignAddress, address) {
    const iface = new ethers.utils.Interface(ERC20_ABI);
    const data = iface.functions.approve.encode([multySignAddress, new ethers.utils.BigNumber('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')]);
    const transactionParameters = {
        to: contractAddress,
        from: address,
        value: '0x00',
        data: data,
    };
    const failed = await validate(transactionParameters);
    if (failed) {
        console.error('failed approveERC20' + failed);
        return { success: false, msg: 'failed approveERC20' + failed }
    }
    delete transactionParameters.from;   //etherjs 4.0 from参数无效 报错
    return sendTransaction(transactionParameters)
    // const res ={}  const r = await wait()
}


/**
 * 查询是否需要授权
 * @param contractAddress 支付币的合约地址
 * @param multySignAddress 要收到支付的合约地址
 * @param address 钱包账户地址
 * 参数里面的contractAddress 是erc20资产的合约地址  multySignAddress是要后端他们部署的那个合约地址
*/
export async function getERC20Allowance(contractAddress, multySignAddress, address) {
    renewProvider()
    const contract = new ethers.Contract(contractAddress, ERC20_ABI, provider);
    const allowancePromise = contract.allowance(address, multySignAddress);
    return allowancePromise
        .then(allowance => {
            const baseAllowance = "39600000000000000000000000000";
            //已授权额度小于baseAllowance,则需要授权
            return allowance.lte(baseAllowance)
        })
        .catch(e => {
            // this.$mes.closeLoading()
            Message({
                type: 'error',
                showClose: true,
                message: 'Failed to get balance.'
            })
            console.error("获取erc20资产授权额度失败" + e);
            return true;
        });
}
/**
 * 查询账户余额或者查询合约余额
 * @param contractAddress ERC20合约地址
 * @param tokenDecimals token小数位数
 * @param address 账户地址
*/
export function getERC20Balance(contractAddress, tokenDecimals, address) {
    renewProvider()
    let contract = new ethers.Contract(contractAddress, erc20BalanceAbiFragment, provider);
    let balancePromise = contract.balanceOf(address);
    return balancePromise.then((balance) => {
        return ethers.utils.formatUnits(balance, tokenDecimals);
    }).catch(e => {
        // this.$mes.closeLoading()
        Message({
            type: 'error',
            showClose: true,
            message: 'Failed to get balance.'
        })
        throw new Error("获取余额失败" + e)
    });
}

// 获取gas的单价
export async function getGasPrice() {
    renewProvider()
    const gasPrice = await provider.getGasPrice();
    return gasPrice.toString();
}

// 预估最大会消耗多少gas
export async function estimateGas(tx) {
    try {
        renewProvider()
        const failed = await validate(tx);
        if (failed) {
            console.error('failed approveERC20' + failed);
            return { success: false, msg: 'failed approveERC20' + failed }
        }
        const gasLimit = await provider.estimateGas(tx);
        return gasLimit.add(10000).toString();
    } catch (e) {
        console.log(e, 'fail to estimateGas, use the defaultGasLimit');
        return '150000';
    }
}

/**
 * 查询gas费(交易手续费)
 * @param tx 参数是发交易的transactionParameters参数
 * @returns gas费
 */

export async function getFee(multySignAddress,pid,numbers, _this) {
    const numberOfTokens = ethers.utils.parseUnits(numbers.toString(), 18);
    const pidTokens = ethers.utils.parseUnits(pid.toString(), 0);
    const iface = new ethers.utils.Interface(Tabi);
    const data = iface.functions.deposit.encode([pidTokens,numberOfTokens]);
    const address = window[sessionStorage.getItem('ethereumType') || window.ethereum].selectedAddress
    let transactionParameters = {
        to: multySignAddress,
        from: address, //验证合约调用需要from,必传
        value: '0x00',
        data: data
    };
    const gasPrice = await getGasPrice();
    const gasLimit = await estimateGas(transactionParameters);
    // accMul 乘法
    const bigNumberFee = _this.$tool.accMul(gasPrice, gasLimit);
    return ethers.utils.formatEther(bigNumberFee);
}