@ledgerhq/coin-aptos
Version:
Ledger Aptos Coin integration
218 lines • 9.82 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.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 = lastTokenOperations.reduce((acc, operation) => {
const { token } = (0, index_2.decodeTokenAccountId)(operation.accountId);
if (!token)
return acc; // TODO: do we need to check blacklistedTokenIds
if (!acc.has(token)) {
acc.set(token, []);
}
acc.get(token)?.push(operation);
return acc;
}, new Map());
// 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] = (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