UNPKG

@ledgerhq/coin-stacks

Version:
160 lines 7.36 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.sync = exports.getAccountShape = void 0; exports.calculateSpendableBalance = calculateSpendableBalance; exports.createTokenAccount = createTokenAccount; exports.buildTokenAccounts = buildTokenAccounts; const index_1 = require("@ledgerhq/coin-framework/account/index"); const jsHelpers_1 = require("@ledgerhq/coin-framework/bridge/jsHelpers"); const transactions_1 = require("@stacks/transactions"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const invariant_1 = __importDefault(require("invariant")); const api_1 = require("../network/api"); const misc_1 = require("./utils/misc"); const logs_1 = require("@ledgerhq/logs"); const state_1 = require("@ledgerhq/cryptoassets/state"); /** * Calculates the spendable balance by subtracting pending transactions from the total balance */ function calculateSpendableBalance(totalBalance, pendingTxs) { let spendableBalance = totalBalance; for (const tx of pendingTxs) { spendableBalance = spendableBalance .minus(new bignumber_js_1.default(tx.fee_rate)) .minus(new bignumber_js_1.default(tx.token_transfer.amount)); } return spendableBalance; } /** * Creates a token account for a specific token */ async function createTokenAccount(address, parentAccountId, tokenId, tokenBalance, transactionsList, initialAccount) { try { const token = await (0, state_1.getCryptoAssetsStore)().findTokenByAddressInCurrency(tokenId, "stacks"); if (!tokenId || !token) { (0, logs_1.log)("error", `stacks token not found, addr: ${tokenId}`); return null; } const bnBalance = new bignumber_js_1.default(tokenBalance || "0"); const tokenAccountId = (0, index_1.encodeTokenAccountId)(parentAccountId, token); // Process operations for this token const operations = transactionsList .flatMap(txn => (0, misc_1.sip010TxnToOperation)(txn, address, tokenAccountId)) .flat() .sort((a, b) => b.date.getTime() - a.date.getTime()); // Skip empty accounts with zero balance and no operations if (operations.length === 0 && bnBalance.isZero()) { return null; } // Preserve existing pending operations if available const maybeExistingSubAccount = initialAccount?.subAccounts?.find(a => a.id === tokenAccountId); const tokenAccount = { type: "TokenAccount", id: tokenAccountId, parentId: parentAccountId, token, balance: bnBalance, spendableBalance: bnBalance, operationsCount: operations.length, operations, pendingOperations: maybeExistingSubAccount?.pendingOperations ?? [], creationDate: operations.length > 0 ? operations[operations.length - 1].date : new Date(), swapHistory: maybeExistingSubAccount?.swapHistory ?? [], balanceHistoryCache: index_1.emptyHistoryCache, // calculated in the jsHelpers }; return tokenAccount; } catch (e) { (0, logs_1.log)("error", "stacks error creating token account", e); return null; } } /** * Builds token accounts for all tokens with transactions or balances */ async function buildTokenAccounts(address, parentAccountId, tokenTxs, tokenBalances, initialAccount) { try { const tokenAccounts = []; // Process all tokens that have transactions for (const [tokenId, transactions] of Object.entries(tokenTxs)) { const balance = tokenBalances[tokenId] || "0"; const tokenAccount = await createTokenAccount(address, parentAccountId, tokenId, balance, transactions, initialAccount); if (tokenAccount) { tokenAccounts.push(tokenAccount); } } // Process any tokens with balances but no transactions for (const [tokenId, balance] of Object.entries(tokenBalances)) { // Skip tokens we've already processed if (tokenTxs[tokenId]) continue; // Skip zero balances if (new bignumber_js_1.default(balance).isZero()) continue; const tokenAccount = await createTokenAccount(address, parentAccountId, tokenId, balance, [], // No transactions initialAccount); if (tokenAccount) { tokenAccounts.push(tokenAccount); } } return tokenAccounts; } catch (e) { (0, logs_1.log)("error", "stacks error building token accounts", e); return []; } } const getAccountShape = async (info) => { const { initialAccount, currency, rest = {}, derivationMode } = info; // for bridge tests specifically the `rest` object is empty and therefore the publicKey is undefined // reconciliatePublicKey tries to get pubKey from rest object and then from accountId const pubKey = (0, misc_1.reconciliatePublicKey)(rest.publicKey, initialAccount); (0, invariant_1.default)(pubKey, "publicKey is required"); const accountId = (0, index_1.encodeAccountId)({ type: "js", version: "2", currencyId: currency.id, xpubOrAddress: pubKey, derivationMode, }); const address = (0, transactions_1.getAddressFromPublicKey)(pubKey); // Make API calls in parallel for better performance const [blockHeight, balanceResp, txsResult, tokenBalances, mempoolTxs] = await Promise.all([ (0, api_1.fetchBlockHeight)(), (0, api_1.fetchBalances)(address), (0, api_1.fetchFullTxs)(address), (0, api_1.fetchAllTokenBalances)(address), (0, api_1.fetchFullMempoolTxs)(address), ]); const [rawTxs, tokenTxs] = txsResult; const balance = new bignumber_js_1.default(balanceResp.balance); // Calculate spendable balance by considering pending transactions const spendableBalance = calculateSpendableBalance(balance, mempoolTxs); // Process pending operations const pendingOperations = mempoolTxs.flatMap((0, misc_1.mapPendingTxToOps)(accountId, address)); // Process operations from confirmed transactions const operations = pendingOperations.concat(rawTxs.flatMap((0, misc_1.mapTxToOps)(accountId, address))); // Build token sub-accounts const tokenAccounts = await buildTokenAccounts(address, accountId, tokenTxs, tokenBalances, initialAccount); const result = { id: accountId, subAccounts: tokenAccounts, xpub: pubKey, freshAddress: address, balance, spendableBalance, // merge operations from both token and account operations: [ ...operations, ...tokenAccounts.flatMap(t => (0, misc_1.sip010OpToParentOp)(t.operations, accountId)), ].sort((a, b) => b.date.getTime() - a.date.getTime()), blockHeight: blockHeight.chain_tip.block_height, }; return result; }; exports.getAccountShape = getAccountShape; exports.sync = (0, jsHelpers_1.makeSync)({ getAccountShape: exports.getAccountShape }); //# sourceMappingURL=synchronization.js.map