Skip to content

Commit

Permalink
Show unfilled in estimate (#715)
Browse files Browse the repository at this point in the history
  • Loading branch information
grod220 authored Mar 11, 2024
1 parent 929796f commit 641b5b1
Show file tree
Hide file tree
Showing 18 changed files with 116 additions and 150 deletions.
46 changes: 40 additions & 6 deletions apps/minifront/src/components/swap/asset-out-box.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useStore } from '../../state';
import { swapSelector } from '../../state/swap';
import { SimulateSwapResult, swapSelector } from '../../state/swap';
import {
buttonVariants,
Tooltip,
Expand All @@ -17,6 +17,8 @@ import { groupByAsset } from '../../fetchers/balances/by-asset';
import { cn } from '@penumbra-zone/ui/lib/utils';
import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
import { Amount } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/num/v1/num_pb';
import { getAmount } from '@penumbra-zone/getters';
import { isZero } from '@penumbra-zone/types';

const findMatchingBalance = (
metadata: Metadata | undefined,
Expand All @@ -43,7 +45,8 @@ interface AssetOutBoxProps {
}

export const AssetOutBox = ({ balances }: AssetOutBoxProps) => {
const { assetOut, setAssetOut, simulateSwap, simulateOutResult } = useStore(swapSelector);
const { assetOut, setAssetOut, simulateSwap, simulateOutLoading, simulateOutResult } =
useStore(swapSelector);

const matchingBalance = findMatchingBalance(assetOut, balances);

Expand All @@ -54,9 +57,9 @@ export const AssetOutBox = ({ balances }: AssetOutBoxProps) => {
</div>
<div className='flex items-center justify-between gap-4'>
{simulateOutResult ? (
<ValueViewComponent view={simulateOutResult} showDenom={false} showIcon={false} />
<Result result={simulateOutResult} />
) : (
<EstimateButton simulateFn={simulateSwap} />
<EstimateButton simulateFn={simulateSwap} loading={simulateOutLoading} />
)}
<AssetOutSelector
balances={balances}
Expand All @@ -75,13 +78,44 @@ export const AssetOutBox = ({ balances }: AssetOutBoxProps) => {
);
};

const EstimateButton = ({ simulateFn }: { simulateFn: () => Promise<void> }) => (
const Result = ({ result: { output, unfilled } }: { result: SimulateSwapResult }) => {
// If no part unfilled, just show plain output amount (no label)
if (isZero(getAmount(unfilled))) {
return <ValueViewComponent view={output} showDenom={false} showIcon={false} />;
}

// Else is partially filled, show amounts with labels
return (
<div className='flex flex-col gap-2'>
<div className='flex flex-col items-center'>
<ValueViewComponent view={output} showIcon={false} />
<span className='font-mono text-[12px] italic text-gray-500'>Filled amount</span>
</div>
<div className='flex flex-col items-center'>
<ValueViewComponent view={unfilled} showIcon={false} />
<span className='font-mono text-[12px] italic text-gray-500'>Unfilled amount</span>
</div>
</div>
);
};

const EstimateButton = ({
simulateFn,
loading,
}: {
simulateFn: () => Promise<void>;
loading: boolean;
}) => (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<div
// Nested buttons are not allowed. Manually passing button classes.
className={cn(buttonVariants({ variant: 'secondary' }), 'w-32 md:h-9')}
className={cn(
buttonVariants({ variant: 'secondary' }),
'w-32 md:h-9',
loading ? 'animate-pulse duration-700' : undefined,
)}
onClick={e => {
e.preventDefault();
void simulateFn();
Expand Down
30 changes: 0 additions & 30 deletions apps/minifront/src/fetchers/simulate.ts

This file was deleted.

6 changes: 3 additions & 3 deletions apps/minifront/src/state/swap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe('Swap Slice', () => {
test('changing assetIn clears simulation', () => {
expect(useStore.getState().swap.simulateOutResult).toBeUndefined();
useStore.setState(state => {
state.swap.simulateOutResult = new ValueView();
state.swap.simulateOutResult = { output: new ValueView(), unfilled: new ValueView() };
return state;
});
expect(useStore.getState().swap.simulateOutResult).toBeDefined();
Expand All @@ -84,7 +84,7 @@ describe('Swap Slice', () => {
test('changing assetOut clears simulation', () => {
expect(useStore.getState().swap.simulateOutResult).toBeUndefined();
useStore.setState(state => {
state.swap.simulateOutResult = new ValueView();
state.swap.simulateOutResult = { output: new ValueView(), unfilled: new ValueView() };
return state;
});
expect(useStore.getState().swap.simulateOutResult).toBeDefined();
Expand All @@ -95,7 +95,7 @@ describe('Swap Slice', () => {
test('changing amount clears simulation', () => {
expect(useStore.getState().swap.simulateOutResult).toBeUndefined();
useStore.setState(state => {
state.swap.simulateOutResult = new ValueView();
state.swap.simulateOutResult = { output: new ValueView(), unfilled: new ValueView() };
return state;
});
expect(useStore.getState().swap.simulateOutResult).toBeDefined();
Expand Down
51 changes: 46 additions & 5 deletions apps/minifront/src/state/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,22 @@ import {
getAssetId,
getAssetIdFromValueView,
getDisplayDenomExponentFromValueView,
getMetadata,
getSwapCommitmentFromTx,
} from '@penumbra-zone/getters';
import { toBaseUnit } from '@penumbra-zone/types';
import { BigNumber } from 'bignumber.js';
import { getAddressByIndex } from '../fetchers/address';
import { StateCommitment } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/crypto/tct/v1/tct_pb';
import { simulateSwapOutput } from '../fetchers/simulate';
import { errorToast, TransactionToast } from '@penumbra-zone/ui';
import { Transaction } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb';
import { toBaseUnit } from '@penumbra-zone/types';
import { SimulateTradeRequest } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/dex/v1/dex_pb';
import { simulateClient } from '../clients';

export interface SimulateSwapResult {
output: ValueView;
unfilled: ValueView;
}

export interface SwapSlice {
assetIn: BalancesResponse | undefined;
Expand All @@ -41,7 +48,8 @@ export interface SwapSlice {
initiateSwapTx: () => Promise<void>;
txInProgress: boolean;
simulateSwap: () => Promise<void>;
simulateOutResult: ValueView | undefined;
simulateOutResult: SimulateSwapResult | undefined;
simulateOutLoading: boolean;
}

export const createSwapSlice = (): SliceCreator<SwapSlice> => (set, get) => {
Expand Down Expand Up @@ -69,8 +77,13 @@ export const createSwapSlice = (): SliceCreator<SwapSlice> => (set, get) => {
},
txInProgress: false,
simulateOutResult: undefined,
simulateOutLoading: false,
simulateSwap: async () => {
try {
set(({ swap }) => {
swap.simulateOutLoading = true;
});

const assetIn = get().swap.assetIn;
const assetOut = get().swap.assetOut;
if (!assetIn || !assetOut) throw new Error('Both asset in and out need to be set');
Expand All @@ -82,13 +95,41 @@ export const createSwapSlice = (): SliceCreator<SwapSlice> => (set, get) => {
getDisplayDenomExponentFromValueView(assetIn.balanceView),
),
});
const req = new SimulateTradeRequest({
input: swapInValue,
output: getAssetId(assetOut),
});
const res = await simulateClient.simulateTrade(req);

const output = new ValueView({
valueView: {
case: 'knownAssetId',
value: {
amount: res.output?.output?.amount,
metadata: assetOut,
},
},
});

const unfilled = new ValueView({
valueView: {
case: 'knownAssetId',
value: {
amount: res.unfilled?.amount,
metadata: getMetadata(assetIn.balanceView),
},
},
});

const outputVal = await simulateSwapOutput(swapInValue, assetOut);
set(({ swap }) => {
swap.simulateOutResult = outputVal;
swap.simulateOutResult = { output, unfilled };
});
} catch (e) {
errorToast(e, 'Error estimating swap').render();
} finally {
set(({ swap }) => {
swap.simulateOutLoading = false;
});
}
},
initiateSwapTx: async () => {
Expand Down
23 changes: 23 additions & 0 deletions packages/types/src/amount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
displayUsd,
fromBaseUnitAmount,
fromValueView,
isZero,
joinLoHiAmount,
} from './amount';
import { Amount } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/num/v1/num_pb';
Expand Down Expand Up @@ -168,3 +169,25 @@ describe('Formatting', () => {
});
});
});

describe('isZero', () => {
it('works with zero amount', () => {
const amount = new Amount({ lo: 0n, hi: 0n });
expect(isZero(amount)).toBeTruthy();
});

it('works with negatives', () => {
const amount = new Amount({ lo: -13n, hi: 1n });
expect(isZero(amount)).toBeFalsy();
});

it('detects hi number presence', () => {
const amount = new Amount({ lo: 0n, hi: 1n });
expect(isZero(amount)).toBeFalsy();
});

it('detects lo number presence', () => {
const amount = new Amount({ lo: 20n, hi: 0n });
expect(isZero(amount)).toBeFalsy();
});
});
4 changes: 4 additions & 0 deletions packages/types/src/amount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export const fromBaseUnitAmount = (amount: Amount, exponent = 0): BigNumber => {
return fromBaseUnit(amount.lo, amount.hi, exponent);
};

export const isZero = (amount: Amount): boolean => {
return joinLoHi(amount.lo, amount.hi) === 0n;
};

export const fromValueView = ({ amount, metadata }: ValueView_KnownAssetId): BigNumber => {
if (!amount) throw new Error('No amount in value view');
if (!metadata) throw new Error('No denom in value view');
Expand Down
2 changes: 0 additions & 2 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export * from './querier';
export * from './amount';
export * from './chain';
export * from './jsonified';
export * from './schemas';
export * from './type-predicates';
export * from './staking';
export * from './identity-key';
export * from './customize-symbol';
6 changes: 0 additions & 6 deletions packages/types/src/schemas/address-index.ts

This file was deleted.

6 changes: 0 additions & 6 deletions packages/types/src/schemas/address.ts

This file was deleted.

7 changes: 0 additions & 7 deletions packages/types/src/schemas/asset-id.ts

This file was deleted.

7 changes: 0 additions & 7 deletions packages/types/src/schemas/asset-image--theme.ts

This file was deleted.

8 changes: 0 additions & 8 deletions packages/types/src/schemas/asset-image.ts

This file was deleted.

15 changes: 0 additions & 15 deletions packages/types/src/schemas/denom-metadata.ts

This file was deleted.

7 changes: 0 additions & 7 deletions packages/types/src/schemas/denom-unit.ts

This file was deleted.

7 changes: 0 additions & 7 deletions packages/types/src/schemas/index.ts

This file was deleted.

25 changes: 0 additions & 25 deletions packages/types/src/type-predicates/address-view.ts

This file was deleted.

2 changes: 0 additions & 2 deletions packages/types/src/type-predicates/index.ts

This file was deleted.

Loading

0 comments on commit 641b5b1

Please sign in to comment.