Skip to content

Commit

Permalink
Account for Dutch auction schedules/ends in the WASM planner (#1017)
Browse files Browse the repository at this point in the history
* Account for Dutch auction schedules/ends in the WASM planner

* Remove useless conversion

* Use real nonce

* Create UI to schedule Dutch auctions (#967)

* Build out Dutch auction UI

* Add more docs to Slider

* Fix metadata

* Rename Prices -> Price

* Fix test name

* Create first tests for assembleRequest

* Add one more test

* Tweak Price layout

* Add auction to swap sublinks

* Remove tabs for now

* Convert to a Record to avoid having to assert presence

* Change min output

* Change to decimal

* Align to the right

* Handle exponent when processing amounts

* Add test coverage for constants
  • Loading branch information
jessepinho authored May 2, 2024
1 parent a9cb099 commit e68ef74
Show file tree
Hide file tree
Showing 42 changed files with 988 additions and 97 deletions.
14 changes: 14 additions & 0 deletions apps/minifront/public/auction-gradient.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions apps/minifront/src/components/dashboard/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { DashboardTabMap } from './types';
import { PagePath } from '../metadata/paths';
import { EduPanel } from '../shared/edu-panels/content';
import { Tab } from '../shared/tabs';

export const dashboardTabs = [
{ title: 'Assets', href: PagePath.DASHBOARD, active: true },
{ title: 'Transactions', href: PagePath.TRANSACTIONS, active: true },
{ title: 'NFTs', href: PagePath.NFTS, active: false },
export const dashboardTabs: Tab[] = [
{ title: 'Assets', href: PagePath.DASHBOARD, enabled: true },
{ title: 'Transactions', href: PagePath.TRANSACTIONS, enabled: true },
{ title: 'NFTs', href: PagePath.NFTS, enabled: false },
];

export const dashboardTabsHelper: DashboardTabMap = {
Expand Down
1 change: 1 addition & 0 deletions apps/minifront/src/components/header/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const headerLinks: HeaderLink[] = [
href: PagePath.SWAP,
label: 'Swap',
active: true,
subLinks: [PagePath.SWAP_AUCTION],
mobileIcon: <SwapIcon />,
},
{
Expand Down
8 changes: 6 additions & 2 deletions apps/minifront/src/components/metadata/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ export const metadata: Record<PagePath, PageMetadata> = {
},
[PagePath.SWAP]: {
title: 'Penumbra | Swap',
description: eduPanelContent[EduPanel.TEMP_FILLER],
description: eduPanelContent[EduPanel.SWAP],
},
[PagePath.SWAP_AUCTION]: {
title: 'Penumbra | Auction',
description: eduPanelContent[EduPanel.SWAP_AUCTION],
},
[PagePath.STAKING]: {
title: 'Penumbra | Staking',
description: eduPanelContent[EduPanel.TEMP_FILLER],
description: eduPanelContent[EduPanel.STAKING],
},
[PagePath.TRANSACTION_DETAILS]: {
title: 'Penumbra | Transaction',
Expand Down
1 change: 1 addition & 0 deletions apps/minifront/src/components/metadata/paths.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export enum PagePath {
INDEX = '/',
SWAP = '/swap',
SWAP_AUCTION = '/swap/auction',
SEND = '/send',
STAKING = '/staking',
RECEIVE = '/send/receive',
Expand Down
18 changes: 16 additions & 2 deletions apps/minifront/src/components/root-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import { SendAssetBalanceLoader, SendForm } from './send/send-form';
import { Receive } from './send/receive';
import { ErrorBoundary } from './shared/error-boundary';
import { SwapLayout } from './swap/layout';
import { SwapLoader } from './swap/swap-loader';
import { SwapLoader } from './swap/swap/swap-loader';
import { StakingLayout, StakingLoader } from './staking/layout';
import { IbcLoader } from './ibc/ibc-loader';
import { IbcLayout } from './ibc/layout';
import { Swap } from './swap/swap';
import { DutchAuction } from './swap/dutch-auction';
import { DutchAuctionLoader } from './swap/dutch-auction/dutch-auction-loader';

export const rootRouter = createHashRouter([
{
Expand Down Expand Up @@ -57,8 +60,19 @@ export const rootRouter = createHashRouter([
},
{
path: PagePath.SWAP,
loader: SwapLoader,
element: <SwapLayout />,
children: [
{
index: true,
loader: SwapLoader,
element: <Swap />,
},
{
path: PagePath.SWAP_AUCTION,
loader: DutchAuctionLoader,
element: <DutchAuction />,
},
],
},
{
path: PagePath.TRANSACTION_DETAILS,
Expand Down
4 changes: 2 additions & 2 deletions apps/minifront/src/components/send/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export const sendTabsHelper: SendTabMap = {
};

export const sendTabs = [
{ title: 'Send', href: PagePath.SEND, active: true },
{ title: 'Receive', href: PagePath.RECEIVE, active: true },
{ title: 'Send', href: PagePath.SEND, enabled: true },
{ title: 'Receive', href: PagePath.RECEIVE, enabled: true },
];
19 changes: 11 additions & 8 deletions apps/minifront/src/components/shared/asset-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ import { ValueViewComponent } from '@penumbra-zone/ui/components/ui/tx/view/valu
import { useEffect, useMemo, useState } from 'react';
import { IconInput } from '@penumbra-zone/ui/components/ui/icon-input';
import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
import { SwapLoaderResponse } from '../swap/swap-loader';
import { useLoaderData } from 'react-router-dom';

interface AssetSelectorProps {
assets: Metadata[];
value?: Metadata;
onChange: (metadata: Metadata) => void;
/**
Expand Down Expand Up @@ -46,8 +45,7 @@ const switchAssetIfNecessary = ({
}
};

const useFilteredAssets = ({ value, onChange, filter }: AssetSelectorProps) => {
const { assets } = useLoaderData() as SwapLoaderResponse;
const useFilteredAssets = ({ assets, value, onChange, filter }: AssetSelectorProps) => {
const sortedAssets = useMemo(
() =>
[...assets].sort((a, b) =>
Expand All @@ -66,7 +64,7 @@ const useFilteredAssets = ({ value, onChange, filter }: AssetSelectorProps) => {
[filter, value, filteredAssets, onChange],
);

return { assets: filteredAssets, search, setSearch };
return { filteredAssets, search, setSearch };
};

const bySearch = (search: string) => (asset: Metadata) =>
Expand All @@ -80,8 +78,13 @@ const bySearch = (search: string) => (asset: Metadata) =>
* For an asset selector that picks from the user's balances, use
* `<BalanceSelector />`.
*/
export const AssetSelector = ({ onChange, value, filter }: AssetSelectorProps) => {
const { assets, search, setSearch } = useFilteredAssets({ value, onChange, filter });
export const AssetSelector = ({ assets, onChange, value, filter }: AssetSelectorProps) => {
const { filteredAssets, search, setSearch } = useFilteredAssets({
assets,
value,
onChange,
filter,
});

/**
* @todo: Refactor to not use `ValueViewComponent`, since it's not intended to
Expand Down Expand Up @@ -109,7 +112,7 @@ export const AssetSelector = ({ onChange, value, filter }: AssetSelectorProps) =
onChange={setSearch}
placeholder='Search assets...'
/>
{assets.map(metadata => (
{filteredAssets.map(metadata => (
<div key={metadata.display} className='flex flex-col'>
<DialogClose>
<div
Expand Down
3 changes: 3 additions & 0 deletions apps/minifront/src/components/shared/edu-panels/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum EduPanel {
RECEIVING_FUNDS,
IBC_WITHDRAW,
SWAP,
SWAP_AUCTION,
STAKING,
TEMP_FILLER,
}
Expand All @@ -25,6 +26,8 @@ export const eduPanelContent: Record<EduPanel, string> = {
'IBC to a connected chain. Note that if the chain is a transparent chain, the transaction will be visible to others.',
[EduPanel.SWAP]:
'Shielded swaps between any kind of cryptoasset, with sealed-bid, batch pricing and no frontrunning. Only the batch totals are revealed, providing long-term privacy. Penumbra has no MEV, because transactions do not leak data about user activity.',
[EduPanel.SWAP_AUCTION]:
"Offer a specific quantity of cryptocurrency at decreasing prices until all the tokens are sold. Buyers can place bids at the price they're willing to pay, with the auction concluding when all tokens are sold or when the auction time expires. This mechanism allows for price discovery based on market demand, with participants potentially acquiring tokens at prices lower than initially offered.",
[EduPanel.STAKING]:
'Explore the available validator nodes and their associated rewards, performance metrics, and staking requirements. Select the validator you wish to delegate your tokens to, based on factors like uptime, reputation, and expected returns. Stay informed about validator performance updates, rewards distribution, and any network upgrades to ensure a seamless staking experience.',
[EduPanel.TEMP_FILLER]:
Expand Down
10 changes: 8 additions & 2 deletions apps/minifront/src/components/shared/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import { cn } from '@penumbra-zone/ui/lib/utils';
import { PagePath } from '../metadata/paths';
import { useNavigate } from 'react-router-dom';

export interface Tab {
title: string;
enabled: boolean;
href: PagePath;
}

interface TabsProps {
tabs: { title: string; active: boolean; href: PagePath }[];
tabs: Tab[];
activeTab: PagePath;
className?: string;
}
Expand All @@ -21,7 +27,7 @@ export const Tabs = ({ tabs, activeTab, className }: TabsProps) => {
>
{tabs.map(
tab =>
tab.active && (
tab.enabled && (
<Button
className={cn(
'w-full transition-all',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Slider } from '@penumbra-zone/ui/components/ui/slider';
import { DURATION_OPTIONS } from '../../../state/dutch-auction/constants';
import { useStoreShallow } from '../../../utils/use-store-shallow';
import { AllSlices } from '../../../state';

const durationSliderSelector = (state: AllSlices) => ({
duration: state.dutchAuction.duration,
setDuration: state.dutchAuction.setDuration,
});

export const DurationSlider = () => {
const { duration, setDuration } = useStoreShallow(durationSliderSelector);

const handleChange = (newValue: number[]) => {
const value = newValue[0]!; // We don't use multiple values in the slider
const option = DURATION_OPTIONS[value]!;

setDuration(option);
};

return (
<div className='flex flex-col items-center gap-4'>
<Slider
min={0}
max={DURATION_OPTIONS.length - 1}
value={[DURATION_OPTIONS.indexOf(duration)]}
onValueChange={handleChange}
/>
{duration}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Button } from '@penumbra-zone/ui/components/ui/button';
import { AllSlices } from '../../../state';
import { useStoreShallow } from '../../../utils/use-store-shallow';
import { InputBlock } from '../../shared/input-block';
import InputToken from '../../shared/input-token';
import { DurationSlider } from './duration-slider';
import { Price } from './price';

const dutchAuctionFormSelector = (state: AllSlices) => ({
balances: state.dutchAuction.balancesResponses,
assetIn: state.dutchAuction.assetIn,
setAssetIn: state.dutchAuction.setAssetIn,
amount: state.dutchAuction.amount,
setAmount: state.dutchAuction.setAmount,
onSubmit: state.dutchAuction.onSubmit,
submitButtonDisabled: state.dutchAuction.txInProgress || !state.dutchAuction.amount,
});

export const DutchAuctionForm = () => {
const { amount, setAmount, assetIn, setAssetIn, balances, onSubmit, submitButtonDisabled } =
useStoreShallow(dutchAuctionFormSelector);

return (
<form
className='flex flex-col gap-4 xl:gap-3'
onSubmit={e => {
e.preventDefault();
void onSubmit();
}}
>
<InputToken
label='Amount to sell'
balances={balances}
selection={assetIn}
setSelection={setAssetIn}
value={amount}
onChange={e => {
if (Number(e.target.value) < 0) return;
setAmount(e.target.value);
}}
placeholder='Enter an amount'
/>

<InputBlock label='Duration'>
<div className='pt-2'>
<DurationSlider />
</div>
</InputBlock>

<InputBlock label='Price'>
<div className='pt-2'>
<Price />
</div>
</InputBlock>

<Button variant='gradient' type='submit' disabled={submitButtonDisabled}>
Start auctions
</Button>
</form>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { throwIfPraxNotConnectedTimeout } from '@penumbra-zone/client';
import { getSwappableBalancesResponses } from '../helpers';
import { useStore } from '../../../state';
import { getAllAssets } from '../../../fetchers/assets';

export const DutchAuctionLoader = async () => {
await throwIfPraxNotConnectedTimeout();

const [assets, balancesResponses] = await Promise.all([
getAllAssets(),
getSwappableBalancesResponses(),
]);
useStore.getState().dutchAuction.setBalancesResponses(balancesResponses);

if (balancesResponses[0]) {
useStore.getState().dutchAuction.setAssetIn(balancesResponses[0]);
useStore.getState().dutchAuction.setAssetOut(assets[0]!);
}

return assets;
};
23 changes: 23 additions & 0 deletions apps/minifront/src/components/swap/dutch-auction/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Card } from '@penumbra-zone/ui/components/ui/card';
import { EduPanel } from '../../shared/edu-panels/content';
import { EduInfoCard } from '../../shared/edu-panels/edu-info-card';
import { DutchAuctionForm } from './dutch-auction-form';

export const DutchAuction = () => {
return (
<div className='grid gap-6 md:grid-cols-2 md:gap-4 xl:grid-cols-3 xl:gap-5'>
<div className='hidden xl:block'></div>

<Card gradient className='order-3 row-span-2 flex-1 p-5 md:order-1 md:p-4 xl:p-5'>
<DutchAuctionForm />
</Card>

<EduInfoCard
className='row-span-1 md:order-2'
src='./auction-gradient.svg'
label='Dutch Auction'
content={EduPanel.SWAP_AUCTION}
/>
</div>
);
};
Loading

0 comments on commit e68ef74

Please sign in to comment.