diff --git a/src/component-library/Select/SelectModal.tsx b/src/component-library/Select/SelectModal.tsx index 16b1000945..b5549aa781 100644 --- a/src/component-library/Select/SelectModal.tsx +++ b/src/component-library/Select/SelectModal.tsx @@ -51,6 +51,7 @@ const SelectModal = forwardRef( void; - selectedVault: VaultApiType | undefined; - isPending: boolean; - error?: boolean; -} - -interface VaultOptionProps { - vault: VaultApiType | undefined; - identity?: string; - error?: boolean; - getCurrencyFromIdPrimitive: (idPrimitive: InterbtcPrimitivesCurrencyId) => CurrencyExt; -} -const VaultOption = ({ vault, error, identity, getCurrencyFromIdPrimitive }: VaultOptionProps): JSX.Element => { - const { t } = useTranslation(); - - if (!vault) { - return t('select_vault'); - } - - const getCurrencyTicker = (vault: VaultApiType): string => - getCurrencyFromIdPrimitive(vault[0].currencies.collateral).ticker; - - return ( -
- {error ? ( - - ) : ( - - )} - - {shortAddress(vault[0].accountId.toString())} - {identity &&
{shortAddress(identity)}
} -
- {getCurrencyTicker(vault)} - - {displayMonetaryAmount(vault[1])} BTC - -
- ); -}; - -const VaultSelect = ({ label, vaults, onChange, selectedVault, isPending, error }: Props): JSX.Element => { - const { t } = useTranslation(); - - const { - isLoading: currenciesLoading, - isIdle: currenciesIdle, - getCurrencyFromIdPrimitive, - error: currenciesError - } = useGetCurrencies(true); - useErrorHandler(currenciesError); - - const { - isIdle: identitiesIdle, - isLoading: identitiesLoading, - data: identities, - error: identitiesError - } = useGetIdentities(true); - useErrorHandler(identitiesError); - - const isLoading = isPending || currenciesIdle || currenciesLoading || identitiesIdle || identitiesLoading; - return ( - - ); -}; - -export default VaultSelect; diff --git a/src/legacy-components/VaultsSelector/index.tsx b/src/legacy-components/VaultsSelector/index.tsx deleted file mode 100644 index ec01286dbe..0000000000 --- a/src/legacy-components/VaultsSelector/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { BitcoinAmount } from '@interlay/monetary-js'; -import clsx from 'clsx'; -import * as React from 'react'; -import { FieldError } from 'react-hook-form'; - -import { VaultApiType } from '@/common/types/vault.types'; -import { TreasuryAction } from '@/types/general'; -import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names'; -import STATUSES from '@/utils/constants/statuses'; - -import VaultSelect from './VaultSelect'; - -interface Props { - label: string; - requiredCapacity: BitcoinAmount; - isShown: boolean; - onSelectionCallback: (vault: VaultApiType | undefined) => void; - error?: FieldError; - treasuryAction: TreasuryAction; -} - -const VaultsSelector = ({ - label, - requiredCapacity, - isShown, - onSelectionCallback, - error, - treasuryAction -}: Props): JSX.Element => { - const [selectedVault, setSelectedVault] = React.useState(undefined); - const [allVaults, setAllVaults] = React.useState([]); - const [vaultsStatus, setVaultsStatus] = React.useState(STATUSES.IDLE); - - const availableVaults = React.useMemo(() => { - // Filters out vaults with lower than required capacity and sorts by accountId - // to have vaults with the same accountId grouped together - return allVaults - .filter((vault) => vault[1].gt(BitcoinAmount.zero())) // TODO: redundant check - .filter((vault) => vault[1].gte(requiredCapacity)) - .sort((vaultA, vaultB) => { - const vaultAId = vaultA[0].accountId.toString(); - const vaultBId = vaultB[0].accountId.toString(); - return vaultAId < vaultBId ? -1 : vaultAId > vaultBId ? 1 : 0; - }); - }, [allVaults, requiredCapacity]); - - const handleVaultSelection = React.useCallback( - (newVault: VaultApiType | undefined) => { - setSelectedVault(newVault); - onSelectionCallback(newVault); - }, - [onSelectionCallback] - ); - - React.useEffect(() => { - (async () => { - setVaultsStatus(STATUSES.PENDING); - let availableVaults; - if (treasuryAction === 'issue') { - availableVaults = await window.bridge.vaults.getVaultsWithIssuableTokens(); - } else if (treasuryAction === 'redeem') { - availableVaults = await window.bridge.vaults.getVaultsWithRedeemableTokens(); - } else { - throw new Error(`Invalid treasuryAction (${treasuryAction})!`); - } - setAllVaults(Array.from(availableVaults)); - setVaultsStatus(STATUSES.RESOLVED); - })(); - }, [treasuryAction]); - - return ( -
- {/* Keeping the component mounted at all times to prevent refetching of the vaults */} - {isShown && ( - <> - -

- {error?.message} -

- - )} -
- ); -}; - -export default VaultsSelector; diff --git a/src/pages/BTC/BTCOverview/__tests__/components/IssueForm.test.tsx b/src/pages/BTC/BTCOverview/__tests__/components/IssueForm.test.tsx new file mode 100644 index 0000000000..fe3aa3a89c --- /dev/null +++ b/src/pages/BTC/BTCOverview/__tests__/components/IssueForm.test.tsx @@ -0,0 +1,249 @@ +import * as api from '@interlay/interbtc-api'; +import { BitcoinAmount } from '@interlay/monetary-js'; + +import { GOVERNANCE_TOKEN, RELAY_CHAIN_NATIVE_TOKEN } from '@/config/relay-chains'; +import { MOCK_FEE, MOCK_ISSUE, MOCK_VAULTS } from '@/test/mocks/@interlay/interbtc-api'; +import { mockIssue } from '@/test/mocks/@interlay/interbtc-api/issue'; +import { waitForFeeEstimate, waitForTransactionExecute } from '@/test/pages/utils/transaction'; +import { act, render, screen, userEvent, waitFor, within } from '@/test/test-utils'; +import { NATIVE_CURRENCIES } from '@/utils/constants/currency'; +import * as oracle from '@/utils/hooks/api/oracle/use-get-oracle-currencies'; + +import { IssueForm } from '../../components'; + +const { request } = MOCK_ISSUE.MODULE; +const { getVaultsWithIssuableTokens, getVaultsWithRedeemableTokens } = MOCK_VAULTS.MODULE; + +const { VAULTS_TOKENS, VAULTS_ID, VAULT_COLLATERAL } = MOCK_VAULTS.DATA; +const { REQUEST_LIMIT, DUST_VALUE, ISSUE_AMOUNTS } = MOCK_ISSUE.DATA; +const { ISSUE_FEE, GRIEFING_COLLATERAL_RATE } = MOCK_FEE.DATA; + +jest.mock('../../components/LegacyIssueModal', () => ({ LegacyIssueModal: () =>
legacy issue modal
})); + +describe('IssueForm', () => { + const { singleVaultMaxIssuable } = REQUEST_LIMIT.FULL; + + beforeEach(() => { + getVaultsWithRedeemableTokens.mockResolvedValue(VAULTS_TOKENS.FULL); + getVaultsWithIssuableTokens.mockResolvedValue(VAULTS_TOKENS.FULL); + jest.spyOn(api, 'getIssueRequestsFromExtrinsicResult').mockResolvedValue([mockIssue]); + }); + + it('should be able to issue request with default security deposit', async () => { + await render( + + ); + + userEvent.type(screen.getByRole('textbox', { name: /amount/i }), singleVaultMaxIssuable.toString()); + + await waitForFeeEstimate(request); + + userEvent.click(screen.getByRole('button', { name: /issue/i })); + + await waitForTransactionExecute(request); + + expect(request).toHaveBeenCalledWith( + singleVaultMaxIssuable, + VAULTS_ID.RELAY.accountId, + VAULT_COLLATERAL.RELAY, + false, + VAULTS_TOKENS.FULL, + GOVERNANCE_TOKEN + ); + + await waitFor(() => { + expect(screen.getByText(/legacy issue modal/i)).toBeInTheDocument(); + }); + + // form should reset + expect(screen.getByRole('textbox', { name: /amount/i })).toHaveTextContent(''); + }); + + it('should be able to issue request with custom security deposit', async () => { + jest.spyOn(oracle, 'useGetOracleCurrencies').mockReturnValue({ data: NATIVE_CURRENCIES }); + + await render( + + ); + + userEvent.click(screen.getByRole('button', { name: /security deposit token/i })); + + await waitFor(() => { + expect(screen.getByRole('dialog', { name: /select token/i })).toBeInTheDocument(); + }); + + const securityDepositDialog = within(screen.getByRole('dialog', { name: /select token/i })); + + userEvent.click(securityDepositDialog.getByRole('row', { name: RELAY_CHAIN_NATIVE_TOKEN.ticker })); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /security deposit token/i })).toHaveTextContent( + RELAY_CHAIN_NATIVE_TOKEN.ticker + ); + }); + + userEvent.type(screen.getByRole('textbox', { name: /amount/i }), singleVaultMaxIssuable.toString()); + + await waitForFeeEstimate(request); + + userEvent.click(screen.getByRole('button', { name: /issue/i })); + + await waitForTransactionExecute(request); + + expect(request).toHaveBeenCalledWith( + singleVaultMaxIssuable, + VAULTS_ID.RELAY.accountId, + VAULT_COLLATERAL.RELAY, + false, + VAULTS_TOKENS.FULL, + RELAY_CHAIN_NATIVE_TOKEN + ); + + await waitFor(() => { + expect(screen.getByText(/legacy issue modal/i)).toBeInTheDocument(); + }); + }); + + it('should be able to issue request with custom vault', async () => { + await render( + + ); + + userEvent.click(screen.getByRole('switch', { name: /manually select vault/i })); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /vault/i })).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('button', { name: /vault/i })); + + const vaultDialog = within(screen.getByRole('dialog', { name: /select vault/i })); + + const vaults = vaultDialog.getAllByRole('row', { name: VAULTS_ID.GOVERNANCE.accountId, exact: false }); + + expect(vaults).toHaveLength(2); + + userEvent.click(vaults[1]); + + userEvent.type(screen.getByRole('textbox', { name: /amount/i }), ISSUE_AMOUNTS.HALF.toString()); + + await waitForFeeEstimate(request); + + userEvent.click(screen.getByRole('button', { name: /issue/i })); + + await waitForTransactionExecute(request); + + expect(request).toHaveBeenCalledWith( + ISSUE_AMOUNTS.HALF, + VAULTS_ID.GOVERNANCE.accountId, + VAULT_COLLATERAL.GOVERNANCE, + false, + VAULTS_TOKENS.FULL, + GOVERNANCE_TOKEN + ); + + await waitFor(() => { + expect(screen.getByText(/legacy issue modal/i)).toBeInTheDocument(); + }); + }); + + it('should only show vaults with available capacity', async () => { + await render( + + ); + + userEvent.type(screen.getByRole('textbox', { name: /amount/i }), singleVaultMaxIssuable.toString()); + + // Wait for debounce + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 500)); + }); + + userEvent.click(screen.getByRole('switch', { name: /manually select vault/i })); + + await waitFor(() => { + expect(screen.getByRole('button', { name: /vault/i })).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('button', { name: /vault/i })); + + const vaultDialog = within(screen.getByRole('dialog', { name: /select vault/i })); + + expect(vaultDialog.getAllByRole('row')).toHaveLength(1); + }); + + it('should not be able to issue request because of request limits', async () => { + await render( + + ); + + userEvent.type(screen.getByRole('textbox', { name: /amount/i }), singleVaultMaxIssuable.toString()); + + userEvent.click(screen.getByRole('button', { name: /issue/i })); + + expect(request).not.toHaveBeenCalled(); + }); + + it('should not be able to issue request because of empty vaults', async () => { + getVaultsWithIssuableTokens.mockResolvedValue(VAULTS_TOKENS.EMPTY); + + await render( + + ); + + userEvent.type(screen.getByRole('textbox', { name: /amount/i }), singleVaultMaxIssuable.toString()); + + userEvent.click(screen.getByRole('button', { name: /issue/i })); + + expect(request).not.toHaveBeenCalled(); + }); + + it('should not be able to issue request because of empty map', async () => { + getVaultsWithIssuableTokens.mockResolvedValue(new Map()); + + await render( + + ); + + userEvent.type(screen.getByRole('textbox', { name: /amount/i }), singleVaultMaxIssuable.toString()); + + userEvent.click(screen.getByRole('button', { name: /issue/i })); + + expect(request).not.toHaveBeenCalled(); + }); +}); diff --git a/src/pages/BTC/BTCOverview/components/IssueForm/IssueForm.tsx b/src/pages/BTC/BTCOverview/components/IssueForm/IssueForm.tsx index ac620188e0..11e69e9be4 100644 --- a/src/pages/BTC/BTCOverview/components/IssueForm/IssueForm.tsx +++ b/src/pages/BTC/BTCOverview/components/IssueForm/IssueForm.tsx @@ -128,6 +128,7 @@ const IssueForm = ({ requestLimits, dustValue, issueFee }: IssueFormProps): JSX. } const securityDeposit = getCurrencyFromTicker(securityDepositTicker); + return [ monetaryAmount, vault.vaultId.accountId, diff --git a/src/test/mocks/@interlay/interbtc-api/index.ts b/src/test/mocks/@interlay/interbtc-api/index.ts index 1a13b8bf07..077532d844 100644 --- a/src/test/mocks/@interlay/interbtc-api/index.ts +++ b/src/test/mocks/@interlay/interbtc-api/index.ts @@ -8,32 +8,18 @@ import { Signer } from '@polkadot/types/types'; import { MOCK_AMM, MOCK_API, + MOCK_FEE, + MOCK_ISSUE, MOCK_LOANS, MOCK_SYSTEM, MOCK_TOKENS, MOCK_TRANSACTION, + MOCK_VAULTS, mockBtcRelayGetLatestBlockHeight, mockElectrsAPIGetLatestBlockHeight, - mockFeeGetIssueFee, - mockFeeGetIssueGriefingCollateralRate, mockGetStableBitcoinConfirmations, mockGetStableParachainConfirmations, - mockIssueGetDustValue, - mockIssueGetRequestLimits, - mockIssueRequest, - mockOracleGetExchangeRate, - mockRedeemBurn, - mockRedeemGetBurnExchangeRate, - mockRedeemGetCurrentInclusionFee, - mockRedeemGetDustValue, - mockRedeemGetFeeRate, - mockRedeemGetMaxBurnableTokens, - mockRedeemGetPremiumRedeemFeeRate, - mockRedeemRequest, - mockVaultsGet, - mockVaultsGetPremiumRedeemVaults, - mockVaultsGetVaultsWithIssuableTokens, - mockVaultsGetVaultsWithRedeemableTokens + mockOracleGetExchangeRate } from './parachain'; import { mockGetForeignAssets } from './parachain/assetRegistry'; import { mockGetStakedBalance, mockVotingBalance } from './parachain/escrow'; @@ -56,37 +42,15 @@ const mockInterBtcApi: Partial> = { getStableBitcoinConfirmations: mockGetStableBitcoinConfirmations }, electrsAPI: { getLatestBlockHeight: mockElectrsAPIGetLatestBlockHeight }, - fee: { - getIssueFee: mockFeeGetIssueFee, - getIssueGriefingCollateralRate: mockFeeGetIssueGriefingCollateralRate - }, - issue: { - getDustValue: mockIssueGetDustValue, - getRequestLimits: mockIssueGetRequestLimits, - request: mockIssueRequest - }, + fee: MOCK_FEE.MODULE, + issue: MOCK_ISSUE.MODULE, loans: MOCK_LOANS.MODULE, oracle: { getExchangeRate: mockOracleGetExchangeRate }, - redeem: { - getMaxBurnableTokens: mockRedeemGetMaxBurnableTokens, - getBurnExchangeRate: mockRedeemGetBurnExchangeRate, - burn: mockRedeemBurn, - getDustValue: mockRedeemGetDustValue, - getPremiumRedeemFeeRate: mockRedeemGetPremiumRedeemFeeRate, - getFeeRate: mockRedeemGetFeeRate, - getCurrentInclusionFee: mockRedeemGetCurrentInclusionFee, - request: mockRedeemRequest - }, system: MOCK_SYSTEM.MODULE, tokens: MOCK_TOKENS.MODULE, - vaults: { - get: mockVaultsGet, - getVaultsWithIssuableTokens: mockVaultsGetVaultsWithIssuableTokens, - getPremiumRedeemVaults: mockVaultsGetPremiumRedeemVaults, - getVaultsWithRedeemableTokens: mockVaultsGetVaultsWithRedeemableTokens - }, + vaults: MOCK_VAULTS.MODULE, amm: MOCK_AMM.MODULE, escrow: { getStakedBalance: mockGetStakedBalance, @@ -100,6 +64,7 @@ jest.mock('@interlay/interbtc-api', () => { return { ...actualInterBtcApi, + getIssueRequestsFromExtrinsicResult: jest.fn(), currencyIdToMonetaryCurrency: jest.fn(), newAccountId: jest.fn().mockReturnValue('a3aTRC4zs1djutYS9QuZSB3XmfRgNzFfyRtbZKaoQyv67Yzcc'), getCollateralCurrencies: jest.fn(() => mockCollateralCurrencies), diff --git a/src/test/mocks/@interlay/interbtc-api/interbtc-primitives.ts b/src/test/mocks/@interlay/interbtc-api/interbtc-primitives.ts new file mode 100644 index 0000000000..cd3a35e651 --- /dev/null +++ b/src/test/mocks/@interlay/interbtc-api/interbtc-primitives.ts @@ -0,0 +1,50 @@ +import { InterbtcPrimitivesVaultId } from '@interlay/interbtc-api'; + +import { GOVERNANCE_TOKEN, RELAY_CHAIN_NATIVE_TOKEN, WRAPPED_TOKEN } from '@/config/relay-chains'; + +const MOCK_PRIMITIVES: Record< + 'RELAY_CHAIN_NATIVE_TOKEN' | 'GOVERNANCE_TOKEN' | 'WRAPPED_TOKEN', + InterbtcPrimitivesVaultId +> = { + RELAY_CHAIN_NATIVE_TOKEN: { + type: 'Token', + // token: + isToken: true, + asToken: { + isDot: true + // isKsm: true + } + }, + GOVERNANCE_TOKEN: { + type: 'Token', + isToken: true, + asToken: { + // isKint: true, + isIntr: true + } + }, + WRAPPED_TOKEN: { + type: 'Token', + isToken: true, + asToken: { + isIbtc: true + // isKbtc: true + } + } +}; + +const mockTokenSymbolToCurrencyFn = (type: any) => { + if (type.asToken.isDot || type.asToken.isKsm) { + return RELAY_CHAIN_NATIVE_TOKEN; + } + + if (type.asToken.isIntr || type.asToken.isKint) { + return GOVERNANCE_TOKEN; + } + + if (type.asToken.isIbtc || type.asToken.isKbtc) { + return WRAPPED_TOKEN; + } +}; + +export { MOCK_PRIMITIVES, mockTokenSymbolToCurrencyFn }; diff --git a/src/test/mocks/@interlay/interbtc-api/issue.ts b/src/test/mocks/@interlay/interbtc-api/issue.ts new file mode 100644 index 0000000000..4e07a2586e --- /dev/null +++ b/src/test/mocks/@interlay/interbtc-api/issue.ts @@ -0,0 +1,27 @@ +import { Issue, newMonetaryAmount } from '@interlay/interbtc-api'; + +import { RELAY_CHAIN_NATIVE_TOKEN, WRAPPED_TOKEN } from '@/config/relay-chains'; + +import { MOCK_PRIMITIVES } from './interbtc-primitives'; + +const mockIssue: Issue = { + id: 'ac30ffed98687e71698733eafa6d90c25d03dfbbfdfa1a7cae5f0c2a633b1a7c', + creationBlock: 298883, + vaultWrappedAddress: 'tb1q85trl4jq0h6zsfce943tmmz52ptvw5uaxmq9c4', + vaultId: { + accountId: 'wdCfqzWZCA6rnNzTPLsaANZ9CKKbFuFJNGMGYGP9HE5jJg9Rk', + currencies: { + collateral: MOCK_PRIMITIVES.RELAY_CHAIN_NATIVE_TOKEN, + wrapped: MOCK_PRIMITIVES.WRAPPED_TOKEN + } + }, + userParachainAddress: 'wdCfqzWZCA6rnNzTPLsaANZ9CKKbFuFJNGMGYGP9HE5jJg9Rk', + vaultWalletPubkey: '0x0248bf0a78ec32f29acdc1bdd5fb2f4f338b962560626eeb906f3d02c06a331e01', + bridgeFee: newMonetaryAmount(0, WRAPPED_TOKEN), + wrappedAmount: newMonetaryAmount(1, WRAPPED_TOKEN, true), + griefingCollateral: newMonetaryAmount(1, RELAY_CHAIN_NATIVE_TOKEN, true), + status: 4, + period: 3600 +}; + +export { mockIssue }; diff --git a/src/test/mocks/@interlay/interbtc-api/parachain/fee.ts b/src/test/mocks/@interlay/interbtc-api/parachain/fee.ts index b15f6417b7..dfcc33da2b 100644 --- a/src/test/mocks/@interlay/interbtc-api/parachain/fee.ts +++ b/src/test/mocks/@interlay/interbtc-api/parachain/fee.ts @@ -1,26 +1,26 @@ -import { BitcoinAmount } from '@interlay/monetary-js'; +import { FeeAPI } from '@interlay/interbtc-api'; import Big from 'big.js'; -import { - DEFAULT_ISSUE_BRIDGE_FEE_RATE, - DEFAULT_ISSUE_GRIEFING_COLLATERAL_RATE, - DEFAULT_REDEEM_BRIDGE_FEE_RATE -} from '@/config/parachain'; +const ISSUE_FEE = new Big(1); -const mockFeeGetIssueFee = jest.fn(() => new Big(DEFAULT_ISSUE_BRIDGE_FEE_RATE)); +const GRIEFING_COLLATERAL_RATE = new Big(1); -const mockFeeGetIssueGriefingCollateralRate = jest.fn(() => new Big(DEFAULT_ISSUE_GRIEFING_COLLATERAL_RATE)); - -const mockRedeemGetPremiumRedeemFeeRate = jest.fn(() => Big(0)); - -const mockRedeemGetFeeRate = jest.fn(() => new Big(DEFAULT_REDEEM_BRIDGE_FEE_RATE)); +const DATA = { + ISSUE_FEE, + GRIEFING_COLLATERAL_RATE +}; -const mockRedeemGetCurrentInclusionFee = jest.fn(() => new BitcoinAmount(0.0000038)); +const MODULE: Record> = { + calculateAPY: jest.fn(), + getGriefingCollateral: jest.fn(), + getIssueFee: jest.fn().mockResolvedValue(ISSUE_FEE), + getIssueGriefingCollateralRate: jest.fn().mockResolvedValue(GRIEFING_COLLATERAL_RATE), + getReplaceGriefingCollateralRate: jest.fn() +}; -export { - mockFeeGetIssueFee, - mockFeeGetIssueGriefingCollateralRate, - mockRedeemGetCurrentInclusionFee, - mockRedeemGetFeeRate, - mockRedeemGetPremiumRedeemFeeRate +const MOCK_FEE = { + DATA, + MODULE }; + +export { MOCK_FEE }; diff --git a/src/test/mocks/@interlay/interbtc-api/parachain/issue.ts b/src/test/mocks/@interlay/interbtc-api/parachain/issue.ts index 9eca8d8c19..28877806a0 100644 --- a/src/test/mocks/@interlay/interbtc-api/parachain/issue.ts +++ b/src/test/mocks/@interlay/interbtc-api/parachain/issue.ts @@ -1,16 +1,59 @@ -import { newMonetaryAmount } from '@interlay/interbtc-api'; -import { BitcoinAmount } from '@interlay/monetary-js'; +import { IssueAPI, newMonetaryAmount } from '@interlay/interbtc-api'; +import { IssueLimits } from '@interlay/interbtc-api/build/src/parachain/issue'; +import { BitcoinAmount, Currency, MonetaryAmount } from '@interlay/monetary-js'; -import { DEFAULT_ISSUE_DUST_AMOUNT } from '@/config/parachain'; import { WRAPPED_TOKEN } from '@/config/relay-chains'; -const mockIssueGetDustValue = jest.fn(() => new BitcoinAmount(DEFAULT_ISSUE_DUST_AMOUNT)); +import { EXTRINSIC_DATA } from '../extrinsic'; -const mockIssueGetRequestLimits = jest.fn(() => ({ - singleVaultMaxIssuable: newMonetaryAmount(0.56527153, WRAPPED_TOKEN, true), - totalMaxIssuable: newMonetaryAmount(4.93817337, WRAPPED_TOKEN, true) -})); +const ISSUE_AMOUNTS: Record<'EMPTY' | 'HALF' | 'FULL', MonetaryAmount> = { + EMPTY: new BitcoinAmount(0), + HALF: new BitcoinAmount(0.5), + FULL: new BitcoinAmount(1) +}; -const mockIssueRequest = jest.fn(); +const REQUEST_LIMIT: Record<'EMPTY' | 'FULL', IssueLimits> = { + EMPTY: { + singleVaultMaxIssuable: ISSUE_AMOUNTS.EMPTY, + totalMaxIssuable: ISSUE_AMOUNTS.EMPTY + }, + FULL: { + singleVaultMaxIssuable: ISSUE_AMOUNTS.FULL, + totalMaxIssuable: ISSUE_AMOUNTS.FULL.add(ISSUE_AMOUNTS.HALF) + } +}; -export { mockIssueGetDustValue, mockIssueGetRequestLimits, mockIssueRequest }; +const DUST_VALUE = newMonetaryAmount(0.0001, WRAPPED_TOKEN); + +const DATA = { + ISSUE_AMOUNTS, + REQUEST_LIMIT, + DUST_VALUE +}; + +const MODULE: Record> = { + getRequestLimits: jest.fn().mockResolvedValue(REQUEST_LIMIT.FULL), + getDustValue: jest.fn().mockResolvedValue(DUST_VALUE), + getFeeRate: jest.fn(), + getFeesToPay: jest.fn(), + getIssuePeriod: jest.fn(), + getRequestById: jest.fn(), + getRequestsByIds: jest.fn(), + getVaultIssuableAmount: jest.fn(), + setIssuePeriod: jest.fn(), + cancel: jest.fn(), + execute: jest.fn(), + list: jest.fn(), + request: jest.fn().mockResolvedValue(EXTRINSIC_DATA), + requestAdvanced: jest.fn(), + buildCancelIssueExtrinsic: jest.fn(), + buildExecuteIssueExtrinsic: jest.fn(), + buildRequestIssueExtrinsic: jest.fn() +}; + +const MOCK_ISSUE = { + DATA, + MODULE +}; + +export { MOCK_ISSUE }; diff --git a/src/test/mocks/@interlay/interbtc-api/parachain/vaults.ts b/src/test/mocks/@interlay/interbtc-api/parachain/vaults.ts index 920878dea1..508df0eb05 100644 --- a/src/test/mocks/@interlay/interbtc-api/parachain/vaults.ts +++ b/src/test/mocks/@interlay/interbtc-api/parachain/vaults.ts @@ -1,46 +1,61 @@ -import '@testing-library/jest-dom'; - -import { CollateralCurrencyExt, CurrencyExt, newMonetaryAmount } from '@interlay/interbtc-api'; -import { BitcoinAmount } from '@interlay/monetary-js'; -import { AccountId } from '@polkadot/types/interfaces'; - -import { RELAY_CHAIN_NATIVE_TOKEN, WRAPPED_TOKEN } from '@/config/relay-chains'; - -const MOCK_COLLATERAL_AMOUNT = '1000000000000'; - -const mockVaultsGet = jest.fn((_accountId: AccountId, collateralCurrency: CollateralCurrencyExt) => ({ - backingCollateral: newMonetaryAmount(MOCK_COLLATERAL_AMOUNT, collateralCurrency) -})); - -const mockNewVaultId = (vaultAddress: string, collateralToken: CurrencyExt) => ({ - accountId: vaultAddress, - currencies: { - collateral: collateralToken, - wrapped: WRAPPED_TOKEN +import { CurrencyExt, InterbtcPrimitivesVaultId, VaultsAPI } from '@interlay/interbtc-api'; +import { MonetaryAmount } from '@interlay/monetary-js'; + +import { GOVERNANCE_TOKEN, RELAY_CHAIN_NATIVE_TOKEN } from '@/config/relay-chains'; +import { DEFAULT_ADDRESS } from '@/test/mocks/@polkadot/constants'; + +import { MOCK_PRIMITIVES } from '../interbtc-primitives'; +import { MOCK_ISSUE } from './issue'; + +const { ISSUE_AMOUNTS } = MOCK_ISSUE.DATA; + +const VAULTS_ID: Record<'RELAY' | 'GOVERNANCE', InterbtcPrimitivesVaultId> = { + RELAY: { + accountId: DEFAULT_ADDRESS, + currencies: { + collateral: MOCK_PRIMITIVES.RELAY_CHAIN_NATIVE_TOKEN, + wrapped: MOCK_PRIMITIVES.WRAPPED_TOKEN + } + }, + GOVERNANCE: { + accountId: DEFAULT_ADDRESS, + currencies: { + collateral: MOCK_PRIMITIVES.GOVERNANCE_TOKEN, + wrapped: MOCK_PRIMITIVES.WRAPPED_TOKEN + } } -}); - -const MOCK_VAULT_ADDRESS = '5GQoBrhX3mfnmKnw2qz2vGvHG8yvf6xT15gGM54865g6qEfE'; - -const MOCK_COLLATERAL_TOKEN = RELAY_CHAIN_NATIVE_TOKEN; +}; -const MOCK_BITCOIN_AMOUNT = 100; +const VAULT_COLLATERAL: Record<'RELAY' | 'GOVERNANCE', CurrencyExt> = { + RELAY: RELAY_CHAIN_NATIVE_TOKEN, + GOVERNANCE: GOVERNANCE_TOKEN +}; -const mockVaultsGetVaultsWithIssuableTokens = jest.fn(() => - new Map().set(mockNewVaultId(MOCK_VAULT_ADDRESS, MOCK_COLLATERAL_TOKEN), new BitcoinAmount(MOCK_BITCOIN_AMOUNT)) -); +const VAULTS_TOKENS = { + EMPTY: new Map>([ + [VAULTS_ID.RELAY, ISSUE_AMOUNTS.EMPTY], + [VAULTS_ID.GOVERNANCE, ISSUE_AMOUNTS.EMPTY] + ]), + FULL: new Map>([ + [VAULTS_ID.RELAY, ISSUE_AMOUNTS.FULL], + [VAULTS_ID.GOVERNANCE, ISSUE_AMOUNTS.HALF] + ]) +}; -const mockVaultsGetPremiumRedeemVaults = jest.fn(() => - new Map().set(mockNewVaultId(MOCK_VAULT_ADDRESS, MOCK_COLLATERAL_TOKEN), new BitcoinAmount(MOCK_BITCOIN_AMOUNT)) -); +const DATA = { VAULTS_ID, VAULTS_TOKENS, VAULT_COLLATERAL }; -const mockVaultsGetVaultsWithRedeemableTokens = jest.fn(() => - new Map().set(mockNewVaultId(MOCK_VAULT_ADDRESS, MOCK_COLLATERAL_TOKEN), new BitcoinAmount(MOCK_BITCOIN_AMOUNT)) -); +const MODULE: Pick< + Record>, + 'getVaultsWithRedeemableTokens' | 'getVaultsWithIssuableTokens' | 'getPremiumRedeemVaults' +> = { + getVaultsWithRedeemableTokens: jest.fn().mockResolvedValue(VAULTS_TOKENS.FULL), + getVaultsWithIssuableTokens: jest.fn().mockResolvedValue(VAULTS_TOKENS.FULL), + getPremiumRedeemVaults: jest.fn().mockRejectedValue(undefined) +}; -export { - mockVaultsGet, - mockVaultsGetPremiumRedeemVaults, - mockVaultsGetVaultsWithIssuableTokens, - mockVaultsGetVaultsWithRedeemableTokens +const MOCK_VAULTS = { + DATA, + MODULE }; + +export { MOCK_VAULTS }; diff --git a/src/test/mocks/hooks/api/bridge/use-get-vaults.mock.tsx b/src/test/mocks/hooks/api/bridge/use-get-vaults.mock.tsx new file mode 100644 index 0000000000..899bf0dc1f --- /dev/null +++ b/src/test/mocks/hooks/api/bridge/use-get-vaults.mock.tsx @@ -0,0 +1,55 @@ +import { GOVERNANCE_TOKEN, RELAY_CHAIN_NATIVE_TOKEN } from '@/config/relay-chains'; +import { MOCK_VAULTS } from '@/test/mocks/@interlay/interbtc-api'; +import { + BridgeVaultData, + GetBridgeVaultData, + GetVaultType, + UseGetBridgeVaultResult +} from '@/utils/hooks/api/bridge/use-get-vaults'; + +const { VAULTS_AMOUNT, VAULTS_ID, VAULTS_TOKENS } = MOCK_VAULTS.DATA; + +const mockRelayBridgeVaultData = (size: keyof typeof VAULTS_AMOUNT): BridgeVaultData => ({ + vaultId: VAULTS_ID.RELAY, + amount: VAULTS_AMOUNT[size], + collateralCurrency: RELAY_CHAIN_NATIVE_TOKEN, + id: '1' +}); + +const mockGovernanceBridgeVaultData = (size: keyof typeof VAULTS_AMOUNT): BridgeVaultData => ({ + vaultId: VAULTS_ID.GOVERNANCE, + amount: VAULTS_AMOUNT[size], + collateralCurrency: GOVERNANCE_TOKEN, + id: '2' +}); + +const mockIssueGetBridgeVaultData = (size: keyof typeof VAULTS_AMOUNT): GetBridgeVaultData => ({ + list: [mockRelayBridgeVaultData(size), mockGovernanceBridgeVaultData(size)], + map: VAULTS_TOKENS[size], + premium: undefined as never +}); + +const mockRedeemGetBridgeVaultData = (size: keyof typeof VAULTS_AMOUNT): GetBridgeVaultData => ({ + list: [mockRelayBridgeVaultData(size), mockGovernanceBridgeVaultData(size)], + map: VAULTS_TOKENS[size], + premium: { + list: [mockRelayBridgeVaultData(size), mockGovernanceBridgeVaultData(size)], + map: VAULTS_TOKENS[size] + } +}); + +const mockUseGetIssueBridgeVaultResult = ( + size: keyof typeof VAULTS_AMOUNT +): UseGetBridgeVaultResult => ({ + data: mockIssueGetBridgeVaultData(size), + getAvailableVaults: jest.fn().mockReturnValue([mockRelayBridgeVaultData(size), mockGovernanceBridgeVaultData(size)]), + refetch: jest.fn() +}); + +export { + mockGovernanceBridgeVaultData, + mockIssueGetBridgeVaultData, + mockRedeemGetBridgeVaultData, + mockRelayBridgeVaultData, + mockUseGetIssueBridgeVaultResult +}; diff --git a/src/test/mocks/hooks/index.ts b/src/test/mocks/hooks/index.ts index f1174f4fa9..c097653dca 100644 --- a/src/test/mocks/hooks/index.ts +++ b/src/test/mocks/hooks/index.ts @@ -34,4 +34,9 @@ jest.mock('@/utils/hooks/api/use-get-prices', () => ({ useGetPrices: jest.fn().mockReturnValue(mockPrices) })); +jest.mock('@/utils/hooks/api/oracle/use-get-oracle-currencies', () => ({ + ...jest.requireActual('@/utils/hooks/api/oracle/use-get-oracle-currencies'), + useGetOracleCurrencies: jest.fn().mockReturnValue({ data: [] }) +})); + export { mockgetDexTotalVolumeUSD, mockGetDexVolumeByTicker }; diff --git a/src/utils/hooks/api/bridge/use-get-vaults.tsx b/src/utils/hooks/api/bridge/use-get-vaults.tsx index 8463c4cbe6..9c832d4e71 100644 --- a/src/utils/hooks/api/bridge/use-get-vaults.tsx +++ b/src/utils/hooks/api/bridge/use-get-vaults.tsx @@ -133,4 +133,4 @@ const useGetVaults = ( }; export { GetVaultType, useGetVaults }; -export type { BridgeVaultData, UseGetBridgeVaultResult }; +export type { BridgeVaultData, GetBridgeVaultData, UseGetBridgeVaultResult };