UNPKG

@ledgerhq/coin-mina

Version:
169 lines (152 loc) 4.96 kB
import { encodeAccountId } from "@ledgerhq/coin-framework/account/accountId"; import type { GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers"; import { makeSync, mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers"; import { getAccount, getBlockInfo, getTransactions } from "../api"; import { MinaAccount, MinaOperation } from "../types/common"; import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; import BigNumber from "bignumber.js"; import { log } from "@ledgerhq/logs"; import invariant from "invariant"; import { RosettaTransaction } from "../api/rosetta/types"; export const mapRosettaTxnToOperation = async ( accountId: string, address: string, txn: RosettaTransaction, ): Promise<MinaOperation[]> => { try { const hash = txn.transaction.transaction_identifier.hash; const blockHeight = txn.block_identifier.index; const blockHash = txn.block_identifier.hash; const date = new Date(txn.timestamp ?? (await getBlockInfo(blockHeight)).block.timestamp); const memo = txn.transaction.metadata?.memo || ""; let value = new BigNumber(0); let fee = new BigNumber(0); let accountCreationFee = new BigNumber(0); let fromAccount: string = ""; let toAccount: string = ""; let isSending = false; let failed = false; let redelegateTransaction = false; for (const op of txn.transaction.operations) { failed = failed || op.status === "Failed"; const opValue = failed ? new BigNumber(0) : new BigNumber(op.amount?.value ?? 0); switch (op.type) { case "fee_payment": { fee = fee.plus(opValue.times(-1)); continue; } case "payment_receiver_inc": { toAccount = op.account.address; value = value.plus(opValue); continue; } case "payment_source_dec": { fromAccount = op.account.address; if (fromAccount === address) { isSending = true; } continue; } case "zkapp_fee_payer_dec": { fromAccount = op.account.address; continue; } case "zkapp_balance_update": { toAccount = op.account.address; value = value.plus(opValue); continue; } case "delegate_change": { fromAccount = op.account.address; toAccount = op.metadata?.delegate_change_target || toAccount || "unknown"; redelegateTransaction = true; continue; } case "account_creation_fee_via_payment": { accountCreationFee = opValue.times(-1); continue; } } } invariant(fromAccount, "mina: missing fromAccount"); invariant(toAccount, "mina: missing toAccount"); const op: MinaOperation = { id: "", type: "NONE", hash, value, fee, blockHeight, hasFailed: failed, blockHash, accountId, senders: [fromAccount], recipients: [toAccount], date, extra: { memo, accountCreationFee: accountCreationFee.toString(), }, }; const ops: MinaOperation[] = []; if (isSending) { const type = "OUT"; ops.push({ ...op, value: value.minus(accountCreationFee).plus(fee), type, id: encodeOperationId(accountId, hash, type), }); } else if (redelegateTransaction) { // delegate change const type = "REDELEGATE"; ops.push({ ...op, value: new BigNumber(0), type, id: encodeOperationId(accountId, hash, type), }); } else { const type = "IN"; ops.push({ ...op, value: value.minus(accountCreationFee), type, id: encodeOperationId(accountId, hash, type), }); } return ops; } catch (e) { log("error", "mina: failed to convert txn to operation", { error: e, transaction: txn, }); return []; } }; export const getAccountShape: GetAccountShape<MinaAccount> = async info => { const { address, initialAccount, currency, derivationMode } = info; const oldOperations = initialAccount?.operations || []; const accountId = encodeAccountId({ type: "js", version: "2", currencyId: currency.id, xpubOrAddress: address, derivationMode, }); const { blockHeight, balance, spendableBalance } = await getAccount(address); const rosettaTxns = await getTransactions(address, initialAccount?.operations.length); const newOperations = await Promise.all( rosettaTxns.flatMap(t => mapRosettaTxnToOperation(accountId, address, t)), ); const operations = mergeOps(oldOperations, newOperations.flat()); const shape: Partial<MinaAccount> = { id: accountId, balance, spendableBalance, operationsCount: operations.length, blockHeight, }; return { ...shape, operations }; }; export const sync = makeSync({ getAccountShape });