diff --git a/.changeset/thick-foxes-repair.md b/.changeset/thick-foxes-repair.md
new file mode 100644
index 000000000000..7b06f99fa1d9
--- /dev/null
+++ b/.changeset/thick-foxes-repair.md
@@ -0,0 +1,8 @@
+---
+"@ledgerhq/coin-cardano": patch
+"ledger-live-desktop": patch
+"live-mobile": patch
+"@ledgerhq/live-common": patch
+---
+
+Cardano fees warning + fix high fees issue
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/DelegationFlowModal/steps/StepDelegation.tsx b/apps/ledger-live-desktop/src/renderer/families/cardano/DelegationFlowModal/steps/StepDelegation.tsx
index 9729c15951e8..7fa3dd1cf29f 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cardano/DelegationFlowModal/steps/StepDelegation.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/cardano/DelegationFlowModal/steps/StepDelegation.tsx
@@ -77,7 +77,7 @@ export function StepDelegationFooter({
}: StepProps) {
invariant(account, "account required");
const { errors } = status;
- const canNext = !bridgePending && !errors.amount && transaction;
+ const canNext = !bridgePending && Object.keys(errors).length === 0 && transaction;
return (
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/DelegationFlowModal/steps/StepSummary.tsx b/apps/ledger-live-desktop/src/renderer/families/cardano/DelegationFlowModal/steps/StepSummary.tsx
index 5bbfa729d979..edb9f62b4e06 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cardano/DelegationFlowModal/steps/StepSummary.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/cardano/DelegationFlowModal/steps/StepSummary.tsx
@@ -12,6 +12,8 @@ import { StepProps } from "../types";
import CardanoLedgerPoolIcon from "../LedgerPoolIcon";
import BigNumber from "bignumber.js";
import { useMaybeAccountUnit } from "~/renderer/hooks/useAccountUnit";
+import IconExclamationCircle from "~/renderer/icons/ExclamationCircle";
+import TranslatedError from "~/renderer/components/TranslatedError";
const FromToWrapper = styled.div``;
const Separator = styled.div`
@@ -27,8 +29,8 @@ function StepSummary(props: StepProps) {
const feesUnit = useMaybeAccountUnit(account);
if (!account || !transaction) return null;
- const { estimatedFees } = status;
-
+ const { estimatedFees, warnings } = status;
+ const { feeTooHigh } = warnings;
const feesCurrency = getAccountCurrency(account);
const showDeposit = !account.cardanoResources?.delegation?.status;
const stakeKeyDeposit = account.cardanoResources?.protocolParams.stakeKeyDeposit;
@@ -105,7 +107,7 @@ function StepSummary(props: StepProps) {
+ {feeTooHigh ? (
+
+
+
+
+
+
+ ) : null}
);
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields.tsx b/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoField.tsx
similarity index 91%
rename from apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields.tsx
rename to apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoField.tsx
index ca36eb021a8c..0c379939cfae 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoField.tsx
@@ -8,7 +8,8 @@ import {
Transaction,
TransactionStatus,
} from "@ledgerhq/live-common/families/cardano/types";
-const Root = (props: {
+
+const MemoField = (props: {
account: CardanoAccount;
transaction: Transaction;
status: TransactionStatus;
@@ -32,7 +33,4 @@ const Root = (props: {
);
};
-export default {
- component: Root,
- fields: ["memo"],
-};
+export default MemoField;
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/MemoValueField.tsx b/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoValueField.tsx
similarity index 99%
rename from apps/ledger-live-desktop/src/renderer/families/cardano/MemoValueField.tsx
rename to apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoValueField.tsx
index 64880c147205..69072f358fa6 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cardano/MemoValueField.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/MemoValueField.tsx
@@ -9,6 +9,7 @@ import {
TransactionStatus,
} from "@ledgerhq/live-common/families/cardano/types";
import { track } from "~/renderer/analytics/segment";
+
const MemoValueField = ({
onChange,
account,
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/index.tsx b/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/index.tsx
new file mode 100644
index 000000000000..859bea084385
--- /dev/null
+++ b/apps/ledger-live-desktop/src/renderer/families/cardano/SendAmountFields/index.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import {
+ CardanoAccount,
+ Transaction,
+ TransactionStatus,
+} from "@ledgerhq/live-common/families/cardano/types";
+
+import MemoField from "./MemoField";
+
+const Root = (props: {
+ account: CardanoAccount;
+ transaction: Transaction;
+ status: TransactionStatus;
+ onChange: (t: Transaction) => void;
+ trackProperties?: Record;
+}) => {
+ return (
+ <>
+
+ >
+ );
+};
+
+export default {
+ component: Root,
+ fields: ["memo"],
+};
diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/UndelegateFlowModal/steps/StepSummary.tsx b/apps/ledger-live-desktop/src/renderer/families/cardano/UndelegateFlowModal/steps/StepSummary.tsx
index 3f491b4686cf..c0974c7a6b3b 100644
--- a/apps/ledger-live-desktop/src/renderer/families/cardano/UndelegateFlowModal/steps/StepSummary.tsx
+++ b/apps/ledger-live-desktop/src/renderer/families/cardano/UndelegateFlowModal/steps/StepSummary.tsx
@@ -13,6 +13,7 @@ import { StepProps } from "../types";
import BigNumber from "bignumber.js";
import Alert from "~/renderer/components/Alert";
import { useMaybeAccountUnit } from "~/renderer/hooks/useAccountUnit";
+import IconExclamationCircle from "~/renderer/icons/ExclamationCircle";
const FromToWrapper = styled.div``;
const Separator = styled.div`
@@ -24,7 +25,8 @@ const Separator = styled.div`
function StepSummary(props: StepProps) {
const { account, transaction, status, error } = props;
- const { estimatedFees, errors } = status;
+ const { estimatedFees, errors, warnings } = status;
+ const { feeTooHigh } = warnings;
const displayError = errors.amount?.message ? errors.amount : "";
const accountUnit = useMaybeAccountUnit(account);
@@ -72,7 +74,7 @@ function StepSummary(props: StepProps) {
+ {feeTooHigh ? (
+
+
+
+
+
+
+ ) : null}
{displayError ? (
@@ -114,22 +130,20 @@ export function StepSummaryFooter({
onClose,
}: StepProps) {
const { errors } = status;
- const canNext = !errors.amount && !bridgePending && !errors.validators && transaction;
+ const canNext = Object.keys(errors).length === 0 && !bridgePending && transaction;
return (
- <>
-
-
-
-
- >
+
+
+
+
);
}
diff --git a/apps/ledger-live-desktop/static/i18n/en/app.json b/apps/ledger-live-desktop/static/i18n/en/app.json
index f3239d24d7f8..76c1ee9a3bb3 100644
--- a/apps/ledger-live-desktop/static/i18n/en/app.json
+++ b/apps/ledger-live-desktop/static/i18n/en/app.json
@@ -6084,6 +6084,18 @@
"CardanoStakeKeyDepositError": {
"title": "Ensure that you have {{depositAmount}} ADA available for the deposit"
},
+ "CardanoFeeHigh": {
+ "title": "Abnormally high network fees.",
+ "description": "Network fees unusually high please double check your transaction."
+ },
+ "CardanoFeeTooHigh": {
+ "title": "Abnormally high network fees.",
+ "description": "ADA transactions are temporarily unavailable. Please resync your account and try again or contact Support if you need assistance."
+ },
+ "CardanoInvalidProtoParams": {
+ "title": "Cardano invalid protocol parameters",
+ "description": "ADA transactions are temporarily unavailable. Please resync your account and try again or contact Support if you need assistance."
+ },
"StacksMemoTooLong": {
"title": "Memo length is too long"
},
diff --git a/apps/ledger-live-mobile/src/families/cardano/DelegationFlow/02-Summary.tsx b/apps/ledger-live-mobile/src/families/cardano/DelegationFlow/02-Summary.tsx
index 6211f832a940..358abca3cfba 100644
--- a/apps/ledger-live-mobile/src/families/cardano/DelegationFlow/02-Summary.tsx
+++ b/apps/ledger-live-mobile/src/families/cardano/DelegationFlow/02-Summary.tsx
@@ -40,6 +40,9 @@ import { StackNavigatorProps } from "~/components/RootNavigator/types/helpers";
import { CardanoDelegationFlowParamList } from "./types";
import TranslatedError from "~/components/TranslatedError";
import { useAccountUnit } from "~/hooks/useAccountUnit";
+import GenericErrorBottomModal from "~/components/GenericErrorBottomModal";
+import RetryButton from "~/components/RetryButton";
+import CancelButton from "~/components/CancelButton";
type Props = StackNavigatorProps<
CardanoDelegationFlowParamList,
@@ -100,6 +103,21 @@ export default function DelegationSummary({ navigation, route }: Props) {
return { account, transaction: tx };
});
+ const [bridgeErr, setBridgeErr] = useState(bridgeError);
+ useEffect(() => setBridgeErr(bridgeError), [bridgeError]);
+
+ const onBridgeErrorCancel = useCallback(() => {
+ setBridgeErr(null);
+ const parent = navigation.getParent();
+ if (parent) parent.goBack();
+ }, [navigation]);
+ const onBridgeErrorRetry = useCallback(() => {
+ setBridgeErr(null);
+ if (!transaction) return;
+ const bridge = getAccountBridge(account, parentAccount);
+ setTransaction(bridge.updateTransaction(transaction, {}));
+ }, [setTransaction, account, parentAccount, transaction]);
+
invariant(transaction, "transaction must be defined");
invariant(transaction.family === "cardano", "transaction cardano");
@@ -141,6 +159,9 @@ export default function DelegationSummary({ navigation, route }: Props) {
const displayError = useMemo(() => {
return status.errors.amount ? status.errors.amount : "";
}, [status]);
+ const displayWarning = useMemo(() => {
+ return status.warnings.feeTooHigh ? status.warnings.feeTooHigh : "";
+ }, [status]);
return (
@@ -181,6 +202,15 @@ export default function DelegationSummary({ navigation, route }: Props) {
) : (
<>>
)}
+ {displayWarning ? (
+
+
+
+
+
+ ) : (
+ <>>
+ )}
);
}
@@ -275,6 +318,13 @@ const styles = StyleSheet.create({
valueText: {
fontSize: 14,
},
+ button: {
+ flex: 1,
+ marginHorizontal: 8,
+ },
+ buttonRight: {
+ marginLeft: 8,
+ },
});
function SummaryWords({
diff --git a/apps/ledger-live-mobile/src/families/cardano/UndelegationFlow/01-Summary.tsx b/apps/ledger-live-mobile/src/families/cardano/UndelegationFlow/01-Summary.tsx
index b59c6bd49c8e..a30785b976e8 100644
--- a/apps/ledger-live-mobile/src/families/cardano/UndelegationFlow/01-Summary.tsx
+++ b/apps/ledger-live-mobile/src/families/cardano/UndelegationFlow/01-Summary.tsx
@@ -1,7 +1,7 @@
import { useTheme } from "@react-navigation/native";
import { BigNumber } from "bignumber.js";
import invariant from "invariant";
-import React, { ReactNode, useCallback, useMemo } from "react";
+import React, { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { SafeAreaView, StyleSheet, View } from "react-native";
import { useSelector } from "react-redux";
@@ -12,6 +12,7 @@ import { formatCurrencyUnit, getCurrencyColor } from "@ledgerhq/live-common/curr
import type {
CardanoAccount,
CardanoDelegation,
+ TransactionStatus,
} from "@ledgerhq/live-common/families/cardano/types";
import { Text, Box } from "@ledgerhq/native-ui";
import { AccountLike } from "@ledgerhq/types-live";
@@ -28,6 +29,9 @@ import { StackNavigatorProps } from "~/components/RootNavigator/types/helpers";
import { CardanoUndelegationFlowParamList } from "./types";
import TranslatedError from "~/components/TranslatedError";
import { useAccountUnit } from "~/hooks/useAccountUnit";
+import GenericErrorBottomModal from "~/components/GenericErrorBottomModal";
+import RetryButton from "~/components/RetryButton";
+import CancelButton from "~/components/CancelButton";
type Props = StackNavigatorProps<
CardanoUndelegationFlowParamList,
@@ -44,26 +48,31 @@ export default function UndelegationSummary({ navigation, route }: Props) {
const mainAccount = getMainAccount(account, parentAccount);
const bridge = getAccountBridge(account, undefined);
- const { transaction, status, bridgePending, bridgeError } = useBridgeTransaction(() => {
- const tx = route.params.transaction;
+ const { transaction, status, bridgePending, bridgeError, setTransaction } = useBridgeTransaction(
+ () => {
+ const tx = route.params.transaction;
- if (!tx) {
- const t = bridge.createTransaction(mainAccount);
+ if (!tx) {
+ const t = bridge.createTransaction(mainAccount);
- return {
- account,
- transaction: bridge.updateTransaction(t, {
- mode: "undelegate",
- }),
- };
- }
+ return {
+ account,
+ transaction: bridge.updateTransaction(t, {
+ mode: "undelegate",
+ }),
+ };
+ }
- return { account, transaction: tx };
- });
+ return { account, transaction: tx };
+ },
+ );
const displayError = useMemo(() => {
return status.errors.amount ? status.errors.amount : "";
}, [status]);
+ const displayWarning = useMemo(() => {
+ return status.warnings.feeTooHigh ? status.warnings.feeTooHigh : "";
+ }, [status]);
const currency = getAccountCurrency(account);
const color = getCurrencyColor(currency);
@@ -77,6 +86,21 @@ export default function UndelegationSummary({ navigation, route }: Props) {
});
}, [status, account, parentAccount, navigation, transaction]);
+ const [bridgeErr, setBridgeErr] = useState(bridgeError);
+ useEffect(() => setBridgeErr(bridgeError), [bridgeError]);
+
+ const onBridgeErrorCancel = useCallback(() => {
+ setBridgeErr(null);
+ const parent = navigation.getParent();
+ if (parent) parent.goBack();
+ }, [navigation]);
+ const onBridgeErrorRetry = useCallback(() => {
+ setBridgeErr(null);
+ if (!transaction) return;
+ const bridge = getAccountBridge(account, parentAccount);
+ setTransaction(bridge.updateTransaction(transaction, {}));
+ }, [setTransaction, account, parentAccount, transaction]);
+
return (
@@ -90,7 +114,7 @@ export default function UndelegationSummary({ navigation, route }: Props) {
-
+
@@ -103,17 +127,38 @@ export default function UndelegationSummary({ navigation, route }: Props) {
) : (
<>>
)}
-
+ {displayWarning ? (
+
+
+
+
+
+ ) : (
+ <>>
+ )}
}
containerStyle={styles.continueButton}
onPress={onContinue}
- disabled={bridgePending || !!bridgeError || !!displayError}
+ disabled={bridgePending || !!bridgeError || Object.keys(status.errors).length > 0}
pending={bridgePending}
/>
+
+
+
+ >
+ }
+ />
);
}
@@ -187,9 +232,23 @@ const styles = StyleSheet.create({
valueText: {
fontSize: 14,
},
+ button: {
+ flex: 1,
+ marginHorizontal: 8,
+ },
+ buttonRight: {
+ marginLeft: 8,
+ },
});
-function SummaryWords({ account }: { account: AccountLike; currentDelegation: CardanoDelegation }) {
+function SummaryWords({
+ account,
+ status,
+}: {
+ account: AccountLike;
+ currentDelegation: CardanoDelegation;
+ status: TransactionStatus;
+}) {
const unit = useAccountUnit(account);
const { t } = useTranslation();
const { colors } = useTheme();
@@ -220,7 +279,7 @@ function SummaryWords({ account }: { account: AccountLike; currentDelegation: Ca
label={t("cardano.delegation.networkFees")}
Component={
- {formatCurrencyUnit(unit, new BigNumber(170000), formatConfig)}
+ {formatCurrencyUnit(unit, new BigNumber(status.estimatedFees), formatConfig)}
}
/>
diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json
index f4a9055f15ba..358ca7ef05b9 100644
--- a/apps/ledger-live-mobile/src/locales/en/common.json
+++ b/apps/ledger-live-mobile/src/locales/en/common.json
@@ -789,6 +789,18 @@
"CardanoStakeKeyDepositError": {
"title": "Ensure that you have {{depositAmount}} ADA available for the deposit"
},
+ "CardanoFeeHigh": {
+ "title": "Abnormally high network fees.",
+ "description": "Network fees unusually high please double check your transaction."
+ },
+ "CardanoFeeTooHigh": {
+ "title": "Abnormally high network fees.",
+ "description": "ADA transactions are temporarily unavailable. Please resync your account and try again or contact Support if you need assistance."
+ },
+ "CardanoInvalidProtoParams": {
+ "title": "Cardano invalid protocol parameters",
+ "description": "ADA transactions are temporarily unavailable. Please resync your account and try again or contact Support if you need assistance."
+ },
"StacksMemoTooLong": {
"title": "Memo length is too long"
},
diff --git a/libs/coin-modules/coin-cardano/src/bridge/index.ts b/libs/coin-modules/coin-cardano/src/bridge/index.ts
index 3e9fcfa90b2e..dd078a9487ee 100644
--- a/libs/coin-modules/coin-cardano/src/bridge/index.ts
+++ b/libs/coin-modules/coin-cardano/src/bridge/index.ts
@@ -19,6 +19,8 @@ import { postSyncPatch } from "../postSyncPatch";
import { CardanoSigner } from "../signer";
import { broadcast } from "../broadcast";
import resolver from "../hw-getAddress";
+import { CoinConfig } from "@ledgerhq/coin-framework/lib/config";
+import cardanoCoinConfig, { CardanoCoinConfig } from "../config";
export function buildCurrencyBridge(signerContext: SignerContext): CurrencyBridge {
const getAddress = resolver(signerContext);
@@ -61,7 +63,11 @@ export function buildAccountBridge(
};
}
-export function createBridges(signerContext: SignerContext) {
+export function createBridges(
+ signerContext: SignerContext,
+ coinConfig: CoinConfig,
+) {
+ cardanoCoinConfig.setCoinConfig(coinConfig);
return {
currencyBridge: buildCurrencyBridge(signerContext),
accountBridge: buildAccountBridge(signerContext),
diff --git a/libs/coin-modules/coin-cardano/src/buildTransaction.ts b/libs/coin-modules/coin-cardano/src/buildTransaction.ts
index 7e246b280a04..185a03c70946 100644
--- a/libs/coin-modules/coin-cardano/src/buildTransaction.ts
+++ b/libs/coin-modules/coin-cardano/src/buildTransaction.ts
@@ -6,7 +6,14 @@ import {
import BigNumber from "bignumber.js";
import type { TokenAccount } from "@ledgerhq/types-live";
import { RewardAddress } from "@stricahq/typhonjs/dist/address";
-import { getAccountStakeCredential, getBaseAddress, getTTL, mergeTokens, isTestnet } from "./logic";
+import {
+ getAccountStakeCredential,
+ getBaseAddress,
+ getTTL,
+ mergeTokens,
+ isTestnet,
+ isProtocolParamsValid,
+} from "./logic";
import { decodeTokenAssetId, decodeTokenCurrencyId, getTokenAssetId } from "./buildSubAccounts";
import { getNetworkParameters } from "./networks";
import {
@@ -18,6 +25,7 @@ import {
Transaction,
} from "./types";
import { CARDANO_MAX_SUPPLY } from "./constants";
+import { CardanoInvalidProtoParams } from "./errors";
function getTyphonInputFromUtxo(utxo: CardanoOutput): TyphonTypes.Input {
const address = TyphonUtils.getAddressFromHex(
@@ -365,7 +373,11 @@ export const buildTransaction = async (
transaction: Transaction,
): Promise => {
const cardanoResources = account.cardanoResources as CardanoResources;
- const protocolParams = cardanoResources.protocolParams;
+ const { protocolParams } = transaction;
+
+ if (!protocolParams || !isProtocolParamsValid(protocolParams)) {
+ throw new CardanoInvalidProtoParams();
+ }
const typhonTx = new TyphonTransaction({
protocolParams: {
diff --git a/libs/coin-modules/coin-cardano/src/config.ts b/libs/coin-modules/coin-cardano/src/config.ts
new file mode 100644
index 000000000000..670ec3c20e9d
--- /dev/null
+++ b/libs/coin-modules/coin-cardano/src/config.ts
@@ -0,0 +1,12 @@
+import buildCoinConfig, { type CurrencyConfig } from "@ledgerhq/coin-framework/config";
+
+export type CardanoConfig = {
+ maxFeesWarning: number;
+ maxFeesError: number;
+};
+
+export type CardanoCoinConfig = CurrencyConfig & CardanoConfig;
+
+const coinConfig = buildCoinConfig();
+
+export default coinConfig;
diff --git a/libs/coin-modules/coin-cardano/src/errors.ts b/libs/coin-modules/coin-cardano/src/errors.ts
index a2f5b61eddf8..26d191ace2b4 100644
--- a/libs/coin-modules/coin-cardano/src/errors.ts
+++ b/libs/coin-modules/coin-cardano/src/errors.ts
@@ -16,3 +16,11 @@ export const CardanoStakeKeyDepositError = createCustomErrorClass("CardanoStakeK
export const CardanoNotEnoughFunds = createCustomErrorClass("CardanoNotEnoughFunds");
export const CardanoInvalidPoolId = createCustomErrorClass("CardanoInvalidPoolId");
+
+/**
+ * Cardano warning/error for high fees
+ */
+export const CardanoFeeHigh = createCustomErrorClass("CardanoFeeHigh");
+export const CardanoFeeTooHigh = createCustomErrorClass("CardanoFeeTooHigh");
+
+export const CardanoInvalidProtoParams = createCustomErrorClass("CardanoInvalidProtoParams");
diff --git a/libs/coin-modules/coin-cardano/src/estimateMaxSpendable.ts b/libs/coin-modules/coin-cardano/src/estimateMaxSpendable.ts
index f73eb6e1d9af..3a16fa1b76ca 100644
--- a/libs/coin-modules/coin-cardano/src/estimateMaxSpendable.ts
+++ b/libs/coin-modules/coin-cardano/src/estimateMaxSpendable.ts
@@ -10,6 +10,7 @@ import { getMainAccount } from "@ledgerhq/coin-framework/account/index";
import type { CardanoAccount, Transaction } from "./types";
import { createTransaction } from "./createTransaction";
import { buildTransaction } from "./buildTransaction";
+import { prepareTransaction } from "./prepareTransaction";
/**
* Returns the maximum possible amount for transaction
@@ -32,7 +33,7 @@ export const estimateMaxSpendable: AccountBridge<
const dummyRecipient =
"addr1qyqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv2t5am";
- const t: Transaction = {
+ let t: Transaction = {
...createTransaction(account),
...transaction,
recipient: dummyRecipient,
@@ -42,6 +43,7 @@ export const estimateMaxSpendable: AccountBridge<
};
let typhonTransaction: TyphonTransaction;
try {
+ t = await prepareTransaction(account, t);
typhonTransaction = await buildTransaction(mainAccount, t);
} catch (error) {
log("cardano-error", "Failed to estimate max spendable: " + String(error));
diff --git a/libs/coin-modules/coin-cardano/src/getTransactionStatus.ts b/libs/coin-modules/coin-cardano/src/getTransactionStatus.ts
index 625c86bace4e..c02f01a47a0a 100644
--- a/libs/coin-modules/coin-cardano/src/getTransactionStatus.ts
+++ b/libs/coin-modules/coin-cardano/src/getTransactionStatus.ts
@@ -19,6 +19,8 @@ import {
CardanoStakeKeyDepositError,
CardanoMinAmountError,
CardanoNotEnoughFunds,
+ CardanoFeeTooHigh,
+ CardanoFeeHigh,
} from "./errors";
import type {
CardanoAccount,
@@ -28,6 +30,7 @@ import type {
TransactionStatus,
} from "./types";
import { CARDANO_MAX_SUPPLY } from "./constants";
+import coinConfig from "./config";
export const getTransactionStatus: AccountBridge<
Transaction,
@@ -51,15 +54,28 @@ export const getTransactionStatus: AccountBridge<
});
}
+ let txStatus: TransactionStatus;
+
if (transaction.mode === "send") {
- return getSendTransactionStatus(account, transaction);
+ txStatus = await getSendTransactionStatus(account, transaction);
} else if (transaction.mode === "delegate") {
- return getDelegateTransactionStatus(account, transaction);
+ txStatus = await getDelegateTransactionStatus(account, transaction);
} else if (transaction.mode === "undelegate") {
- return getUndelegateTransactionStatus(account, transaction);
+ txStatus = await getUndelegateTransactionStatus(account, transaction);
} else {
throw new Error("Invalid transaction mode");
}
+
+ const MAX_FEES_WARN = coinConfig.getCoinConfig().maxFeesWarning;
+ const MAX_FEES_THROW = coinConfig.getCoinConfig().maxFeesError;
+
+ if (txStatus.estimatedFees.gt(MAX_FEES_THROW)) {
+ throw new CardanoFeeTooHigh();
+ } else if (txStatus.estimatedFees.gt(MAX_FEES_WARN)) {
+ txStatus.warnings.feeTooHigh = new CardanoFeeHigh();
+ }
+
+ return txStatus;
};
async function getSendTransactionStatus(
diff --git a/libs/coin-modules/coin-cardano/src/logic.ts b/libs/coin-modules/coin-cardano/src/logic.ts
index 2e886e5d4a30..ec1c4a6b89d2 100644
--- a/libs/coin-modules/coin-cardano/src/logic.ts
+++ b/libs/coin-modules/coin-cardano/src/logic.ts
@@ -20,6 +20,7 @@ import {
StakeChain,
StakeCredential,
Token,
+ ProtocolParams,
} from "./types";
import { Bip32PublicKey } from "@stricahq/bip32ed25519";
import BigNumber from "bignumber.js";
@@ -328,3 +329,26 @@ export function getBech32PoolId(poolId: string, networkName: string): string {
const encoded = bech32.encode(networkParams.poolIdPrefix, words, 1000);
return encoded;
}
+
+export function isValidNumString(value: unknown): boolean {
+ if (typeof value !== "string" && typeof value !== "number") return false;
+ if (isNaN(Number(value))) return false;
+ if (new BigNumber(value).isNaN()) return false;
+ return true;
+}
+
+export function isProtocolParamsValid(pp: ProtocolParams): boolean {
+ const paramsRequiredCheck = [
+ pp.minFeeA,
+ pp.minFeeB,
+ pp.stakeKeyDeposit,
+ pp.lovelacePerUtxoWord,
+ pp.collateralPercent,
+ pp.priceSteps,
+ pp.priceMem,
+ pp.maxTxSize,
+ pp.maxValueSize,
+ pp.utxoCostPerByte,
+ ];
+ return paramsRequiredCheck.every(isValidNumString);
+}
diff --git a/libs/coin-modules/coin-cardano/src/logic.unit.test.ts b/libs/coin-modules/coin-cardano/src/logic.unit.test.ts
index 16282417636b..00b9a33d10f5 100644
--- a/libs/coin-modules/coin-cardano/src/logic.unit.test.ts
+++ b/libs/coin-modules/coin-cardano/src/logic.unit.test.ts
@@ -1,5 +1,5 @@
import BigNumber from "bignumber.js";
-import { canStake, isAlreadyStaking } from "./logic";
+import { canStake, isAlreadyStaking, isValidNumString } from "./logic";
import { CardanoAccount } from "./types";
describe("canStake", () => {
@@ -37,3 +37,20 @@ describe("isAlreadyStaking", () => {
expect(isAlreadyStaking(noResourcesAcc)).toEqual(true);
});
});
+
+describe("isValidNumString", () => {
+ it("should return false for invalid number", () => {
+ expect(isValidNumString("")).toEqual(false);
+ expect(isValidNumString(undefined)).toEqual(false);
+ expect(isValidNumString(null)).toEqual(false);
+ expect(isValidNumString({})).toEqual(false);
+ expect(isValidNumString([])).toEqual(false);
+ });
+
+ it("should return true for valid number", () => {
+ expect(isValidNumString(123)).toEqual(true);
+ expect(isValidNumString(123.321)).toEqual(true);
+ expect(isValidNumString("123")).toEqual(true);
+ expect(isValidNumString("123.321")).toEqual(true);
+ });
+});
diff --git a/libs/coin-modules/coin-cardano/src/prepareTransaction.ts b/libs/coin-modules/coin-cardano/src/prepareTransaction.ts
index 7762ad51e167..771a736697ba 100644
--- a/libs/coin-modules/coin-cardano/src/prepareTransaction.ts
+++ b/libs/coin-modules/coin-cardano/src/prepareTransaction.ts
@@ -5,6 +5,7 @@ import { defaultUpdateTransaction } from "@ledgerhq/coin-framework/bridge/jsHelp
import { CardanoAccount, Transaction } from "./types";
import { buildTransaction } from "./buildTransaction";
import { AccountBridge } from "@ledgerhq/types-live";
+import { fetchNetworkInfo } from "./api/getNetworkInfo";
/**
* Prepare transaction before checking status
@@ -17,6 +18,14 @@ export const prepareTransaction: AccountBridge<
CardanoAccount
>["prepareTransaction"] = async (account, transaction) => {
let patch = {};
+
+ if (!transaction.protocolParams) {
+ const networkInfo = await fetchNetworkInfo(account.currency);
+ transaction = defaultUpdateTransaction(transaction, {
+ protocolParams: networkInfo.protocolParams,
+ });
+ }
+
try {
const cardanoTransaction = await buildTransaction(account, transaction);
const transactionFees = cardanoTransaction.getFee();
diff --git a/libs/coin-modules/coin-cardano/src/signOperation.ts b/libs/coin-modules/coin-cardano/src/signOperation.ts
index 9b0eda1f40a8..d661684cd258 100644
--- a/libs/coin-modules/coin-cardano/src/signOperation.ts
+++ b/libs/coin-modules/coin-cardano/src/signOperation.ts
@@ -11,6 +11,7 @@ import { buildTransaction } from "./buildTransaction";
import { getNetworkParameters } from "./networks";
import { CardanoSigner, Witness } from "./signer";
import typhonSerializer from "./typhonSerializer";
+import { CardanoInvalidProtoParams } from "./errors";
/**
* Sign Transaction with Ledger hardware
@@ -28,6 +29,10 @@ export const buildSignOperation =
throw new FeeNotLoaded();
}
+ if (!transaction.protocolParams) {
+ throw new CardanoInvalidProtoParams();
+ }
+
const unsignedTransaction = await buildTransaction(account, transaction);
const signerTransaction = typhonSerializer(unsignedTransaction, account.index);
diff --git a/libs/coin-modules/coin-cardano/src/transaction.ts b/libs/coin-modules/coin-cardano/src/transaction.ts
index 319c34303777..bcd34b0ac2aa 100644
--- a/libs/coin-modules/coin-cardano/src/transaction.ts
+++ b/libs/coin-modules/coin-cardano/src/transaction.ts
@@ -41,6 +41,7 @@ export const fromTransactionRaw = (tr: TransactionRaw): Transaction => {
fees: tr.fees ? new BigNumber(tr.fees) : undefined,
memo: tr.memo,
poolId: tr.poolId,
+ protocolParams: tr.protocolParams,
};
};
@@ -53,6 +54,7 @@ export const toTransactionRaw = (t: Transaction): TransactionRaw => {
fees: t.fees?.toString() || undefined,
memo: t.memo,
poolId: t.poolId,
+ protocolParams: t.protocolParams,
};
};
diff --git a/libs/coin-modules/coin-cardano/src/types.ts b/libs/coin-modules/coin-cardano/src/types.ts
index 4d1e8bb437a5..388f853305d7 100644
--- a/libs/coin-modules/coin-cardano/src/types.ts
+++ b/libs/coin-modules/coin-cardano/src/types.ts
@@ -177,6 +177,7 @@ export type Transaction = TransactionCommon & {
fees?: BigNumber;
memo?: string;
poolId: string | undefined;
+ protocolParams?: ProtocolParams;
// add here all transaction-specific fields if you implement other modes than "send"
};
@@ -189,6 +190,7 @@ export type TransactionRaw = TransactionCommonRaw & {
fees?: string;
memo?: string;
poolId: string | undefined;
+ protocolParams?: ProtocolParams;
// also the transaction fields as raw JSON data
};
diff --git a/libs/ledger-live-common/src/families/cardano/config.ts b/libs/ledger-live-common/src/families/cardano/config.ts
index c0a3d93b10a1..465a1254be11 100644
--- a/libs/ledger-live-common/src/families/cardano/config.ts
+++ b/libs/ledger-live-common/src/families/cardano/config.ts
@@ -1,12 +1,15 @@
-import { ConfigInfo } from "@ledgerhq/live-config/LiveConfig";
+import { CardanoCoinConfig } from "@ledgerhq/coin-cardano/config";
+import { CurrencyLiveConfigDefinition } from "../../config";
-export const cardanoConfig: Record = {
+export const cardanoConfig: CurrencyLiveConfigDefinition = {
config_currency_cardano: {
type: "object",
default: {
status: {
type: "active",
},
- },
+ maxFeesWarning: 5e6,
+ maxFeesError: 10e6,
+ } as CardanoCoinConfig,
},
};
diff --git a/libs/ledger-live-common/src/families/cardano/setup.ts b/libs/ledger-live-common/src/families/cardano/setup.ts
index d2b486493ef7..de2517266374 100644
--- a/libs/ledger-live-common/src/families/cardano/setup.ts
+++ b/libs/ledger-live-common/src/families/cardano/setup.ts
@@ -24,6 +24,9 @@ import type {
import { CreateSigner, createResolver, executeWithSigner } from "../../bridge/setup";
import type { Resolver } from "../../hw/getAddress/types";
import signerSerializer from "./signerSerializer";
+import { getCurrencyConfiguration } from "../../config";
+import { CardanoCoinConfig } from "@ledgerhq/coin-cardano/config";
+import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
function findNetwork(networkParams: CardanoLikeNetworkParameters) {
return networkParams.networkId === Networks.Mainnet.networkId
@@ -81,8 +84,13 @@ const createSigner: CreateSigner = (transport: Transport) => {
};
};
+const getCurrencyConfig = () => {
+ return getCurrencyConfiguration(getCryptoCurrencyById("cardano"));
+};
+
const bridge: Bridge = createBridges(
executeWithSigner(createSigner),
+ getCurrencyConfig,
);
const resolver: Resolver = createResolver(createSigner, cardanoResolver);