-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add limit order form * Construct limit order position * Refactor order form stores & introduce pnum util * Update pnum description * Use remote pnum * Fix lint issues * Fix handling of input fields numbers * tmp commit * Convert to base units * Start fleshing out shared position logic * Add tests for position math * Integrate position logic into range liquidity form * Define limit order position function * Add some documentation to position math * Wire up limit position form * Remove slider from limit order form * Remove key error * Apply same p,q,r1,r2 values as rust code * Construct positions taking into account asset order * Have position plans take in display units for reserves * Fix limit order amount handling + gas fee calc * Fix display amounts for range liquidity form * Bug: don't display weird text in order input * Define PriceLinkedInputs store This store will be very useful for market and limit orders, and already addresses some yet unreported sources of UX oddities, like the inputs not updating with the price. * Define new store for MarketOrderForm * Move AssetInfo to separate file * Add store for limit order position * Add RangeOrderFormStore * Basic wiring of form refactor * Implement order submission * Fix range liquidity fee tier selection * Correct range liquidity * Gas fee calculation, remove unused stuff * Fix remaining lints * Correct order of p and q in position construction * Use a more robust method of getting p and q in range This should handle cases where they're out of range by losing only the necessary precision, and it should handle cases where the price gets flattened to 0 * Remove wheel input changes * Fix market slider input change * Refactor setter methods for mobx strict mode, fix rerenders * Fix hydration issue caused by connectionStore.connected * Fix inputs disappearing * Add active states to select groups * Remove setup connection hoc * Wrapp app with observer --------- Co-authored-by: Lucas Meier <lucas@cronokirby.com>
- Loading branch information
1 parent
843a6bb
commit 699f741
Showing
30 changed files
with
1,921 additions
and
1,007 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { AssetId, Metadata, Value } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; | ||
import { Amount } from '@penumbra-zone/protobuf/penumbra/core/num/v1/num_pb'; | ||
import { pnum } from '@penumbra-zone/types/pnum'; | ||
|
||
/** A basic utility class containing information we need about an asset. | ||
* | ||
* This extracts out the useful components we might need for the current | ||
* asset. | ||
*/ | ||
export class AssetInfo { | ||
/** | ||
* @param balance the balance, in display units. | ||
* @param the exponent to convert from base units to display units. | ||
*/ | ||
constructor( | ||
public id: AssetId, | ||
public exponent: number, | ||
public symbol: string, | ||
public balance?: number, | ||
) {} | ||
|
||
static fromMetadata(metadata: Metadata, balance?: Amount): undefined | AssetInfo { | ||
const displayDenom = metadata.denomUnits.find(x => x.denom === metadata.display); | ||
if (!displayDenom || !metadata.penumbraAssetId) { | ||
return undefined; | ||
} | ||
return new AssetInfo( | ||
metadata.penumbraAssetId, | ||
displayDenom.exponent, | ||
metadata.symbol, | ||
balance && pnum(balance, displayDenom.exponent).toNumber(), | ||
); | ||
} | ||
|
||
/** Convert an amount, in display units, into a Value (of this asset). */ | ||
value(display: number): Value { | ||
return new Value({ | ||
amount: pnum(display, this.exponent).toAmount(), | ||
assetId: this.id, | ||
}); | ||
} | ||
|
||
/** Format an amount (in display units) as a simple string. */ | ||
formatDisplayAmount(amount: number): string { | ||
const amountString = pnum(amount, this.exponent).toFormattedString({ | ||
commas: true, | ||
decimals: 4, | ||
trailingZeros: false, | ||
}); | ||
return `${amountString} ${this.symbol}`; | ||
} | ||
|
||
/** Format the balance of this asset as a simple string. */ | ||
formatBalance(): undefined | string { | ||
return this.balance !== undefined ? this.formatDisplayAmount(this.balance) : undefined; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { useSummary } from './useSummary'; | ||
|
||
export const useMarketPrice = () => { | ||
const { data: summary } = useSummary('1d'); | ||
if (!summary || 'noData' in summary) { | ||
return undefined; | ||
} | ||
return summary.price; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,41 @@ | ||
import { useState } from 'react'; | ||
import { useAutoAnimate } from '@formkit/auto-animate/react'; | ||
import { Tabs } from '@penumbra-zone/ui/Tabs'; | ||
import { Density } from '@penumbra-zone/ui/Density'; | ||
import { MarketOrderForm } from './order-form/order-form-market'; | ||
import { LimitOrderForm } from './order-form/order-form-limit'; | ||
import { RangeLiquidityOrderForm } from './order-form/order-form-range-liquidity'; | ||
import { isWhichForm, useOrderFormStore } from './order-form/store/OrderFormStore'; | ||
import { observer } from 'mobx-react-lite'; | ||
|
||
enum FormTabsType { | ||
Market = 'market', | ||
Limit = 'limit', | ||
Range = 'range', | ||
} | ||
|
||
export const FormTabs = () => { | ||
export const FormTabs = observer(() => { | ||
const [parent] = useAutoAnimate(); | ||
const [tab, setTab] = useState<FormTabsType>(FormTabsType.Market); | ||
const store = useOrderFormStore(); | ||
|
||
return ( | ||
<div ref={parent} className='flex flex-col'> | ||
<div className='px-4 lg:pt-2 border-b border-b-other-solidStroke'> | ||
<Density compact> | ||
<Tabs | ||
value={tab} | ||
value={store.whichForm} | ||
actionType='accent' | ||
onChange={value => setTab(value as FormTabsType)} | ||
onChange={value => { | ||
if (isWhichForm(value)) { | ||
store.setWhichForm(value); | ||
} | ||
}} | ||
options={[ | ||
{ value: FormTabsType.Market, label: 'Market' }, | ||
{ value: FormTabsType.Limit, label: 'Limit' }, | ||
{ value: FormTabsType.Range, label: 'Range Liquidity' }, | ||
{ value: 'Market', label: 'Market' }, | ||
{ value: 'Limit', label: 'Limit' }, | ||
{ value: 'Range', label: 'Range Liquidity' }, | ||
]} | ||
/> | ||
</Density> | ||
</div> | ||
|
||
<div className='overflow-y-auto'> | ||
{tab === FormTabsType.Market && <MarketOrderForm />} | ||
{tab === FormTabsType.Limit && ( | ||
<div className='h-[380px] p-4 text-text-secondary'>Limit order form</div> | ||
)} | ||
{tab === FormTabsType.Range && <RangeLiquidityOrderForm />} | ||
{store.whichForm === 'Market' && <MarketOrderForm parentStore={store} />} | ||
{store.whichForm === 'Limit' && <LimitOrderForm parentStore={store} />} | ||
{store.whichForm === 'Range' && <RangeLiquidityOrderForm parentStore={store} />} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { InfoRow } from './info-row'; | ||
|
||
export const InfoRowGasFee = ({ | ||
gasFee, | ||
symbol, | ||
isLoading, | ||
}: { | ||
gasFee: string; | ||
symbol: string; | ||
isLoading: boolean; | ||
}) => { | ||
return ( | ||
<InfoRow | ||
label='Gas Fee' | ||
isLoading={isLoading} | ||
value={`${gasFee} ${symbol}`} | ||
toolTip='The gas cost of the transaction. Gas fees are burned as part of transaction processing.' | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { InfoRow } from './info-row'; | ||
|
||
export const InfoRowTradingFee = () => { | ||
return ( | ||
<InfoRow | ||
label='Trading Fee' | ||
value='Free' | ||
valueColor='success' | ||
toolTip='Penumbra has no platform trading fee. LPs set their own fees, which are included in the quoted price.' | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { observer } from 'mobx-react-lite'; | ||
import { Button } from '@penumbra-zone/ui/Button'; | ||
import { Text } from '@penumbra-zone/ui/Text'; | ||
import { connectionStore } from '@/shared/model/connection'; | ||
import { OrderInput } from './order-input'; | ||
import { SegmentedControl } from './segmented-control'; | ||
import { ConnectButton } from '@/features/connect/connect-button'; | ||
import { InfoRowTradingFee } from './info-row-trading-fee'; | ||
import { InfoRowGasFee } from './info-row-gas-fee'; | ||
import { SelectGroup } from './select-group'; | ||
import { OrderFormStore } from './store/OrderFormStore'; | ||
import { BuyLimitOrderOptions, SellLimitOrderOptions } from './store/LimitOrderFormStore'; | ||
|
||
export const LimitOrderForm = observer(({ parentStore }: { parentStore: OrderFormStore }) => { | ||
const { connected } = connectionStore; | ||
const store = parentStore.limitForm; | ||
|
||
const isBuy = store.direction === 'buy'; | ||
|
||
return ( | ||
<div className='p-4'> | ||
<SegmentedControl direction={store.direction} setDirection={store.setDirection} /> | ||
<div className='mb-4'> | ||
<div className='mb-2'> | ||
<OrderInput | ||
label={`When ${store.baseAsset?.symbol} is`} | ||
value={store.priceInput} | ||
onChange={price => store.setPriceInput(price)} | ||
denominator={store.quoteAsset?.symbol} | ||
/> | ||
</div> | ||
<SelectGroup | ||
options={Object.values(isBuy ? BuyLimitOrderOptions : SellLimitOrderOptions)} | ||
value={store.priceInputOption} | ||
onChange={option => | ||
store.setPriceInputOption(option as BuyLimitOrderOptions | SellLimitOrderOptions) | ||
} | ||
/> | ||
</div> | ||
<div className='mb-4'> | ||
<OrderInput | ||
label={isBuy ? 'Buy' : 'Sell'} | ||
value={store.baseInput} | ||
onChange={store.setBaseInput} | ||
denominator={store.baseAsset?.symbol} | ||
/> | ||
</div> | ||
<div className='mb-4'> | ||
<OrderInput | ||
label={isBuy ? 'Pay with' : 'Receive'} | ||
value={store.quoteInput} | ||
onChange={store.setQuoteInput} | ||
denominator={store.quoteAsset?.symbol} | ||
/> | ||
</div> | ||
<div className='mb-4'> | ||
<InfoRowTradingFee /> | ||
<InfoRowGasFee | ||
gasFee={parentStore.gasFee.display} | ||
symbol={parentStore.gasFee.symbol} | ||
isLoading={parentStore.gasFeeLoading} | ||
/> | ||
</div> | ||
<div className='mb-4'> | ||
{connected ? ( | ||
<Button | ||
actionType='accent' | ||
disabled={!parentStore.canSubmit} | ||
onClick={() => void parentStore.submit()} | ||
> | ||
{isBuy ? 'Buy' : 'Sell'} {store.baseAsset?.symbol} | ||
</Button> | ||
) : ( | ||
<ConnectButton actionType='default' /> | ||
)} | ||
</div> | ||
{parentStore.marketPrice && ( | ||
<div className='flex justify-center p-1'> | ||
<Text small color='text.secondary'> | ||
1 {store.baseAsset?.symbol} ={' '} | ||
<Text small color='text.primary'> | ||
{store.quoteAsset?.formatDisplayAmount(parentStore.marketPrice)} | ||
</Text> | ||
</Text> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}); |
Oops, something went wrong.