const { createApp } = Vue; const API_BASE = "https://xwork.kingsome.cn"; const API_BASE_LOCAL = "http://localhost:3002"; const TASK_INFO_URL = "/workflow/task"; var vConsole = new window.VConsole(); const DEFAULT_CHAIN_DATA = { name: "Matic Testnet RPC", type: "Testnet", rpc: "https://rpc-mumbai.maticvigil.com", id: 80001, symbol: "MATIC", explorerurl: "https://mumbai.polygonscan.com/", }; async function loadJson(url) { return fetch(url).then((response) => response.json()); } function toHexChainId(chainId) { return "0x" + chainId.toString(16); } async function connectMetaMask() { let provider = null; if (typeof window.ethereum !== "undefined") { provider = window.ethereum; try { await provider.request({ method: "eth_requestAccounts" }); } catch (error) { if (error.code === -32002) { throw new Error("MeatMask not login, Open MeatMask and login first"); } else { throw new Error("User Rejected"); } } } else if (window.web3) { provider = window.web3.currentProvider; } else if (window.celo) { provider = window.celo; } else { throw new Error("No Web3 Provider found"); } return provider; } const fetchTaskInfo = function (taskId) { const host = location.host === "localhost" ? API_BASE_LOCAL : API_BASE; const url = `${host}${TASK_INFO_URL}/${taskId}`; return fetch(url) .then((response) => { if (!response.ok) { throw new Error("Failed to fetch task info"); } return response.json(); }) .then((data) => { if (data.errcode) { throw new Error(data.errmsg); } return data.data; }); }; createApp({ data() { return { id: "", task: {}, subTasks: [], address: "", types: {}, walletConnect: false, contract: null, wallet: null, disabled: false, loading: false, hasRole: false, loadingMask: false, showCancel: false, showConfirm: false, loadingTxt: "加载中", chainId: 0, hasPermission: false, // 当前用户是否有权限执行confirm alertShow: false, alertMsg: "", alertType: "success", }; }, async created() { this.loading = true; try { let params = getAllParameter(); this.id = params.id; console.log("taskid: ", this.id); let result = await fetchTaskInfo(this.id); this.task = result.chainTask; this.subTasks = result.requestTasks; this.address = result.address; this.types = result.types; console.log(result); await this.initWallet(); } catch (err) { console.log(err); this.alert("加载数据失败", "danger", true); } this.loading = false; }, methods: { alert(msg, type, autoClose = false) { this.alertMsg = msg; this.alertType = type; this.alertShow = true; if (autoClose) { setTimeout(() => { this.closeAlert(); }, 3000); } }, closeAlert() { this.alertShow = false; }, showType(type) { return this.types[type]; }, // 显示加载动画 showLoadingMask(txt) { this.loadingTxt = txt || "加载中"; this.loadingMask = true; }, // 隐藏加载动画 hideLoadingMask() { this.loadingMask = false; }, async initInstance(user, address, jsonUrl) { let json = await loadJson(jsonUrl); return new this.web3.eth.Contract(json.abi, address, { from: user }); }, async initWallet() { this.provider = await connectMetaMask(); this.web3 = new Web3(this.provider); let accounts = await this.web3.eth.getAccounts(); if (accounts.length > 0) { this.account = accounts[0]; } this.contract = await this.initInstance( this.account, this.address, "assets/abis/BEMultiSigWallet.json" ); this.chainId = await this.web3.eth.getChainId(); console.log("current chain id: " + this.chainId); await this.checkChain(); this.hasRole = await this.checkRole(); console.log("current has operation role: " + this.hasRole); this.subscribeToEvents(); this.walletConnect = true; console.log(this.chainId, this.account); await this.refreshData(); }, async refreshData() { let cando = true; let canCancel = false; for (let sub of this.subTasks) { let status = await this.querySchedule(sub.scheduleId); console.log("task status: ", status); sub.taskExists = status[0]; // 任务是否在链上存在 sub.executed = status[1]; // 是否已经执行了 sub.confirmed = status[2]; // 是否已经满足多签条件 sub.selfConfirm = status[3]; // 当前用户是否已经确认 let statusArr = []; if (!sub.taskExists) { cando = false; statusArr.push(["任务未上链", "danger"]); } if (sub.selfConfirm) { cando = false; statusArr.push(["已确认", "danger"]); } if (sub.confirmed) { cando = false; statusArr.push(["已满足条件", "danger"]); } if (sub.executed) { cando = false; statusArr.push(["已执行", "danger"]); } if (sub.selfConfirm && !sub.executed) { canCancel = true; } if (statusArr.length === 0 && cando) { statusArr.push(["可操作", "info"]); } sub.statusArr = statusArr; } this.showCancel = canCancel; this.showConfirm = cando; }, async checkRole() { let roleConfirm = await this.contract.methods.CONFIRM_ROLE().call(); let result = await this.contract.methods .hasRole(roleConfirm, this.account) .call(); return result; }, checkChain() { return new Promise((resolve, reject) => { this.confirmNeededChain((err, res) => { if (err) { reject(err); } else { resolve(res); } }); }); }, async confirmNeededChain(cb) { const chainId = this.chainId; if (chainId !== DEFAULT_CHAIN_DATA.id) { console.log( chainId + "chain id not match, switch to: ", DEFAULT_CHAIN_DATA.name ); await this.switchEthereumChain(cb); } else { cb && cb(null, 1); } }, async switchEthereumChain(cb) { let self = this; let data = DEFAULT_CHAIN_DATA; let hexChainId = toHexChainId(data.id); const onChainChange = async function (chainId) { const chainIdNum = parseInt(chainId); console.log("chainChanged: ", chainId, chainIdNum); self.chainId = chainIdNum; self.provider.removeListener("chainChanged", onChainChange); await self.confirmNeededChain(cb); }; self.provider.on("chainChanged", onChainChange); try { await self.provider.request({ method: "wallet_addEthereumChain", params: [ { chainId: hexChainId, chainName: data.name, nativeCurrency: { name: data.symbol, symbol: data.symbol, decimals: data.decimals || 18, }, blockExplorerUrls: [data.explorerurl], rpcUrls: [data.rpc], }, ], }); console.log("add chain success"); } catch (addError) { console.error("add chain error: ", addError); self.provider.removeListener("chainChanged", onChainChange); cb && cb(addError, 0); } // try { // await this.provider.request({ // method: 'wallet_switchEthereumChain', // params: [{ chainId: hexChainId }], // }) // console.log('switch chain success') // } catch (e) { // console.log('switch chain error: ', e) // if (e.code === 4902 || e.message.indexOf('Unrecognized chain ID') >= 0) { // try { // await this.provider.request({ // method: 'wallet_addEthereumChain', // params: [ // { // chainId: hexChainId, // chainName: data.name, // nativeCurrency: { // name: data.symbol, // symbol: data.symbol, // decimals: data.decimals || 18, // }, // blockExplorerUrls: [data.explorerurl], // rpcUrls: [data.rpc], // }, // ], // }) // console.log('add chain success') // } catch (addError) { // console.error('add chain error: ', addError) // } // } // } }, subscribeToEvents() { this.provider.on("accountsChanged", async (accounts) => { if (accounts && accounts.length > 0) { if (this.account !== accounts[0]) { console.log("account change", this.account, accounts[0]); this.account = accounts[0]; this.hasRole = await this.checkRole(); } } }); // Subscribe to chainId change this.provider.on("chainChanged", async (chainId) => { const chainIdNum = parseInt(chainId); console.log("chainChanged", chainId, chainIdNum); if (this.chainId !== chainIdNum) { this.chainId = chainIdNum; location.reload(); } }); // Subscribe to session disconnection this.provider.on("disconnect", (err) => { console.log("disconnect", err); location.reload(); }); }, makeBatchRequest(calls, callFrom) { let batch = new this.web3.BatchRequest(); let promises = calls.map((call) => { return new Promise((resolve, reject) => { let request = call.request({ from: callFrom }, (error, data) => { if (error) { reject(error); } else { resolve(data); } }); batch.add(request); }); }); batch.execute(); return Promise.all(promises); }, async confirmTask() { if (!this.hasRole) { this.alert("没有操作权限", "warning", true); return; } let ids = this.subTasks.map((o) => o.scheduleId); console.log("confirm task", ids); this.showLoadingMask("处理上链请求"); try { let gas = await this.contract.methods .confirmTransaction(ids) .estimateGas(); gas = gas | 0; await this.contract.methods.confirmTransaction(ids).send({ gas }); await this.refreshData(); } catch (err) { console.log("error confirm task", err); } this.hideLoadingMask(); }, async rejectTask() { console.log("reject task"); if (!this.hasRole) { this.alert("没有操作权限", "warning", true); return; } let ids = this.subTasks.map((o) => o.scheduleId); this.showLoadingMask("撤销确认信息"); try { let gas = await this.contract.methods .revokeConfirmation(ids) .estimateGas(); gas = gas | 0; await this.contract.methods.revokeConfirmation(ids).send({ gas }); await this.refreshData(); } catch (err) { console.log("error confirm task", err); } this.hideLoadingMask(); }, async querySchedule(id) { let instance = this.contract; return this.makeBatchRequest([ instance.methods.isOperation(id).call, //id是否在合约里 // instance.methods.isOperationPending(id).call, // 任务是否在等待执行 // instance.methods.isOperationReady(id).call, // 任务是否已经准备好, 但未执行 instance.methods.isOperationDone(id).call, // 任务是否已经执行 instance.methods.isConfirmed(id).call, // 任务是否已经已经满足多签的条件 instance.methods.confirmations(id, this.account).call, //当前用户是否已经确认 instance.methods.required().call, //多签的最小确认数 instance.methods.getTimestamp(id).call, ]); }, }, }).mount("#app");