@ledgerhq/coin-multiversx
Version:
Ledger MultiversX Coin integration
154 lines (133 loc) • 5.3 kB
text/typescript
import { findTokenById, listTokensForCryptoCurrency } from "@ledgerhq/cryptoassets";
import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
import { Account, SyncConfig, TokenAccount } from "@ledgerhq/types-live";
import BigNumber from "bignumber.js";
import { emptyHistoryCache, encodeTokenAccountId } from "@ledgerhq/coin-framework/account/index";
import { mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers";
import { getESDTOperations, getAccountESDTTokens } from "./api";
import { addPrefixToken, extractTokenId } from "./logic";
async function buildMultiversXESDTTokenAccount({
parentAccountId,
accountAddress,
token,
balance,
}: {
parentAccountId: string;
accountAddress: string;
token: TokenCurrency;
balance: BigNumber;
}) {
const tokenAccountId = encodeTokenAccountId(parentAccountId, token);
const tokenIdentifierHex = extractTokenId(token.id);
const tokenIdentifier = Buffer.from(tokenIdentifierHex, "hex").toString();
const operations = await getESDTOperations(tokenAccountId, accountAddress, tokenIdentifier, 0);
const tokenAccount: 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: emptyHistoryCache, // calculated in the jsHelpers
};
return tokenAccount;
}
async function syncESDTTokenAccountOperations(
tokenAccount: TokenAccount,
address: string,
): Promise<TokenAccount> {
const oldOperations = tokenAccount?.operations || [];
// Needed for incremental synchronisation
const startAt = oldOperations.length ? Math.floor(oldOperations[0].date.valueOf() / 1000) : 0;
const tokenIdentifierHex = extractTokenId(tokenAccount.token.id);
const tokenIdentifier = Buffer.from(tokenIdentifierHex, "hex").toString();
// Merge new operations with the previously synced ones
const newOperations = await getESDTOperations(tokenAccount.id, address, tokenIdentifier, startAt);
const operations = mergeOps(oldOperations, newOperations);
if (operations === oldOperations) return tokenAccount;
const copy = { ...tokenAccount };
copy.operations = operations;
copy.operationsCount = operations.length;
return copy;
}
async function MultiversXBuildESDTTokenAccounts({
currency,
accountId,
accountAddress,
existingAccount,
syncConfig,
}: {
currency: CryptoCurrency;
accountId: string;
accountAddress: string;
existingAccount: Account | null | undefined;
syncConfig: SyncConfig;
}): Promise<TokenAccount[] | undefined> {
const { blacklistedTokenIds = [] } = syncConfig;
if (listTokensForCryptoCurrency(currency).length === 0) {
return undefined;
}
const tokenAccounts: TokenAccount[] = [];
const existingAccountByTicker: { [key: string]: TokenAccount } = {}; // used for fast lookup
const existingAccountTickers: string[] = []; // used to keep track of ordering
if (existingAccount && existingAccount.subAccounts) {
for (const existingSubAccount of existingAccount.subAccounts) {
if (existingSubAccount.type === "TokenAccount") {
const { ticker, id } = existingSubAccount.token;
if (!blacklistedTokenIds.includes(id)) {
existingAccountTickers.push(ticker);
existingAccountByTicker[ticker] = existingSubAccount;
}
}
}
}
const accountESDTs = await getAccountESDTTokens(accountAddress);
for (const esdt of accountESDTs) {
const esdtIdentifierHex = Buffer.from(esdt.identifier).toString("hex");
const token = findTokenById(addPrefixToken(esdtIdentifierHex));
if (token && !blacklistedTokenIds.includes(token.id)) {
let tokenAccount = existingAccountByTicker[token.ticker];
if (!tokenAccount) {
tokenAccount = await buildMultiversXESDTTokenAccount({
parentAccountId: accountId,
accountAddress,
token,
balance: new BigNumber(esdt.balance),
});
} else {
const inputTokenAccount = tokenAccount;
tokenAccount = await syncESDTTokenAccountOperations(inputTokenAccount, accountAddress);
const balance = new BigNumber(esdt.balance);
if (!balance.eq(tokenAccount.balance)) {
// only recreate the object if balance changed
if (inputTokenAccount === tokenAccount) {
tokenAccount = { ...tokenAccount };
}
tokenAccount.balance = balance;
tokenAccount.spendableBalance = balance;
}
}
if (tokenAccount) {
tokenAccounts.push(tokenAccount);
existingAccountTickers.push(token.ticker);
existingAccountByTicker[token.ticker] = tokenAccount;
}
}
}
// Preserve order of tokenAccounts from the existing token accounts
tokenAccounts.sort((a, b) => {
const i = existingAccountTickers.indexOf(a.token.ticker);
const j = existingAccountTickers.indexOf(b.token.ticker);
if (i === j) return 0;
if (i < 0) return 1;
if (j < 0) return -1;
return i - j;
});
return tokenAccounts;
}
export default MultiversXBuildESDTTokenAccounts;