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

Commit

Permalink
feat(frontend): display detailed balance information on AccountDetail…
Browse files Browse the repository at this point in the history
…s page (#447)

Resolve #291

# Test Plan

* relevant unit test snapshots updated and reviewed
* manually confirmed the backend compatibility
  • Loading branch information
icerove authored Oct 20, 2020
1 parent e1446af commit 71652aa
Show file tree
Hide file tree
Showing 14 changed files with 1,069 additions and 94 deletions.
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

0 comments on commit 71652aa

Please sign in to comment.