@ledgerhq/coin-near
Version:
278 lines • 11.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getValidators = exports.getStakingPositions = exports.broadcastTransaction = exports.getAccessKey = exports.getGasPrice = exports.getProtocolConfig = exports.getAccount = exports.fetchAccountDetails = void 0;
const live_network_1 = __importDefault(require("@ledgerhq/live-network"));
const cache_1 = require("@ledgerhq/live-network/cache");
const network_1 = __importDefault(require("@ledgerhq/live-network/network"));
const logs_1 = require("@ledgerhq/logs");
const bignumber_js_1 = require("bignumber.js");
const nearAPI = __importStar(require("near-api-js"));
const config_1 = require("../config");
const constants_1 = require("../constants");
const logic_1 = require("../logic");
const preload_data_1 = require("../preload-data");
const fetchAccountDetails = async (address) => {
const currencyConfig = (0, config_1.getCoinConfig)();
const { data } = await (0, network_1.default)({
method: "POST",
url: currencyConfig.infra.API_NEAR_PRIVATE_NODE,
data: {
jsonrpc: "2.0",
id: "id",
method: "query",
params: {
request_type: "view_account",
finality: "final",
account_id: address,
},
},
});
return data.result;
};
exports.fetchAccountDetails = fetchAccountDetails;
const getAccount = async (address) => {
let accountDetails;
accountDetails = await (0, exports.fetchAccountDetails)(address);
if (!accountDetails) {
accountDetails = {
amount: "0",
block_height: 0,
storage_usage: 0,
};
}
const { stakingPositions, totalStaked, totalAvailable, totalPending } = await (0, exports.getStakingPositions)(address);
const { storageCost } = (0, preload_data_1.getCurrentNearPreloadData)();
const balance = new bignumber_js_1.BigNumber(accountDetails.amount);
const storageUsage = storageCost.multipliedBy(accountDetails.storage_usage);
const minBalanceBuffer = new bignumber_js_1.BigNumber(constants_1.MIN_ACCOUNT_BALANCE_BUFFER);
let spendableBalance = balance.minus(storageUsage).minus(minBalanceBuffer);
if (spendableBalance.lt(0)) {
spendableBalance = new bignumber_js_1.BigNumber(0);
}
return {
blockHeight: accountDetails.block_height,
balance: balance.plus(totalStaked).plus(totalAvailable).plus(totalPending),
spendableBalance,
nearResources: {
stakedBalance: totalStaked,
availableBalance: totalAvailable,
pendingBalance: totalPending,
storageUsageBalance: storageUsage.plus(minBalanceBuffer),
stakingPositions,
},
};
};
exports.getAccount = getAccount;
const getProtocolConfig = async () => {
const currencyConfig = (0, config_1.getCoinConfig)();
const { data } = await (0, network_1.default)({
method: "POST",
url: currencyConfig.infra.API_NEAR_PRIVATE_NODE,
data: {
jsonrpc: "2.0",
id: "id",
method: "EXPERIMENTAL_protocol_config",
params: {
finality: "final",
},
},
});
return data.result;
};
exports.getProtocolConfig = getProtocolConfig;
const getGasPrice = async () => {
const currencyConfig = (0, config_1.getCoinConfig)();
const response = await (0, live_network_1.default)({
url: `${currencyConfig.infra.API_NEARBLOCKS_INDEXER}/v1/stats`,
});
return response.data.stats[0].gas_price;
};
exports.getGasPrice = getGasPrice;
const getAccessKey = async ({ address, publicKey, }) => {
const currencyConfig = (0, config_1.getCoinConfig)();
const { data } = await (0, network_1.default)({
method: "POST",
url: currencyConfig.infra.API_NEAR_PRIVATE_NODE,
data: {
jsonrpc: "2.0",
id: "id",
method: "query",
params: {
request_type: "view_access_key",
finality: "final",
account_id: address,
public_key: publicKey,
},
},
});
return data.result || {};
};
exports.getAccessKey = getAccessKey;
/**
* Implements a retry mechanism for broadcasting a transaction
* based on the near documentation: https://docs.near.org/api/rpc/transactions#what-could-go-wrong-send-tx
*
* `TIMEOUT_ERROR` can be thrown when the transaction is not yet executed in less than 10 seconds.
* Documentation advises to "re-submit the request with the identical transaction" in this case.
*/
const broadcastTransaction = async (transaction, retries = 6) => {
const currencyConfig = (0, config_1.getCoinConfig)();
const { data } = await (0, network_1.default)({
method: "POST",
url: currencyConfig.infra.API_NEAR_PRIVATE_NODE,
data: {
jsonrpc: "2.0",
id: "id",
method: "send_tx",
params: {
signed_tx_base64: transaction,
wait_until: "EXECUTED_OPTIMISTIC",
},
},
});
if (data.error) {
if (data.error?.cause?.name === "TIMEOUT_ERROR" && retries > 0) {
(0, logs_1.log)("Near", "broadcastTransaction retrying after error", {
data,
payload: {
jsonrpc: "2.0",
id: "id",
method: "send_tx",
params: {
signed_tx_base64: transaction,
wait_until: "EXECUTED_OPTIMISTIC",
},
},
retries,
});
return (0, exports.broadcastTransaction)(transaction, retries - 1);
}
(0, logs_1.log)("Near", "broadcastTransaction error", data.error);
throw new Error((data.error?.cause?.name || "UNKOWWN CAUSE") + ": " + data.error.message);
}
return data.result.transaction.hash;
};
exports.broadcastTransaction = broadcastTransaction;
const getStakingPositions = async (address) => {
const currencyConfig = (0, config_1.getCoinConfig)();
const { connect, keyStores } = nearAPI;
const config = {
networkId: "mainnet",
keyStore: new keyStores.InMemoryKeyStore(),
nodeUrl: currencyConfig.infra.API_NEAR_PRIVATE_NODE,
headers: {},
};
const near = await connect(config);
const account = await near.account(address);
let totalStaked = new bignumber_js_1.BigNumber(0);
let totalAvailable = new bignumber_js_1.BigNumber(0);
let totalPending = new bignumber_js_1.BigNumber(0);
const stakingThreshold = (0, logic_1.getYoctoThreshold)();
const delegatedValidators = await (0, live_network_1.default)({
url: `${currencyConfig.infra.API_NEARBLOCKS_INDEXER}/v1/kitwallet/staking-deposits/${address}`,
});
const stakingPositions = await Promise.all(delegatedValidators.data.map(async ({ validator_id: validatorId }) => {
const contract = new nearAPI.Contract(account, validatorId, {
viewMethods: [
"get_account_staked_balance",
"get_account_unstaked_balance",
"is_account_unstaked_balance_available",
],
changeMethods: [],
useLocalViewExecution: false,
});
const [rawStaked, rawUnstaked, isAvailable] = await Promise.all([
contract.get_account_staked_balance({
account_id: address,
}),
contract.get_account_unstaked_balance({
account_id: address,
}),
contract.is_account_unstaked_balance_available({
account_id: address,
}),
]);
const unstaked = new bignumber_js_1.BigNumber(rawUnstaked);
let available = new bignumber_js_1.BigNumber(0);
let pending = unstaked;
if (isAvailable) {
available = unstaked;
pending = new bignumber_js_1.BigNumber(0);
}
const staked = new bignumber_js_1.BigNumber(rawStaked);
available = new bignumber_js_1.BigNumber(available);
pending = new bignumber_js_1.BigNumber(pending);
if (staked.gte(stakingThreshold)) {
totalStaked = totalStaked.plus(staked);
}
if (available.gte(stakingThreshold)) {
totalAvailable = totalAvailable.plus(available);
}
if (pending.gte(stakingThreshold)) {
totalPending = totalPending.plus(pending);
}
return {
staked,
available,
pending,
validatorId,
};
}));
return {
stakingPositions: stakingPositions.filter(sp => (0, logic_1.canUnstake)(sp) || (0, logic_1.canWithdraw)(sp) || sp.pending.gt(0)),
totalStaked,
totalAvailable,
totalPending,
};
};
exports.getStakingPositions = getStakingPositions;
async function fetchValidators({ per_page, page }) {
const currencyConfig = (0, config_1.getCoinConfig)();
const delegatedValidators = await (0, live_network_1.default)({
url: `${currencyConfig.infra.API_NEARBLOCKS_INDEXER}/v1/validators?per_page=${per_page}&page=${page}`,
});
const validators = delegatedValidators.data.validatorFullData.map(({ accountId, currentEpoch, poolInfo }) => ({
account_id: accountId,
stake: currentEpoch.stake,
commission: Math.round((poolInfo.fee.numerator / poolInfo.fee.denominator) * 100),
}));
return validators || [];
}
exports.getValidators = (0, cache_1.makeLRUCache)(fetchValidators, () => "", { ttl: 30 * 60 * 1000 });
//# sourceMappingURL=node.js.map