Skip to content

Commit

Permalink
feat(frontend): update utxos fee store and context
Browse files Browse the repository at this point in the history
  • Loading branch information
DenysKarmazynDFINITY committed Nov 22, 2024
1 parent 03f5fae commit d3b74e1
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import {
UTXOS_FEE_CONTEXT_KEY,
type UtxosFeeContext as UtxosFeeContextType,
initUtxosFeeStore
utxosFeeStore
} from '$btc/stores/utxos-fee.store';
import { btcAddressStore } from '$icp/stores/btc.store';
import ButtonBack from '$lib/components/ui/ButtonBack.svelte';
Expand Down Expand Up @@ -43,8 +43,6 @@
export let convertProgressStep: string;
export let formCancelAction: 'back' | 'close' = 'close';
const utxosFeeStore = initUtxosFeeStore();
setContext<UtxosFeeContextType>(UTXOS_FEE_CONTEXT_KEY, {
store: utxosFeeStore
});
Expand Down
15 changes: 12 additions & 3 deletions src/frontend/src/btc/components/fee/UtxosFeeContext.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,25 @@
return;
}
const parsedAmount = nonNullish(amount) ? Number(amount) : undefined;
// we need to make the value is not 0 because the utxos call fails if amount = 0
if (amountError || isNullish(networkId) || isNullish(amount) || Number(amount) === 0) {
if (amountError || isNullish(networkId) || isNullish(parsedAmount) || parsedAmount === 0) {
store.reset();
return;
}
// WizardModal re-renders content on step change (e.g. when switching between Convert to Review steps)
// To avoid re-fetching the fees, we need to check if amount hasn't changed since the last request
if (nonNullish($store) && $store.amount === parsedAmount) {
return;
}
const network = mapNetworkIdToBitcoinNetwork(networkId);
const utxosFee = nonNullish(network)
? await selectUtxosFeeApi({
amount,
amount: parsedAmount,
network,
identity: $authIdentity
})
Expand All @@ -43,7 +51,8 @@
}
store.setUtxosFee({
utxosFee
utxosFee,
amount: parsedAmount
});
};
Expand Down
6 changes: 5 additions & 1 deletion src/frontend/src/btc/stores/utxos-fee.store.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import type { UtxosFee } from '$btc/types/btc-send';
import type { OptionAmount } from '$lib/types/send';
import type { Option } from '$lib/types/utils';
import { writable, type Readable } from 'svelte/store';

export type UtxosFeeStoreData = Option<{
utxosFee?: UtxosFee;
amount?: OptionAmount;
}>;

export interface UtxosFeeStore extends Readable<UtxosFeeStoreData> {
setUtxosFee: (data: UtxosFeeStoreData) => void;
reset: () => void;
}

export const initUtxosFeeStore = (): UtxosFeeStore => {
const initUtxosFeeStore = (): UtxosFeeStore => {
const { subscribe, set } = writable<UtxosFeeStoreData>(undefined);

return {
Expand All @@ -32,3 +34,5 @@ export interface UtxosFeeContext {
}

export const UTXOS_FEE_CONTEXT_KEY = Symbol('utxos-fee');

export const utxosFeeStore = initUtxosFeeStore();
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import BtcConvertFeeTotal from '$btc/components/convert/BtcConvertFeeTotal.svelte';
import { BTC_CONVERT_FEE } from '$btc/constants/btc.constants';
import {
initUtxosFeeStore,
UTXOS_FEE_CONTEXT_KEY,
utxosFeeStore,
type UtxosFeeStore
} from '$btc/stores/utxos-fee.store';
import { BTC_MAINNET_TOKEN } from '$env/tokens/tokens.btc.env';
Expand All @@ -17,17 +17,16 @@ import { render } from '@testing-library/svelte';
import { readable } from 'svelte/store';

describe('BtcConvertFeeTotal', () => {
let store: UtxosFeeStore;
const exchangeRate = 0.01;
const mockContext = ({
utxosFeeStore,
mockUtxosFeeStore,
destinationTokenId = ICP_TOKEN.id
}: {
utxosFeeStore: UtxosFeeStore;
mockUtxosFeeStore: UtxosFeeStore;
destinationTokenId?: TokenId;
}) =>
new Map([
[UTXOS_FEE_CONTEXT_KEY, { store: utxosFeeStore }],
[UTXOS_FEE_CONTEXT_KEY, { store: mockUtxosFeeStore }],
[
CONVERT_CONTEXT_KEY,
{
Expand All @@ -40,21 +39,20 @@ describe('BtcConvertFeeTotal', () => {

beforeEach(() => {
mockPage.reset();
store = initUtxosFeeStore();
store.reset();
utxosFeeStore.reset();
});

it('should calculate totalFee correctly if only default fee is available', () => {
const { component } = render(BtcConvertFeeTotal, {
context: mockContext({ utxosFeeStore: store })
context: mockContext({ mockUtxosFeeStore: utxosFeeStore })
});
expect(component.$$.ctx[component.$$.props['totalFee']]).toBe(BTC_CONVERT_FEE);
});

it('should calculate totalFee correctly if default and utxos fees are available', () => {
store.setUtxosFee({ utxosFee: mockUtxosFee });
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });
const { component } = render(BtcConvertFeeTotal, {
context: mockContext({ utxosFeeStore: store })
context: mockContext({ mockUtxosFeeStore: utxosFeeStore })
});
expect(component.$$.ctx[component.$$.props['totalFee']]).toBe(
BTC_CONVERT_FEE + mockUtxosFee.feeSatoshis
Expand All @@ -64,18 +62,18 @@ describe('BtcConvertFeeTotal', () => {
it('should calculate totalFee correctly if default and ckBTC minter fees are available', () => {
const tokenId = setupCkBTCStores();
const { component } = render(BtcConvertFeeTotal, {
context: mockContext({ utxosFeeStore: store, destinationTokenId: tokenId })
context: mockContext({ mockUtxosFeeStore: utxosFeeStore, destinationTokenId: tokenId })
});
expect(component.$$.ctx[component.$$.props['totalFee']]).toBe(
BTC_CONVERT_FEE + mockCkBtcMinterInfo.kyt_fee
);
});

it('should calculate totalFee correctly if all fees are available', () => {
store.setUtxosFee({ utxosFee: mockUtxosFee });
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });
const tokenId = setupCkBTCStores();
const { component } = render(BtcConvertFeeTotal, {
context: mockContext({ utxosFeeStore: store, destinationTokenId: tokenId })
context: mockContext({ mockUtxosFeeStore: utxosFeeStore, destinationTokenId: tokenId })
});
expect(component.$$.ctx[component.$$.props['totalFee']]).toBe(
BTC_CONVERT_FEE + mockCkBtcMinterInfo.kyt_fee + mockUtxosFee.feeSatoshis
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import BtcConvertForm from '$btc/components/convert/BtcConvertForm.svelte';
import * as btcPendingSendTransactionsStatusStore from '$btc/derived/btc-pending-sent-transactions-status.derived';
import {
initUtxosFeeStore,
UTXOS_FEE_CONTEXT_KEY,
utxosFeeStore,
type UtxosFeeStore
} from '$btc/stores/utxos-fee.store';
import { BTC_MAINNET_TOKEN } from '$env/tokens/tokens.btc.env';
Expand All @@ -17,7 +17,6 @@ import { BigNumber } from 'alchemy-sdk';
import { readable } from 'svelte/store';

describe('BtcConvertForm', () => {
let store: UtxosFeeStore;
const mockContext = ({
utxosFeeStore,
sourceTokenBalance = 1000000n
Expand Down Expand Up @@ -56,47 +55,46 @@ describe('BtcConvertForm', () => {

beforeEach(() => {
mockPage.reset();
store = initUtxosFeeStore();
store.reset();
utxosFeeStore.reset();
});

it('should keep the next button clickable if all requirements are met', () => {
store.setUtxosFee({ utxosFee: mockUtxosFee });
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });
mockBtcPendingSendTransactionsStatusStore();

const { getByTestId } = render(BtcConvertForm, {
props,
context: mockContext({ utxosFeeStore: store })
context: mockContext({ utxosFeeStore })
});

expect(getByTestId(buttonTestId)).not.toHaveAttribute('disabled');
});

it('should keep the next button disabled if amount is undefined', () => {
store.setUtxosFee({ utxosFee: mockUtxosFee });
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });
mockBtcPendingSendTransactionsStatusStore();

const { getByTestId } = render(BtcConvertForm, {
props: {
...props,
sendAmount: undefined
},
context: mockContext({ utxosFeeStore: store })
context: mockContext({ utxosFeeStore })
});

expect(getByTestId(buttonTestId)).toHaveAttribute('disabled');
});

it('should keep the next button disabled if amount is invalid', () => {
store.setUtxosFee({ utxosFee: mockUtxosFee });
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });
mockBtcPendingSendTransactionsStatusStore();

const { getByTestId } = render(BtcConvertForm, {
props: {
...props,
sendAmount: -1
},
context: mockContext({ utxosFeeStore: store })
context: mockContext({ utxosFeeStore })
});

expect(getByTestId(buttonTestId)).toHaveAttribute('disabled');
Expand All @@ -107,19 +105,19 @@ describe('BtcConvertForm', () => {

const { getByTestId } = render(BtcConvertForm, {
props,
context: mockContext({ utxosFeeStore: store })
context: mockContext({ utxosFeeStore })
});

expect(getByTestId(buttonTestId)).toHaveAttribute('disabled');
});

it('should keep the next button disabled if utxos are not available', () => {
store.setUtxosFee({ utxosFee: { ...mockUtxosFee, utxos: [] } });
utxosFeeStore.setUtxosFee({ utxosFee: { ...mockUtxosFee, utxos: [] } });
mockBtcPendingSendTransactionsStatusStore();

const { getByTestId } = render(BtcConvertForm, {
props,
context: mockContext({ utxosFeeStore: store })
context: mockContext({ utxosFeeStore })
});

expect(getByTestId(buttonTestId)).toHaveAttribute('disabled');
Expand All @@ -132,7 +130,7 @@ describe('BtcConvertForm', () => {

const { getByTestId } = render(BtcConvertForm, {
props,
context: mockContext({ utxosFeeStore: store, sourceTokenBalance: 0n })
context: mockContext({ utxosFeeStore, sourceTokenBalance: 0n })
});

await waitFor(() => {
Expand All @@ -149,7 +147,7 @@ describe('BtcConvertForm', () => {

const { getByTestId } = render(BtcConvertForm, {
props,
context: mockContext({ utxosFeeStore: store })
context: mockContext({ utxosFeeStore })
});

await waitFor(() => {
Expand All @@ -168,7 +166,7 @@ describe('BtcConvertForm', () => {

const { getByTestId } = render(BtcConvertForm, {
props,
context: mockContext({ utxosFeeStore: store })
context: mockContext({ utxosFeeStore })
});

await waitFor(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import BtcConvertTokenWizard from '$btc/components/convert/BtcConvertTokenWizard.svelte';
import * as btcPendingSentTransactionsStore from '$btc/services/btc-pending-sent-transactions.services';
import * as utxosFeeStore from '$btc/stores/utxos-fee.store';
import type { UtxosFee } from '$btc/types/btc-send';
import { utxosFeeStore } from '$btc/stores/utxos-fee.store';
import { convertNumberToSatoshis } from '$btc/utils/btc-send.utils';
import { BTC_MAINNET_TOKEN } from '$env/tokens/tokens.btc.env';
import { ETHEREUM_TOKEN } from '$env/tokens/tokens.eth.env';
Expand Down Expand Up @@ -73,14 +72,6 @@ describe('BtcConvertTokenWizard', () => {
vi
.spyOn(btcPendingSentTransactionsStore, 'loadBtcPendingSentTransactions')
.mockResolvedValue({ success: true });
const mockUtxosFeeStore = (utxosFee?: UtxosFee) => {
const store = utxosFeeStore.initUtxosFeeStore();

vi.spyOn(utxosFeeStore, 'initUtxosFeeStore').mockImplementation(() => {
store.setUtxosFee({ utxosFee });
return store;
});
};
const clickConvertButton = async (container: HTMLElement) => {
const convertButtonSelector = '[data-tid="convert-review-button-next"]';
const button: HTMLButtonElement | null = container.querySelector(convertButtonSelector);
Expand All @@ -91,6 +82,7 @@ describe('BtcConvertTokenWizard', () => {
beforeEach(() => {
mockPage.reset();
mockBtcPendingSentTransactionsStore();
utxosFeeStore.reset();
});

it('should call sendBtc if all requirements are met', async () => {
Expand All @@ -99,7 +91,7 @@ describe('BtcConvertTokenWizard', () => {
mockAuthStore();
mockBtcAddressStore();
mockAddressesStore();
mockUtxosFeeStore(mockUtxosFee);
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });

const { container } = render(BtcConvertTokenWizard, {
props,
Expand Down Expand Up @@ -130,7 +122,7 @@ describe('BtcConvertTokenWizard', () => {
mockAuthStore(null);
mockBtcAddressStore();
mockAddressesStore();
mockUtxosFeeStore(mockUtxosFee);
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });

const { container } = render(BtcConvertTokenWizard, {
props,
Expand All @@ -149,7 +141,7 @@ describe('BtcConvertTokenWizard', () => {
mockAuthStore();
mockAddressesStore();
mockBtcAddressStore();
mockUtxosFeeStore(mockUtxosFee);
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });

const { container } = render(BtcConvertTokenWizard, {
props,
Expand All @@ -168,7 +160,7 @@ describe('BtcConvertTokenWizard', () => {
mockAuthStore();
mockAddressesStore();
mockBtcAddressStore('');
mockUtxosFeeStore(mockUtxosFee);
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });

const { container } = render(BtcConvertTokenWizard, {
props,
Expand All @@ -187,7 +179,7 @@ describe('BtcConvertTokenWizard', () => {
mockAuthStore();
mockAddressesStore();
mockBtcAddressStore();
mockUtxosFeeStore(mockUtxosFee);
utxosFeeStore.setUtxosFee({ utxosFee: mockUtxosFee });

const { container } = render(BtcConvertTokenWizard, {
props: {
Expand All @@ -209,7 +201,6 @@ describe('BtcConvertTokenWizard', () => {
mockAuthStore();
mockAddressesStore();
mockBtcAddressStore();
mockUtxosFeeStore(undefined);

const { container } = render(BtcConvertTokenWizard, {
props,
Expand Down
Loading

0 comments on commit d3b74e1

Please sign in to comment.