UNPKG

@ledgerhq/coin-aptos

Version:
218 lines 9.85 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getStakingPoolAddresses = exports.getAccountShape = exports.getSubAccounts = exports.getSubAccountShape = exports.mergeSubAccounts = void 0; const index_1 = require("@ledgerhq/coin-framework/serialization/index"); const account_1 = require("@ledgerhq/coin-framework/account"); const jsHelpers_1 = require("@ledgerhq/coin-framework/bridge/jsHelpers"); const network_1 = require("../network"); const logic_1 = require("./logic"); const index_2 = require("@ledgerhq/coin-framework/account/index"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const live_env_1 = require("@ledgerhq/live-env"); /** * List of properties of a sub account that can be updated when 2 "identical" accounts are found */ const updatableSubAccountProperties = [ { name: "balance", isOps: false }, { name: "spendableBalance", isOps: false }, { name: "balanceHistoryCache", isOps: false }, { name: "operations", isOps: true }, { name: "pendingOperations", isOps: true }, ]; /** * In charge of smartly merging sub accounts while maintaining references as much as possible */ const mergeSubAccounts = (initialAccount, newSubAccounts) => { const oldSubAccounts = initialAccount?.subAccounts; if (!oldSubAccounts) { return newSubAccounts; } // Creating a map of already existing sub accounts by id const oldSubAccountsById = {}; for (const oldSubAccount of oldSubAccounts) { oldSubAccountsById[oldSubAccount.id] = oldSubAccount; } // Looping on new sub accounts to compare them with already existing ones // Already existing will be updated if necessary (see `updatableSubAccountProperties`) // Fresh new sub accounts will be added/pushed after already existing const newSubAccountsToAdd = []; for (const newSubAccount of newSubAccounts) { const duplicatedAccount = oldSubAccountsById[newSubAccount.id]; // If this sub account was not already in the initialAccount if (!duplicatedAccount) { // We'll add it later newSubAccountsToAdd.push(newSubAccount); continue; } const updates = {}; for (const { name, isOps } of updatableSubAccountProperties) { if (!isOps) { if (newSubAccount[name] !== duplicatedAccount[name]) { updates[name] = newSubAccount[name]; } } else { updates[name] = (0, jsHelpers_1.mergeOps)(duplicatedAccount[name], newSubAccount[name]) ?? []; } } // Updating the operationsCount in case the mergeOps changed it updates.operationsCount = updates.operations?.length || duplicatedAccount?.operations?.length || 0; // Modifying the Map with the updated sub account with a new ref oldSubAccountsById[newSubAccount.id] = { ...duplicatedAccount, ...updates, }; } const updatedSubAccounts = Object.values(oldSubAccountsById); return [...updatedSubAccounts, ...newSubAccountsToAdd]; }; exports.mergeSubAccounts = mergeSubAccounts; /** * Fetch the balance for a token and creates a TokenAccount based on this and the provided operations */ const getSubAccountShape = async (currency, address, parentId, token, operations) => { const aptosClient = new network_1.AptosAPI(currency.id); const tokenAccountId = (0, index_2.encodeTokenAccountId)(parentId, token); const balances = await aptosClient.getBalances(address, token.contractAddress); const balance = balances.length > 0 ? balances[0].amount : (0, bignumber_js_1.default)(0); const firstOperation = operations .sort((a, b) => b.date.getTime() - a.date.getTime()) .at(operations.length - 1); return { type: "TokenAccount", id: tokenAccountId, parentId, token, balance, spendableBalance: balance, creationDate: firstOperation?.date || new Date(0), operations, operationsCount: operations.length, pendingOperations: [], balanceHistoryCache: index_2.emptyHistoryCache, swapHistory: [], }; }; exports.getSubAccountShape = getSubAccountShape; /** * Getting all token related operations in order to provide TokenAccounts */ const getSubAccounts = async (infos, address, accountId, lastTokenOperations) => { const { currency } = infos; // Creating a Map of Operations by TokenCurrencies in order to know which TokenAccounts should be synced as well const operationsByToken = new Map(); for (const operation of lastTokenOperations) { const { token } = await (0, index_2.decodeTokenAccountId)(operation.accountId); if (!token) continue; // TODO: do we need to check blacklistedTokenIds if (!operationsByToken.has(token)) { operationsByToken.set(token, []); } operationsByToken.get(token)?.push(operation); } // Fetching all TokenAccounts possible and providing already filtered operations const subAccountsPromises = []; for (const [token, ops] of operationsByToken.entries()) { subAccountsPromises.push((0, exports.getSubAccountShape)(currency, address, accountId, token, ops)); } return Promise.all(subAccountsPromises); }; exports.getSubAccounts = getSubAccounts; const getAccountShape = async (info) => { const { address, initialAccount, currency, derivationMode, rest } = info; const publicKey = rest?.publicKey || (initialAccount && (0, account_1.decodeAccountId)(initialAccount.id).xpubOrAddress); const accountId = (0, account_1.encodeAccountId)({ type: "js", version: "2", currencyId: currency.id, xpubOrAddress: publicKey || address, derivationMode, }); // "xpub" field is used to store publicKey to simulate transaction during sending tokens. // We can't get access to the Nano X via bluetooth on the step of simulation // but we need public key to simulate transaction. // "xpub" field is used because this field exists in ledger operation type const xpub = initialAccount?.xpub || publicKey || ""; const oldOperations = initialAccount?.operations || []; const aptosClient = new network_1.AptosAPI(currency.id); const { balance, transactions, blockHeight } = await aptosClient.getAccountInfo(address); const [newOperations, tokenOperations, stakingOperations] = await (0, logic_1.txsToOps)(info, accountId, transactions); const operations = (0, jsHelpers_1.mergeOps)(oldOperations, newOperations); const newSubAccounts = await (0, exports.getSubAccounts)(info, address, accountId, tokenOperations); const shouldSyncFromScratch = initialAccount === undefined; const subAccounts = shouldSyncFromScratch ? newSubAccounts : (0, exports.mergeSubAccounts)(initialAccount, newSubAccounts); operations?.forEach(op => { const subOperations = (0, index_1.inferSubOperations)(op.hash, subAccounts); op.subOperations = subOperations.length === 1 ? subOperations : subOperations.filter(op => !!op.blockHash); }); const stakingPositions = []; let activeBalance = (0, bignumber_js_1.default)(0); let inactiveBalance = (0, bignumber_js_1.default)(0); let pendingInactiveBalance = (0, bignumber_js_1.default)(0); if ((0, live_env_1.getEnv)("APTOS_ENABLE_STAKING") === true) { const stakingPoolAddresses = (0, exports.getStakingPoolAddresses)(stakingOperations); for (const stakingPoolAddress of stakingPoolAddresses) { const [active_string, inactive_string, pending_inactive_string] = await aptosClient.getDelegatorBalanceInPool(stakingPoolAddress, address); const active = (0, bignumber_js_1.default)(active_string); const inactive = (0, bignumber_js_1.default)(inactive_string); const pendingInactive = (0, bignumber_js_1.default)(pending_inactive_string); stakingPositions.push({ active, inactive, pendingInactive, validatorId: stakingPoolAddress, }); activeBalance = activeBalance.plus(active); inactiveBalance = inactiveBalance.plus(inactive); pendingInactiveBalance = pendingInactiveBalance.plus(pendingInactive); } } const aptosResources = { activeBalance, inactiveBalance, pendingInactiveBalance, stakingPositions, }; const shape = { type: "Account", id: accountId, xpub, balance: balance .plus(aptosResources.activeBalance) .plus(aptosResources.pendingInactiveBalance) .plus(aptosResources.inactiveBalance), spendableBalance: balance, operations, operationsCount: operations.length, blockHeight, lastSyncDate: new Date(), subAccounts, aptosResources, }; return shape; }; exports.getAccountShape = getAccountShape; const getStakingPoolAddresses = (stakingOperations) => { const stakingPoolsAddrs = []; for (const op of stakingOperations) { if (!op.recipients.length) continue; const poolAddress = op.recipients[0]; if (poolAddress === "0x1") continue; if (!stakingPoolsAddrs.includes(poolAddress)) stakingPoolsAddrs.push(poolAddress); } return stakingPoolsAddrs; }; exports.getStakingPoolAddresses = getStakingPoolAddresses; //# sourceMappingURL=synchronisation.js.map