Created
June 4, 2025 03:42
-
-
Save mikedotexe/9443d4a1e7ec76d4599a1c62e6e44f86 to your computer and use it in GitHub Desktop.
Cheesy hack to add timeout avoiding 429s
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // this was a hacky replacement because this tool is busted: | |
| // https://github.com/near/account-lookup | |
| import "regenerator-runtime"; | |
| import * as nearAPI from "near-api-js"; | |
| import BN from "bn.js"; | |
| import sha256 from "js-sha256"; | |
| import { decode } from "bs58"; | |
| import Mustache from "mustache"; | |
| const DOES_NOT_EXIST = " doesn't exist"; | |
| function sleep(ms) { | |
| return new Promise((resolve) => setTimeout(resolve, ms)); | |
| } | |
| function accountToLockup(masterAccountId, accountId) { | |
| return `${sha256(Buffer.from(accountId)) | |
| .toString("hex") | |
| .slice(0, 40)}.${masterAccountId}`; | |
| } | |
| function prepareAccountId(data) { | |
| if (data.toLowerCase().endsWith(".near")) { | |
| return data | |
| .replace("@", "") | |
| .replace("https://wallet.near.org/send-money/", "") | |
| .toLowerCase(); | |
| } | |
| if (data.length === 64 && !data.startsWith("ed25519:")) { | |
| return data; | |
| } | |
| let publicKey; | |
| if (data.startsWith("NEAR")) { | |
| publicKey = decode(data.slice(4)).slice(0, -4); | |
| } else { | |
| publicKey = decode(data.replace("ed25519:", "")); | |
| } | |
| return publicKey.toString("hex"); | |
| } | |
| const readOption = (reader, f, defaultValue) => { | |
| let x = reader.read_u8(); | |
| return x === 1 ? f() : defaultValue; | |
| }; | |
| async function viewLockupState(connection, contractId) { | |
| const result = await connection.provider.sendJsonRpc("query", { | |
| request_type: "view_state", | |
| finality: "final", | |
| account_id: contractId, | |
| prefix_base64: "U1RBVEU=", | |
| }); | |
| let value = Buffer.from(result.values[0].value, "base64"); | |
| let reader = new nearAPI.utils.serialize.BinaryReader(value); | |
| let owner = reader.read_string(); | |
| let lockupAmount = reader.read_u128().toString(); | |
| let terminationWithdrawnTokens = reader.read_u128().toString(); | |
| let lockupDuration = reader.read_u64().toString(); | |
| let releaseDuration = readOption( | |
| reader, | |
| () => reader.read_u64().toString(), | |
| "0" | |
| ); | |
| let lockupTimestamp = readOption( | |
| reader, | |
| () => reader.read_u64().toString(), | |
| "0" | |
| ); | |
| let tiType = reader.read_u8(); | |
| let transferInformation; | |
| if (tiType === 0) { | |
| transferInformation = { | |
| transfers_timestamp: reader.read_u64(), | |
| }; | |
| } else { | |
| transferInformation = { | |
| transfer_poll_account_id: reader.read_string(), | |
| }; | |
| } | |
| let vestingType = reader.read_u8(); | |
| let vestingInformation; | |
| switch (vestingType) { | |
| case 1: | |
| vestingInformation = { | |
| vestingHash: reader.read_array(() => reader.read_u8()), | |
| }; | |
| break; | |
| case 2: | |
| let start = reader.read_u64(); | |
| let cliff = reader.read_u64(); | |
| let end = reader.read_u64(); | |
| vestingInformation = { start, cliff, end }; | |
| break; | |
| case 3: | |
| let unvestedAmount = reader.read_u128(); | |
| let terminationStatus = reader.read_u8(); | |
| vestingInformation = { unvestedAmount, terminationStatus }; | |
| break; | |
| default: | |
| vestingInformation = "TODO"; | |
| break; | |
| } | |
| return { | |
| owner, | |
| lockupAmount: new BN(lockupAmount), | |
| terminationWithdrawnTokens: new BN(terminationWithdrawnTokens), | |
| lockupDuration: new BN(lockupDuration), | |
| releaseDuration: new BN(releaseDuration), | |
| lockupTimestamp: new BN(lockupTimestamp), | |
| transferInformation, | |
| vestingInformation, | |
| }; | |
| } | |
| const options = { | |
| nodeUrl: "https://rpc.mainnet.fastnear.com", | |
| networkId: "mainnet", | |
| deps: {}, | |
| }; | |
| async function lookupLockup(near, accountId) { | |
| const lockupAccountId = accountToLockup("lockup.near", accountId); | |
| try { | |
| const lockupAccount = await near.account(lockupAccountId); | |
| const lockupAccountBalance = await lockupAccount.viewFunction( | |
| lockupAccountId, | |
| "get_balance", | |
| {} | |
| ); | |
| const lockupState = await viewLockupState(near.connection, lockupAccountId); | |
| lockupState.hasBrokenTimestamp = [ | |
| "3kVY9qcVRoW3B5498SMX6R3rtSLiCdmBzKs7zcnzDJ7Q", | |
| "DiC9bKCqUHqoYqUXovAnqugiuntHWnM3cAc7KrgaHTu", | |
| ].includes((await lockupAccount.state()).code_hash); | |
| return { lockupAccountId, lockupAccountBalance, lockupState }; | |
| } catch (error) { | |
| console.log(error); | |
| return { | |
| lockupAccountId: `${lockupAccountId}${DOES_NOT_EXIST}`, | |
| lockupAmount: 0, | |
| }; | |
| } | |
| } | |
| async function fetchPools(masterAccount) { | |
| console.log("Fetching validator pools..."); | |
| try { | |
| const result = await masterAccount.connection.provider.sendJsonRpc("validators", [null]); | |
| console.log("Got validators result:", result); | |
| const pools = new Set(); | |
| const stakes = new Map(); | |
| result.current_validators.forEach((validator) => { | |
| pools.add(validator.account_id); | |
| stakes.set(validator.account_id, validator.stake); | |
| }); | |
| result.next_validators.forEach((validator) => pools.add(validator.account_id)); | |
| result.current_proposals.forEach((validator) => pools.add(validator.account_id)); | |
| console.log(`Found ${pools.size} total pools`); | |
| let poolsWithFee = []; | |
| let processedCount = 0; | |
| // Process pools sequentially with rate limiting | |
| for (let accountId of pools) { | |
| try { | |
| console.log(`Processing pool ${processedCount + 1}/${pools.size}: ${accountId}`); | |
| let stake = stakes.get(accountId) ? | |
| nearAPI.utils.format.formatNearAmount(stakes.get(accountId), 2) : "0"; | |
| let fee; | |
| try { | |
| fee = await masterAccount.viewFunction( | |
| accountId, | |
| "get_reward_fee_fraction", | |
| {} | |
| ); | |
| poolsWithFee.push({ | |
| accountId, | |
| stake, | |
| fee: `${(fee.numerator * 100) / fee.denominator}%`, | |
| }); | |
| console.log(`✓ Successfully processed ${accountId}`); | |
| } catch (feeError) { | |
| console.log(`Failed to fetch fee for ${accountId}:`, feeError.message); | |
| // Still add the pool without fee info | |
| poolsWithFee.push({ | |
| accountId, | |
| stake, | |
| fee: "N/A", | |
| }); | |
| } | |
| processedCount++; | |
| // Add delay between requests to avoid rate limiting | |
| if (processedCount < pools.size) { | |
| await sleep(123); | |
| } | |
| } catch (e) { | |
| console.error(`Error processing pool ${accountId}:`, e); | |
| processedCount++; | |
| } | |
| } | |
| console.log(`Finished processing pools. Got ${poolsWithFee.length} pools with data.`); | |
| return poolsWithFee; | |
| } catch (error) { | |
| console.error("Error in fetchPools:", error); | |
| throw error; | |
| } | |
| } | |
| async function updateStaking(near, accountId, lookupAccountId) { | |
| const template = document.getElementById("pool-template").innerHTML; | |
| document.getElementById("loader").classList.add("active"); | |
| document.getElementById("error").style.display = "none"; | |
| console.log("Starting updateStaking for:", accountId); | |
| try { | |
| let masterAccount = await near.account(accountId); | |
| console.log("Got master account, fetching pools..."); | |
| let pools = await fetchPools(masterAccount); | |
| console.log("Got pools:", pools.length); | |
| let result = []; | |
| for (let i = 0; i < pools.length; ++i) { | |
| console.log(`Checking balances for pool ${i + 1}/${pools.length}: ${pools[i].accountId}`); | |
| try { | |
| let directBalance = await masterAccount.viewFunction( | |
| pools[i].accountId, | |
| "get_account_total_balance", | |
| { account_id: accountId } | |
| ); | |
| console.log(`Direct balance for ${pools[i].accountId}: ${directBalance}`); | |
| await sleep(123); | |
| let lockupBalance = "0"; | |
| if (lookupAccountId && !lookupAccountId.includes(DOES_NOT_EXIST)) { | |
| try { | |
| lockupBalance = await masterAccount.viewFunction( | |
| pools[i].accountId, | |
| "get_account_total_balance", | |
| { account_id: lookupAccountId } | |
| ); | |
| console.log(`Lockup balance for ${pools[i].accountId}: ${lockupBalance}`); | |
| await sleep(123); | |
| } catch (lockupError) { | |
| console.log(`Failed to get lockup balance for ${pools[i].accountId}:`, lockupError.message); | |
| } | |
| } | |
| if (directBalance !== "0" || lockupBalance !== "0") { | |
| result.push({ | |
| accountId: pools[i].accountId, | |
| directBalance: nearAPI.utils.format.formatNearAmount(directBalance, 2), | |
| lockupBalance: nearAPI.utils.format.formatNearAmount(lockupBalance, 2), | |
| }); | |
| console.log(`Added ${pools[i].accountId} to results`); | |
| } | |
| // Update UI progressively | |
| document.getElementById("pools").innerHTML = Mustache.render(template, { | |
| result, | |
| scannedNotDone: i < pools.length - 1, | |
| scanned: i + 1, | |
| totalPools: pools.length, | |
| }); | |
| } catch (balanceError) { | |
| console.error(`Error getting balance for ${pools[i].accountId}:`, balanceError); | |
| } | |
| } | |
| console.log("Finished checking all pools. Final result count:", result.length); | |
| } catch (error) { | |
| console.error("Error in updateStaking:", error); | |
| document.getElementById("error").style.display = "block"; | |
| document.getElementById("error").textContent = `Error: ${error.message}`; | |
| } finally { | |
| document.getElementById("loader").classList.remove("active"); | |
| console.log("updateStaking completed"); | |
| } | |
| } | |
| function getStartLockupTimestamp(lockupState) { | |
| const phase2Time = new BN("1602614338293769340"); | |
| let lockupTimestamp = BN.max( | |
| phase2Time.add(lockupState.lockupDuration), | |
| lockupState.lockupTimestamp | |
| ); | |
| return lockupState.hasBrokenTimestamp ? phase2Time : lockupTimestamp; | |
| } | |
| const saturatingSub = (a, b) => { | |
| let res = a.sub(b); | |
| return res.gte(new BN(0)) ? res : new BN(0); | |
| }; | |
| async function getLockedTokenAmount(lockupState) { | |
| const phase2Time = new BN("1602614338293769340"); | |
| let now = new BN((new Date().getTime() * 1000000).toString()); | |
| if (now.lte(phase2Time)) { | |
| return saturatingSub( | |
| lockupState.lockupAmount, | |
| lockupState.terminationWithdrawnTokens | |
| ); | |
| } | |
| let lockupTimestamp = BN.max( | |
| phase2Time.add(lockupState.lockupDuration), | |
| lockupState.lockupTimestamp | |
| ); | |
| let blockTimestamp = now; | |
| if (blockTimestamp.lt(lockupTimestamp)) { | |
| return saturatingSub( | |
| lockupState.lockupAmount, | |
| lockupState.terminationWithdrawnTokens | |
| ); | |
| } | |
| let unreleasedAmount; | |
| if (lockupState.releaseDuration) { | |
| let startTimestamp = getStartLockupTimestamp(lockupState); | |
| let endTimestamp = startTimestamp.add(lockupState.releaseDuration); | |
| if (endTimestamp.lt(blockTimestamp)) { | |
| unreleasedAmount = new BN(0); | |
| } else { | |
| let timeLeft = endTimestamp.sub(blockTimestamp); | |
| unreleasedAmount = lockupState.lockupAmount | |
| .mul(timeLeft) | |
| .div(lockupState.releaseDuration); | |
| } | |
| } else { | |
| unreleasedAmount = new BN(0); | |
| } | |
| let unvestedAmount; | |
| if (lockupState.vestingInformation) { | |
| if (lockupState.vestingInformation.unvestedAmount) { | |
| unvestedAmount = lockupState.vestingInformation.unvestedAmount; | |
| } else if (lockupState.vestingInformation.start) { | |
| if (blockTimestamp.lt(lockupState.vestingInformation.cliff)) { | |
| unvestedAmount = lockupState.lockupAmount; | |
| } else if (blockTimestamp.gte(lockupState.vestingInformation.end)) { | |
| unvestedAmount = new BN(0); | |
| } else { | |
| let timeLeft = lockupState.vestingInformation.end.sub(blockTimestamp); | |
| let totalTime = lockupState.vestingInformation.end.sub( | |
| lockupState.vestingInformation.start | |
| ); | |
| unvestedAmount = lockupState.lockupAmount.mul(timeLeft).div(totalTime); | |
| } | |
| } | |
| } | |
| if (unvestedAmount === undefined) { | |
| unvestedAmount = new BN(0); | |
| } | |
| return BN.max( | |
| saturatingSub(unreleasedAmount, lockupState.terminationWithdrawnTokens), | |
| unvestedAmount | |
| ); | |
| } | |
| function formatVestingInfo(info) { | |
| if (!info.hasOwnProperty("start")) return "TODO"; | |
| const start = new Date(info.start.divn(1000000).toNumber()); | |
| const cliff = new Date(info.cliff.divn(1000000).toNumber()); | |
| const end = new Date(info.end.divn(1000000).toNumber()); | |
| return `from ${start} until ${end} with cliff at ${cliff}`; | |
| } | |
| async function lookup() { | |
| console.log("Starting lookup..."); | |
| const inputAccountId = document.querySelector("#account").value; | |
| console.log("Input account ID:", inputAccountId); | |
| window.location.hash = inputAccountId; | |
| try { | |
| const near = await nearAPI.connect(options); | |
| console.log("Connected to NEAR"); | |
| let accountId = prepareAccountId(inputAccountId); | |
| console.log("Prepared account ID:", accountId); | |
| let lockupAccountId = "", | |
| lockupAccountBalance = 0, | |
| ownerAccountBalance = 0, | |
| lockupReleaseStartTimestamp = new BN(0), | |
| lockupState = null, | |
| lockedAmount = 0; | |
| const template = document.getElementById("template").innerHTML; | |
| document.getElementById("pools").innerHTML = ""; | |
| try { | |
| console.log("Getting account state..."); | |
| let account = await near.account(accountId); | |
| let state = await account.state(); | |
| ownerAccountBalance = state.amount; | |
| console.log("Owner account balance:", ownerAccountBalance); | |
| console.log("Looking up lockup..."); | |
| ({ lockupAccountId, lockupAccountBalance, lockupState } = await lookupLockup(near, accountId)); | |
| console.log("Lockup account ID:", lockupAccountId); | |
| console.log("Lockup account balance:", lockupAccountBalance); | |
| if (lockupState) { | |
| console.log("Processing lockup state..."); | |
| lockupReleaseStartTimestamp = getStartLockupTimestamp(lockupState); | |
| lockedAmount = await getLockedTokenAmount(lockupState); | |
| lockupState.releaseDuration = lockupState.releaseDuration | |
| .div(new BN("1000000000")) | |
| .divn(60) | |
| .divn(60) | |
| .divn(24) | |
| .toString(10); | |
| lockupState.vestingInformation = formatVestingInfo(lockupState.vestingInformation); | |
| } | |
| console.log("Rendering template..."); | |
| document.getElementById("output").innerHTML = Mustache.render(template, { | |
| accountId, | |
| lockupAccountId, | |
| ownerAccountBalance: nearAPI.utils.format.formatNearAmount(ownerAccountBalance, 2), | |
| lockedAmount: nearAPI.utils.format.formatNearAmount(lockedAmount.toString(), 2), | |
| liquidAmount: nearAPI.utils.format.formatNearAmount( | |
| new BN(lockupAccountBalance).sub(new BN(lockedAmount)).toString(), | |
| 2 | |
| ), | |
| totalAmount: nearAPI.utils.format.formatNearAmount( | |
| new BN(ownerAccountBalance).add(new BN(lockupAccountBalance)).toString(), | |
| 2 | |
| ), | |
| lockupReleaseStartDate: new Date(lockupReleaseStartTimestamp.divn(1000000).toNumber()), | |
| lockupState, | |
| }); | |
| console.log("Starting staking update..."); | |
| await updateStaking(near, accountId, lockupAccountId); | |
| } catch (error) { | |
| console.error("Error in main lookup process:", error); | |
| document.getElementById("error").style.display = "block"; | |
| document.getElementById("error").textContent = `Error: ${error.message}`; | |
| document.getElementById("loader").classList.remove("active"); | |
| } | |
| } catch (connectionError) { | |
| console.error("Failed to connect to NEAR:", connectionError); | |
| document.getElementById("error").style.display = "block"; | |
| document.getElementById("error").textContent = `Connection Error: ${connectionError.message}`; | |
| document.getElementById("loader").classList.remove("active"); | |
| } | |
| } | |
| window.nearAPI = nearAPI; | |
| window.lookup = lookup; | |
| window.onload = () => { | |
| if (window.location.hash) { | |
| document.querySelector("#account").value = window.location.hash.slice(1); | |
| lookup(); | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment