import Web3 from "web3"; import IPPTAbi from './ippt-abi.json' import USDTAbi from './usdt-abi.json' // 将科学计数法形式的数字或字符串转换为可读的字符串 function scientificNotationToString(param) { let strParam = String(param); let flag = /e/.test(strParam); if (!flag) return param.toString(); // 指数符号 true: 正,false: 负 let sysbol = true; if (/e-/.test(strParam)) { sysbol = false; } // 指数 let index = Number(strParam.match(/\d+$/)[0]); // 基数 let basis = strParam.match(/^[\d\.]+/)[0].replace(/\./, ""); if (sysbol) { return basis.padEnd(index + 1, "0"); } else { return basis.padStart(index + basis.length, "0").replace(/^0/, "0."); } } // 将数字转换到指定精度 function toDecimals(value, decimals) { return Number(value) * 10 ** Number(decimals); } // 从指定精度转换数字 function fromDecimals(value, decimals) { return value / Math.pow(10, decimals); } class ERC20 { decimals = 0; /** * 合约实例 * @type {Contract} */ contract; /** * 合约地址 * @type {string} */ contractAddress; /** * 合约 ABI */ abi; // 当前的钱包地址, 当 connected 为 true 时可用 get selectedAddress () { return window.ethereum.selectedAddress; } /** * * @return {*} */ get ethereum() { return window.ethereum } /** * @param contractAddress{string} * @param abi{Array} * @param web3{Web3} */ constructor (contractAddress, abi, web3) { this.contractAddress = contractAddress; this.abi = abi; this.contract = new web3.eth.Contract(abi, contractAddress); } /** * 加密 abi * @param methodDefinition {string} - 方法名 * @param parameters {Array<{type: "uint256" | "address"; value: number | string}>} - 参数 * @return {string} */ encodeABI(methodDefinition, parameters) { let m = Web3.utils.sha3(methodDefinition).slice(0, 10); parameters.forEach((p) => { switch (p.type) { case "uint256": case "address": m += p.value .toString() .toLowerCase() .replace("0x", "") .padStart(64, "0"); break; } }); return m; } handleError(err) { const message = err ? err.message ? err.message : err.toString() : 'Unknown Error' if (message.includes("User denied")) { uni.showToast({ title: 'failed', icon: 'none' }) } } /** * 相当于 contract.methods.xx.send * @param contractSendMethod * @param sendOptions * @return {PromiEvent} */ send (contractSendMethod, sendOptions) { const promiEvent = contractSendMethod.send(sendOptions); promiEvent.catch(err => this.handleError(err)); return promiEvent; } /** * 获取代币余额 * * @param account{string} * * @return Promise */ async $balanceOf (account) { const balance = await this.contract.methods.balanceOf(account).call({from: this.selectedAddress}) const decimals = await this.$decimals() return Number(fromDecimals(Number(balance), decimals)) } /** * 获取授权给某个合约的数量 * @param owner {string} - 钱包地址 * @param spender {string} - 授权地址 * @return {Promise} */ $allowance(owner, spender) { return this.contract.methods.allowance(owner, spender).call() .then(async (res) => { return Number(fromDecimals(Number(res), await this.$decimals())) }) } /** * 获取 vnft 合约的精度 * * @return Promise */ $decimals () { if (this.decimals) { return Promise.resolve(this.decimals) } return this.contract.methods .decimals() .call() .then(res => { this.decimals = Number(res) return this.decimals }) } /** * 授权代币给某个合约 * @param spender - 授权地址 * @param amount - 授权金额 * @return {PromiEvent} */ $approve(spender, amount) { amount = toDecimals(amount, this.decimals); return this.send(this.contract.methods.approve(spender, scientificNotationToString(amount)), {from: this.selectedAddress}) } /** * 授权最大数量 * @param spender * @return {PromiEvent} */ approveMAX(spender) { return this.send(this.contract.methods.approve(spender, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), {from: this.selectedAddress}) } async $burn(amount, sendOptions) { amount = toDecimals(amount, await this.$decimals()); return this.send(this.contract.methods.burn(amount), sendOptions); } /** * 转移 token * @param recipient - 接受者 * @param amount - 转移数量 * @return {PromiEvent} */ async $transfer(recipient, amount) { amount = toDecimals(amount, await this.$decimals()); // debugger return this.send(this.contract.methods.transfer(recipient, amount.toString()), {from: this.selectedAddress}); } } class USDT extends ERC20 { decimals = 18; } class IPPT extends ERC20 { /** * 调用IPPT 合约绑定上级关系 * creatCode(address superior, address plivate) public returns (bool) * @return {PromiEvent} */ $creatCode (superior, plivate) { return this.send(this.contract.methods.creatCode(superior, plivate), {from: this.selectedAddress}); } } /** * // 链接钱包, 所有 web3x.xx 方法都需要先执行 connectViaInPage 成功 * web3x.connectViaInPage() * .then(res => { * // 当前钱包地址 * console.log("当前钱包地址", web3x.selectedAddress); * * // 授权当前钱包的 USDT 给 IPPT (参数为 IPPT 合约地址) * web3x.usdt.approveMAX("0x6b175474e89094c44da98b954eedeac495271d0f") * .on("confirmation", hash => { * console.log("授权成功", hash); * }) * * // 调用IPPT 合约绑定上级关系 * // 0xFb4FC7Ddb8c4aa6b944703CE1e89D2B9Aa67a400: 上级地址 * // web3x.selectedAddress: 当前钱包地址 * web3x.ippt.$creatCode("0xFb4FC7Ddb8c4aa6b944703CE1e89D2B9Aa67a400", web3x.selectedAddress) * .on("confirmation", hash => { * console.log("绑定上级关系成功", res); * }) * }) * // 切换到 BSC 测试网, 需先调用 web3x.addBscTestnet() * web3x.switchToBscTestnet * // 添加 BSC 测试网 * web3x.addBscTestnet **/ class Web3X { /** * 是否已链接 * @type {boolean} */ connected = false; /** * web3 实例 * @type {Web3} */ #web3; /** * @type {IPPT} */ ippt /** * @type {ERC20} */ usdt /** * @type {string} */ ipptContractAddress /** * usdt 合约地址 * @type {string} */ usdtContractAddress constructor (ipptContractAddress, usdtContractAddress) { this.ipptContractAddress = ipptContractAddress; this.usdtContractAddress = usdtContractAddress; } // 当前的钱包地址, 当 connected 为 true 时可用 get selectedAddress () { return window.ethereum.selectedAddress; } /** * 是否存在运行环境中 * @return {boolean} */ hasRuntime () { return !!window.ethereum; } // 调用 rpc 方法 async #request (method, params) { if (!this.hasRuntime()) { throw new Error('No runtime'); } return await window.ethereum.request({ method, params, }); } /** * switch to a new chain (by chain ID) */ async switchChain (chainId) { return await this.#request('wallet_switchEthereumChain', [{ chainId }]); } /** * add a new chain (by chain object) */ async addChain (chainParams) { return await this.#request('wallet_addEthereumChain', [chainParams]); } /** * 切换到BSC 测试网, 需先调用 web3x.addBscTestnet() * @example * web3x.connectViaInPage() * .then(() => { * web3x.addBscTestnet() * .then(() => { * web3x.switchToBscTestnet() * }) * }) */ async switchToBscTestnet () { return await this.switchChain('0x61'); } /** * 添加 BSC 正式网 */ async addBscMainnet () { return await this.addChain({ chainId: '0x38', chainName: 'Binance Smart Chain Mainnet', nativeCurrency: { name: 'BNB', symbol: 'bnb', decimals: 18, }, rpcUrls: ['https://bsc-dataseed.binance.org/'], blockExplorerUrls: ['https://bscscan.com'], }); } /** * 切换到 BSC 正式网, 需先调用 web3x.addBscMainnet() */ async switchToBscMainnet () { return await this.switchChain('0x38'); } /** * 添加 BSC 测试网 * @example * web3x.connectViaInPage() * .then(() => { * web3x.addBscTestnet() * .then(() => { * web3x.switchToBscTestnet() * }) * }) */ async addBscTestnet () { return await this.addChain({ chainId: '0x61', chainName: 'Binance Smart Chain Testnet', nativeCurrency: { name: 'BNB', symbol: 'bnb', decimals: 18, }, rpcUrls: ['https://data-seed-prebsc-1-s1.binance.org:8545/'], blockExplorerUrls: ['https://testnet.bscscan.com'], }); } /** * 链接钱包 * @return {Promise} - 16 进制的钱包地址数组 */ async connectViaInPage () { return this.#request('eth_requestAccounts') .then(res => { this.#web3 = new Web3(window.ethereum); this.ippt = new IPPT(this.ipptContractAddress, IPPTAbi, this.#web3); this.usdt = new USDT(this.usdtContractAddress, USDTAbi, this.#web3); this.connected = true; return Promise.resolve(res) }); } /** * 监听钱包地址改变 * @param callback{() => void} - 回调函数, 参数为新的钱包地址 */ onAccountsChanged (callback) { return window.ethereum.on('accountsChanged', callback) } } export const web3x = new Web3X( // BSC链连接是https://endpoints.omniatech.io/v1/bsc/mainnet/public // BSC链Id是56 // ippt 合约 "0xa28B49C8F7ce83074EBE0bB0cc5eE670b5F6dF06", // usdt 合约 0x0a70dDf7cDBa3E8b6277C9DDcAf2185e8B6f539f "0x55d398326f99059fF775485246999027B3197955" );