Skip to content

Commit

Permalink
Merge pull request #371 from ergolabs/dev
Browse files Browse the repository at this point in the history
v1.1.3
  • Loading branch information
yasha-black authored Feb 4, 2022
2 parents 9839e35 + 13438b9 commit 4dd5a9b
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 57 deletions.
16 changes: 13 additions & 3 deletions src/api/ammPools.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
import { PoolId } from '@ergolabs/ergo-dex-sdk';
import { map, Observable, publishReplay, refCount, switchMap } from 'rxjs';

import { applicationConfig } from '../applicationConfig';
import { AmmPool } from '../common/models/AmmPool';
import { selectedNetwork$ } from '../network/network';

export const ammPools$ = selectedNetwork$.pipe(
switchMap((network) => network.ammPools$),
map((pools) =>
pools.filter(
(p) =>
!applicationConfig.hiddenAssets.includes(p.x.asset.id) &&
!applicationConfig.hiddenAssets.includes(p.y.asset.id) &&
!applicationConfig.blacklistedPools.includes(p.id),
),
),
publishReplay(1),
refCount(),
);

export const getAmmPoolById = (
ammPoolId: PoolId,
): Observable<AmmPool | undefined> =>
ammPools$.pipe(
selectedNetwork$.pipe(
switchMap((network) => network.ammPools$),
map((pools) => pools.find((position) => position.id === ammPoolId)),
);

const byAssetPair = (xId: string, yId: string) => (p: AmmPool) =>
(p.x.asset.id === xId || p.y.asset.id === xId) &&
(p.x.asset.id === yId || p.y.asset.id === yId);
(p.x.asset.id === xId && p.y.asset.id === yId) ||
(p.x.asset.id === yId && p.y.asset.id === xId);
export const getAmmPoolsByAssetPair = (
xId: string,
yId: string,
Expand Down
4 changes: 3 additions & 1 deletion src/components/common/ActionForm/ActionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface ActionFormProps<T> {
readonly children?: ReactNode | ReactNode[];
readonly isTokensNotSelected?: (form: T) => boolean;
readonly isAmountNotEntered?: (form: T) => boolean;
readonly isLoading?: (form: T) => boolean;
readonly isLiquidityInsufficient?: (form: T) => boolean;
readonly isSwapLocked?: (form: T) => boolean;
readonly getInsufficientTokenNameForTx?: (form: T) => undefined | string;
Expand All @@ -32,6 +33,7 @@ export const ActionForm: FC<ActionFormProps<any>> = ({
getInsufficientTokenNameForFee,
isSwapLocked,
getInsufficientTokenNameForTx,
isLoading,
children,
}) => {
const [isOnline] = useObservable(isOnline$);
Expand All @@ -51,7 +53,7 @@ export const ActionForm: FC<ActionFormProps<any>> = ({
useEffect(() => {
if (!isOnline) {
setButtonData({ state: ActionButtonState.CHECK_INTERNET_CONNECTION });
} else if (isWalletLoading) {
} else if (isWalletLoading || (isLoading && isLoading(value))) {
setButtonData({ state: ActionButtonState.LOADING });
} else if (isTokensNotSelected && isTokensNotSelected(value)) {
setButtonData({ state: ActionButtonState.SELECT_TOKEN });
Expand Down
9 changes: 0 additions & 9 deletions src/network/ergo/ammPools/ammPools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
switchMap,
} from 'rxjs';

import { applicationConfig } from '../../../applicationConfig';
import { AmmPool } from '../../../common/models/AmmPool';
import { appTick$ } from '../../../common/streams/appTick';
import { nativeNetworkPools, networkPools } from './common';
Expand Down Expand Up @@ -40,14 +39,6 @@ export const ammPools$ = combineLatest([
map(([nativeNetworkPools, networkPools]) =>
nativeNetworkPools.concat(networkPools),
),
map((pools) =>
pools.filter(
(p) =>
!applicationConfig.hiddenAssets.includes(p.x.asset.id) &&
!applicationConfig.hiddenAssets.includes(p.y.asset.id) &&
!applicationConfig.blacklistedPools.includes(p.id),
),
),
map((pools) => pools.map((p) => new AmmPool(p))),
publishReplay(1),
refCount(),
Expand Down
9 changes: 9 additions & 0 deletions src/network/ergo/assets/assets.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { uniqBy } from 'lodash';
import { map, publishReplay, refCount } from 'rxjs';

import { applicationConfig } from '../../../applicationConfig';
import { ammPools$ } from '../ammPools/ammPools';

export const assets$ = ammPools$.pipe(
map((pools) =>
pools.filter(
(p) =>
!applicationConfig.hiddenAssets.includes(p.x.asset.id) &&
!applicationConfig.hiddenAssets.includes(p.y.asset.id) &&
!applicationConfig.blacklistedPools.includes(p.id),
),
),
map((pools) => pools.flatMap((p) => [p.x.asset, p.y.asset])),
map((assets) => uniqBy(assets, 'id')),
publishReplay(1),
Expand Down
9 changes: 9 additions & 0 deletions src/network/ergo/assets/lpAssets.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { uniqBy } from 'lodash';
import { map, publishReplay, refCount } from 'rxjs';

import { applicationConfig } from '../../../applicationConfig';
import { ammPools$ } from '../ammPools/ammPools';

export const lpAssets$ = ammPools$.pipe(
map((pools) =>
pools.filter(
(p) =>
!applicationConfig.hiddenAssets.includes(p.x.asset.id) &&
!applicationConfig.hiddenAssets.includes(p.y.asset.id) &&
!applicationConfig.blacklistedPools.includes(p.id),
),
),
map((pools) => pools.map((p) => p.lp.asset)),
map((assets) => uniqBy(assets, 'id')),
publishReplay(1),
Expand Down
4 changes: 4 additions & 0 deletions src/pages/PoolOverview/PoolOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
import { useHistory, useParams } from 'react-router-dom';

import { getPositionByAmmPoolId } from '../../api/positions';
import { applicationConfig } from '../../applicationConfig';
import { ReactComponent as RelockIcon } from '../../assets/icons/relock-icon.svg';
import { ReactComponent as WithdrawalIcon } from '../../assets/icons/withdrawal-icon.svg';
import { useSubject } from '../../common/hooks/useObservable';
Expand Down Expand Up @@ -150,6 +151,9 @@ export const PoolOverview: React.FC = () => {
size="large"
icon={<PlusOutlined />}
onClick={handleAddLiquidity}
disabled={applicationConfig.blacklistedPools.includes(
position.pool.id,
)}
block
>
Increase Liquidity
Expand Down
137 changes: 93 additions & 44 deletions src/pages/Swap/Swap.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import './Swap.less';

import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo';
import { maxBy } from 'lodash';
import { DateTime } from 'luxon';
import React, { useEffect, useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
BehaviorSubject,
combineLatest,
debounceTime,
distinctUntilChanged,
filter,
first,
map,
Observable,
of,
skip,
switchMap,
tap,
} from 'rxjs';
Expand Down Expand Up @@ -45,13 +47,21 @@ import { SwapTooltip } from './SwapTooltip/SwapTooltip';
const getToAssets = (fromAsset?: string) =>
fromAsset ? getAvailableAssetFor(fromAsset) : assets$;

const isAssetsPairEquals = (
[prevFrom, prevTo]: [AssetInfo | undefined, AssetInfo | undefined],
[nextFrom, nextTo]: [AssetInfo | undefined, AssetInfo | undefined],
) =>
(prevFrom?.id === nextFrom?.id && prevTo?.id === nextTo?.id) ||
(prevFrom?.id === nextTo?.id && prevTo?.id === nextFrom?.id);

const getSelectedPool = (
xId?: string,
yId?: string,
): Observable<AmmPool | undefined> =>
xId && yId
? getAmmPoolsByAssetPair(xId, yId).pipe(
map((pools) => maxBy(pools, (p) => p.lp.amount)),
first(),
)
: of(undefined);

Expand All @@ -63,6 +73,7 @@ export const Swap = (): JSX.Element => {
toAsset: undefined,
pool: undefined,
});
const [lastEditedField, setLastEditedField] = useState<'from' | 'to'>('from');
const networkAsset = useNetworkAsset();
const [balance] = useAssetsBalance();
const totalFees = useMaxTotalFees();
Expand Down Expand Up @@ -109,6 +120,9 @@ export const Swap = (): JSX.Element => {
(toAsset?.id === LOCKED_TOKEN_ID || fromAsset?.id === LOCKED_TOKEN_ID) &&
DateTime.now().toUTC().toMillis() < END_TIMER_DATE.toMillis();

const isPoolLoading = ({ fromAsset, toAsset, pool }: SwapFormModel) =>
!!fromAsset && !!toAsset && !pool;

const submitSwap = (value: Required<SwapFormModel>) => {
openConfirmationModal(
(next) => {
Expand All @@ -133,14 +147,6 @@ export const Swap = (): JSX.Element => {
updateToAssets$.next(token?.id),
);

useSubscription(form.controls.fromAsset.valueChanges$, () =>
form.patchValue({
toAsset: undefined,
fromAmount: undefined,
toAmount: undefined,
}),
);

useSubscription(
combineLatest([
form.controls.fromAsset.valueChangesWithSilent$.pipe(
Expand All @@ -151,62 +157,104 @@ export const Swap = (): JSX.Element => {
),
]).pipe(
debounceTime(100),
distinctUntilChanged(([prevFrom, prevTo], [nextFrom, nextTo]) => {
return (
(prevFrom?.id === nextFrom?.id && prevTo?.id === nextTo?.id) ||
(prevFrom?.id === nextTo?.id && prevTo?.id === nextFrom?.id)
);
}),
distinctUntilChanged(isAssetsPairEquals),
tap(() => form.patchValue({ pool: undefined })),
switchMap(([fromAsset, toAsset]) =>
getSelectedPool(fromAsset?.id, toAsset?.id),
),
),
(pool) => form.patchValue({ pool }),
(pool) => {
if (pool) {
form.patchValue({ pool });
} else {
form.patchValue(
{
pool,
toAsset: undefined,
toAmount:
lastEditedField === 'to' ? form.value.toAmount : undefined,
fromAmount:
lastEditedField === 'from' ? form.value.fromAmount : undefined,
},
{ emitEvent: 'silent' },
);
}
},
[lastEditedField],
);

useSubscription(
combineLatest([
form.controls.fromAmount.valueChangesWithSystem$,
form.controls.pool.valueChanges$,
]).pipe(
debounceTime(100),
filter(([, pool]) => !!form.value.fromAsset && !!pool),
),
([amount, pool]) => {
form.patchValue(
{ toAmount: amount ? pool?.calculateOutputAmount(amount) : undefined },
{ emitEvent: 'silent' },
);
form.controls.fromAmount.valueChanges$.pipe(skip(1)),
(value) => {
setLastEditedField('from');

if (form.value.pool && value) {
form.controls.toAmount.patchValue(
form.value.pool.calculateOutputAmount(value),
{ emitEvent: 'silent' },
);
} else {
form.controls.toAmount.patchValue(undefined, { emitEvent: 'silent' });
}
},
);

useSubscription(
form.controls.toAmount.valueChanges$.pipe(
debounceTime(100),
filter(() => !!form.value.toAsset && !!form.value.pool),
),
(amount) => {
form.patchValue(
{
fromAmount: amount
? form.value.pool?.calculateInputAmount(amount)
: undefined,
},
{ emitEvent: 'silent' },
);
form.controls.toAmount.valueChanges$.pipe(skip(1)),
(value) => {
setLastEditedField('to');

if (form.value.pool && value) {
form.controls.fromAmount.patchValue(
form.value.pool.calculateInputAmount(value),
{ emitEvent: 'silent' },
);
} else {
form.controls.fromAmount.patchValue(undefined, { emitEvent: 'silent' });
}
},
);

useSubscription(
combineLatest([
form.controls.toAsset.valueChanges$,
form.controls.fromAsset.valueChanges$,
form.controls.pool.valueChanges$,
]).pipe(debounceTime(200)),
() => {
const { fromAmount, toAmount, pool } = form.value;

if (!pool) {
return;
}

if (lastEditedField === 'from' && fromAmount && fromAmount.isPositive()) {
form.controls.toAmount.patchValue(
pool.calculateOutputAmount(fromAmount),
{ emitEvent: 'silent' },
);
}
if (lastEditedField === 'to' && toAmount && toAmount.isPositive()) {
form.controls.fromAmount.patchValue(
pool.calculateInputAmount(toAmount),
{ emitEvent: 'silent' },
);
}
},
[lastEditedField],
);

const switchAssets = () => {
form.patchValue(
{
fromAsset: form.value.toAsset,
fromAmount: form.value.toAmount,
toAsset: form.value.fromAsset,
fromAmount: form.value.toAmount,
toAmount: form.value.fromAmount,
},
{ emitEvent: 'system' },
{ emitEvent: 'silent' },
);
setLastEditedField((prev) => (prev === 'from' ? 'to' : 'from'));
};

const { t } = useTranslation();
Expand All @@ -218,6 +266,7 @@ export const Swap = (): JSX.Element => {
actionButton="Swap"
getInsufficientTokenNameForFee={getInsufficientTokenNameForFee}
getInsufficientTokenNameForTx={getInsufficientTokenNameForTx}
isLoading={isPoolLoading}
isAmountNotEntered={isAmountNotEntered}
isTokensNotSelected={isTokensNotSelected}
isLiquidityInsufficient={isLiquidityInsufficient}
Expand Down

0 comments on commit 4dd5a9b

Please sign in to comment.