UNPKG

@ledgerhq/coin-filecoin

Version:
233 lines (207 loc) 6.54 kB
import { Account, Operation } from "@ledgerhq/types-live"; import type { Unit } from "@ledgerhq/types-cryptoassets"; import { log } from "@ledgerhq/logs"; import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies"; import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets"; import { BigNumber } from "bignumber.js"; import { BroadcastTransactionRequest, TransactionResponse, TxStatus, Transaction } from "../types"; import { GetAccountShape, AccountShapeInfo } from "@ledgerhq/coin-framework/bridge/jsHelpers"; import { fetchBalances, fetchBlockHeight, fetchTxs } from "../api/api"; import { encodeAccountId } from "@ledgerhq/coin-framework/account"; import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; import flatMap from "lodash/flatMap"; import { buildTokenAccounts } from "../erc20/tokenAccounts"; type TxsById = { [id: string]: | { Send: TransactionResponse; Fee?: TransactionResponse; } | { InvokeContract: TransactionResponse; Fee?: TransactionResponse; }; }; export const getUnit = () => getCryptoCurrencyById("filecoin").units[0]; export const processTxs = (txs: TransactionResponse[]): TransactionResponse[] => { // Group all tx types related to same tx cid into the same object const txsByTxCid = txs.reduce((txsByTxCidResult: TxsById, currentTx) => { const { hash: txCid, type: txType } = currentTx; const txByType = txsByTxCidResult[txCid] || {}; switch (txType) { case "Send": (txByType as { Send: TransactionResponse }).Send = currentTx; break; case "InvokeContract": (txByType as { InvokeContract: TransactionResponse }).InvokeContract = currentTx; break; case "Fee": (txByType as { Fee?: TransactionResponse }).Fee = currentTx; break; default: log("warn", `tx type [${txType}] on tx cid [${txCid}] was not recognized.`); break; } txsByTxCidResult[txCid] = txByType; return txsByTxCidResult; }, {}); // Once all tx types have been grouped, we want to find const processedTxs: TransactionResponse[] = []; for (const txCid in txsByTxCid) { const item = txsByTxCid[txCid]; const feeTx = item.Fee; let mainTx: TransactionResponse | undefined; if ("Send" in item) { mainTx = item.Send; } else if ("InvokeContract" in item) { mainTx = item.InvokeContract; } else { log( "warn", `unexpected tx type, tx with cid [${txCid}] and payload [${JSON.stringify(item)}]`, ); } if (!mainTx) { if (feeTx) { log("warn", `feeTx [${feeTx.hash}] found without a mainTx linked to it.`); } continue; } if (feeTx) { mainTx.fee = feeTx.amount; } processedTxs.push(mainTx); } return processedTxs; }; export const mapTxToOps = (accountId: string, { address }: AccountShapeInfo) => (tx: TransactionResponse): Operation[] => { const { to, from, hash, timestamp, amount, fee, status } = tx; const ops: Operation[] = []; const date = new Date(timestamp * 1000); const value = parseCurrencyUnit(getUnit(), amount.toString()); const feeToUse = parseCurrencyUnit(getUnit(), (fee || 0).toString()); const isSending = address === from; const isReceiving = address === to; const hasFailed = status !== TxStatus.Ok; if (isSending) { const type = value.eq(0) ? "FEES" : "OUT"; ops.push({ id: encodeOperationId(accountId, hash, type), hash, type, value: value.plus(feeToUse), fee: feeToUse, blockHeight: tx.height, blockHash: null, accountId, senders: [from], recipients: [to], date, extra: {}, hasFailed, }); } if (isReceiving) { const type = value.eq(0) ? "FEES" : "IN"; ops.push({ id: encodeOperationId(accountId, hash, type), hash, type, value, fee: feeToUse, blockHeight: tx.height, blockHash: null, accountId, senders: [from], recipients: [to], date, extra: {}, hasFailed, }); } return ops; }; export const getAddress = ( a: Account, ): { address: string; derivationPath: string; } => ({ address: a.freshAddress, derivationPath: a.freshAddressPath }); export const getTxToBroadcast = ( operation: Operation, signature: string, rawData: Record<string, any>, ): BroadcastTransactionRequest => { const { sender, recipient, gasLimit, gasFeeCap, gasPremium, method, version, nonce, signatureType, params, value, } = rawData; return { message: { version, method, nonce, params: params ?? "", to: recipient, from: sender, gaslimit: gasLimit.toNumber(), gaspremium: gasPremium.toString(), gasfeecap: gasFeeCap.toString(), value, }, signature: { type: signatureType, data: signature, }, }; }; export const getAccountShape: GetAccountShape = async info => { const { address, currency, derivationMode } = info; const accountId = encodeAccountId({ type: "js", version: "2", currencyId: currency.id, xpubOrAddress: address, derivationMode, }); const blockHeight = await fetchBlockHeight(); const balance = await fetchBalances(address); const rawTxs = await fetchTxs(address); const tokenAccounts = await buildTokenAccounts(address, accountId, info.initialAccount); const result: Partial<Account> = { id: accountId, subAccounts: tokenAccounts, balance: new BigNumber(balance.total_balance), spendableBalance: new BigNumber(balance.spendable_balance), operations: flatMap(processTxs(rawTxs), mapTxToOps(accountId, info)).sort( (a, b) => b.date.getTime() - a.date.getTime(), ), blockHeight: blockHeight.current_block_identifier.index, }; return result; }; export const getSubAccount = (account: Account, tx: Transaction) => { const subAccount = tx.subAccountId && account.subAccounts ? account.subAccounts.find(sa => sa.id === tx.subAccountId) : null; return subAccount; }; /** * convert a value in a given unit to a normalized value * For instance, for 1 BTC, valueFromUnit(1, btcUnit) returns 100000000 * @memberof countervalue */ export const valueFromUnit = (valueInUnit: BigNumber, unit: Unit) => valueInUnit.times(new BigNumber(10).pow(unit.magnitude));