@ledgerhq/coin-hedera
Version:
Ledger Hedera Coin integration
198 lines • 8.4 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.enrichERC20Transfers = exports.getERC20Operations = void 0;
exports.createTransactionId = createTransactionId;
exports.parseTransfers = parseTransfers;
exports.getERC20BalancesForAccount = getERC20BalancesForAccount;
exports.getERC20BalancesForAccountV2 = getERC20BalancesForAccountV2;
exports.parseThirdwebTransactionParams = parseThirdwebTransactionParams;
const sdk_1 = require("@hashgraph/sdk");
const state_1 = require("@ledgerhq/cryptoassets/state");
const live_env_1 = require("@ledgerhq/live-env");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const constants_1 = require("../constants");
const utils_1 = require("../logic/utils");
const api_1 = require("./api");
const hgraph_1 = require("./hgraph");
async function createTransactionId(accountId, config) {
if (!config.useNetworkTimestamp) {
return sdk_1.TransactionId.generate(accountId);
}
try {
const lastBlock = await api_1.apiClient.getLatestBlock();
const validStart = (0, utils_1.toTimestamp)(lastBlock.timestamp.to ?? lastBlock.timestamp.from);
return sdk_1.TransactionId.withValidStart(sdk_1.AccountId.fromString(accountId), validStart);
}
catch {
return sdk_1.TransactionId.generate(accountId);
}
}
function isValidRecipient(accountId, recipients) {
if (accountId.shard.eq(0) && accountId.realm.eq(0)) {
// account is a node, only add to list if we have none
if (accountId.num.lt(100)) {
return recipients.length === 0;
}
// account is a system account that is not a node, do NOT add
if (accountId.num.lt(1000)) {
return false;
}
}
return true;
}
function parseTransfers(mirrorTransfers, address, stakingReward = new bignumber_js_1.default(0)) {
let value = new bignumber_js_1.default(0);
let type = "NONE";
const senders = [];
const recipients = [];
const rewardPayerAddress = (0, live_env_1.getEnv)("HEDERA_STAKING_REWARD_ACCOUNT_ID");
for (const transfer of mirrorTransfers) {
const amount = new bignumber_js_1.default(transfer.amount);
const accountId = sdk_1.AccountId.fromString(transfer.account);
// staking reward is included in transfer, so it can be positive even if user sent less HBARs than the reward is
const amountWithoutReward = transfer.account === address ? amount.minus(stakingReward) : amount;
if (transfer.account === address) {
value = amountWithoutReward.abs();
type = amountWithoutReward.isNegative() ? "OUT" : "IN";
}
if (amountWithoutReward.isNegative()) {
// exclude reward payer from senders list, because rewards are shown as separate operations
const shouldIgnoreAddress = transfer.account === rewardPayerAddress && stakingReward.gt(0);
if (shouldIgnoreAddress) {
continue;
}
senders.push(transfer.account);
}
else if (isValidRecipient(accountId, recipients)) {
recipients.push(transfer.account);
}
}
// NOTE: earlier addresses are the "fee" addresses
senders.reverse();
recipients.reverse();
return {
type,
value,
senders,
recipients,
};
}
// TODO: remove once migration to new API is complete
async function getERC20BalancesForAccount(evmAccountId, supportedTokenIds = constants_1.SUPPORTED_ERC20_TOKENS.map(token => token.id)) {
const availableTokens = [];
for (const erc20TokenId of supportedTokenIds) {
const calToken = await (0, state_1.getCryptoAssetsStore)().findTokenById(erc20TokenId);
if (calToken) {
availableTokens.push(calToken);
}
}
const promises = availableTokens.map(async (erc20token) => {
const balance = await api_1.apiClient.getERC20Balance(evmAccountId, erc20token.contractAddress);
return {
balance,
token: erc20token,
};
});
const balances = await Promise.all(promises);
return balances;
}
async function getERC20BalancesForAccountV2(address) {
const balances = [];
const rawBalances = await hgraph_1.hgraphClient.getERC20Balances({ address });
for (const rawBalance of rawBalances) {
const rawBalanceTokenId = (0, utils_1.toEntityId)({ num: rawBalance.token_id });
const supportedToken = constants_1.SUPPORTED_ERC20_TOKENS.find(token => {
return token.tokenId === rawBalanceTokenId;
});
if (!supportedToken) {
continue;
}
const calToken = await (0, state_1.getCryptoAssetsStore)().findTokenById(supportedToken.id);
if (!calToken) {
continue;
}
balances.push({
token: calToken,
balance: new bignumber_js_1.default(rawBalance.balance),
});
}
return balances;
}
// TODO: remove once migration to new API is complete
const getERC20Operations = async (latestERC20Transactions) => {
const latestERC20Operations = [];
for (const thirdwebTransaction of latestERC20Transactions) {
const tokenId = thirdwebTransaction.address;
const token = await (0, state_1.getCryptoAssetsStore)().findTokenByAddressInCurrency(tokenId, "hedera");
if (!token)
continue;
const hash = thirdwebTransaction.transactionHash;
const contractCallResult = await api_1.apiClient.getContractCallResult(hash);
const mirrorTransaction = await api_1.apiClient.findTransactionByContractCall(contractCallResult.timestamp, contractCallResult.contract_id);
if (!mirrorTransaction)
continue;
latestERC20Operations.push({
thirdwebTransaction,
mirrorTransaction,
contractCallResult,
token,
});
}
return latestERC20Operations;
};
exports.getERC20Operations = getERC20Operations;
// TODO: remove once migration to new API is complete
function parseThirdwebTransactionParams(transaction) {
const { from, to, value } = transaction.decoded.params;
if (typeof from !== "string" || typeof to !== "string" || typeof value !== "string") {
return null;
}
return { from, to, value };
}
/**
* Enriches raw ERC20 transfers from Hgraph with additional data needed for operations:
* - fetches contract call result containing gas metrics and block hash
* - finds the corresponding Mirror Node transaction by consensus timestamp
*
* @param erc20Transfers - Raw ERC20 transfers from Hgraph API
* @returns Array of enriched transfers with complete operation data, filtered to supported tokens only
*/
const enrichERC20Transfers = async (erc20Transfers) => {
const enrichedTransfers = [];
// with hgraph we can get two different transfers with the same transaction hash
const groupedByTxHash = new Map();
for (const transfer of erc20Transfers) {
const group = groupedByTxHash.get(transfer.transaction_hash);
if (!group) {
groupedByTxHash.set(transfer.transaction_hash, [transfer]);
continue;
}
group.push(transfer);
}
for (const [txHash, transfers] of groupedByTxHash.entries()) {
const payerAddress = (0, utils_1.toEntityId)({ num: transfers[0].payer_account_id });
const inaccurateConsensusTimestampNs = new bignumber_js_1.default(transfers[0].consensus_timestamp);
const inaccurateConsensusTimestamp = (0, utils_1.nanosToSeconds)(inaccurateConsensusTimestampNs).toFixed(9);
const [contractCallResult, mirrorTransaction] = await Promise.all([
api_1.apiClient.getContractCallResult(txHash),
api_1.apiClient.findTransactionByContractCallV2({
payerAddress,
timestamp: inaccurateConsensusTimestamp,
}),
]);
if (!mirrorTransaction) {
continue;
}
enrichedTransfers.push({
transfers,
contractCallResult,
mirrorTransaction,
});
}
return enrichedTransfers;
};
exports.enrichERC20Transfers = enrichERC20Transfers;
//# sourceMappingURL=utils.js.map