@ledgerhq/coin-tron
Version:
Ledger Tron Coin integration
195 lines • 9.22 kB
JavaScript
"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 jsHelpers_1 = require("@ledgerhq/coin-framework/bridge/jsHelpers");
const operation_1 = require("@ledgerhq/coin-framework/operation");
const index_1 = require("@ledgerhq/cryptoassets/index");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const compact_1 = __importDefault(require("lodash/compact"));
const get_1 = __importDefault(require("lodash/get"));
const logic_1 = require("../logic");
const pagination_1 = require("../logic/pagination");
const network_1 = require("../network");
const utils_1 = require("./utils");
// the balance does not update straightaway so we should ignore recent operations if they are in pending for a bit
const PREFER_PENDING_OPERATIONS_UNTIL_BLOCK_VALIDATION = 35;
const getAccountShape = async ({ initialAccount, currency, address, derivationMode }, syncConfig) => {
const { height: blockHeight } = await (0, network_1.getLastBlock)();
const tronAcc = await (0, network_1.fetchTronAccount)(address);
const accountId = (0, account_1.encodeAccountId)({
type: "js",
version: "2",
currencyId: currency.id,
xpubOrAddress: address,
derivationMode: derivationMode,
});
if (tronAcc.length === 0) {
return {
id: accountId,
blockHeight,
balance: new bignumber_js_1.default(0),
tronResources: utils_1.defaultTronResources,
};
}
const acc = tronAcc[0];
const cacheTransactionInfoById = initialAccount
? {
...(initialAccount?.tronResources?.cacheTransactionInfoById || {}),
}
: {};
const operationsPageSize = Math.min(1000, (0, pagination_1.getOperationsPageSize)(initialAccount && initialAccount.id, syncConfig));
// FIXME: this is not optional especially that we might already have initialAccount
// use minimalOperationsBuilderSync to reconciliate and KEEP REF
const txs = await (0, network_1.fetchTronAccountTxs)(address, txs => txs.length < operationsPageSize, cacheTransactionInfoById);
const tronResources = await (0, utils_1.getTronResources)(acc, txs, cacheTransactionInfoById);
// const tronResources = await getTronResources(acc);
const spendableBalance = acc.balance ? new bignumber_js_1.default(acc.balance) : new bignumber_js_1.default(0);
const balance = (0, logic_1.computeBalanceBridge)(acc);
const parentTxs = txs.filter(utils_1.isParentTx);
const parentOperations = (0, compact_1.default)(parentTxs.map(tx => (0, utils_1.txInfoToOperation)(accountId, address, tx)));
const trc10Tokens = (0, get_1.default)(acc, "assetV2", []).reduce((accumulator, { key, value }) => {
const tokenInfo = (0, index_1.findTokenById)(`tron/trc10/${key}`);
if (tokenInfo) {
accumulator.push({
key,
type: "trc10",
tokenId: tokenInfo.id,
balance: value.toString(),
});
}
return accumulator;
}, []);
const trc20Tokens = (0, get_1.default)(acc, "trc20", []).reduce((accumulator, trc20) => {
const [[contractAddress, balance]] = Object.entries(trc20);
const tokenInfo = (0, index_1.findTokenByAddressInCurrency)(contractAddress, currency.id);
if (tokenInfo) {
accumulator.push({
key: contractAddress,
type: "trc20",
tokenId: tokenInfo.id,
balance,
});
}
return accumulator;
}, []);
const { blacklistedTokenIds = [] } = syncConfig;
const subAccounts = (0, compact_1.default)(trc10Tokens.concat(trc20Tokens).map(({ key, tokenId, balance }) => {
const { blacklistedTokenIds = [] } = syncConfig;
const token = (0, index_1.findTokenById)(tokenId);
if (!token || blacklistedTokenIds.includes(tokenId))
return;
const id = (0, account_1.encodeTokenAccountId)(accountId, token);
const tokenTxs = txs.filter(tx => tx.tokenId === key);
const operations = (0, compact_1.default)(tokenTxs.map(tx => (0, utils_1.txInfoToOperation)(id, address, tx)));
const maybeExistingSubAccount = initialAccount?.subAccounts?.find(a => a.id === id);
const bnBalance = new bignumber_js_1.default(balance);
const sub = {
type: "TokenAccount",
id,
parentId: accountId,
token,
balance: bnBalance,
spendableBalance: bnBalance,
operationsCount: operations.length,
operations,
pendingOperations: maybeExistingSubAccount ? maybeExistingSubAccount.pendingOperations : [],
creationDate: operations.length > 0 ? operations[operations.length - 1].date : new Date(),
swapHistory: maybeExistingSubAccount ? maybeExistingSubAccount.swapHistory : [],
balanceHistoryCache: account_1.emptyHistoryCache, // calculated in the jsHelpers
};
return sub;
}));
// Filter blacklisted tokens from the initial account's subAccounts
// Could be use to filter out tokens that got their CAL id changed
const filteredInitialSubAccounts = (initialAccount?.subAccounts || []).filter(subAccount => !blacklistedTokenIds.includes(subAccount.token.id));
// keep old account with emptyBalance and a history not returned by the BE fixes LIVE-12797
const mergedSubAccounts = mergeSubAccounts(subAccounts, filteredInitialSubAccounts);
// get 'OUT' token operations with fee
const subOutOperationsWithFee = subAccounts
.flatMap(s => s.operations)
.filter(o => o.type === "OUT" && o.fee.isGreaterThan(0))
.map((o) => ({
...o,
accountId,
value: o.fee,
id: (0, operation_1.encodeOperationId)(accountId, o.hash, "OUT"),
extra: o.extra,
}));
// add them to the parent operations and sort by date desc
/**
* FIXME
*
* We have a problem here as we're just concatenating ops without ever really linking them.
* It means no operation can be "FEES" of a subOp by example. It leads to our issues with TRC10/TRC20
* optimistic operation never really existing in the end.
*/
const parentOpsAndSubOutOpsWithFee = parentOperations
.concat(subOutOperationsWithFee)
.sort((a, b) => b.date.valueOf() - a.date.valueOf());
return {
id: accountId,
balance,
spendableBalance,
operationsCount: parentOpsAndSubOutOpsWithFee.length,
operations: parentOpsAndSubOutOpsWithFee,
subAccounts: mergedSubAccounts,
tronResources,
blockHeight,
};
};
exports.getAccountShape = getAccountShape;
const postSync = (initial, parent) => {
function evictRecentOpsIfPending(a) {
a.pendingOperations.forEach(pending => {
const i = a.operations.findIndex(o => o.id === pending.id);
if (i !== -1) {
const diff = parent.blockHeight - (a.operations[i].blockHeight || 0);
if (diff < PREFER_PENDING_OPERATIONS_UNTIL_BLOCK_VALIDATION) {
a.operations.splice(i, 1);
}
}
});
}
evictRecentOpsIfPending(parent);
parent.subAccounts && parent.subAccounts.forEach(evictRecentOpsIfPending);
return parent;
};
/**
* Merges two arrays of subAccounts according to specific rules:
* - The first array (subAccounts1) is up-to-date and should not be modified.
* - Old duplicates from the second array (subAccounts2) should be filtered out.
* - Only new subAccounts with a unique ID from the second array should be included.
* - The balance and spendableBalance fields of the second array's subAccounts should be set to 0.
*
* @param {Array} subAccounts1 - The first array of subAccounts, which is up-to-date and should not be modified.
* @param {Array} subAccounts2 - The second array of subAccounts, from which only new unique subAccounts should be included.
* @returns {Array} - The merged array of subAccounts.
*/
const mergeSubAccounts = (subAccounts1, subAccounts2) => {
const existingIds = new Set(subAccounts1.map(subAccount => subAccount.id));
const filteredSubAccounts2 = subAccounts2
.map(subAccount => {
if (existingIds.has(subAccount.id)) {
return null;
}
else {
// Set balance and spendableBalance to 0 has if they are not here it means balance is 0
return {
...subAccount,
balance: new bignumber_js_1.default(0),
spendableBalance: new bignumber_js_1.default(0),
};
}
})
.filter((elt) => elt !== null);
return subAccounts1.concat(filteredSubAccounts2);
};
exports.sync = (0, jsHelpers_1.makeSync)({
getAccountShape: exports.getAccountShape,
postSync,
});
//# sourceMappingURL=synchronization.js.map