Skip to content
This repository has been archived by the owner on Sep 26, 2024. It is now read-only.

feat(frontend): display detailed balance information on AccountDetails page #447

Merged
merged 19 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"homepage": "https://github.com/near/near-explorer#readme",
"dependencies": {
"autobahn": "19.4.1",
"bn.js": "^5.1.1",
"geoip-lite": "^1.4.2",
"js-sha256": "^0.9.0",
"moment": "^2.24.0",
"near-api-js": "^0.27.0",
"pg": "^8.3.0",
Expand Down
3 changes: 3 additions & 0 deletions backend/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ exports.wampNearExplorerBackendSecret =
process.env.WAMP_NEAR_EXPLORER_BACKEND_SECRET || "back";

exports.genesisRecordsUrl = process.env.NEAR_GENESIS_RECORDS_URL;

exports.nearLockupAccountIdSuffix =
process.env.NEAR_LOCKUP_ACCOUNT_SUFFIX || "lockup.near";
12 changes: 9 additions & 3 deletions backend/src/near.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
const nearlib = require("near-api-js");
const nearApi = require("near-api-js");

const { nearRpcUrl, wampNearNetworkName } = require("./config");

const nearRpc = new nearlib.providers.JsonRpcProvider(nearRpcUrl);
const nearRpc = new nearApi.providers.JsonRpcProvider(nearRpcUrl);

// TODO: Provide an equivalent method in near-api-js, so we don't need to hack it around.
nearRpc.callViewMethod = async function (contractName, methodName, args) {
const account = new nearApi.Account({ provider: this });
return await account.viewFunction(contractName, methodName, args);
};

const queryFinalTimestamp = async () => {
const finalBlock = await nearRpc.sendJsonRpc("block", { finality: "final" });
Expand Down Expand Up @@ -34,7 +40,7 @@ const getCurrentNodes = (nodes) => {
const {
newValidators,
removedValidators,
} = nearlib.validators.diffEpochValidators(currentValidators, nextValidators);
} = nearApi.validators.diffEpochValidators(currentValidators, nextValidators);
signNewValidators(newValidators);
signRemovedValidators(removedValidators);
currentValidators = currentValidators.concat(newValidators);
Expand Down
124 changes: 124 additions & 0 deletions backend/src/wamp.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
const autobahn = require("autobahn");
const BN = require("bn.js");
const geoip = require("geoip-lite");
const { sha256 } = require("js-sha256");

const models = require("../models");

const {
wampNearNetworkName,
wampNearExplorerUrl,
wampNearExplorerBackendSecret,
nearLockupAccountIdSuffix,
} = require("./config");

const { nearRpc } = require("./near");

const wampHandlers = {};
Expand Down Expand Up @@ -98,6 +102,126 @@ wampHandlers["nearcore-validators"] = async () => {
return await nearRpc.sendJsonRpc("validators", [null]);
};

wampHandlers["get-account-details"] = async ([accountId]) => {
function generateLockupAccountIdFromAccountId(accountId) {
// copied from https://github.com/near/near-wallet/blob/f52a3b1a72b901d87ab2c9cee79770d697be2bd9/src/utils/wallet.js#L601
return (
sha256(Buffer.from(accountId)).substring(0, 40) +
"." +
nearLockupAccountIdSuffix
);
}

function ignore_if_does_not_exist(error) {
if (
typeof error.message === "string" &&
error.message.includes("doesn't exist")
) {
return null;
}
throw error;
}

let lockupAccountId;
if (accountId.endsWith(nearLockupAccountIdSuffix)) {
lockupAccountId = accountId;
} else {
lockupAccountId = generateLockupAccountIdFromAccountId(accountId);
}

const [
accountInfo,
lockupAccountInfo,
lockupLockedBalance,
lockupStakingPoolAccountId,
genesisConfig,
] = await Promise.all([
nearRpc.sendJsonRpc("query", {
request_type: "view_account",
finality: "final",
account_id: accountId,
}),
accountId !== lockupAccountId
? nearRpc
.sendJsonRpc("query", {
request_type: "view_account",
finality: "final",
account_id: lockupAccountId,
})
.catch(ignore_if_does_not_exist)
: null,
nearRpc
.callViewMethod(lockupAccountId, "get_locked_amount", {})
.then((balance) => new BN(balance))
.catch(ignore_if_does_not_exist),
nearRpc
.callViewMethod(lockupAccountId, "get_staking_pool_account_id", {})
.catch(ignore_if_does_not_exist),
nearRpc.sendJsonRpc("EXPERIMENTAL_genesis_config", {}),
]);

const storageUsage = new BN(accountInfo.storage_usage);
const storageAmountPerByte = new BN(
genesisConfig.runtime_config.storage_amount_per_byte
);
const stakedBalance = new BN(accountInfo.locked);
const nonStakedBalance = new BN(accountInfo.amount);
const minimumBalance = storageAmountPerByte.mul(storageUsage);
const availableBalance = nonStakedBalance.sub(
BN.max(stakedBalance, minimumBalance)
);

const accountDetails = {
storageUsage: storageUsage.toString(),
stakedBalance: stakedBalance.toString(),
nonStakedBalance: nonStakedBalance.toString(),
minimumBalance: minimumBalance.toString(),
availableBalance: availableBalance.toString(),
};

let lockupDelegatedToStakingPoolBalance;
if (lockupStakingPoolAccountId) {
lockupDelegatedToStakingPoolBalance = await nearRpc
.callViewMethod(lockupStakingPoolAccountId, "get_account_total_balance", {
account_id: lockupAccountId,
})
.then((balance) => new BN(balance))
.catch(ignore_if_does_not_exist);
}

let totalBalance = stakedBalance.add(nonStakedBalance);
// The following section could be compressed into more complicated checks,
// but it is left in a readable form.
if (accountId !== lockupAccountId && !lockupAccountInfo) {
// It is a regular account without lockup
} else if (accountId !== lockupAccountId) {
// It is a regular account with lockup
const lockupStakedBalance = new BN(lockupAccountInfo.locked);
const lockupNonStakedBalance = new BN(lockupAccountInfo.amount);
let lockupTotalBalance = lockupStakedBalance.add(lockupNonStakedBalance);
if (lockupDelegatedToStakingPoolBalance) {
console.log(lockupTotalBalance, lockupDelegatedToStakingPoolBalance);
lockupTotalBalance.iadd(lockupDelegatedToStakingPoolBalance);
}
totalBalance.iadd(lockupTotalBalance);
accountDetails.lockupAccountId = lockupAccountId;
accountDetails.lockupTotalBalance = lockupTotalBalance.toString();
accountDetails.lockupLockedBalance = lockupLockedBalance.toString();
accountDetails.lockupUnlockedBalance = lockupTotalBalance
.sub(lockupLockedBalance)
.toString();
} else if (accountId === lockupAccountId) {
// It is a lockup account
if (lockupDelegatedToStakingPoolBalance) {
totalBalance.iadd(lockupDelegatedToStakingPoolBalance);
}
}

accountDetails.totalBalance = totalBalance.toString();

return accountDetails;
};

function setupWamp() {
const wamp = new autobahn.Connection({
realm: "near-explorer",
Expand Down
6 changes: 3 additions & 3 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"hexy": "^0.3.0",
"lodash": "^4.17.15",
"moment": "^2.26.0",
"near-api-js": "^0.27.0",
"near-api-js": "^0.30.0",
"next": "^9.4.1",
"react": "^16.13.1",
"react-bootstrap": "^1.0.1",
Expand Down
Loading