401 lines
12 KiB
JavaScript
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");
|