2023-04-20 10:53:30 +08:00

401 lines
12 KiB
JavaScript

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");