From b04bedae7b8eb9a8b4a51bcf25c476945095e592 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Sim=C3=A3o?=
Date: Tue, 23 May 2023 10:44:46 +0100
Subject: [PATCH] feat: add useTransaction (#1189)
---
.../ConfirmedIssueRequest/index.tsx | 37 ++---
.../ManualIssueExecutionUI/index.tsx | 44 +++---
.../components/DepositForm/DepositForm.tsx | 26 +---
.../PoolsInsights/PoolsInsights.tsx | 15 +-
.../components/WithdrawForm/WithdrawForm.tsx | 25 +---
.../AMM/Swap/components/SwapForm/SwapForm.tsx | 55 +++-----
src/pages/Bridge/BurnForm/index.tsx | 9 +-
src/pages/Bridge/IssueForm/index.tsx | 12 +-
src/pages/Bridge/RedeemForm/index.tsx | 27 ++--
.../CollateralModal/CollateralModal.tsx | 48 +++----
.../components/LoanForm/LoanForm.tsx | 59 +++++---
.../LoansInsights/LoansInsights.tsx | 30 ++--
.../Staking/ClaimRewardsButton/index.tsx | 32 ++---
src/pages/Staking/index.tsx | 123 +++++++---------
src/pages/Transfer/TransferForm/index.tsx | 12 +-
.../Vaults/Vault/RequestIssueModal/index.tsx | 11 +-
.../Vaults/Vault/RequestRedeemModal/index.tsx | 10 +-
.../Vault/RequestReplacementModal/index.tsx | 10 +-
.../Vault/UpdateCollateralModal/index.tsx | 8 +-
.../Vault/components/Rewards/Rewards.tsx | 56 ++++----
.../DespositCollateralStep.tsx | 27 ++--
.../hooks/api/loans/use-loan-mutation.tsx | 49 -------
src/utils/hooks/transaction/index.ts | 2 +
src/utils/hooks/transaction/types/amm.ts | 28 ++++
src/utils/hooks/transaction/types/escrow.ts | 46 ++++++
src/utils/hooks/transaction/types/index.ts | 79 +++++++++++
src/utils/hooks/transaction/types/issue.ts | 18 +++
src/utils/hooks/transaction/types/loans.ts | 62 ++++++++
src/utils/hooks/transaction/types/redeem.ts | 23 +++
src/utils/hooks/transaction/types/replace.ts | 13 ++
src/utils/hooks/transaction/types/rewards.ts | 13 ++
src/utils/hooks/transaction/types/tokens.ts | 13 ++
src/utils/hooks/transaction/types/vaults.ts | 23 +++
.../hooks/transaction/use-transaction.ts | 122 ++++++++++++++++
.../hooks/transaction/utils/extrinsic.ts | 133 ++++++++++++++++++
src/utils/hooks/transaction/utils/submit.ts | 107 ++++++++++++++
36 files changed, 960 insertions(+), 447 deletions(-)
delete mode 100644 src/utils/hooks/api/loans/use-loan-mutation.tsx
create mode 100644 src/utils/hooks/transaction/index.ts
create mode 100644 src/utils/hooks/transaction/types/amm.ts
create mode 100644 src/utils/hooks/transaction/types/escrow.ts
create mode 100644 src/utils/hooks/transaction/types/index.ts
create mode 100644 src/utils/hooks/transaction/types/issue.ts
create mode 100644 src/utils/hooks/transaction/types/loans.ts
create mode 100644 src/utils/hooks/transaction/types/redeem.ts
create mode 100644 src/utils/hooks/transaction/types/replace.ts
create mode 100644 src/utils/hooks/transaction/types/rewards.ts
create mode 100644 src/utils/hooks/transaction/types/tokens.ts
create mode 100644 src/utils/hooks/transaction/types/vaults.ts
create mode 100644 src/utils/hooks/transaction/use-transaction.ts
create mode 100644 src/utils/hooks/transaction/utils/extrinsic.ts
create mode 100644 src/utils/hooks/transaction/utils/submit.ts
diff --git a/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx b/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx
index 3a758d2989..e76d52fe2d 100644
--- a/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx
+++ b/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx
@@ -1,8 +1,7 @@
-import { ISubmittableResult } from '@polkadot/types/types';
import clsx from 'clsx';
import { useTranslation } from 'react-i18next';
import { FaCheckCircle } from 'react-icons/fa';
-import { useMutation, useQueryClient } from 'react-query';
+import { useQueryClient } from 'react-query';
import { toast } from 'react-toastify';
import { BTC_EXPLORER_TRANSACTION_API } from '@/config/blockstream-explorer-links';
@@ -16,7 +15,7 @@ import { TABLE_PAGE_LIMIT } from '@/utils/constants/general';
import { QUERY_PARAMETERS } from '@/utils/constants/links';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import { getColorShade } from '@/utils/helpers/colors';
-import { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useQueryParams from '@/utils/hooks/use-query-params';
import ManualIssueExecutionUI from '../ManualIssueExecutionUI';
@@ -34,21 +33,15 @@ const ConfirmedIssueRequest = ({ request }: Props): JSX.Element => {
const selectedPageIndex = selectedPage - 1;
const queryClient = useQueryClient();
- // TODO: should type properly (`Relay`)
- const executeMutation = useMutation(
- (variables: any) => {
- if (!variables.backingPayment.btcTxId) {
- throw new Error('Bitcoin transaction ID not identified yet.');
- }
- return submitExtrinsicPromise(window.bridge.issue.execute(variables.id, variables.backingPayment.btcTxId));
- },
- {
- onSuccess: (_, variables) => {
- queryClient.invalidateQueries([ISSUES_FETCHER, selectedPageIndex * TABLE_PAGE_LIMIT, TABLE_PAGE_LIMIT]);
- toast.success(t('issue_page.successfully_executed', { id: variables.id }));
- }
+
+ // TODO: check if this transaction is necessary
+ const transaction = useTransaction(Transaction.ISSUE_EXECUTE, {
+ onSuccess: (_, variables) => {
+ const [requestId] = variables.args;
+ queryClient.invalidateQueries([ISSUES_FETCHER, selectedPageIndex * TABLE_PAGE_LIMIT, TABLE_PAGE_LIMIT]);
+ toast.success(t('issue_page.successfully_executed', { id: requestId }));
}
- );
+ });
return (
<>
@@ -82,16 +75,14 @@ const ConfirmedIssueRequest = ({ request }: Props): JSX.Element => {
- {executeMutation.isError && executeMutation.error && (
+ {transaction.isError && transaction.error && (
{
- executeMutation.reset();
+ transaction.reset();
}}
title='Error'
- description={
- typeof executeMutation.error === 'string' ? executeMutation.error : executeMutation.error.message
- }
+ description={typeof transaction.error === 'string' ? transaction.error : transaction.error.message}
/>
)}
>
diff --git a/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx b/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx
index 5aee169f45..c93aa9aae2 100644
--- a/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx
+++ b/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx
@@ -5,10 +5,9 @@ import {
newAccountId,
newMonetaryAmount
} from '@interlay/interbtc-api';
-import { ISubmittableResult } from '@polkadot/types/types';
import clsx from 'clsx';
import { useTranslation } from 'react-i18next';
-import { useMutation, useQuery, useQueryClient } from 'react-query';
+import { useQuery, useQueryClient } from 'react-query';
import { toast } from 'react-toastify';
import { displayMonetaryAmount } from '@/common/utils/utils';
@@ -21,7 +20,7 @@ import { TABLE_PAGE_LIMIT } from '@/utils/constants/general';
import { QUERY_PARAMETERS } from '@/utils/constants/links';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import { getColorShade } from '@/utils/helpers/colors';
-import { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useQueryParams from '@/utils/hooks/use-query-params';
// TODO: issue requests should not be typed here but further above in the app
@@ -57,21 +56,13 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
const queryClient = useQueryClient();
- // TODO: should type properly (`Relay`)
- const executeMutation = useMutation(
- (variables: any) => {
- if (!variables.backingPayment.btcTxId) {
- throw new Error('Bitcoin transaction ID not identified yet.');
- }
- return submitExtrinsicPromise(window.bridge.issue.execute(variables.id, variables.backingPayment.btcTxId));
- },
- {
- onSuccess: (_, variables) => {
- queryClient.invalidateQueries([ISSUES_FETCHER, selectedPageIndex * TABLE_PAGE_LIMIT, TABLE_PAGE_LIMIT]);
- toast.success(t('issue_page.successfully_executed', { id: variables.id }));
- }
+ const transaction = useTransaction(Transaction.ISSUE_EXECUTE, {
+ onSuccess: (_, variables) => {
+ const [requestId] = variables.args;
+ queryClient.invalidateQueries([ISSUES_FETCHER, selectedPageIndex * TABLE_PAGE_LIMIT, TABLE_PAGE_LIMIT]);
+ toast.success(t('issue_page.successfully_executed', { id: requestId }));
}
- );
+ });
const { data: vaultCapacity, error: vaultCapacityError } = useQuery({
queryKey: 'vault-capacity',
@@ -91,7 +82,12 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
// TODO: should type properly (`Relay`)
const handleExecute = (request: any) => () => {
- executeMutation.mutate(request);
+ if (!request.backingPayment.btcTxId) {
+ console.error('Bitcoin transaction ID not identified yet.');
+ return;
+ }
+
+ transaction.execute(request.id, request.backingPayment.btcTxId);
};
const backingPaymentAmount = newMonetaryAmount(request.backingPayment.amount, WRAPPED_TOKEN);
@@ -135,7 +131,7 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
)}
@@ -143,16 +139,14 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
wrappedTokenSymbol: WRAPPED_TOKEN_SYMBOL
})}
- {executeMutation.isError && executeMutation.error && (
+ {transaction.isError && transaction.error && (
{
- executeMutation.reset();
+ transaction.reset();
}}
title='Error'
- description={
- typeof executeMutation.error === 'string' ? executeMutation.error : executeMutation.error.message
- }
+ description={typeof transaction.error === 'string' ? transaction.error : transaction.error.message}
/>
)}
diff --git a/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx b/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx
index 4a7030bc4c..4baf947a1b 100644
--- a/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx
+++ b/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx
@@ -1,11 +1,8 @@
-import { CurrencyExt, LiquidityPool, newMonetaryAmount, PooledCurrencies } from '@interlay/interbtc-api';
-import { AccountId } from '@polkadot/types/interfaces';
-import { ISubmittableResult } from '@polkadot/types/types';
+import { CurrencyExt, LiquidityPool, newMonetaryAmount } from '@interlay/interbtc-api';
import { mergeProps } from '@react-aria/utils';
import Big from 'big.js';
import { ChangeEventHandler, RefObject, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { toast } from 'react-toastify';
import { displayMonetaryAmountInUSDFormat, newSafeMonetaryAmount } from '@/common/utils/utils';
@@ -21,10 +18,10 @@ import {
} from '@/lib/form';
import { SlippageManager } from '@/pages/AMM/shared/components';
import { AMM_DEADLINE_INTERVAL } from '@/utils/constants/api';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { PoolName } from '../PoolName';
@@ -35,17 +32,6 @@ import { DepositOutputAssets } from './DepositOutputAssets';
const isCustomAmountsMode = (form: ReturnType) =>
form.dirty && Object.values(form.touched).filter(Boolean).length > 0;
-type DepositData = {
- amounts: PooledCurrencies;
- pool: LiquidityPool;
- slippage: number;
- deadline: number;
- accountId: AccountId;
-};
-
-const mutateDeposit = ({ amounts, pool, slippage, deadline, accountId }: DepositData) =>
- submitExtrinsic(window.bridge.amm.addLiquidity(amounts, pool, slippage, deadline, accountId));
-
type DepositFormProps = {
pool: LiquidityPool;
slippageModalRef: RefObject;
@@ -65,7 +51,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
const governanceBalance = getBalance(GOVERNANCE_TOKEN.ticker)?.free || newMonetaryAmount(0, GOVERNANCE_TOKEN);
- const depositMutation = useMutation(mutateDeposit, {
+ const transaction = useTransaction(Transaction.AMM_ADD_LIQUIDITY, {
onSuccess: () => {
onDeposit?.();
toast.success('Deposit successful');
@@ -85,7 +71,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
const deadline = await window.bridge.system.getFutureBlockNumber(AMM_DEADLINE_INTERVAL);
- return depositMutation.mutate({ amounts, pool, slippage, deadline, accountId });
+ return transaction.execute(amounts, pool, slippage, deadline, accountId);
} catch (err: any) {
toast.error(err.toString());
}
@@ -106,7 +92,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
initialValues: defaultValues,
validationSchema: depositLiquidityPoolSchema({ transactionFee: TRANSACTION_FEE_AMOUNT, governanceBalance, tokens }),
onSubmit: handleSubmit,
- disableValidation: depositMutation.isLoading
+ disableValidation: transaction.isLoading
});
const handleChange: ChangeEventHandler = (e) => {
@@ -203,7 +189,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
-
+
{t('amm.pools.add_liquidity')}
diff --git a/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx b/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx
index a845df1639..1689c20a32 100644
--- a/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx
+++ b/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx
@@ -1,16 +1,15 @@
import { LiquidityPool } from '@interlay/interbtc-api';
import Big from 'big.js';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { toast } from 'react-toastify';
import { formatUSD } from '@/common/utils/utils';
import { Card, Dl, DlGroup } from '@/component-library';
import { AuthCTA } from '@/components';
import { calculateAccountLiquidityUSD, calculateTotalLiquidityUSD } from '@/pages/AMM/shared/utils';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { AccountPoolsData } from '@/utils/hooks/api/amm/use-get-account-pools';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { StyledDd, StyledDt } from './PoolsInsights.style';
import { calculateClaimableFarmingRewardUSD } from './utils';
@@ -55,17 +54,11 @@ const PoolsInsights = ({ pools, accountPoolsData, refetch }: PoolsInsightsProps)
refetch();
};
- const mutateClaimRewards = async () => {
- if (accountPoolsData !== undefined) {
- await submitExtrinsic(window.bridge.amm.claimFarmingRewards(accountPoolsData.claimableRewards));
- }
- };
-
- const claimRewardsMutation = useMutation(mutateClaimRewards, {
+ const transaction = useTransaction(Transaction.AMM_CLAIM_REWARDS, {
onSuccess: handleSuccess
});
- const handleClickClaimRewards = () => claimRewardsMutation.mutate();
+ const handleClickClaimRewards = () => accountPoolsData && transaction.execute(accountPoolsData.claimableRewards);
const hasClaimableRewards = totalClaimableRewardUSD > 0;
return (
@@ -88,7 +81,7 @@ const PoolsInsights = ({ pools, accountPoolsData, refetch }: PoolsInsightsProps)
{formatUSD(totalClaimableRewardUSD, { compact: true })}
{hasClaimableRewards && (
-
+
Claim
)}
diff --git a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx b/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
index 3eeb392479..4f2ea60b55 100644
--- a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
+++ b/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
@@ -1,11 +1,7 @@
-import { LiquidityPool, LpCurrency, newMonetaryAmount } from '@interlay/interbtc-api';
-import { MonetaryAmount } from '@interlay/monetary-js';
-import { AccountId } from '@polkadot/types/interfaces';
-import { ISubmittableResult } from '@polkadot/types/types';
+import { LiquidityPool, newMonetaryAmount } from '@interlay/interbtc-api';
import Big from 'big.js';
import { RefObject, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { toast } from 'react-toastify';
import {
@@ -20,27 +16,16 @@ import { isFormDisabled, useForm, WITHDRAW_LIQUIDITY_POOL_FIELD } from '@/lib/fo
import { WithdrawLiquidityPoolFormData, withdrawLiquidityPoolSchema } from '@/lib/form/schemas';
import { SlippageManager } from '@/pages/AMM/shared/components';
import { AMM_DEADLINE_INTERVAL } from '@/utils/constants/api';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { PoolName } from '../PoolName';
import { WithdrawAssets } from './WithdrawAssets';
import { StyledDl } from './WithdrawForm.styles';
-type DepositData = {
- amount: MonetaryAmount;
- pool: LiquidityPool;
- slippage: number;
- deadline: number;
- accountId: AccountId;
-};
-
-const mutateWithdraw = ({ amount, pool, slippage, deadline, accountId }: DepositData) =>
- submitExtrinsic(window.bridge.amm.removeLiquidity(amount, pool, slippage, deadline, accountId));
-
type WithdrawFormProps = {
pool: LiquidityPool;
slippageModalRef: RefObject;
@@ -55,7 +40,7 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
const prices = useGetPrices();
const { getBalance } = useGetBalances();
- const withdrawMutation = useMutation(mutateWithdraw, {
+ const transaction = useTransaction(Transaction.AMM_REMOVE_LIQUIDITY, {
onSuccess: () => {
onWithdraw?.();
toast.success('Withdraw successful');
@@ -85,7 +70,7 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
const amount = newMonetaryAmount(data[WITHDRAW_LIQUIDITY_POOL_FIELD] || 0, lpToken, true);
const deadline = await window.bridge.system.getFutureBlockNumber(AMM_DEADLINE_INTERVAL);
- return withdrawMutation.mutate({ amount, pool, deadline, slippage, accountId });
+ return transaction.execute(amount, pool, slippage, deadline, accountId);
} catch (err: any) {
toast.error(err.toString());
}
@@ -157,7 +142,7 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
-
+
{t('amm.pools.remove_liquidity')}
diff --git a/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx b/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx
index 716ca91398..a4d60bbcf6 100644
--- a/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx
+++ b/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx
@@ -1,12 +1,8 @@
import { CurrencyExt, LiquidityPool, newMonetaryAmount, Trade } from '@interlay/interbtc-api';
-import { MonetaryAmount } from '@interlay/monetary-js';
-import { AddressOrPair } from '@polkadot/api/types';
-import { ISubmittableResult } from '@polkadot/types/types';
import { mergeProps } from '@react-aria/utils';
import Big from 'big.js';
import { ChangeEventHandler, Key, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import { toast } from 'react-toastify';
import { useDebounce } from 'react-use';
@@ -26,11 +22,11 @@ import {
import { SlippageManager } from '@/pages/AMM/shared/components';
import { SwapPair } from '@/types/swap';
import { SWAP_PRICE_IMPACT_LIMIT } from '@/utils/constants/swap';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetCurrencies } from '@/utils/hooks/api/use-get-currencies';
import { Prices, useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { PriceImpactModal } from '../PriceImpactModal';
@@ -83,16 +79,6 @@ const getPoolPriceImpact = (trade: Trade | null | undefined, inputAmountUSD: num
: new Big(0)
});
-type SwapData = {
- trade: Trade;
- minimumAmountOut: MonetaryAmount;
- recipient: AddressOrPair;
- deadline: string | number;
-};
-
-const mutateSwap = ({ deadline, minimumAmountOut, recipient, trade }: SwapData) =>
- submitExtrinsic(window.bridge.amm.swap(trade, minimumAmountOut, recipient, deadline));
-
type Props = {
pair: SwapPair;
liquidityPools: LiquidityPool[];
@@ -126,6 +112,18 @@ const SwapForm = ({
const { data: balances, getBalance, getAvailableBalance } = useGetBalances();
const { data: currencies } = useGetCurrencies(bridgeLoaded);
+ const transaction = useTransaction(Transaction.AMM_SWAP, {
+ onSuccess: () => {
+ toast.success('Swap successful');
+ setTrade(undefined);
+ setInputAmount(undefined);
+ onSwap();
+ },
+ onError: (err) => {
+ toast.error(err.message);
+ }
+ });
+
useDebounce(
() => {
if (!pair.input || !pair.output || !inputAmount) {
@@ -141,18 +139,6 @@ const SwapForm = ({
[inputAmount, pair]
);
- const swapMutation = useMutation(mutateSwap, {
- onSuccess: () => {
- toast.success('Swap successful');
- setTrade(undefined);
- setInputAmount(undefined);
- onSwap();
- },
- onError: (err) => {
- toast.error(err.message);
- }
- });
-
const inputBalance = pair.input && getAvailableBalance(pair.input.ticker);
const outputBalance = pair.output && getAvailableBalance(pair.output.ticker);
@@ -174,12 +160,7 @@ const SwapForm = ({
const deadline = await window.bridge.system.getFutureBlockNumber(30 * 60);
- return swapMutation.mutate({
- trade,
- recipient: accountId,
- minimumAmountOut,
- deadline
- });
+ return transaction.execute(trade, minimumAmountOut, accountId, deadline);
} catch (err: any) {
toast.error(err.toString());
}
@@ -212,7 +193,7 @@ const SwapForm = ({
initialValues,
validationSchema: swapSchema({ [SWAP_INPUT_AMOUNT_FIELD]: inputSchemaParams }),
onSubmit: handleSubmit,
- disableValidation: swapMutation.isLoading,
+ disableValidation: transaction.isLoading,
validateOnMount: true
});
@@ -239,11 +220,11 @@ const SwapForm = ({
useEffect(() => {
const isAmountFieldEmpty = form.values[SWAP_INPUT_AMOUNT_FIELD] === '';
- if (isAmountFieldEmpty || !swapMutation.isSuccess) return;
+ if (isAmountFieldEmpty || !transaction.isSuccess) return;
form.setFieldValue(SWAP_INPUT_AMOUNT_FIELD, '');
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [swapMutation.isSuccess]);
+ }, [transaction.isSuccess]);
const handleChangeInput: ChangeEventHandler = (e) => {
setInputAmount(e.target.value);
@@ -341,7 +322,7 @@ const SwapForm = ({
/>
{trade && }
-
+
diff --git a/src/pages/Bridge/BurnForm/index.tsx b/src/pages/Bridge/BurnForm/index.tsx
index 622b842372..2063218a57 100644
--- a/src/pages/Bridge/BurnForm/index.tsx
+++ b/src/pages/Bridge/BurnForm/index.tsx
@@ -25,11 +25,11 @@ import Tokens, { TokenOption } from '@/legacy-components/Tokens';
import { ForeignAssetIdLiteral } from '@/types/currency';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import STATUSES from '@/utils/constants/statuses';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetCollateralCurrencies } from '@/utils/hooks/api/use-get-collateral-currencies';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
const WRAPPED_TOKEN_AMOUNT = 'wrapped-token-amount';
@@ -73,6 +73,8 @@ const BurnForm = (): JSX.Element | null => {
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
const [submitError, setSubmitError] = React.useState(null);
+ const transaction = useTransaction(Transaction.REDEEM_BURN);
+
const handleUpdateCollateral = (collateral: TokenOption) => {
const selectedCollateral = burnableCollateral?.find(
(token: BurnableCollateral) => token.currency.ticker === collateral.token.ticker
@@ -150,9 +152,8 @@ const BurnForm = (): JSX.Element | null => {
const onSubmit = async (data: BurnFormData) => {
try {
setSubmitStatus(STATUSES.PENDING);
- await submitExtrinsic(
- window.bridge.redeem.burn(new BitcoinAmount(data[WRAPPED_TOKEN_AMOUNT]), selectedCollateral.currency)
- );
+
+ await transaction.executeAsync(new BitcoinAmount(data[WRAPPED_TOKEN_AMOUNT]), selectedCollateral.currency);
setSubmitStatus(STATUSES.RESOLVED);
} catch (error) {
diff --git a/src/pages/Bridge/IssueForm/index.tsx b/src/pages/Bridge/IssueForm/index.tsx
index 6a871b7c1c..2e3b83db45 100644
--- a/src/pages/Bridge/IssueForm/index.tsx
+++ b/src/pages/Bridge/IssueForm/index.tsx
@@ -56,11 +56,11 @@ import genericFetcher, { GENERIC_FETCHER } from '@/services/fetchers/generic-fet
import { ForeignAssetIdLiteral } from '@/types/currency';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import STATUSES from '@/utils/constants/statuses';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getExchangeRate } from '@/utils/helpers/oracle';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import ManualVaultSelectUI from '../ManualVaultSelectUI';
import SubmittedIssueRequestModal from './SubmittedIssueRequestModal';
@@ -142,6 +142,8 @@ const IssueForm = (): JSX.Element | null => {
});
useErrorHandler(requestLimitsError);
+ const transaction = useTransaction(Transaction.ISSUE_REQUEST);
+
React.useEffect(() => {
if (!bridgeLoaded) return;
if (!dispatch) return;
@@ -321,18 +323,14 @@ const IssueForm = (): JSX.Element | null => {
const collateralToken = await currencyIdToMonetaryCurrency(window.bridge.api, vaultId.currencies.collateral);
- const extrinsicData = await window.bridge.issue.request(
+ const result = await transaction.executeAsync(
monetaryBtcAmount,
vaultId.accountId,
collateralToken,
false, // default
vaults
);
- // When requesting an issue, wait for the finalized event because we cannot revert BTC transactions.
- // For more details see: https://github.com/interlay/interbtc-api/pull/373#issuecomment-1058949000
- const finalizedStatus = getExtrinsicStatus('Finalized');
- const extrinsicResult = await submitExtrinsic(extrinsicData, finalizedStatus);
- const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, extrinsicResult);
+ const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, result);
// TODO: handle issue aggregation
const issueRequest = issueRequests[0];
diff --git a/src/pages/Bridge/RedeemForm/index.tsx b/src/pages/Bridge/RedeemForm/index.tsx
index 1248b5167d..357bfcf540 100644
--- a/src/pages/Bridge/RedeemForm/index.tsx
+++ b/src/pages/Bridge/RedeemForm/index.tsx
@@ -1,5 +1,10 @@
-import { CollateralCurrencyExt, InterbtcPrimitivesVaultId, newMonetaryAmount, Redeem } from '@interlay/interbtc-api';
-import { getRedeemRequestsFromExtrinsicResult } from '@interlay/interbtc-api';
+import {
+ CollateralCurrencyExt,
+ getRedeemRequestsFromExtrinsicResult,
+ InterbtcPrimitivesVaultId,
+ newMonetaryAmount,
+ Redeem
+} from '@interlay/interbtc-api';
import { Bitcoin, BitcoinAmount, ExchangeRate } from '@interlay/monetary-js';
import Big from 'big.js';
import clsx from 'clsx';
@@ -44,11 +49,11 @@ import { ForeignAssetIdLiteral } from '@/types/currency';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import STATUSES from '@/utils/constants/statuses';
import { getColorShade } from '@/utils/helpers/colors';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getExchangeRate } from '@/utils/helpers/oracle';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import ManualVaultSelectUI from '../ManualVaultSelectUI';
import SubmittedRedeemRequestModal from './SubmittedRedeemRequestModal';
@@ -114,6 +119,8 @@ const RedeemForm = (): JSX.Element | null => {
const [selectedVault, setSelectedVault] = React.useState();
+ const transaction = useTransaction(Transaction.REDEEM_REQUEST);
+
React.useEffect(() => {
if (!monetaryWrappedTokenAmount) return;
if (!maxRedeemableCapacity) return;
@@ -295,16 +302,10 @@ const RedeemForm = (): JSX.Element | null => {
const relevantVaults = new Map();
// FIXME: a bit of a dirty workaround with the capacity
relevantVaults.set(vaultId, monetaryWrappedTokenAmount.mul(2));
- const extrinsicData = await window.bridge.redeem.request(
- monetaryWrappedTokenAmount,
- data[BTC_ADDRESS],
- vaultId
- );
- // When requesting a redeem, wait for the finalized event because we cannot revert BTC transactions.
- // For more details see: https://github.com/interlay/interbtc-api/pull/373#issuecomment-1058949000
- const finalizedStatus = getExtrinsicStatus('Finalized');
- const extrinsicResult = await submitExtrinsic(extrinsicData, finalizedStatus);
- const redeemRequests = await getRedeemRequestsFromExtrinsicResult(window.bridge, extrinsicResult);
+
+ const result = await transaction.executeAsync(monetaryWrappedTokenAmount, data[BTC_ADDRESS], vaultId);
+
+ const redeemRequests = await getRedeemRequestsFromExtrinsicResult(window.bridge, result);
// TODO: handle redeem aggregator
const redeemRequest = redeemRequests[0];
diff --git a/src/pages/Loans/LoansOverview/components/CollateralModal/CollateralModal.tsx b/src/pages/Loans/LoansOverview/components/CollateralModal/CollateralModal.tsx
index 9b545e17ff..2ec3d03b04 100644
--- a/src/pages/Loans/LoansOverview/components/CollateralModal/CollateralModal.tsx
+++ b/src/pages/Loans/LoansOverview/components/CollateralModal/CollateralModal.tsx
@@ -1,31 +1,19 @@
-import { CollateralPosition, CurrencyExt, LoanAsset } from '@interlay/interbtc-api';
-import { ISubmittableResult } from '@polkadot/types/types';
+import { CollateralPosition, LoanAsset } from '@interlay/interbtc-api';
import { TFunction, useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { toast } from 'react-toastify';
import { Flex, Modal, ModalBody, ModalFooter, ModalHeader, ModalProps, Status } from '@/component-library';
import { AuthCTA } from '@/components';
import ErrorModal from '@/legacy-components/ErrorModal';
-import { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
import { useGetAccountLendingStatistics } from '@/utils/hooks/api/loans/use-get-account-lending-statistics';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { useGetLTV } from '../../hooks/use-get-ltv';
import { BorrowLimit } from '../BorrowLimit';
import { LoanActionInfo } from '../LoanActionInfo';
import { StyledDescription } from './CollateralModal.style';
-type ToggleCollateralVariables = { isEnabling: boolean; underlyingCurrency: CurrencyExt };
-
-const toggleCollateral = ({ isEnabling, underlyingCurrency }: ToggleCollateralVariables) => {
- if (isEnabling) {
- return submitExtrinsicPromise(window.bridge.loans.enableAsCollateral(underlyingCurrency));
- } else {
- return submitExtrinsicPromise(window.bridge.loans.disableAsCollateral(underlyingCurrency));
- }
-};
-
type CollateralModalVariant = 'enable' | 'disable' | 'disable-error';
const getContentMap = (t: TFunction, variant: CollateralModalVariant, asset: LoanAsset) =>
@@ -73,14 +61,12 @@ const CollateralModal = ({ asset, position, onClose, ...props }: CollateralModal
const { getLTV } = useGetLTV();
const prices = useGetPrices();
- const handleSuccess = () => {
- toast.success('Successfully toggled collateral');
- onClose?.();
- refetch();
- };
-
- const toggleCollateralMutation = useMutation(toggleCollateral, {
- onSuccess: handleSuccess
+ const transaction = useTransaction({
+ onSuccess: () => {
+ toast.success('Successfully toggled collateral');
+ onClose?.();
+ refetch();
+ }
});
if (!asset || !position) {
@@ -100,9 +86,11 @@ const CollateralModal = ({ asset, position, onClose, ...props }: CollateralModal
return onClose?.();
}
- const isEnabling = variant === 'enable';
-
- return toggleCollateralMutation.mutate({ isEnabling, underlyingCurrency: position.amount.currency });
+ if (variant === 'enable') {
+ return transaction.execute(Transaction.LOANS_ENABLE_COLLATERAL, asset.currency);
+ } else {
+ return transaction.execute(Transaction.LOANS_DISABLE_COLLATERAL, asset.currency);
+ }
};
return (
@@ -117,17 +105,17 @@ const CollateralModal = ({ asset, position, onClose, ...props }: CollateralModal
-
+
{content.buttonLabel}
- {toggleCollateralMutation.isError && (
+ {transaction.isError && (
toggleCollateralMutation.reset()}
+ open={transaction.isError}
+ onClose={() => transaction.reset()}
title='Error'
- description={toggleCollateralMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
>
diff --git a/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx b/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx
index bd89f6b57e..edc7763901 100644
--- a/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx
+++ b/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx
@@ -12,8 +12,8 @@ import { AuthCTA } from '@/components';
import { isFormDisabled, LoanFormData, loanSchema, LoanValidationParams, useForm } from '@/lib/form';
import { LoanAction } from '@/types/loans';
import { useGetAccountPositions } from '@/utils/hooks/api/loans/use-get-account-positions';
-import { useLoanMutation } from '@/utils/hooks/api/loans/use-loan-mutation';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { useLoanFormData } from '../../hooks/use-loan-form-data';
import { isLendAsset } from '../../utils/is-loan-asset';
@@ -116,18 +116,45 @@ const LoanForm = ({ asset, variant, position, onChangeLoan }: LoanFormProps): JS
[inputAmount]
);
- const handleSuccess = () => {
- toast.success(`Successful ${content.title.toLowerCase()}`);
- onChangeLoan?.();
- refetch();
- };
+ const transaction = useTransaction({
+ onSuccess: () => {
+ toast.success(`Successful ${content.title.toLowerCase()}`);
+ onChangeLoan?.();
+ refetch();
+ },
+ onError: (error: Error) => {
+ toast.error(error.message);
+ }
+ });
- const handleError = (error: Error) => {
- toast.error(error.message);
+ const handleSubmit = (data: LoanFormData) => {
+ try {
+ const amount = data[variant] || 0;
+ const monetaryAmount = newMonetaryAmount(amount, asset.currency, true);
+
+ switch (variant) {
+ case 'lend':
+ return transaction.execute(Transaction.LOANS_LEND, monetaryAmount.currency, monetaryAmount);
+ case 'withdraw':
+ if (isMaxAmount) {
+ return transaction.execute(Transaction.LOANS_WITHDRAW_ALL, monetaryAmount.currency);
+ } else {
+ return transaction.execute(Transaction.LOANS_WITHDRAW, monetaryAmount.currency, monetaryAmount);
+ }
+ case 'borrow':
+ return transaction.execute(Transaction.LOANS_BORROW, monetaryAmount.currency, monetaryAmount);
+ case 'repay':
+ if (isMaxAmount) {
+ return transaction.execute(Transaction.LOANS_REPAY_ALL, monetaryAmount.currency);
+ } else {
+ return transaction.execute(Transaction.LOANS_REPAY, monetaryAmount.currency, monetaryAmount);
+ }
+ }
+ } catch (err: any) {
+ toast.error(err.toString());
+ }
};
- const loanMutation = useLoanMutation({ onSuccess: handleSuccess, onError: handleError });
-
const schemaParams: LoanValidationParams = {
governanceBalance,
transactionFee,
@@ -135,16 +162,6 @@ const LoanForm = ({ asset, variant, position, onChangeLoan }: LoanFormProps): JS
maxAmount: assetAmount.available
};
- const handleSubmit = (data: LoanFormData) => {
- try {
- const submittedAmount = data[variant] || 0;
- const submittedMonetaryAmount = newMonetaryAmount(submittedAmount, asset.currency, true);
- loanMutation.mutate({ amount: submittedMonetaryAmount, loanType: variant, isMaxAmount });
- } catch (err: any) {
- toast.error(err.toString());
- }
- };
-
const form = useForm({
initialValues: { [variant]: '' },
validationSchema: loanSchema(variant, schemaParams),
@@ -199,7 +216,7 @@ const LoanForm = ({ asset, variant, position, onChangeLoan }: LoanFormProps): JS
-
+
{content.title}
diff --git a/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx b/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx
index 41bfd6a148..6ad85f8d82 100644
--- a/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx
+++ b/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx
@@ -1,20 +1,16 @@
-import { ISubmittableResult } from '@polkadot/types/types';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { toast } from 'react-toastify';
import { formatNumber, formatPercentage, formatUSD } from '@/common/utils/utils';
import { Card, Dl, DlGroup } from '@/component-library';
import { AuthCTA } from '@/components';
import ErrorModal from '@/legacy-components/ErrorModal';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { AccountLendingStatistics } from '@/utils/hooks/api/loans/use-get-account-lending-statistics';
import { useGetAccountSubsidyRewards } from '@/utils/hooks/api/loans/use-get-account-subsidy-rewards';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { StyledDd, StyledDt } from './LoansInsights.style';
-const mutateClaimRewards = () => submitExtrinsic(window.bridge.loans.claimAllSubsidyRewards());
-
type LoansInsightsProps = {
statistics?: AccountLendingStatistics;
};
@@ -23,16 +19,14 @@ const LoansInsights = ({ statistics }: LoansInsightsProps): JSX.Element => {
const { t } = useTranslation();
const { data: subsidyRewards, refetch } = useGetAccountSubsidyRewards();
- const handleSuccess = () => {
- toast.success(t('successfully_claimed_rewards'));
- refetch();
- };
-
- const claimRewardsMutation = useMutation(mutateClaimRewards, {
- onSuccess: handleSuccess
+ const transaction = useTransaction(Transaction.LOANS_CLAIM_REWARDS, {
+ onSuccess: () => {
+ toast.success(t('successfully_claimed_rewards'));
+ refetch();
+ }
});
- const handleClickClaimRewards = () => claimRewardsMutation.mutate();
+ const handleClickClaimRewards = () => transaction.execute();
const { supplyAmountUSD, netAPY } = statistics || {};
@@ -76,18 +70,18 @@ const LoansInsights = ({ statistics }: LoansInsightsProps): JSX.Element => {
{subsidyRewardsAmountLabel}
{hasSubsidyRewards && (
-
+
Claim
)}
- {claimRewardsMutation.isError && (
+ {transaction.isError && (
claimRewardsMutation.reset()}
+ open={transaction.isError}
+ onClose={() => transaction.reset()}
title='Error'
- description={claimRewardsMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
>
diff --git a/src/pages/Staking/ClaimRewardsButton/index.tsx b/src/pages/Staking/ClaimRewardsButton/index.tsx
index 2ad34879cd..442da162c0 100644
--- a/src/pages/Staking/ClaimRewardsButton/index.tsx
+++ b/src/pages/Staking/ClaimRewardsButton/index.tsx
@@ -1,6 +1,5 @@
-import { ISubmittableResult } from '@polkadot/types/types';
import clsx from 'clsx';
-import { useMutation, useQueryClient } from 'react-query';
+import { useQueryClient } from 'react-query';
import { GOVERNANCE_TOKEN_SYMBOL } from '@/config/relay-chains';
import InterlayDenimOrKintsugiSupernovaContainedButton, {
@@ -9,7 +8,7 @@ import InterlayDenimOrKintsugiSupernovaContainedButton, {
import ErrorModal from '@/legacy-components/ErrorModal';
import { useSubstrateSecureState } from '@/lib/substrate';
import { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
interface CustomProps {
claimableRewardAmount: string;
@@ -24,20 +23,15 @@ const ClaimRewardsButton = ({
const queryClient = useQueryClient();
- const claimRewardsMutation = useMutation(
- () => {
- return submitExtrinsic(window.bridge.escrow.withdrawRewards());
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewardEstimate', selectedAccount?.address]);
- queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewards', selectedAccount?.address]);
- }
+ const transaction = useTransaction(Transaction.ESCROW_WITHDRAW_REWARDS, {
+ onSuccess: () => {
+ queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewardEstimate', selectedAccount?.address]);
+ queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewards', selectedAccount?.address]);
}
- );
+ });
const handleClaimRewards = () => {
- claimRewardsMutation.mutate();
+ transaction.execute();
};
return (
@@ -45,19 +39,19 @@ const ClaimRewardsButton = ({
Claim {claimableRewardAmount} {GOVERNANCE_TOKEN_SYMBOL} Rewards
- {claimRewardsMutation.isError && (
+ {transaction.isError && (
{
- claimRewardsMutation.reset();
+ transaction.reset();
}}
title='Error'
- description={claimRewardsMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
>
diff --git a/src/pages/Staking/index.tsx b/src/pages/Staking/index.tsx
index e7c8dfd505..043d6b1185 100644
--- a/src/pages/Staking/index.tsx
+++ b/src/pages/Staking/index.tsx
@@ -1,5 +1,4 @@
import { newMonetaryAmount } from '@interlay/interbtc-api';
-import { ISubmittableResult } from '@polkadot/types/types';
import Big from 'big.js';
import clsx from 'clsx';
import { add, format } from 'date-fns';
@@ -7,7 +6,7 @@ import * as React from 'react';
import { useErrorHandler, withErrorBoundary } from 'react-error-boundary';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
-import { useMutation, useQuery, useQueryClient } from 'react-query';
+import { useQuery, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import { StoreType } from '@/common/types/util.types';
@@ -44,10 +43,10 @@ import {
} from '@/services/fetchers/staking-transaction-fee-reserve-fetcher';
import { ZERO_GOVERNANCE_TOKEN_AMOUNT, ZERO_VOTE_GOVERNANCE_TOKEN_AMOUNT } from '@/utils/constants/currency';
import { YEAR_MONTH_DAY_PATTERN } from '@/utils/constants/date-time';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { useSignMessage } from '@/utils/hooks/use-sign-message';
import BalancesUI from './BalancesUI';
@@ -100,11 +99,6 @@ interface StakedAmountAndEndBlock {
endBlock: number;
}
-interface LockingAmountAndTime {
- amount: GovernanceTokenMonetaryAmount;
- time: number; // Weeks
-}
-
const Staking = (): JSX.Element => {
const [blockLockTimeExtension, setBlockLockTimeExtension] = React.useState(0);
@@ -248,63 +242,25 @@ const Staking = (): JSX.Element => {
);
useErrorHandler(transactionFeeReserveError);
- const initialStakeMutation = useMutation(
- (variables: LockingAmountAndTime) => {
- if (currentBlockNumber === undefined) {
- throw new Error('Something went wrong!');
- }
- const unlockHeight = currentBlockNumber + convertWeeksToBlockNumbers(variables.time);
-
- return submitExtrinsic(window.bridge.escrow.createLock(variables.amount, unlockHeight));
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: [GENERIC_FETCHER, 'escrow'] });
- reset({
- [LOCKING_AMOUNT]: '0.0',
- [LOCK_TIME]: '0'
- });
- }
+ const initialStakeTransaction = useTransaction(Transaction.ESCROW_CREATE_LOCK, {
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [GENERIC_FETCHER, 'escrow'] });
+ reset({
+ [LOCKING_AMOUNT]: '0.0',
+ [LOCK_TIME]: '0'
+ });
}
- );
-
- const moreStakeMutation = useMutation(
- (variables: LockingAmountAndTime) => {
- return (async () => {
- if (stakedAmountAndEndBlock === undefined) {
- throw new Error('Something went wrong!');
- }
+ });
- if (checkIncreaseLockAmountAndExtendLockTime(variables.time, variables.amount)) {
- const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(variables.time);
-
- const txs = [
- window.bridge.api.tx.escrow.increaseAmount(variables.amount.toString(true)),
- window.bridge.api.tx.escrow.increaseUnlockHeight(unlockHeight)
- ];
- const batch = window.bridge.api.tx.utility.batchAll(txs);
- await submitExtrinsic({ extrinsic: batch });
- } else if (checkOnlyIncreaseLockAmount(variables.time, variables.amount)) {
- await submitExtrinsic(window.bridge.escrow.increaseAmount(variables.amount));
- } else if (checkOnlyExtendLockTime(variables.time, variables.amount)) {
- const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(variables.time);
-
- await submitExtrinsic(window.bridge.escrow.increaseUnlockHeight(unlockHeight));
- } else {
- throw new Error('Something went wrong!');
- }
- })();
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: [GENERIC_FETCHER, 'escrow'] });
- reset({
- [LOCKING_AMOUNT]: '0.0',
- [LOCK_TIME]: '0'
- });
- }
+ const existingStakeTransaction = useTransaction({
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [GENERIC_FETCHER, 'escrow'] });
+ reset({
+ [LOCKING_AMOUNT]: '0.0',
+ [LOCK_TIME]: '0'
+ });
}
- );
+ });
React.useEffect(() => {
if (isValidating || !isValid || !estimatedRewardAmountAndAPYRefetch) return;
@@ -409,15 +365,30 @@ const Staking = (): JSX.Element => {
const numberTime = parseInt(lockTimeWithFallback);
if (votingBalanceGreaterThanZero) {
- moreStakeMutation.mutate({
- amount: monetaryAmount,
- time: numberTime
- });
+ if (stakedAmountAndEndBlock === undefined) {
+ throw new Error('Something went wrong!');
+ }
+
+ if (checkIncreaseLockAmountAndExtendLockTime(numberTime, monetaryAmount)) {
+ const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(numberTime);
+
+ existingStakeTransaction.execute(
+ Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT,
+ monetaryAmount.toString(true),
+ unlockHeight
+ );
+ } else if (checkOnlyIncreaseLockAmount(numberTime, monetaryAmount)) {
+ existingStakeTransaction.execute(Transaction.ESCROW_INCREASE_LOCKED_AMOUNT, monetaryAmount);
+ } else if (checkOnlyExtendLockTime(numberTime, monetaryAmount)) {
+ const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(numberTime);
+
+ existingStakeTransaction.execute(Transaction.ESCROW_INCREASE_LOCKED_TIME, unlockHeight);
+ } else {
+ throw new Error('Something went wrong!');
+ }
} else {
- initialStakeMutation.mutate({
- amount: monetaryAmount,
- time: numberTime
- });
+ const unlockHeight = currentBlockNumber + convertWeeksToBlockNumbers(numberTime);
+ initialStakeTransaction.execute(monetaryAmount, unlockHeight);
}
};
@@ -856,7 +827,7 @@ const Staking = (): JSX.Element => {
size='large'
type='submit'
disabled={initializing || unlockFirst || !isValid}
- loading={initialStakeMutation.isLoading || moreStakeMutation.isLoading}
+ loading={initialStakeTransaction.isLoading || existingStakeTransaction.isLoading}
>
{submitButtonLabel}{' '}
{unlockFirst ? (
@@ -866,15 +837,15 @@ const Staking = (): JSX.Element => {
- {(initialStakeMutation.isError || moreStakeMutation.isError) && (
+ {(initialStakeTransaction.isError || existingStakeTransaction.isError) && (
{
- initialStakeMutation.reset();
- moreStakeMutation.reset();
+ initialStakeTransaction.reset();
+ existingStakeTransaction.reset();
}}
title='Error'
- description={initialStakeMutation.error?.message || moreStakeMutation.error?.message || ''}
+ description={initialStakeTransaction.error?.message || existingStakeTransaction.error?.message || ''}
/>
)}
>
diff --git a/src/pages/Transfer/TransferForm/index.tsx b/src/pages/Transfer/TransferForm/index.tsx
index e588456dc1..a488cd288f 100644
--- a/src/pages/Transfer/TransferForm/index.tsx
+++ b/src/pages/Transfer/TransferForm/index.tsx
@@ -18,8 +18,8 @@ import Tokens, { TokenOption } from '@/legacy-components/Tokens';
import InterlayButtonBase from '@/legacy-components/UI/InterlayButtonBase';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import STATUSES from '@/utils/constants/statuses';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import isValidPolkadotAddress from '@/utils/helpers/is-valid-polkadot-address';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import TokenAmountField from '../TokenAmountField';
@@ -50,6 +50,8 @@ const TransferForm = (): JSX.Element => {
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
const [submitError, setSubmitError] = React.useState(null);
+ const transaction = useTransaction(Transaction.TOKENS_TRANSFER);
+
const onSubmit = async (data: TransferFormData) => {
if (!activeToken) return;
if (data[TRANSFER_AMOUNT] === undefined) return;
@@ -57,11 +59,9 @@ const TransferForm = (): JSX.Element => {
try {
setSubmitStatus(STATUSES.PENDING);
- await submitExtrinsic(
- window.bridge.tokens.transfer(
- data[RECIPIENT_ADDRESS],
- newMonetaryAmount(data[TRANSFER_AMOUNT], activeToken.token, true)
- )
+ await transaction.executeAsync(
+ data[RECIPIENT_ADDRESS],
+ newMonetaryAmount(data[TRANSFER_AMOUNT], activeToken.token, true)
);
setSubmitStatus(STATUSES.RESOLVED);
diff --git a/src/pages/Vaults/Vault/RequestIssueModal/index.tsx b/src/pages/Vaults/Vault/RequestIssueModal/index.tsx
index 91c08d48cb..4e9215ce82 100644
--- a/src/pages/Vaults/Vault/RequestIssueModal/index.tsx
+++ b/src/pages/Vaults/Vault/RequestIssueModal/index.tsx
@@ -44,11 +44,11 @@ import SubmittedIssueRequestModal from '@/pages/Bridge/IssueForm/SubmittedIssueR
import { ForeignAssetIdLiteral } from '@/types/currency';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import STATUSES from '@/utils/constants/statuses';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getExchangeRate } from '@/utils/helpers/oracle';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
const WRAPPED_TOKEN_AMOUNT = 'amount';
@@ -108,6 +108,8 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro
const vaultAccountId = useAccountId(vaultAddress);
+ const transaction = useTransaction(Transaction.ISSUE_REQUEST);
+
React.useEffect(() => {
if (!bridgeLoaded) return;
if (!handleError) return;
@@ -180,17 +182,14 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro
const vaults = await window.bridge.vaults.getVaultsWithIssuableTokens();
- const extrinsicData = await window.bridge.issue.request(
+ const extrinsicResult = await transaction.executeAsync(
wrappedTokenAmount,
vaultAccountId,
collateralToken,
false, // default
vaults
);
- // When requesting an issue, wait for the finalized event because we cannot revert BTC transactions.
- // For more details see: https://github.com/interlay/interbtc-api/pull/373#issuecomment-1058949000
- const finalizedStatus = getExtrinsicStatus('Finalized');
- const extrinsicResult = await submitExtrinsic(extrinsicData, finalizedStatus);
+
const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, extrinsicResult);
// TODO: handle issue aggregation
diff --git a/src/pages/Vaults/Vault/RequestRedeemModal/index.tsx b/src/pages/Vaults/Vault/RequestRedeemModal/index.tsx
index 300211d7ec..dde21974e5 100644
--- a/src/pages/Vaults/Vault/RequestRedeemModal/index.tsx
+++ b/src/pages/Vaults/Vault/RequestRedeemModal/index.tsx
@@ -17,7 +17,7 @@ import ErrorMessage from '@/legacy-components/ErrorMessage';
import NumberInput from '@/legacy-components/NumberInput';
import TextField from '@/legacy-components/TextField';
import InterlayModal, { InterlayModalInnerWrapper, InterlayModalTitle } from '@/legacy-components/UI/InterlayModal';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
const WRAPPED_TOKEN_AMOUNT = 'amount';
const BTC_ADDRESS = 'btc-address';
@@ -47,6 +47,8 @@ const RequestRedeemModal = ({ onClose, open, collateralToken, vaultAddress, lock
const { t } = useTranslation();
const focusRef = React.useRef(null);
+ const transaction = useTransaction(Transaction.REDEEM_REQUEST);
+
const onSubmit = handleSubmit(async (data) => {
setRequestPending(true);
try {
@@ -61,11 +63,7 @@ const RequestRedeemModal = ({ onClose, open, collateralToken, vaultAddress, lock
}
const vaultId = newVaultId(window.bridge.api, vaultAddress, collateralToken, WRAPPED_TOKEN);
- const extrinsicData = await window.bridge.redeem.request(amountPolkaBtc, data[BTC_ADDRESS], vaultId);
- // When requesting a redeem, wait for the finalized event because we cannot revert BTC transactions.
- // For more details see: https://github.com/interlay/interbtc-api/pull/373#issuecomment-1058949000
- const finalizedStatus = getExtrinsicStatus('Finalized');
- await submitExtrinsic(extrinsicData, finalizedStatus);
+ await transaction.executeAsync(amountPolkaBtc, data[BTC_ADDRESS], vaultId);
queryClient.invalidateQueries(['vaultsOverview', vaultAddress, collateralToken.ticker]);
diff --git a/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx b/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx
index b296f04bf0..a92acc73b2 100644
--- a/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx
+++ b/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx
@@ -25,9 +25,9 @@ import PrimaryColorEllipsisLoader from '@/legacy-components/PrimaryColorEllipsis
import InterlayModal, { InterlayModalInnerWrapper, InterlayModalTitle } from '@/legacy-components/UI/InterlayModal';
import { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
import STATUSES from '@/utils/constants/statuses';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getExchangeRate } from '@/utils/helpers/oracle';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
const AMOUNT = 'amount';
@@ -78,6 +78,8 @@ const RequestReplacementModal = ({
);
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
+ const transaction = useTransaction(Transaction.REPLACE_REQUEST);
+
useEffect(() => {
if (!bridgeLoaded) return;
if (!handleError) return;
@@ -105,10 +107,8 @@ const RequestReplacementModal = ({
try {
setSubmitStatus(STATUSES.PENDING);
const amountPolkaBtc = new BitcoinAmount(data[AMOUNT]);
- // When requesting a replace, wait for the finalized event because we cannot revert BTC transactions.
- // For more details see: https://github.com/interlay/interbtc-api/pull/373#issuecomment-1058949000
- const finalizedStatus = getExtrinsicStatus('Finalized');
- submitExtrinsic(window.bridge.replace.request(amountPolkaBtc, collateralToken), finalizedStatus);
+
+ await transaction.executeAsync(amountPolkaBtc, collateralToken);
const vaultId = window.bridge.api.createType(ACCOUNT_ID_TYPE_NAME, vaultAddress);
queryClient.invalidateQueries([GENERIC_FETCHER, 'mapReplaceRequests', vaultId]);
diff --git a/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx b/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx
index 772fa93e3d..dad669da97 100644
--- a/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx
+++ b/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx
@@ -21,10 +21,10 @@ import TokenField from '@/legacy-components/TokenField';
import InterlayModal, { InterlayModalInnerWrapper, InterlayModalTitle } from '@/legacy-components/UI/InterlayModal';
import genericFetcher, { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
import STATUSES from '@/utils/constants/statuses';
-import { submitExtrinsic, submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
enum CollateralUpdateStatus {
Close,
@@ -129,6 +129,8 @@ const UpdateCollateralModal = ({
);
useErrorHandler(vaultCollateralizationError);
+ const transaction = useTransaction();
+
const handleClose = chain(() => resetField(COLLATERAL_TOKEN_AMOUNT), onClose);
const onSubmit = async (data: UpdateCollateralFormData) => {
@@ -142,9 +144,9 @@ const UpdateCollateralModal = ({
true
) as MonetaryAmount;
if (collateralUpdateStatus === CollateralUpdateStatus.Deposit) {
- await submitExtrinsic(window.bridge.vaults.depositCollateral(collateralTokenAmount));
+ await transaction.executeAsync(Transaction.VAULTS_DEPOSIT_COLLATERAL, collateralTokenAmount);
} else if (collateralUpdateStatus === CollateralUpdateStatus.Withdraw) {
- await submitExtrinsicPromise(window.bridge.vaults.withdrawCollateral(collateralTokenAmount));
+ await transaction.executeAsync(Transaction.VAULTS_WITHDRAW_COLLATERAL, collateralTokenAmount);
} else {
throw new Error('Something went wrong!');
}
diff --git a/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx b/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx
index e2bc840f77..2b8cedbe6b 100644
--- a/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx
+++ b/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx
@@ -1,7 +1,6 @@
import { CollateralCurrencyExt, newVaultId, WrappedCurrency, WrappedIdLiteral } from '@interlay/interbtc-api';
-import { ISubmittableResult } from '@polkadot/types/types';
import Big from 'big.js';
-import { useMutation, useQueryClient } from 'react-query';
+import { useQueryClient } from 'react-query';
import { toast } from 'react-toastify';
import { formatNumber, formatUSD } from '@/common/utils/utils';
@@ -10,8 +9,8 @@ import { LoadingSpinner } from '@/component-library/LoadingSpinner';
import { GOVERNANCE_TOKEN_SYMBOL, WRAPPED_TOKEN } from '@/config/relay-chains';
import ErrorModal from '@/legacy-components/ErrorModal';
import { ZERO_GOVERNANCE_TOKEN_AMOUNT } from '@/utils/constants/currency';
-import { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
import { VaultData } from '@/utils/hooks/api/vaults/get-vault-data';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { InsightListItem, InsightsList } from '../InsightsList';
@@ -49,31 +48,24 @@ const Rewards = ({
const queryClient = useQueryClient();
const vaultAccountId = useAccountId(vaultAddress);
- const claimRewardsMutation = useMutation(
- () => {
- if (vaultAccountId === undefined) {
- throw new Error('Something went wrong!');
- }
-
- const vaultId = newVaultId(
- window.bridge.api,
- vaultAccountId.toString(),
- collateralToken,
- WRAPPED_TOKEN as WrappedCurrency
- );
-
- return submitExtrinsicPromise(window.bridge.rewards.withdrawRewards(vaultId));
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries(['vaultsOverview', vaultAddress, collateralToken.ticker]);
- toast.success('Your rewards were successfully withdrawn.');
- }
+ const transaction = useTransaction(Transaction.REWARDS_WITHDRAW, {
+ onSuccess: () => {
+ queryClient.invalidateQueries(['vaultsOverview', vaultAddress, collateralToken.ticker]);
+ toast.success('Your rewards were successfully withdrawn.');
}
- );
+ });
const handleClickWithdrawRewards = () => {
- claimRewardsMutation.mutate();
+ if (vaultAccountId === undefined) return;
+
+ const vaultId = newVaultId(
+ window.bridge.api,
+ vaultAccountId.toString(),
+ collateralToken,
+ WRAPPED_TOKEN as WrappedCurrency
+ );
+
+ transaction.execute(vaultId);
};
const hasWithdrawableRewards =
@@ -87,11 +79,11 @@ const Rewards = ({
size='small'
variant='outlined'
onClick={handleClickWithdrawRewards}
- disabled={!hasWithdrawableRewards || claimRewardsMutation.isLoading}
- $loading={claimRewardsMutation.isLoading}
+ disabled={!hasWithdrawableRewards || transaction.isLoading}
+ $loading={transaction.isLoading}
>
{/* TODO: temporary approach. Loading spinner should be added to the CTA itself */}
- {claimRewardsMutation.isLoading && (
+ {transaction.isLoading && (
@@ -99,12 +91,12 @@ const Rewards = ({
Withdraw all rewards
)}
- {claimRewardsMutation.isError && (
+ {transaction.isError && (
claimRewardsMutation.reset()}
+ open={transaction.isError}
+ onClose={() => transaction.reset()}
title='Error'
- description={claimRewardsMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
diff --git a/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx b/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx
index 32f28166d1..4fbe4efd89 100644
--- a/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx
+++ b/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx
@@ -1,9 +1,7 @@
import { CollateralCurrencyExt, newMonetaryAmount } from '@interlay/interbtc-api';
import { MonetaryAmount } from '@interlay/monetary-js';
-import { ISubmittableResult } from '@polkadot/types/types';
import { useId } from '@react-aria/utils';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils';
import { CTA, ModalBody, ModalDivider, ModalFooter, ModalHeader, Span, Stack, TokenInput } from '@/component-library';
@@ -16,8 +14,8 @@ import {
isFormDisabled,
useForm
} from '@/lib/form';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { StepComponentProps, withStep } from '@/utils/hocs/step';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { useDepositCollateral } from '../../utils/use-deposit-collateral';
import { StyledDd, StyledDItem, StyledDl, StyledDt, StyledHr } from './CreateVaultWizard.styles';
@@ -39,6 +37,10 @@ const DepositCollateralStep = ({
const { t } = useTranslation();
const { collateral, fee, governance } = useDepositCollateral(collateralCurrency, minCollateralAmount);
+ const transaction = useTransaction(Transaction.VAULTS_REGISTER_NEW_COLLATERAL, {
+ onSuccess: onSuccessfulDeposit
+ });
+
const validationParams = {
minAmount: collateral.min.raw,
maxAmount: collateral.balance.raw,
@@ -50,7 +52,7 @@ const DepositCollateralStep = ({
if (!data.deposit) return;
const amount = newMonetaryAmount(data.deposit || 0, collateral.currency, true);
- registerNewVaultMutation.mutate(amount);
+ transaction.execute(amount);
};
const form = useForm({
@@ -59,13 +61,6 @@ const DepositCollateralStep = ({
onSubmit: handleSubmit
});
- const registerNewVaultMutation = useMutation>(
- (collateralAmount) => submitExtrinsic(window.bridge.vaults.registerNewCollateralVault(collateralAmount)),
- {
- onSuccess: onSuccessfulDeposit
- }
- );
-
const inputCollateralAmount = newSafeMonetaryAmount(form.values.deposit || 0, collateral.currency, true);
const isBtnDisabled = isFormDisabled(form);
@@ -108,17 +103,17 @@ const DepositCollateralStep = ({
-
+
{t('vault.deposit_collateral')}
- {registerNewVaultMutation.isError && (
+ {transaction.isError && (
registerNewVaultMutation.reset()}
+ open={transaction.isError}
+ onClose={() => transaction.reset()}
title='Error'
- description={registerNewVaultMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
>
diff --git a/src/utils/hooks/api/loans/use-loan-mutation.tsx b/src/utils/hooks/api/loans/use-loan-mutation.tsx
deleted file mode 100644
index 0057369b36..0000000000
--- a/src/utils/hooks/api/loans/use-loan-mutation.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { CurrencyExt } from '@interlay/interbtc-api';
-import { MonetaryAmount } from '@interlay/monetary-js';
-import { ISubmittableResult } from '@polkadot/types/types';
-import { useMutation, UseMutationResult } from 'react-query';
-
-import { LoanAction } from '@/types/loans';
-import { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
-
-type CreateLoanVariables = { loanType: LoanAction; amount: MonetaryAmount; isMaxAmount: boolean };
-
-const mutateLoan = ({ loanType, amount, isMaxAmount }: CreateLoanVariables) => {
- const extrinsicData = (() => {
- switch (loanType) {
- case 'lend':
- return window.bridge.loans.lend(amount.currency, amount);
- case 'withdraw':
- if (isMaxAmount) {
- return window.bridge.loans.withdrawAll(amount.currency);
- } else {
- return window.bridge.loans.withdraw(amount.currency, amount);
- }
- case 'borrow':
- return window.bridge.loans.borrow(amount.currency, amount);
- case 'repay':
- if (isMaxAmount) {
- return window.bridge.loans.repayAll(amount.currency);
- } else {
- return window.bridge.loans.repay(amount.currency, amount);
- }
- }
- })();
-
- return submitExtrinsicPromise(extrinsicData);
-};
-
-type UseLoanMutation = { onSuccess: () => void; onError: (error: Error) => void };
-
-const useLoanMutation = ({
- onSuccess,
- onError
-}: UseLoanMutation): UseMutationResult => {
- return useMutation(mutateLoan, {
- onSuccess,
- onError
- });
-};
-
-export { useLoanMutation };
-export type { UseLoanMutation };
diff --git a/src/utils/hooks/transaction/index.ts b/src/utils/hooks/transaction/index.ts
new file mode 100644
index 0000000000..3a845f06ae
--- /dev/null
+++ b/src/utils/hooks/transaction/index.ts
@@ -0,0 +1,2 @@
+export { Transaction } from './types';
+export { useTransaction } from './use-transaction';
diff --git a/src/utils/hooks/transaction/types/amm.ts b/src/utils/hooks/transaction/types/amm.ts
new file mode 100644
index 0000000000..7b6cc56af9
--- /dev/null
+++ b/src/utils/hooks/transaction/types/amm.ts
@@ -0,0 +1,28 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface SwapAction extends TransactionAction {
+ type: Transaction.AMM_SWAP;
+ args: Parameters;
+}
+
+interface PoolAddLiquidityAction extends TransactionAction {
+ type: Transaction.AMM_ADD_LIQUIDITY;
+ args: Parameters;
+}
+
+interface PoolRemoveLiquidityAction extends TransactionAction {
+ type: Transaction.AMM_REMOVE_LIQUIDITY;
+ args: Parameters;
+}
+
+interface PoolClaimRewardsAction extends TransactionAction {
+ type: Transaction.AMM_CLAIM_REWARDS;
+ args: Parameters;
+}
+
+type AMMActions = SwapAction | PoolAddLiquidityAction | PoolRemoveLiquidityAction | PoolClaimRewardsAction;
+
+export type { AMMActions };
diff --git a/src/utils/hooks/transaction/types/escrow.ts b/src/utils/hooks/transaction/types/escrow.ts
new file mode 100644
index 0000000000..7003d1f796
--- /dev/null
+++ b/src/utils/hooks/transaction/types/escrow.ts
@@ -0,0 +1,46 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface EscrowCreateLockAction extends TransactionAction {
+ type: Transaction.ESCROW_CREATE_LOCK;
+ args: Parameters;
+}
+
+interface EscrowInscreaseLookedTimeAndAmountAction extends TransactionAction {
+ type: Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT;
+ args: [
+ ...Parameters,
+ ...Parameters
+ ];
+}
+interface EscrowIncreaseLockAmountAction extends TransactionAction {
+ type: Transaction.ESCROW_INCREASE_LOCKED_AMOUNT;
+ args: Parameters;
+}
+
+interface EscrowIncreaseLockTimeAction extends TransactionAction {
+ type: Transaction.ESCROW_INCREASE_LOCKED_TIME;
+ args: Parameters;
+}
+
+interface EscrowWithdrawRewardsAction extends TransactionAction {
+ type: Transaction.ESCROW_WITHDRAW_REWARDS;
+ args: Parameters;
+}
+
+interface EscrowWithdrawAction extends TransactionAction {
+ type: Transaction.ESCROW_WITHDRAW;
+ args: Parameters;
+}
+
+type EscrowActions =
+ | EscrowCreateLockAction
+ | EscrowInscreaseLookedTimeAndAmountAction
+ | EscrowIncreaseLockAmountAction
+ | EscrowIncreaseLockTimeAction
+ | EscrowWithdrawRewardsAction
+ | EscrowWithdrawAction;
+
+export type { EscrowActions };
diff --git a/src/utils/hooks/transaction/types/index.ts b/src/utils/hooks/transaction/types/index.ts
new file mode 100644
index 0000000000..538f820678
--- /dev/null
+++ b/src/utils/hooks/transaction/types/index.ts
@@ -0,0 +1,79 @@
+import { ExtrinsicStatus } from '@polkadot/types/interfaces';
+
+import { AMMActions } from './amm';
+import { EscrowActions } from './escrow';
+import { IssueActions } from './issue';
+import { LoansActions } from './loans';
+import { RedeemActions } from './redeem';
+import { ReplaceActions } from './replace';
+import { RewardsActions } from './rewards';
+import { TokensActions } from './tokens';
+import { VaultsActions } from './vaults';
+
+enum Transaction {
+ // Issue
+ ISSUE_REQUEST = 'ISSUE_REQUEST',
+ ISSUE_EXECUTE = 'ISSUE_EXECUTE',
+ // Redeem
+ REDEEM_REQUEST = 'REDEEM_REQUEST',
+ REDEEM_CANCEL = 'REDEEM_CANCEL',
+ REDEEM_BURN = 'REDEEM_BURN',
+ // Replace
+ REPLACE_REQUEST = 'REPLACE_REQUEST',
+ // Escrow
+ ESCROW_CREATE_LOCK = 'ESCROW_CREATE_LOCK',
+ ESCROW_INCREASE_LOCKED_TIME = 'ESCROW_INCREASE_LOCKED_TIME',
+ ESCROW_INCREASE_LOCKED_AMOUNT = 'ESCROW_INCREASE_LOCKED_AMOUNT',
+ ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT = 'ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT',
+ ESCROW_WITHDRAW_REWARDS = 'ESCROW_WITHDRAW_REWARDS',
+ ESCROW_WITHDRAW = 'ESCROW_WITHDRAW',
+ // Tokens
+ TOKENS_TRANSFER = 'TOKENS_TRANSFER',
+ // Vaults
+ VAULTS_DEPOSIT_COLLATERAL = 'VAULTS_DEPOSIT_COLLATERAL',
+ VAULTS_WITHDRAW_COLLATERAL = 'VAULTS_WITHDRAW_COLLATERAL',
+ VAULTS_REGISTER_NEW_COLLATERAL = 'VAULTS_REGISTER_NEW_COLLATERAL',
+ // Rewards
+ REWARDS_WITHDRAW = 'REWARDS_WITHDRAW',
+ // Loans
+ LOANS_CLAIM_REWARDS = 'LOANS_CLAIM_REWARDS',
+ LOANS_ENABLE_COLLATERAL = 'LOANS_ENABLE_COLLATERAL',
+ LOANS_DISABLE_COLLATERAL = 'LOANS_DISABLE_COLLATERAL',
+ LOANS_LEND = 'LOANS_LEND',
+ LOANS_WITHDRAW = 'LOANS_WITHDRAW',
+ LOANS_WITHDRAW_ALL = 'LOANS_WITHDRAW_ALL',
+ LOANS_BORROW = 'LOANS_BORROW',
+ LOANS_REPAY = 'LOANS_REPAY',
+ LOANS_REPAY_ALL = 'LOANS_REPAY_ALL',
+ // AMM
+ AMM_SWAP = 'AMM_SWAP',
+ AMM_ADD_LIQUIDITY = 'AMM_ADD_LIQUIDITY',
+ AMM_REMOVE_LIQUIDITY = 'AMM_REMOVE_LIQUIDITY',
+ AMM_CLAIM_REWARDS = 'AMM_CLAIM_REWARDS'
+}
+
+type TransactionEvents = {
+ onReady?: () => void;
+};
+
+interface TransactionAction {
+ accountAddress: string;
+ events: TransactionEvents;
+ customStatus?: ExtrinsicStatus['type'];
+}
+
+type TransactionActions =
+ | EscrowActions
+ | IssueActions
+ | RedeemActions
+ | ReplaceActions
+ | TokensActions
+ | LoansActions
+ | AMMActions
+ | VaultsActions
+ | RewardsActions;
+
+type TransactionArgs = Extract['args'];
+
+export { Transaction };
+export type { TransactionAction, TransactionActions, TransactionArgs, TransactionEvents };
diff --git a/src/utils/hooks/transaction/types/issue.ts b/src/utils/hooks/transaction/types/issue.ts
new file mode 100644
index 0000000000..dfa3b9d5a3
--- /dev/null
+++ b/src/utils/hooks/transaction/types/issue.ts
@@ -0,0 +1,18 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface IssueRequestAction extends TransactionAction {
+ type: Transaction.ISSUE_REQUEST;
+ args: Parameters;
+}
+
+interface IssueExecuteAction extends TransactionAction {
+ type: Transaction.ISSUE_EXECUTE;
+ args: Parameters;
+}
+
+type IssueActions = IssueRequestAction | IssueExecuteAction;
+
+export type { IssueActions };
diff --git a/src/utils/hooks/transaction/types/loans.ts b/src/utils/hooks/transaction/types/loans.ts
new file mode 100644
index 0000000000..27797c68d9
--- /dev/null
+++ b/src/utils/hooks/transaction/types/loans.ts
@@ -0,0 +1,62 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface LoansClaimRewardsAction extends TransactionAction {
+ type: Transaction.LOANS_CLAIM_REWARDS;
+ args: Parameters;
+}
+
+interface LoansEnabledCollateralAction extends TransactionAction {
+ type: Transaction.LOANS_ENABLE_COLLATERAL;
+ args: Parameters;
+}
+
+interface LoansDisabledCollateralAction extends TransactionAction {
+ type: Transaction.LOANS_DISABLE_COLLATERAL;
+ args: Parameters;
+}
+
+interface LoansLendAction extends TransactionAction {
+ type: Transaction.LOANS_LEND;
+ args: Parameters;
+}
+
+interface LoansWithdrawAction extends TransactionAction {
+ type: Transaction.LOANS_WITHDRAW;
+ args: Parameters;
+}
+
+interface LoansWithdrawAllAction extends TransactionAction {
+ type: Transaction.LOANS_WITHDRAW_ALL;
+ args: Parameters;
+}
+
+interface LoansBorrowAction extends TransactionAction {
+ type: Transaction.LOANS_BORROW;
+ args: Parameters;
+}
+
+interface LoansRepayAction extends TransactionAction {
+ type: Transaction.LOANS_REPAY;
+ args: Parameters;
+}
+
+interface LoansRepayAllAction extends TransactionAction {
+ type: Transaction.LOANS_REPAY_ALL;
+ args: Parameters;
+}
+
+type LoansActions =
+ | LoansClaimRewardsAction
+ | LoansEnabledCollateralAction
+ | LoansDisabledCollateralAction
+ | LoansLendAction
+ | LoansWithdrawAction
+ | LoansWithdrawAllAction
+ | LoansBorrowAction
+ | LoansRepayAction
+ | LoansRepayAllAction;
+
+export type { LoansActions };
diff --git a/src/utils/hooks/transaction/types/redeem.ts b/src/utils/hooks/transaction/types/redeem.ts
new file mode 100644
index 0000000000..1282278693
--- /dev/null
+++ b/src/utils/hooks/transaction/types/redeem.ts
@@ -0,0 +1,23 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface RedeemCancelAction extends TransactionAction {
+ type: Transaction.REDEEM_CANCEL;
+ args: Parameters;
+}
+
+interface RedeemBurnAction extends TransactionAction {
+ type: Transaction.REDEEM_BURN;
+ args: Parameters;
+}
+
+interface RedeemRequestAction extends TransactionAction {
+ type: Transaction.REDEEM_REQUEST;
+ args: Parameters;
+}
+
+type RedeemActions = RedeemRequestAction | RedeemCancelAction | RedeemBurnAction;
+
+export type { RedeemActions };
diff --git a/src/utils/hooks/transaction/types/replace.ts b/src/utils/hooks/transaction/types/replace.ts
new file mode 100644
index 0000000000..4fab08e0e7
--- /dev/null
+++ b/src/utils/hooks/transaction/types/replace.ts
@@ -0,0 +1,13 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface ReplaceRequestAction extends TransactionAction {
+ type: Transaction.REPLACE_REQUEST;
+ args: Parameters;
+}
+
+type ReplaceActions = ReplaceRequestAction;
+
+export type { ReplaceActions };
diff --git a/src/utils/hooks/transaction/types/rewards.ts b/src/utils/hooks/transaction/types/rewards.ts
new file mode 100644
index 0000000000..f77f61f7c4
--- /dev/null
+++ b/src/utils/hooks/transaction/types/rewards.ts
@@ -0,0 +1,13 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '.';
+import { TransactionAction } from '.';
+
+interface RewardsWithdrawAction extends TransactionAction {
+ type: Transaction.REWARDS_WITHDRAW;
+ args: Parameters;
+}
+
+type RewardsActions = RewardsWithdrawAction;
+
+export type { RewardsActions };
diff --git a/src/utils/hooks/transaction/types/tokens.ts b/src/utils/hooks/transaction/types/tokens.ts
new file mode 100644
index 0000000000..a1c1e0da64
--- /dev/null
+++ b/src/utils/hooks/transaction/types/tokens.ts
@@ -0,0 +1,13 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface TokensTransferAction extends TransactionAction {
+ type: Transaction.TOKENS_TRANSFER;
+ args: Parameters;
+}
+
+type TokensActions = TokensTransferAction;
+
+export type { TokensActions };
diff --git a/src/utils/hooks/transaction/types/vaults.ts b/src/utils/hooks/transaction/types/vaults.ts
new file mode 100644
index 0000000000..1c4040fd17
--- /dev/null
+++ b/src/utils/hooks/transaction/types/vaults.ts
@@ -0,0 +1,23 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface VaultsDepositCollateralAction extends TransactionAction {
+ type: Transaction.VAULTS_DEPOSIT_COLLATERAL;
+ args: Parameters;
+}
+
+interface VaultsWithdrawCollateralAction extends TransactionAction {
+ type: Transaction.VAULTS_WITHDRAW_COLLATERAL;
+ args: Parameters;
+}
+
+interface VaultsRegisterNewCollateralAction extends TransactionAction {
+ type: Transaction.VAULTS_REGISTER_NEW_COLLATERAL;
+ args: Parameters;
+}
+
+type VaultsActions = VaultsDepositCollateralAction | VaultsWithdrawCollateralAction | VaultsRegisterNewCollateralAction;
+
+export type { VaultsActions };
diff --git a/src/utils/hooks/transaction/use-transaction.ts b/src/utils/hooks/transaction/use-transaction.ts
new file mode 100644
index 0000000000..d18291f94c
--- /dev/null
+++ b/src/utils/hooks/transaction/use-transaction.ts
@@ -0,0 +1,122 @@
+import { ExtrinsicStatus } from '@polkadot/types/interfaces';
+import { ISubmittableResult } from '@polkadot/types/types';
+import { useCallback } from 'react';
+import { MutationFunction, useMutation, UseMutationOptions, UseMutationResult } from 'react-query';
+
+import { useSubstrate } from '@/lib/substrate';
+
+import { Transaction, TransactionActions, TransactionArgs } from './types';
+import { getExtrinsic, getStatus } from './utils/extrinsic';
+import { submitTransaction } from './utils/submit';
+
+type UseTransactionOptions = Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> & {
+ customStatus?: ExtrinsicStatus['type'];
+};
+
+// TODO: add feeEstimate and feeEstimateAsync
+type ExecuteArgs = {
+ // Executes the transaction
+ execute(...args: TransactionArgs): void;
+ // Similar to execute but returns a promise which can be awaited.
+ executeAsync(...args: TransactionArgs): Promise;
+};
+
+// TODO: add feeEstimate and feeEstimateAsync
+type ExecuteTypeArgs = {
+ execute(type: D, ...args: TransactionArgs): void;
+ executeAsync(type: D, ...args: TransactionArgs): Promise;
+};
+
+type InheritAttrs = Omit<
+ UseMutationResult,
+ 'mutate' | 'mutateAsync'
+>;
+
+type UseTransactionResult = InheritAttrs & (ExecuteArgs | ExecuteTypeArgs);
+
+const mutateTransaction: MutationFunction = async (params) => {
+ const extrinsics = await getExtrinsic(params);
+ const expectedStatus = params.customStatus || getStatus(params.type);
+
+ return submitTransaction(window.bridge.api, params.accountAddress, extrinsics, expectedStatus, params.events);
+};
+
+// The three declared functions are use to infer types on diferent implementations
+// TODO: missing xcm transaction
+function useTransaction(
+ type: T,
+ options?: UseTransactionOptions
+): Exclude, ExecuteTypeArgs>;
+function useTransaction(
+ options?: UseTransactionOptions
+): Exclude, ExecuteArgs>;
+function useTransaction(
+ typeOrOptions?: T | UseTransactionOptions,
+ options?: UseTransactionOptions
+): UseTransactionResult {
+ const { state } = useSubstrate();
+
+ const hasOnlyOptions = typeof typeOrOptions !== 'string';
+
+ const { mutate, mutateAsync, ...transactionMutation } = useMutation(
+ mutateTransaction,
+ (hasOnlyOptions ? typeOrOptions : options) as UseTransactionOptions
+ );
+
+ // Handles params for both type of implementations
+ const getParams = useCallback(
+ (args: Parameters['execute']>) => {
+ let params = {};
+
+ // Assign correct params for when transaction type is declared on hook params
+ if (typeof typeOrOptions === 'string') {
+ params = { type: typeOrOptions, args };
+ } else {
+ // Assign correct params for when transaction type is declared on execution level
+ const [type, ...restArgs] = args;
+ params = { type, args: restArgs };
+ }
+
+ // Execution should only ran when authenticated
+ const accountAddress = state.selectedAccount?.address;
+
+ // TODO: add event `onReady`
+ return {
+ ...params,
+ accountAddress,
+ customStatus: options?.customStatus
+ } as TransactionActions;
+ },
+ [options?.customStatus, state.selectedAccount?.address, typeOrOptions]
+ );
+
+ const handleExecute = useCallback(
+ (...args: Parameters['execute']>) => {
+ const params = getParams(args);
+
+ return mutate(params);
+ },
+ [getParams, mutate]
+ );
+
+ const handleExecuteAsync = useCallback(
+ (...args: Parameters['executeAsync']>) => {
+ const params = getParams(args);
+
+ return mutateAsync(params);
+ },
+ [getParams, mutateAsync]
+ );
+
+ return {
+ ...transactionMutation,
+ execute: handleExecute,
+ executeAsync: handleExecuteAsync
+ };
+}
+
+export { useTransaction };
+export type { UseTransactionResult };
diff --git a/src/utils/hooks/transaction/utils/extrinsic.ts b/src/utils/hooks/transaction/utils/extrinsic.ts
new file mode 100644
index 0000000000..23346819db
--- /dev/null
+++ b/src/utils/hooks/transaction/utils/extrinsic.ts
@@ -0,0 +1,133 @@
+import { ExtrinsicData } from '@interlay/interbtc-api';
+import { ExtrinsicStatus } from '@polkadot/types/interfaces';
+
+import { Transaction, TransactionActions } from '../types';
+
+/**
+ * SUMMARY: Maps each transaction to the correct lib call,
+ * while maintaining a safe-type check.
+ * HOW TO ADD NEW TRANSACTION: find the correct module to add the transaction
+ * in the types folder. In case you are adding a new type to the loans modules, go
+ * to types/loans and add your new transaction as an action. This actions needs to also be added to the
+ * types/index TransactionActions type. After that, you should be able to add it to the function.
+ * @param {TransactionActions} params contains the type of transaction and
+ * the related args to call the mapped lib call
+ * @return {Promise} every transaction return an extrinsic
+ */
+const getExtrinsic = async (params: TransactionActions): Promise => {
+ switch (params.type) {
+ /* START - AMM */
+ case Transaction.AMM_SWAP:
+ return window.bridge.amm.swap(...params.args);
+ case Transaction.AMM_ADD_LIQUIDITY:
+ return window.bridge.amm.addLiquidity(...params.args);
+ case Transaction.AMM_REMOVE_LIQUIDITY:
+ return window.bridge.amm.removeLiquidity(...params.args);
+ case Transaction.AMM_CLAIM_REWARDS:
+ return window.bridge.amm.claimFarmingRewards(...params.args);
+ /* END - AMM */
+
+ /* START - ISSUE */
+ case Transaction.ISSUE_REQUEST:
+ return window.bridge.issue.request(...params.args);
+ case Transaction.ISSUE_EXECUTE:
+ return window.bridge.issue.execute(...params.args);
+ /* END - ISSUE */
+
+ /* START - REDEEM */
+ case Transaction.REDEEM_CANCEL:
+ return window.bridge.redeem.cancel(...params.args);
+ case Transaction.REDEEM_BURN:
+ return window.bridge.redeem.burn(...params.args);
+ case Transaction.REDEEM_REQUEST:
+ return window.bridge.redeem.request(...params.args);
+ /* END - REDEEM */
+
+ /* START - REPLACE */
+ case Transaction.REPLACE_REQUEST:
+ return window.bridge.replace.request(...params.args);
+ /* END - REPLACE */
+
+ /* START - TOKENS */
+ case Transaction.TOKENS_TRANSFER:
+ return window.bridge.tokens.transfer(...params.args);
+ /* END - TOKENS */
+
+ /* START - LOANS */
+ case Transaction.LOANS_CLAIM_REWARDS:
+ return window.bridge.loans.claimAllSubsidyRewards();
+ case Transaction.LOANS_BORROW:
+ return window.bridge.loans.borrow(...params.args);
+ case Transaction.LOANS_LEND:
+ return window.bridge.loans.lend(...params.args);
+ case Transaction.LOANS_REPAY:
+ return window.bridge.loans.repay(...params.args);
+ case Transaction.LOANS_REPAY_ALL:
+ return window.bridge.loans.repayAll(...params.args);
+ case Transaction.LOANS_WITHDRAW:
+ return window.bridge.loans.withdraw(...params.args);
+ case Transaction.LOANS_WITHDRAW_ALL:
+ return window.bridge.loans.withdrawAll(...params.args);
+ case Transaction.LOANS_DISABLE_COLLATERAL:
+ return window.bridge.loans.disableAsCollateral(...params.args);
+ case Transaction.LOANS_ENABLE_COLLATERAL:
+ return window.bridge.loans.enableAsCollateral(...params.args);
+ /* END - LOANS */
+
+ /* START - LOANS */
+ case Transaction.VAULTS_DEPOSIT_COLLATERAL:
+ return window.bridge.vaults.depositCollateral(...params.args);
+ case Transaction.VAULTS_WITHDRAW_COLLATERAL:
+ return window.bridge.vaults.withdrawCollateral(...params.args);
+ case Transaction.VAULTS_REGISTER_NEW_COLLATERAL:
+ return window.bridge.vaults.registerNewCollateralVault(...params.args);
+ /* START - REWARDS */
+ case Transaction.REWARDS_WITHDRAW:
+ return window.bridge.rewards.withdrawRewards(...params.args);
+ /* START - REWARDS */
+ /* END - LOANS */
+
+ /* START - ESCROW */
+ case Transaction.ESCROW_CREATE_LOCK:
+ return window.bridge.escrow.createLock(...params.args);
+ case Transaction.ESCROW_INCREASE_LOCKED_AMOUNT:
+ return window.bridge.escrow.increaseAmount(...params.args);
+ case Transaction.ESCROW_INCREASE_LOCKED_TIME:
+ return window.bridge.escrow.increaseUnlockHeight(...params.args);
+ case Transaction.ESCROW_WITHDRAW:
+ return window.bridge.escrow.withdraw(...params.args);
+ case Transaction.ESCROW_WITHDRAW_REWARDS:
+ return window.bridge.escrow.withdrawRewards(...params.args);
+ case Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT: {
+ const [amount, unlockHeight] = params.args;
+ const txs = [
+ window.bridge.api.tx.escrow.increaseAmount(amount),
+ window.bridge.api.tx.escrow.increaseUnlockHeight(unlockHeight)
+ ];
+ const batch = window.bridge.api.tx.utility.batchAll(txs);
+
+ return { extrinsic: batch };
+ }
+ /* END - ESCROW */
+ }
+};
+
+/**
+ * The status where we want to be notified on the transaction completion
+ * @param {Transaction} type type of transaction
+ * @return {ExtrinsicStatus.type} transaction status
+ */
+const getStatus = (type: Transaction): ExtrinsicStatus['type'] => {
+ switch (type) {
+ // When requesting a replace, wait for the finalized event because we cannot revert BTC transactions.
+ // For more details see: https://github.com/interlay/interbtc-api/pull/373#issuecomment-1058949000
+ case Transaction.ISSUE_REQUEST:
+ case Transaction.REDEEM_REQUEST:
+ case Transaction.REPLACE_REQUEST:
+ return 'Finalized';
+ default:
+ return 'InBlock';
+ }
+};
+
+export { getExtrinsic, getStatus };
diff --git a/src/utils/hooks/transaction/utils/submit.ts b/src/utils/hooks/transaction/utils/submit.ts
new file mode 100644
index 0000000000..d1c832b023
--- /dev/null
+++ b/src/utils/hooks/transaction/utils/submit.ts
@@ -0,0 +1,107 @@
+import { ExtrinsicData } from '@interlay/interbtc-api';
+import { ApiPromise } from '@polkadot/api';
+import { AddressOrPair, SubmittableExtrinsic } from '@polkadot/api/types';
+import { DispatchError } from '@polkadot/types/interfaces';
+import { ExtrinsicStatus } from '@polkadot/types/interfaces/author';
+import { ISubmittableResult } from '@polkadot/types/types';
+
+import { TransactionEvents } from '../types';
+
+type HandleTransactionResult = { result: ISubmittableResult; unsubscribe: () => void };
+
+// When passing { nonce: -1 } to signAndSend the API will use system.accountNextIndex to determine the nonce
+const transactionOptions = { nonce: -1 };
+
+const handleTransaction = async (
+ account: AddressOrPair,
+ extrinsicData: ExtrinsicData,
+ expectedStatus?: ExtrinsicStatus['type'],
+ callbacks?: TransactionEvents
+) => {
+ let isComplete = false;
+
+ // Extrinsic status
+ let isReady = false;
+
+ return new Promise((resolve, reject) => {
+ let unsubscribe: () => void;
+
+ (extrinsicData.extrinsic as SubmittableExtrinsic<'promise'>)
+ .signAndSend(account, transactionOptions, callback)
+ .then((unsub) => (unsubscribe = unsub))
+ .catch((error) => reject(error));
+
+ function callback(result: ISubmittableResult): void {
+ const { onReady } = callbacks || {};
+
+ if (!isReady && result.status.isReady) {
+ onReady?.();
+ isReady = true;
+ }
+
+ if (!isComplete) {
+ isComplete = expectedStatus === result.status.type;
+ }
+
+ if (isComplete) {
+ resolve({ unsubscribe, result });
+ }
+ }
+ });
+};
+
+const getErrorMessage = (api: ApiPromise, dispatchError: DispatchError) => {
+ const { isModule, asModule, isBadOrigin } = dispatchError;
+
+ // Construct error message
+ const message = 'The transaction failed.';
+
+ // Runtime error in one of the parachain modules
+ if (isModule) {
+ // for module errors, we have the section indexed, lookup
+ const decoded = api.registry.findMetaError(asModule);
+ const { docs, name, section } = decoded;
+ return message.concat(` The error code is ${section}.${name}. ${docs.join(' ')}`);
+ }
+
+ // Bad origin
+ if (isBadOrigin) {
+ return message.concat(
+ ` The error is caused by using an incorrect account. The error code is BadOrigin ${dispatchError}.`
+ );
+ }
+
+ return message.concat(` The error is ${dispatchError}.`);
+};
+
+/**
+ * Handles transaction submittion and error
+ * @param {ApiPromise} api polkadot api wrapper
+ * @param {AddressOrPair} account account address
+ * @param {ExtrinsicData} extrinsicData transaction extrinsic data
+ * @param {ExtrinsicStatus.type} expectedStatus status where the transaction is counted as fulfilled
+ * @param {TransactionEvents} callbacks a set of events emitted accross the lifecycle of the transaction (i.e Bro)
+ * @return {Promise} transaction data that also can contain meta data in case of error
+ */
+const submitTransaction = async (
+ api: ApiPromise,
+ account: AddressOrPair,
+ extrinsicData: ExtrinsicData,
+ expectedStatus?: ExtrinsicStatus['type'],
+ callbacks?: TransactionEvents
+): Promise => {
+ const { result, unsubscribe } = await handleTransaction(account, extrinsicData, expectedStatus, callbacks);
+
+ unsubscribe();
+
+ const { dispatchError } = result;
+
+ if (dispatchError) {
+ const message = getErrorMessage(api, dispatchError);
+ throw new Error(message);
+ }
+
+ return result;
+};
+
+export { submitTransaction };