UNPKG

@ledgerhq/coin-hedera

Version:
245 lines (223 loc) 7.13 kB
import { findSubAccountById } from "@ledgerhq/ledger-wallet-framework/account/helpers"; import { encodeOperationId } from "@ledgerhq/ledger-wallet-framework/operation"; import type { Account, Operation, OperationType, TokenAccount } from "@ledgerhq/types-live"; import BigNumber from "bignumber.js"; import invariant from "invariant"; import { HEDERA_TRANSACTION_MODES, MAP_STAKING_MODE_TO_OPERATION_TYPE } from "../constants"; import { safeParseAccountId, isTokenAssociateTransaction, isStakingTransaction, } from "../logic/utils"; import type { HederaAccount, HederaOperationExtra, Transaction } from "../types"; const buildOptimisticTokenAssociateOperation = async ({ account, transaction, }: { account: Account; transaction: Transaction; }): Promise<Operation> => { invariant(isTokenAssociateTransaction(transaction), "invalid transaction properties"); const fee = transaction.maxFee ?? new BigNumber(0); const type: OperationType = "ASSOCIATE_TOKEN"; const operation: Operation = { id: encodeOperationId(account.id, "", type), hash: "", type, value: fee, fee, blockHash: null, blockHeight: null, senders: [account.freshAddress.toString()], recipients: [transaction.recipient], accountId: account.id, date: new Date(), extra: { associatedTokenId: transaction.properties.token.contractAddress, } satisfies HederaOperationExtra, }; return operation; }; const buildOptimisticCoinOperation = async ({ account, transaction, transactionType, }: { account: Account; transaction: Transaction; transactionType?: OperationType; }): Promise<Operation> => { const fee = transactionType === "FEES" ? transaction.amount : (transaction.maxFee ?? new BigNumber(0)); const value = transaction.amount; const type: OperationType = transactionType ?? "OUT"; const [_, recipientAddress] = await safeParseAccountId(transaction.recipient); const recipientWithoutChecksum = recipientAddress?.accountId ?? transaction.recipient; const memo = transaction.memo; const operation: Operation = { id: encodeOperationId(account.id, "", type), hash: "", type, value, fee, blockHash: null, blockHeight: null, senders: [account.freshAddress.toString()], recipients: [recipientWithoutChecksum], accountId: account.id, date: new Date(), extra: { ...(memo && { memo }), } satisfies HederaOperationExtra, }; return operation; }; const buildOptimisticHTSTokenOperation = async ({ account, tokenAccount, transaction, }: { account: Account; tokenAccount: TokenAccount; transaction: Transaction; }): Promise<Operation> => { const fee = transaction.maxFee ?? new BigNumber(0); const value = transaction.amount; const type: OperationType = "OUT"; const [_, recipientAddress] = await safeParseAccountId(transaction.recipient); const recipientWithoutChecksum = recipientAddress?.accountId ?? transaction.recipient; const memo = transaction.memo; const coinOperation = await buildOptimisticCoinOperation({ account, transaction: { ...transaction, amount: fee, }, transactionType: "FEES", }); const operation: Operation = { ...coinOperation, subOperations: [ { id: encodeOperationId(tokenAccount.id, "", type), hash: "", type, value, fee, blockHash: null, blockHeight: null, senders: [account.freshAddress.toString()], recipients: [recipientWithoutChecksum], accountId: tokenAccount.id, date: new Date(), extra: { ...(memo && { memo }), } satisfies HederaOperationExtra, }, ], }; return operation; }; const buildOptimisticERC20TokenOperation = async ({ account, tokenAccount, transaction, }: { account: Account; tokenAccount: TokenAccount; transaction: Transaction; }): Promise<Operation> => { const fee = transaction.maxFee ?? new BigNumber(0); const value = transaction.amount; const type: OperationType = "OUT"; const memo = transaction.memo; const coinOperation = await buildOptimisticCoinOperation({ account, transaction: { ...transaction, amount: fee, }, transactionType: "FEES", }); const operation: Operation = { ...coinOperation, subOperations: [ { id: encodeOperationId(tokenAccount.id, "", type), hash: "", type, value, fee, blockHash: null, blockHeight: null, senders: [account.freshAddress.toString()], recipients: [transaction.recipient], accountId: tokenAccount.id, date: new Date(), extra: { ...(memo && { memo }), } satisfies HederaOperationExtra, }, ], }; return operation; }; const buildOptimisticUpdateAccountOperation = async ({ account, transaction, }: { account: HederaAccount; transaction: Transaction; }): Promise<Operation> => { invariant(isStakingTransaction(transaction), "invalid transaction properties"); const fee = transaction.maxFee ?? new BigNumber(0); const value = transaction.amount; const type: OperationType = MAP_STAKING_MODE_TO_OPERATION_TYPE[transaction.mode]; const operation: Operation = { id: encodeOperationId(account.id, "", type), hash: "", type, value, fee, blockHash: null, blockHeight: null, senders: [account.freshAddress.toString()], recipients: [transaction.recipient], accountId: account.id, date: new Date(), extra: { memo: transaction.memo ?? null, targetStakingNodeId: transaction.properties?.stakingNodeId ?? null, previousStakingNodeId: account.hederaResources?.delegation?.nodeId ?? null, } satisfies Partial<HederaOperationExtra>, }; return operation; }; export const buildOptimisticOperation = async ({ account, transaction, }: { account: Account; transaction: Transaction; }): Promise<Operation> => { const subAccount = findSubAccountById(account, transaction.subAccountId || ""); const isHTSTokenTransaction = transaction.mode === HEDERA_TRANSACTION_MODES.Send && subAccount?.token.tokenType === "hts"; const isERC20TokenTransaction = transaction.mode === HEDERA_TRANSACTION_MODES.Send && subAccount?.token.tokenType === "erc20"; if (isTokenAssociateTransaction(transaction)) { return buildOptimisticTokenAssociateOperation({ account, transaction }); } else if (isHTSTokenTransaction) { return buildOptimisticHTSTokenOperation({ account, tokenAccount: subAccount, transaction }); } else if (isERC20TokenTransaction) { return buildOptimisticERC20TokenOperation({ account, tokenAccount: subAccount, transaction }); } else if ( transaction.mode === HEDERA_TRANSACTION_MODES.Redelegate || transaction.mode === HEDERA_TRANSACTION_MODES.Undelegate || transaction.mode === HEDERA_TRANSACTION_MODES.Delegate ) { return buildOptimisticUpdateAccountOperation({ account, transaction }); } else { return buildOptimisticCoinOperation({ account, transaction }); } };