import { reaction, makeAutoObservable, onBecomeObserved, onBecomeUnobserved, } from "mobx"; import { cryptography } from "@liskhq/lisk-client"; import { passphrase } from "@liskhq/lisk-client"; import { getNodeInfo } from "../api/node"; import { fetchWrapper } from "../shared/fetchWrapper"; import { labelMap } from "../shared/labelMap"; import { statusMap } from "../shared/statusMap"; import { generateSvgAvatar } from "../images/GenerateOnboardingSvg/GenerateSvg"; import { decryptedData } from "../utils/decryptedData"; import { encryptAccountData, encryptSharedData, generateTransaction, hashAccountData, } from "../utils/Utils"; import sodium from "sodium-universal"; class Store { _accountData = []; _passPhrase = ""; _nodeInfo = {}; _keysArray = []; _sharedData = []; _transactionsInfo = []; _accountInfo = {}; _loading = false; _tempTransactions = []; _vpnServers = []; _canVPN = false; _delegates = { data: [], meta: { count: 0, limit: 0, offset: 0, }, }; constructor() { makeAutoObservable(this, {}); reaction( () => this.keysArray, () => this.fetchSharedData() ); reaction( () => this.address, () => this.fetchAccountInfo() ); onBecomeObserved(this, "decryptedAccountData", () => this.fetchNewAccountData() ); onBecomeUnobserved(this, "decryptedAccountData", () => this.unobservedAccountData() ); onBecomeObserved(this, "sharedData", () => this.fetchKeysArray()); onBecomeUnobserved(this, "sharedData", () => this.unobservedSharedData()); onBecomeObserved(this, "transactionsInfo", () => this.fetchTransactionsInfo() ); onBecomeUnobserved(this, "transactionsInfo", () => this.unobservedTransactionsInfo() ); this.fetchNodeInfo(); } fetchVPNServers() { fetchWrapper .getAuth("vpn/servers", { networkIdentifier: this.nodeInfo.networkIdentifier, lastBlockID: this.nodeInfo.lastBlockID, }) .then((r) => this.fetchVPNServersSuccess(r)); } fetchDelegates(offset = 0, limit = 10) { fetchWrapper .getAuth(`delegates?limit=${limit}&offset=${offset}`) .then((r) => this.fetchDelegatesSuccess(r)); } fetchNewAccountData() { getNodeInfo() .then((info) => { this.fetchNodeInfoSuccess(info); fetchWrapper .getAuth("data/private", { networkIdentifier: this.nodeInfo.networkIdentifier, lastBlockID: this.nodeInfo.lastBlockID, }) .then((res) => this.accountDataFetchChange(res)) .catch((err) => this.throwError(err)); }) .catch((err) => this.throwError(err)); } fetchAccountInfo() { fetchWrapper .get(`accounts/${this.address}`) .then((res) => this.fetchAccountInfoSuccess(res)) .catch((err) => this.throwError(err)); } fetchKeysArray() { getNodeInfo() .then((info) => { this.fetchNodeInfoSuccess(info); fetchWrapper .getAuth("data/shared/", { networkIdentifier: this.nodeInfo.networkIdentifier, lastBlockID: this.nodeInfo.lastBlockID, }) .then((res) => this.keysArrayFetchChange(res)); }) .catch((err) => this.throwError(err)); } fetchTransactionsInfo() { fetchWrapper .get(`account/transactions/${this.address}`, { networkIdentifier: this.nodeInfo.networkIdentifier, lastBlockID: this.nodeInfo.lastBlockID, }) .then((res) => this.saveInfoTransactions(res)) .catch((err) => this.throwError(err)); } fetchTempTransactions() { fetchWrapper .get("node/transactions") .then((res) => this.fetchTempTransactionsSuccess(res)) .catch((err) => this.throwError(err)); } fetchPassPhrase() { this._passPhrase = sessionStorage.getItem("passPhrase") || ""; } fetchSharedData() { this._keysArray.forEach((elem) => { fetchWrapper .get(`data/shared/${elem}`) .then((res) => this.changeSharedData(res, elem)) .catch((err) => this.throwError(err)); }); } fetchNodeInfo() { getNodeInfo() .then((info) => this.fetchNodeInfoSuccess(info)) .catch((err) => this.throwError(err)); } pushSharedData(data, pubKey = this.pubKey) { data = data.filter((item) => item.status !== statusMap.blockchained); if (data.length > 0) { fetchWrapper .postAuth( "data/shared", { networkIdentifier: this.nodeInfo.networkIdentifier, lastBlockID: this.nodeInfo.lastBlockID, }, { publickey: this.pubKey, shared: encryptSharedData(data, this.passPhrase, pubKey), } ) .catch((err) => this.throwError(err)); } } pushAccountData(data = this.accountData) { fetchWrapper .postAuth( "data/private", { networkIdentifier: this.nodeInfo.networkIdentifier, lastBlockID: this.nodeInfo.lastBlockID, }, [...encryptAccountData(data, this.passPhrase, this.pubKey)] ) .then(() => this.fetchNewAccountData()) .catch((err) => this.throwError(err)); } pushAccountDataToBlockchain(data = this.decryptedAccountData) { const [removed, changed] = hashAccountData( data, this.decryptedAccountData, this.accountFeaturesMap ); const builder = generateTransaction( BigInt(this.accountInfo?.sequence?.nonce || 0), this.addressAndPubKey.publicKey, this.nodeInfo.networkIdentifier, this.passPhrase ); let signedTx = null; if (removed.length !== 0) signedTx = builder.remove(removed); if (changed.length !== 0) signedTx = builder.update(changed); if (signedTx) { fetchWrapper .post("transactions", {}, signedTx) .then(() => this.fetchTempTransactions()) .catch((err) => this.throwError(err)); this.accountInfo.sequence.nonce++; } } pushVoteTransaction(delegateAddress, amount) { const builder = generateTransaction( BigInt(this.accountInfo?.sequence?.nonce || 0), this.addressAndPubKey.publicKey, this.nodeInfo.networkIdentifier, this.passPhrase ); const signedTx = builder.vote(delegateAddress, amount); if (signedTx) { fetchWrapper .post("transactions", {}, signedTx) .then(() => this.fetchTempTransactions()) .catch((err) => this.throwError(err)); this.accountInfo.sequence.nonce++; } } pushUnlockTransaction(delegateAddress) { const builder = generateTransaction( BigInt(this.accountInfo?.sequence?.nonce || 0), this.addressAndPubKey.publicKey, this.nodeInfo.networkIdentifier, this.passPhrase ); const { amount, unvoteHeight } = this.accountLockedVotesCanReturn[delegateAddress]; const signedTx = builder.unlock(delegateAddress, amount, unvoteHeight); if (signedTx) { fetchWrapper .post("transactions", {}, signedTx) .then(() => this.fetchTempTransactions()) .catch((err) => this.throwError(err)); this.accountInfo.sequence.nonce++; } } generatePassPhrase() { this._passPhrase = passphrase.Mnemonic.generateMnemonic(); sessionStorage.setItem("passPhrase", this._passPhrase); } savePastPassPhrase(phrase) { this._passPhrase = phrase; sessionStorage.setItem("passPhrase", this._passPhrase); } unobservedTransactionsInfo() { this._transactionsInfo = []; } unobservedSharedData() { this._sharedData = []; } unobservedAccountData() { this._accountData = []; } changeSharedData(incomingData, id) { this._sharedData.push({ data: incomingData.data, id, }); } saveDataRegistration(data) { this._accountData = data; } clearDataRegistration() { this._accountData = []; } saveInfoTransactions(transaction) { this._transactionsInfo = transaction.data; } accountDataFetchChange(res) { if (res.data) { this._accountData = res.data; } this.fetchTempTransactions(); } keysArrayFetchChange(res) { this._keysArray = res.data; } fetchVPNServersSuccess(res) { this._canVPN = res.meta; this._vpnServers = res.servers; } fetchDelegatesSuccess(res) { this._delegates = res; } fetchNodeInfoSuccess(info) { this._nodeInfo = info.data; this.fetchPassPhrase(); } fetchAccountInfoSuccess(res) { this._accountInfo = res.data; if (this.accountInfo?.sequence?.nonce) this.accountInfo.sequence.nonce = parseInt(this.accountInfo.sequence.nonce) || 0; } fetchTempTransactionsSuccess(res) { this._tempTransactions = res.data; } throwError(err) { console.log(err); } get sharedData() { return this._sharedData.map((elem) => ({ data: elem.data.map((item) => ({ label: labelMap[item.label] || item.label, value: item.value, })), id: elem.id, })); } get transactionsInfo() { return this._transactionsInfo .filter((e) => e.moduleID === 1001) .map((item) => { return { id: item.id, sender_avatar: item.asset.recipientAddress && generateSvgAvatar(item.senderPublicKey), avatar_size: 24, transaction: item.asset.features.map((asset) => { return { transaction_id: item.id, address: item.asset.recipientAddress && cryptography.bufferToHex( cryptography.getAddressFromPublicKey( cryptography.hexToBuffer(item.senderPublicKey) ) ), value: asset.value, label: labelMap[asset.label] || asset.label, }; }), }; }); } get decryptedAccountData() { return decryptedData( this._accountData, this.accountFeatures, this.passPhrase, this.pubKey, this.processedFeatures ); } get firstName() { return this.decryptedAccountData.find((item) => item.key === "firstname") ?.value; } get lastName() { return this.decryptedAccountData.find((item) => item.key === "secondname") ?.value; } get accountName() { return this.firstName || this.lastName || this.pubKey; } get keysArray() { return this._keysArray; } get passPhrase() { return this._passPhrase; } get accountData() { return this._accountData; } get accountInfo() { return this._accountInfo; } get nodeInfo() { return this._nodeInfo; } get addressAndPubKey() { return cryptography.getAddressAndPublicKeyFromPassphrase(this.passPhrase); } get privateKey() { return cryptography.getPrivateAndPublicKeyFromPassphrase(this.passPhrase) ?.privateKey; } get vpnPrivateKey() { let x25519_sk = Buffer.alloc(sodium.crypto_box_SECRETKEYBYTES); sodium.crypto_sign_ed25519_sk_to_curve25519( x25519_sk, Buffer.from(this.privateKey, "hex") ); return x25519_sk.toString("Base64"); } get pubKey() { return this.addressAndPubKey.publicKey.toString("hex"); } get address() { return this.addressAndPubKey.address.toString("hex"); } get tokenKey() { const stringToSign = this.nodeInfo.networkIdentifier + this.nodeInfo.lastBlockID; if (!stringToSign) return ""; const sign = cryptography .signDataWithPassphrase(Buffer.from(stringToSign, "hex"), this.passPhrase) .toString("hex"); return this.pubKey + ":" + sign; } get accountIdentity() { return this.accountInfo?.identity || {}; } get accountFeatures() { return this.accountIdentity?.features || []; } get accountBalance() { return ( BigInt(this.accountInfo?.token?.balance || 0) / 100000000n ).toString(); } get accountSentVotes() { return (this.accountInfo?.dpos?.sentVotes || []).reduce( (acc, e) => ({ ...acc, [e.delegateAddress]: BigInt(e.amount || 0) / 100000000n + (acc[e.delegateAddress] || 0n), }), {} ); } get accountLockedVotes() { return (this.accountInfo?.dpos?.unlocking || []).reduce( (acc, e) => ({ ...acc, [e.delegateAddress]: BigInt(e.amount || 0) / 100000000n + (acc[e.delegateAddress] || 0n), }), {} ); } get accountLockedVotesCanReturn() { return (this.accountInfo?.dpos?.unlocking || []) .filter((e) => this.nodeInfo.height - e.unvoteHeight > 2000) .reduce( (acc, e) => ({ ...acc, [e.delegateAddress]: { amount: BigInt(e.amount || 0) / 100000000n + (acc[e.delegateAddress]?.amount || 0n), unvoteHeight: e.unvoteHeight, }, }), {} ); } get accountFeaturesMap() { return this.accountFeatures.reduce( (acc, item) => ({ ...acc, [item.label]: item.value, }), {} ); } get loading() { return this._loading; } set loading(value) { this._loading = value; } get tempTransactions() { return this._tempTransactions; } get processedFeatures() { return this.tempTransactions .filter((item) => item.senderPublicKey === this.pubKey) .reduce((acc, item) => { const { features } = item.asset; return { ...acc, ...(features?.reduce( (obj, feature) => ({ ...obj, [feature.label]: true }), {} ) || {}), }; }, {}); } get processedVotes() { return this.tempTransactions .filter((item) => item.senderPublicKey === this.pubKey) .reduce((acc, item) => { const { votes } = item.asset; return { ...acc, ...(votes?.reduce( (obj, vote) => ({ ...obj, [vote.delegateAddress]: BigInt(vote.amount) / 100000000n + (obj[vote.delegateAddress] || 0n), }), {} ) || {}), }; }, {}); } get processedUnlocking() { return this.tempTransactions .filter((item) => item.senderPublicKey === this.pubKey) .reduce((acc, item) => { const { unlockObjects } = item.asset; return { ...acc, ...(unlockObjects?.reduce( (obj, vote) => ({ ...obj, [vote.delegateAddress]: BigInt(vote.amount) / 100000000n + (obj[vote.delegateAddress] || 0n), }), {} ) || {}), }; }, {}); } get vpnServers() { return this._vpnServers; } get canVPN() { return this._canVPN; } get delegates() { const getStatus = (delegate) => { const { address } = delegate; let amount = this.processedVotes[address]; if (amount) return "Pending"; amount = this.accountSentVotes[address]; if (amount) return "Unvote"; return "Vote"; }; return this._delegates.data.map((delegate) => ({ ...delegate, status: getStatus(delegate), token: { ...delegate.token, balance: Number(BigInt(delegate.token.balance) / 100000000n).toString(), }, dpos: { ...delegate.dpos, delegate: { ...delegate.dpos.delegate, totalVotesReceived: Number( BigInt(delegate.dpos.delegate.totalVotesReceived) / 100000000n ).toString(), }, }, })); } get delegatesMeta() { return this._delegates.meta; } } export const store = new Store();