UNPKG

@ledgerhq/coin-hedera

Version:
143 lines (131 loc) 5.11 kB
import type { AssetInfo, FeeEstimation } from "@ledgerhq/coin-module-framework/api/types"; import { findSubAccountById } from "@ledgerhq/ledger-wallet-framework/account/helpers"; import type { SignerContext } from "@ledgerhq/ledger-wallet-framework/signer"; import type { AccountBridge } from "@ledgerhq/types-live"; import { Observable } from "rxjs"; import hederaCoinConfig from "../config"; import { DEFAULT_GAS_LIMIT, HEDERA_TRANSACTION_MODES } from "../constants"; import { combine } from "../logic/combine"; import { craftTransaction } from "../logic/craftTransaction"; import { serializeSignature, serializeTransaction, getHederaTransactionBodyBytes, isTokenAssociateTransaction, isStakingTransaction, } from "../logic/utils"; import type { Transaction, HederaSigner, HederaTxData, HederaAccount } from "../types"; import { buildOptimisticOperation } from "./buildOptimisticOperation"; export const buildSignOperation = ( signerContext: SignerContext<HederaSigner>, ): AccountBridge<Transaction, HederaAccount>["signOperation"] => ({ account, transaction, deviceId }) => new Observable(o => { void (async function () { try { o.next({ type: "device-signature-requested", }); let type: Transaction["mode"]; let asset: AssetInfo; let data: HederaTxData | undefined; const accountAddress = account.freshAddress; const accountPublicKey = account.seedIdentifier; const coinConfig = hederaCoinConfig.getCoinConfig(account.currency.id); 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)) { type = HEDERA_TRANSACTION_MODES.TokenAssociate; asset = { type: transaction.properties.token.tokenType, assetReference: transaction.properties.token.contractAddress, }; } else if (isHTSTokenTransaction) { type = HEDERA_TRANSACTION_MODES.Send; asset = { type: subAccount.token.tokenType, assetReference: subAccount.token.contractAddress, assetOwner: accountAddress, }; } else if (isERC20TokenTransaction) { type = HEDERA_TRANSACTION_MODES.Send; asset = { type: subAccount.token.tokenType, assetReference: subAccount.token.contractAddress, assetOwner: accountAddress, }; data = { type: "erc20", gasLimit: BigInt((transaction.gasLimit ?? DEFAULT_GAS_LIMIT).toString()), }; } else if (isStakingTransaction(transaction)) { type = transaction.mode; asset = { type: "native", }; data = { type: "staking", stakingNodeId: transaction.properties?.stakingNodeId, }; } else { type = HEDERA_TRANSACTION_MODES.Send; asset = { type: "native", }; } const customFees: FeeEstimation | undefined = transaction.maxFee ? { value: BigInt(transaction.maxFee.toString()) } : undefined; const signedTx = await signerContext(deviceId, async signer => { const { tx } = await craftTransaction({ txIntent: { intentType: "transaction", type, asset, amount: BigInt(transaction.amount.toString()), sender: accountAddress, recipient: transaction.recipient, memo: { kind: "text", type: "string", value: transaction.memo ?? "", }, ...(data && { data }), }, ...(customFees && { customFees }), config: coinConfig, }); const txBodyBytes = getHederaTransactionBodyBytes(tx); const signatureBytes = await signer.signTransaction(txBodyBytes); return combine( serializeTransaction(tx), serializeSignature(signatureBytes), accountPublicKey, ); }); o.next({ type: "device-signature-granted", }); const operation = await buildOptimisticOperation({ account, transaction, }); o.next({ type: "signed", signedOperation: { operation, signature: signedTx, }, }); o.complete(); } catch (err) { o.error(err); } })(); });