From d1bee961b632637db3af80fab56524c9b63300c3 Mon Sep 17 00:00:00 2001 From: lvndry Date: Thu, 17 Aug 2023 13:30:18 +0200 Subject: [PATCH] cardano bridge mock --- .../tests/specs/accounts/account.spec.ts | 2 +- .../src/families/cardano/bridge/mock.ts | 170 ++++++++++++++++++ .../cardano/js-estimateMaxSpendable.ts | 8 +- .../cardano/js-getTransactionStatus.ts | 2 +- .../src/generated/bridge/mock.ts | 2 + 5 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 libs/ledger-live-common/src/families/cardano/bridge/mock.ts diff --git a/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts b/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts index 72390e52a4a1..405bc6e0f231 100644 --- a/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts @@ -9,7 +9,7 @@ import { AccountsPage } from "../../models/AccountsPage"; test.use({ userdata: "skip-onboarding" }); -const currencies = ["BTC", "LTC", "ETH", "ATOM", "XTZ", "XRP", "Tron"]; +const currencies = ["BTC", "LTC", "ETH", "ATOM", "XTZ", "XRP", "Tron", "Cardano"]; test.describe.parallel("Accounts @smoke", () => { for (const currency of currencies) { diff --git a/libs/ledger-live-common/src/families/cardano/bridge/mock.ts b/libs/ledger-live-common/src/families/cardano/bridge/mock.ts new file mode 100644 index 000000000000..9d778ce6d6ab --- /dev/null +++ b/libs/ledger-live-common/src/families/cardano/bridge/mock.ts @@ -0,0 +1,170 @@ +import BigNumber from "bignumber.js"; +import { + CardanoAccount, + Token, + Transaction as CardanoTransaction, + TransactionStatus, +} from "../types"; +import { utils as TyphonUtils } from "@stricahq/typhonjs"; +import type { AccountBridge, CurrencyBridge, Account } from "@ledgerhq/types-live"; +import { decodeTokenAssetId, decodeTokenCurrencyId } from "../buildSubAccounts"; +import { + AmountRequired, + FeeNotLoaded, + InvalidAddress, + NotEnoughBalance, + RecipientRequired, +} from "@ledgerhq/errors"; +import { isValidAddress } from "../logic"; +import { getNetworkParameters } from "../networks"; +import { CardanoMinAmountError, CardanoNotEnoughFunds } from "../errors"; +import { buildTransaction } from "../js-buildTransaction"; +import { defaultUpdateTransaction } from "@ledgerhq/coin-framework/bridge/jsHelpers"; +import { + scanAccounts, + signOperation, + broadcast, + sync, + makeAccountBridgeReceive, +} from "../../../bridge/mockHelpers"; + +const receive = makeAccountBridgeReceive(); + +const createTransaction = (): CardanoTransaction => { + return { + family: "cardano", + mode: "send", + amount: new BigNumber(100), + recipient: "", + poolId: "", + }; +}; + +const estimateMaxSpendable = ({ account }) => { + return account.balance; +}; + +const getTransactionStatus = async ( + account: CardanoAccount, + transaction: CardanoTransaction, +): Promise => { + const errors = { fees: new Error(), recipient: new Error(), amount: new Error() }; + const warnings = {}; + const estimatedFees = transaction.fees || new BigNumber(0); + + const tokenAccount = + transaction.subAccountId && account.subAccounts + ? account.subAccounts.find(a => { + return a.id === transaction.subAccountId; + }) + : undefined; + + const mockAccount = tokenAccount || account; + let amount = transaction.useAllAmount + ? await estimateMaxSpendable({ account: mockAccount }) + : transaction.amount; + + let totalSpent = transaction.amount.plus(estimatedFees); + const networkParams = getNetworkParameters(account.currency.id); + + const useAllAmount = !!transaction.useAllAmount; + + let tokensToSend: Array = []; + + if (transaction.subAccountId) { + // Token transaction + if (!tokenAccount || tokenAccount.type !== "TokenAccount") { + throw new Error("TokenAccount not found"); + } + + const { assetId } = decodeTokenCurrencyId(tokenAccount.token.id); + const { policyId, assetName } = decodeTokenAssetId(assetId); + amount = transaction.useAllAmount ? tokenAccount.balance : transaction.amount; + totalSpent = amount; + tokensToSend = [ + { + policyId, + assetName, + amount, + }, + ]; + } else { + // ADA transaction + amount = transaction.useAllAmount ? await estimateMaxSpendable({ account }) : amount; + totalSpent = amount.plus(estimatedFees); + } + + const minTransactionAmount = TyphonUtils.calculateMinUtxoAmount( + tokensToSend, + new BigNumber(account.cardanoResources.protocolParams.lovelacePerUtxoWord), + false, + ); + + if (!transaction.fees) { + errors.fees = new FeeNotLoaded(); + } + + if (!transaction.recipient) { + errors.recipient = new RecipientRequired(); + } else if (!isValidAddress(transaction.recipient, networkParams.networkId)) { + errors.recipient = new InvalidAddress(); + } + + if (!amount.gt(0)) { + errors.amount = useAllAmount ? new CardanoNotEnoughFunds() : new AmountRequired(); + } else if (!transaction.subAccountId && amount.lt(minTransactionAmount)) { + errors.amount = new CardanoMinAmountError("", { + amount: minTransactionAmount.div(1e6).toString(), + }); + } else if (tokenAccount ? totalSpent.gt(tokenAccount.balance) : totalSpent.gt(account.balance)) { + errors.amount = new NotEnoughBalance(); + } else { + try { + await buildTransaction(account, transaction); + } catch (e: any) { + if ( + e.message.toLowerCase() === "not enough ada" || + e.message.toLowerCase() === "not enough tokens" + ) { + errors.amount = new CardanoNotEnoughFunds(); + } else { + throw e; + } + } + } + + return Promise.resolve({ + errors, + warnings, + estimatedFees, + amount, + totalSpent, + }); +}; + +const prepareTransaction = async (account: Account, transaction: CardanoTransaction) => { + return transaction; +}; + +const accountBridge: AccountBridge = { + createTransaction, + updateTransaction: defaultUpdateTransaction, + getTransactionStatus, + estimateMaxSpendable, + prepareTransaction, + sync, + receive, + signOperation, + broadcast, +}; + +const currencyBridge: CurrencyBridge = { + preload: () => Promise.resolve({}), + hydrate: () => {}, + scanAccounts, +}; + +export default { + accountBridge, + currencyBridge, +}; diff --git a/libs/ledger-live-common/src/families/cardano/js-estimateMaxSpendable.ts b/libs/ledger-live-common/src/families/cardano/js-estimateMaxSpendable.ts index e7a9cb9aafe9..f85ac3f68a34 100644 --- a/libs/ledger-live-common/src/families/cardano/js-estimateMaxSpendable.ts +++ b/libs/ledger-live-common/src/families/cardano/js-estimateMaxSpendable.ts @@ -4,7 +4,11 @@ import type { AccountLike, Account } from "@ledgerhq/types-live"; import { getMainAccount } from "../../account"; import type { CardanoAccount, Transaction } from "./types"; import { createTransaction } from "./js-transaction"; -import { address as TyphonAddress, types as TyphonTypes } from "@stricahq/typhonjs"; +import { + address as TyphonAddress, + types as TyphonTypes, + Transaction as TyphonTransaction, +} from "@stricahq/typhonjs"; import { buildTransaction } from "./js-buildTransaction"; /** @@ -36,7 +40,7 @@ const estimateMaxSpendable = async ({ amount: new BigNumber(0), useAllAmount: true, }; - let typhonTransaction; + let typhonTransaction: TyphonTransaction; try { typhonTransaction = await buildTransaction(a as CardanoAccount, t); } catch (error) { diff --git a/libs/ledger-live-common/src/families/cardano/js-getTransactionStatus.ts b/libs/ledger-live-common/src/families/cardano/js-getTransactionStatus.ts index eabdbeed45a3..04c1f2d603c6 100644 --- a/libs/ledger-live-common/src/families/cardano/js-getTransactionStatus.ts +++ b/libs/ledger-live-common/src/families/cardano/js-getTransactionStatus.ts @@ -30,7 +30,7 @@ async function getSendTransactionStatus( const warnings: Record = {}; const useAllAmount = !!t.useAllAmount; - const cardanoResources = a.cardanoResources as CardanoResources; + const cardanoResources = a.cardanoResources; const networkParams = getNetworkParameters(a.currency.id); const estimatedFees = t.fees || new BigNumber(0); diff --git a/libs/ledger-live-common/src/generated/bridge/mock.ts b/libs/ledger-live-common/src/generated/bridge/mock.ts index 47add9d36e0e..3c49f6442f2c 100644 --- a/libs/ledger-live-common/src/generated/bridge/mock.ts +++ b/libs/ledger-live-common/src/generated/bridge/mock.ts @@ -1,5 +1,6 @@ import algorand from "../../families/algorand/bridge/mock"; import bitcoin from "../../families/bitcoin/bridge/mock"; +import cardano from "../../families/cardano/bridge/mock"; import cosmos from "../../families/cosmos/bridge/mock"; import ethereum from "../../families/ethereum/bridge/mock"; import polkadot from "../../families/polkadot/bridge/mock"; @@ -12,6 +13,7 @@ import tron from "../../families/tron/bridge/mock"; export default { algorand, bitcoin, + cardano, cosmos, ethereum, polkadot,