diff --git a/src/LiNEAR.jsx b/src/LiNEAR.jsx index bfb5687..cc23c50 100644 --- a/src/LiNEAR.jsx +++ b/src/LiNEAR.jsx @@ -5,10 +5,6 @@ const NEAR_DECIMALS = 24; const LiNEAR_DECIMALS = 24; const BIG_ROUND_DOWN = 0; const MIN_BALANCE_CHANGE = 0.5; -//time in ms -const SECONDS = 1000; -const MINUTES = 60 * SECONDS; -const HOURS = 60 * MINUTES; function isValid(a) { if (!a) return false; @@ -41,201 +37,11 @@ function getConfig(network) { } const config = getConfig(context.networkId); -function getNearBalance(accountId) { - const account = fetch(config.nodeUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "dontcare", - method: "query", - params: { - request_type: "view_account", - finality: "final", - account_id: accountId, - }, - }), - }); - const { amount, storage_usage } = account.body.result; - const COMMON_MIN_BALANCE = 0.05; - if (!amount) return "-"; - const availableBalance = Big(amount || 0).minus( - Big(storage_usage).mul(Big(10).pow(19)) - ); - const balance = availableBalance - .div(Big(10).pow(NEAR_DECIMALS)) - .minus(COMMON_MIN_BALANCE); - return balance.lt(0) ? "0" : balance.toFixed(5, BIG_ROUND_DOWN); -} - -function getLinearBalance(accountId) { - const linearBalanceRaw = Near.view(config.contractId, "ft_balance_of", { - account_id: accountId, - }); - if (!linearBalanceRaw) return "-"; - const balance = Big(linearBalanceRaw).div(Big(10).pow(LiNEAR_DECIMALS)); - return balance.lt(0) ? "0" : balance.toFixed(); -} - -function getValidators() { - const options = { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "dontcare", - method: "validators", - params: [null], - }), - }; - return fetch(config.nodeUrl, options).body.result; -} - -function getBlock(blockId) { - const options = { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "dontcare", - method: "block", - params: !!blockId - ? { - block_id: blockId, - } - : { - finality: "final", - }, - }), - }; - return fetch(config.nodeUrl, options).body.result; -} - -function lastestBlock() { - const lastBlock = getBlock(); - const startBlock = getBlock(lastBlock.header.next_epoch_id); - const prevBlock = getBlock(lastBlock.header.epoch_id); - let prev_timestamp = Math.round((prevBlock.header.timestamp ?? 0) / 1e6); - let start_block_height = startBlock.header.height; - let start_timestamp = Math.round((startBlock.header.timestamp ?? 0) / 1e6); - let last_block_timestamp = Math.round( - (lastBlock.header.timestamp ?? 0) / 1e6 - ); - - if (start_timestamp < new Date().getTime() - 48 * HOURS) { - //genesis or hard-fork - start_timestamp = new Date().getTime() - 6 * HOURS; - } - if (prev_timestamp < new Date().getTime() - 48 * HOURS) { - //genesis or hard-fork - prev_timestamp = new Date().getTime() - 12 * HOURS; - } - - //const noPrevBloc = startBlock.header.height == prevBlock.header.height; - let length = startBlock.header.height - prevBlock.header.height, - duration_ms = 0, - advance; - let start_dtm, ends_dtm, duration_till_now_ms; - if (length === 0) { - //!prevBlock, genesis or hard-fork - length = 43200; - duration_ms = 12 * HOURS; - //estimated start & prev timestamps - advance = - Math.round( - Number( - ((BigInt(lastBlock.header.height) - BigInt(this.start_block_height)) * - BigInt(1000000)) / - BigInt(this.length) - ) - ) / 1000000; - start_timestamp = last_block_timestamp - duration_ms * advance; - prev_timestamp = start_timestamp - duration_ms; - } else { - duration_ms = start_timestamp - prev_timestamp; - } - - start_dtm = new Date(start_timestamp); - ends_dtm = new Date(start_timestamp + duration_ms); - duration_till_now_ms = last_block_timestamp - start_timestamp; - - // update function - if (isValid(lastBlock.header.height) && isValid(start_block_height)) { - advance = - Math.round( - Big(lastBlock.header.height) - .minus(start_block_height) - .times(1000000) - .div(length) - .toNumber() - ) / 1000000; - if (advance > 0.1) { - ends_dtm = new Date( - start_timestamp + - duration_till_now_ms + - duration_till_now_ms * (1 - advance) - ); - } - } - return { - lastEpochDurationHours: duration_ms / HOURS, - hoursToEnd: - Math.round( - ((start_timestamp + duration_ms - new Date().getTime()) / HOURS) * 100 - ) / 100, - }; -} - -function padNumber(n) { - if (n < 10) return `0${n}`; - return n.toString(); -} -function queryFinishedTime(epochHeight) { - const nowValidator = getValidators(); - let nowEpochHeight = nowValidator.epoch_height; - - if (nowEpochHeight >= epochHeight) return null; - - const { hoursToEnd, lastEpochDurationHours } = lastestBlock(); - const BUFFER = 3; // 3 HOURs buffer - const durationHours = - hoursToEnd + - (epochHeight - nowEpochHeight - 1) * lastEpochDurationHours + - BUFFER; - - if (durationHours) { - const finishedTime = new Date(new Date().getTime() + durationHours * HOURS); - return { - finishedTime: `${finishedTime.getFullYear()}/${padNumber( - finishedTime.getMonth() + 1 - )}/${padNumber(finishedTime.getDate())} ${padNumber( - finishedTime.getHours() - )}:${padNumber(finishedTime.getMinutes())}:${padNumber( - finishedTime.getSeconds() - )}`, - durationHours: Math.floor(durationHours).toString(), - }; - } else { - return {}; - } -} - -const account = Near.view(config.contractId, "get_account_details", { - account_id: accountId, -}); - -const finishedTime = queryFinishedTime(account.unstaked_available_epoch_height); - State.init({ tabName: "stake", // stake | unstake page: "stake", // "stake" | "account" nearBalance: "", + unstakeInfo: {}, }); const Main = styled.div` @@ -343,15 +149,13 @@ function updateAccountInfo(callback) { }, 500); } -if (state.page === "stake") { - return ( +function onLoad(data) { + State.update({ unstakeInfo: data }); +} + +const body = + state.page === "stake" ? (
- )}
- ); -} else { - return ( + ) : ( ); -} + +return ( +
+ + + {body} +
+); diff --git a/src/LiNEAR/Account.jsx b/src/LiNEAR/Account.jsx index 2452c60..38e2741 100644 --- a/src/LiNEAR/Account.jsx +++ b/src/LiNEAR/Account.jsx @@ -107,11 +107,10 @@ const { config, nearBalance, linearBalance, - account, - finishedTime, + unstakeInfo, } = props; if (!config) { - return "Component not be loaded. Missing `config` props"; + return "Component cannot be loaded. Missing `config` props"; } State.init({ @@ -165,18 +164,14 @@ const formattedLinearBalance = ? "-" : Big(linearBalance).toFixed(5, BIG_ROUND_DOWN); +const finishedTime = unstakeInfo.endTime || {}; + return (
- My Account @@ -249,15 +244,15 @@ return ( )} - {account && - account.unstaked_balance && - Big(account.unstaked_balance).gte(ONE_MICRO_NEAR) && ( + {unstakeInfo && + unstakeInfo.amount && + Big(unstakeInfo.amount).gte(ONE_MICRO_NEAR) && (
- {account.unstaked_balance - ? Big(account.unstaked_balance).div(YOCTONEAR).toFixed(5) + {unstakeInfo.amount + ? Big(unstakeInfo.amount).div(YOCTONEAR).toFixed(5) : "-"}
diff --git a/src/LiNEAR/Data.jsx b/src/LiNEAR/Data/Stake.jsx similarity index 100% rename from src/LiNEAR/Data.jsx rename to src/LiNEAR/Data/Stake.jsx diff --git a/src/LiNEAR/Data/Unstake.jsx b/src/LiNEAR/Data/Unstake.jsx new file mode 100644 index 0000000..ee5fa0f --- /dev/null +++ b/src/LiNEAR/Data/Unstake.jsx @@ -0,0 +1,186 @@ +const accountId = props.accountId || context.accountId; + +const { config, onLoad } = props; +if (!config) { + return "Component cannot be loaded. Missing `config` props"; +} + +//time in ms +const SECONDS = 1000; +const MINUTES = 60 * SECONDS; +const HOURS = 60 * MINUTES; + +function isValid(a) { + if (!a) return false; + if (isNaN(Number(a))) return false; + if (a === "") return false; + return true; +} + +function getValidators() { + const options = { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "dontcare", + method: "validators", + params: [null], + }), + }; + return fetch(config.nodeUrl, options).body.result; +} + +function getBlock(blockId) { + const options = { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "dontcare", + method: "block", + params: !!blockId + ? { + block_id: blockId, + } + : { + finality: "final", + }, + }), + }; + return fetch(config.nodeUrl, options).body.result; +} + +function latestBlock() { + const lastBlock = getBlock(); + const startBlock = getBlock(lastBlock.header.next_epoch_id); + const prevBlock = getBlock(lastBlock.header.epoch_id); + let prev_timestamp = Math.round((prevBlock.header.timestamp ?? 0) / 1e6); + let start_block_height = startBlock.header.height; + let start_timestamp = Math.round((startBlock.header.timestamp ?? 0) / 1e6); + let last_block_timestamp = Math.round( + (lastBlock.header.timestamp ?? 0) / 1e6 + ); + + if (start_timestamp < new Date().getTime() - 48 * HOURS) { + //genesis or hard-fork + start_timestamp = new Date().getTime() - 6 * HOURS; + } + if (prev_timestamp < new Date().getTime() - 48 * HOURS) { + //genesis or hard-fork + prev_timestamp = new Date().getTime() - 12 * HOURS; + } + + //const noPrevBloc = startBlock.header.height == prevBlock.header.height; + let length = startBlock.header.height - prevBlock.header.height, + duration_ms = 0, + advance; + let start_dtm, ends_dtm, duration_till_now_ms; + if (length === 0) { + //!prevBlock, genesis or hard-fork + length = 43200; + duration_ms = 12 * HOURS; + //estimated start & prev timestamps + advance = + Math.round( + Number( + ((BigInt(lastBlock.header.height) - BigInt(this.start_block_height)) * + BigInt(1000000)) / + BigInt(this.length) + ) + ) / 1000000; + start_timestamp = last_block_timestamp - duration_ms * advance; + prev_timestamp = start_timestamp - duration_ms; + } else { + duration_ms = start_timestamp - prev_timestamp; + } + + start_dtm = new Date(start_timestamp); + ends_dtm = new Date(start_timestamp + duration_ms); + duration_till_now_ms = last_block_timestamp - start_timestamp; + + // update function + if (isValid(lastBlock.header.height) && isValid(start_block_height)) { + advance = + Math.round( + Big(lastBlock.header.height) + .minus(start_block_height) + .times(1000000) + .div(length) + .toNumber() + ) / 1000000; + if (advance > 0.1) { + ends_dtm = new Date( + start_timestamp + + duration_till_now_ms + + duration_till_now_ms * (1 - advance) + ); + } + } + return { + lastEpochDurationHours: duration_ms / HOURS, + hoursToEnd: + Math.round( + ((start_timestamp + duration_ms - new Date().getTime()) / HOURS) * 100 + ) / 100, + }; +} + +function padNumber(n) { + if (n < 10) return `0${n}`; + return n.toString(); +} + +function queryFinishedTime(epochHeight) { + const nowValidator = getValidators(); + let nowEpochHeight = nowValidator.epoch_height; + + if (nowEpochHeight >= epochHeight) return {}; + + const { hoursToEnd, lastEpochDurationHours } = latestBlock(); + const BUFFER = 3; // 3 HOURs buffer + const durationHours = + hoursToEnd + + (epochHeight - nowEpochHeight - 1) * lastEpochDurationHours + + BUFFER; + + if (durationHours) { + const finishedTime = new Date(new Date().getTime() + durationHours * HOURS); + return { + finishedTime: `${finishedTime.getFullYear()}/${padNumber( + finishedTime.getMonth() + 1 + )}/${padNumber(finishedTime.getDate())} ${padNumber( + finishedTime.getHours() + )}:${padNumber(finishedTime.getMinutes())}:${padNumber( + finishedTime.getSeconds() + )}`, + durationHours: Math.floor(durationHours).toString(), + }; + } else { + return {}; + } +} + +if (onLoad) { + const accountDetails = Near.view(config.contractId, "get_account_details", { + account_id: accountId, + }); + const endTime = + accountDetails && accountDetails.unstaked_available_epoch_height + ? queryFinishedTime(accountDetails.unstaked_available_epoch_height) + : {}; + + if (accountDetails) { + onLoad({ + amount: accountDetails.unstaked_balance, + canWithdraw: accountDetails.can_withdraw, + endTime, + }); + } +} + +return
;