UNPKG

@ledgerhq/coin-algorand

Version:
309 lines 12.4 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; const account_1 = require("@ledgerhq/coin-framework/account"); const serialization_1 = require("@ledgerhq/coin-framework/serialization"); const jsHelpers_1 = require("@ledgerhq/coin-framework/bridge/jsHelpers"); const state_1 = require("@ledgerhq/cryptoassets/state"); const operation_1 = require("@ledgerhq/coin-framework/operation"); const live_promise_1 = require("@ledgerhq/live-promise"); const bignumber_js_1 = require("bignumber.js"); const api_1 = __importDefault(require("./api")); const api_2 = require("./api"); const logic_1 = require("./logic"); const tokens_1 = require("./tokens"); const SECONDS_TO_MILLISECONDS = 1000; const getASAOperationAmount = (transaction, accountAddress) => { let assetAmount = new bignumber_js_1.BigNumber(0); if (transaction.type === api_2.AlgoTransactionType.ASSET_TRANSFER) { const details = transaction.details; const assetSender = details.assetSenderAddress ? details.assetSenderAddress : transaction.senderAddress; // Account is either sender or recipient (if both the balance is unchanged) if ((assetSender === accountAddress) !== (details.assetRecipientAddress === accountAddress)) { assetAmount = assetAmount.plus(details.assetAmount); } // Account is either sender or close-to, but not both if ((assetSender === accountAddress) !== (details.assetCloseToAddress && details.assetCloseToAddress === accountAddress)) { if (details.assetCloseAmount) { assetAmount = assetAmount.plus(details.assetCloseAmount); } } } return assetAmount; }; const getOperationAmounts = (transaction, accountAddress) => { let amount = new bignumber_js_1.BigNumber(0); let rewards = new bignumber_js_1.BigNumber(0); if (transaction.senderAddress === accountAddress) { const senderRewards = transaction.senderRewards; amount = amount.minus(senderRewards).plus(transaction.fee); rewards = rewards.plus(senderRewards); } if (transaction.type === api_2.AlgoTransactionType.PAYMENT) { const details = transaction.details; if (transaction.senderAddress === details.recipientAddress) { return { amount, rewards, }; } if (transaction.senderAddress === accountAddress) { amount = amount.plus(details.amount); } if (details.recipientAddress === accountAddress) { const recipientRewards = transaction.recipientRewards; amount = amount.plus(details.amount).plus(recipientRewards); rewards = rewards.plus(recipientRewards); } if (transaction.closeRewards && details.closeAmount && details.closeToAddress === accountAddress) { const closeRewards = transaction.closeRewards; amount = amount.plus(details.closeAmount).plus(closeRewards); rewards = rewards.plus(closeRewards); } } return { amount, rewards, }; }; const getASAOperationType = (transaction, accountAddress) => { return transaction.senderAddress === accountAddress ? "OUT" : "IN"; }; const getOperationType = (transaction, accountAddress) => { if (transaction.type === api_2.AlgoTransactionType.ASSET_TRANSFER) { const details = transaction.details; if (details.assetAmount.isZero() && transaction.senderAddress === details.assetRecipientAddress) { return "OPT_IN"; } else if (details.assetCloseToAddress && transaction.senderAddress === accountAddress) { return "OPT_OUT"; } else { return "FEES"; } } return transaction.senderAddress === accountAddress ? "OUT" : "IN"; }; const getOperationSenders = (transaction) => { return [transaction.senderAddress]; }; const getOperationRecipients = (transaction) => { const recipients = []; if (transaction.type === api_2.AlgoTransactionType.PAYMENT) { const details = transaction.details; recipients.push(details.recipientAddress); if (details.closeToAddress) { recipients.push(details.closeToAddress); } } else if (transaction.type === api_2.AlgoTransactionType.ASSET_TRANSFER) { const details = transaction.details; recipients.push(details.assetRecipientAddress); if (details.assetCloseToAddress) { recipients.push(details.assetCloseToAddress); } } return recipients; }; const getOperationAssetId = (transaction) => { if (transaction.type === api_2.AlgoTransactionType.ASSET_TRANSFER) { const details = transaction.details; return details.assetId; } }; const mapTransactionToOperation = (tx, accountId, accountAddress, subAccounts) => { const hash = tx.id; const blockHeight = tx.round; const date = new Date(Number.parseInt(tx.timestamp) * SECONDS_TO_MILLISECONDS); const fee = tx.fee; const memo = tx.note; const senders = getOperationSenders(tx); const recipients = getOperationRecipients(tx); const { amount, rewards } = getOperationAmounts(tx, accountAddress); const type = getOperationType(tx, accountAddress); const assetId = getOperationAssetId(tx); const subOperations = subAccounts ? (0, serialization_1.inferSubOperations)(tx.id, subAccounts) : []; return { id: (0, operation_1.encodeOperationId)(accountId, hash, type), hash, date, type, value: amount, fee, senders, recipients, blockHeight, blockHash: null, accountId, subOperations, extra: { rewards, memo, assetId, }, }; }; const mapTransactionToASAOperation = (tx, accountId, accountAddress) => { const hash = tx.id; const blockHeight = tx.round; const date = new Date(Number.parseInt(tx.timestamp) * SECONDS_TO_MILLISECONDS); const fee = tx.fee; const senders = getOperationSenders(tx); const recipients = getOperationRecipients(tx); const type = getASAOperationType(tx, accountAddress); const amount = getASAOperationAmount(tx, accountAddress); return { id: (0, operation_1.encodeOperationId)(accountId, hash, type), hash, date, type, value: amount, fee, senders, recipients, blockHeight, blockHash: null, accountId, extra: {}, }; }; const getAccountShape = async (info, syncConfig) => { const { address, initialAccount, currency, derivationMode } = info; const oldOperations = initialAccount?.operations || []; const startAt = oldOperations.length ? (oldOperations[0].blockHeight || 0) + 1 : 0; const accountId = (0, account_1.encodeAccountId)({ type: "js", version: "2", currencyId: currency.id, xpubOrAddress: address, derivationMode, }); const { round, balance, pendingRewards, assets } = await api_1.default.getAccount(address); const nbAssets = assets.length; // NOTE Actual spendable amount depends on the transaction const spendableBalance = (0, logic_1.computeAlgoMaxSpendable)({ accountBalance: balance, nbAccountAssets: nbAssets, mode: "send", }); const newTransactions = await api_1.default.getAccountTransactions(address, startAt); const subAccounts = await buildSubAccounts({ currency, accountId, initialAccount, initialAccountAddress: address, assets, newTransactions, syncConfig, }); const newOperations = newTransactions.map(tx => mapTransactionToOperation(tx, accountId, address, subAccounts)); const operations = (0, jsHelpers_1.mergeOps)(oldOperations, newOperations); return { id: accountId, xpub: address, blockHeight: round, balance, spendableBalance, operations, operationsCount: operations.length, subAccounts: subAccounts || [], algorandResources: { rewards: pendingRewards, nbAssets, }, }; }; exports.getAccountShape = getAccountShape; async function buildSubAccount({ parentAccountId, parentAccountAddress, token, initialTokenAccount, newTransactions, balance, }) { const extractedId = (0, tokens_1.extractTokenId)(token.id); const tokenAccountId = parentAccountId + "+" + extractedId; const oldOperations = initialTokenAccount?.operations || []; const newOperations = newTransactions .filter(tx => tx.type === api_2.AlgoTransactionType.ASSET_TRANSFER) .filter(tx => { const details = tx.details; return Number(details.assetId) === Number(extractedId); }) .filter(tx => getOperationType(tx, parentAccountAddress) !== "OPT_IN") .map(tx => mapTransactionToASAOperation(tx, tokenAccountId, parentAccountAddress)); const operations = (0, jsHelpers_1.mergeOps)(oldOperations, newOperations); const tokenAccount = { type: "TokenAccount", id: tokenAccountId, parentId: parentAccountId, token, operationsCount: operations.length, operations, pendingOperations: [], balance, spendableBalance: balance, swapHistory: [], creationDate: operations.length > 0 ? operations[operations.length - 1].date : new Date(), balanceHistoryCache: account_1.emptyHistoryCache, }; return tokenAccount; } async function buildSubAccounts({ accountId, initialAccount, initialAccountAddress, assets, newTransactions, syncConfig, }) { const { blacklistedTokenIds = [] } = syncConfig; const tokenAccounts = []; const existingAccountByTicker = {}; // used for fast lookup const existingAccountTickers = []; // used to keep track of ordering const assetsIds = assets.map(asset => asset.assetId); if (initialAccount && initialAccount.subAccounts) { for (const existingSubAccount of initialAccount.subAccounts) { if (existingSubAccount.type === "TokenAccount") { const { ticker, id } = existingSubAccount.token; if (!blacklistedTokenIds.includes(id)) { existingAccountTickers.push(ticker); existingAccountByTicker[ticker] = existingSubAccount; } } } } // filter by token existence await (0, live_promise_1.promiseAllBatched)(3, assets, async (asset) => { const token = await (0, state_1.getCryptoAssetsStore)().findTokenById((0, tokens_1.addPrefixToken)(asset.assetId)); if (token && !blacklistedTokenIds.includes(token.id)) { const initialTokenAccount = existingAccountByTicker[token.ticker]; const tokenAccount = await buildSubAccount({ parentAccountId: accountId, parentAccountAddress: initialAccountAddress, initialTokenAccount, token, newTransactions, balance: asset.balance, }); if (tokenAccount) tokenAccounts.push(tokenAccount); } }); // Preserve order of tokenAccounts from the existing token accounts, or use the // order of input assets if no token accounts exist yet tokenAccounts.sort((a, b) => { const i = existingAccountTickers.length ? existingAccountTickers.indexOf(a.token.ticker) : assetsIds.indexOf((0, tokens_1.extractTokenId)(a.token.id)); const j = existingAccountTickers.length ? existingAccountTickers.indexOf(b.token.ticker) : assetsIds.indexOf((0, tokens_1.extractTokenId)(b.token.id)); if (i === j) return 0; if (i < 0) return 1; if (j < 0) return -1; return i - j; }); return tokenAccounts; } exports.sync = (0, jsHelpers_1.makeSync)({ getAccountShape: exports.getAccountShape }); //# sourceMappingURL=synchronization.js.map