mirror of
https://github.com/kremalicious/asi-calculator.git
synced 2024-12-22 17:33:18 +01:00
refactor to allow switching markets
This commit is contained in:
parent
1496238b4a
commit
c75354a54d
@ -1,9 +1,9 @@
|
|||||||
import styles from './page.module.css'
|
import styles from './page.module.css'
|
||||||
import { Swap, Buy } from '@/components/Strategies'
|
import { Swap, Buy } from '@/features/strategies'
|
||||||
import { Content } from '@/components/Content'
|
import { Content } from '@/components/Content'
|
||||||
import { CalculationBase } from '@/components/CalculationBaseOutput'
|
|
||||||
import { Header } from '@/components/Header'
|
import { Header } from '@/components/Header'
|
||||||
import { Footer } from '@/components/Footer'
|
import { Footer } from '@/components/Footer'
|
||||||
|
import { MarketData } from '@/features/prices'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
@ -16,7 +16,7 @@ export default function Home() {
|
|||||||
<Swap />
|
<Swap />
|
||||||
<Buy />
|
<Buy />
|
||||||
</div>
|
</div>
|
||||||
<CalculationBase />
|
<MarketData />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from './CalculationBase'
|
|
@ -1,26 +0,0 @@
|
|||||||
import { InputAmount } from '@/components/FormAmount/Inputs/InputAmount'
|
|
||||||
import { InputToken } from './Inputs/InputToken'
|
|
||||||
import styles from './FormAmount.module.css'
|
|
||||||
import { Dispatch, SetStateAction } from 'react'
|
|
||||||
import { TokenSymbol } from '@/types'
|
|
||||||
|
|
||||||
export function FormAmount({
|
|
||||||
amount,
|
|
||||||
setAmount,
|
|
||||||
token,
|
|
||||||
setToken,
|
|
||||||
isFiat
|
|
||||||
}: {
|
|
||||||
amount: number
|
|
||||||
setAmount: Dispatch<SetStateAction<number>>
|
|
||||||
token: TokenSymbol | string
|
|
||||||
setToken?: Dispatch<SetStateAction<TokenSymbol>>
|
|
||||||
isFiat?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<form className={styles.form}>
|
|
||||||
<InputAmount amount={amount} setAmount={setAmount} />
|
|
||||||
<InputToken token={token} setToken={setToken} isFiat={isFiat} />
|
|
||||||
</form>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { Dispatch, SetStateAction } from 'react'
|
|
||||||
import styles from './InputToken.module.css'
|
|
||||||
import { CaretDownIcon } from '@radix-ui/react-icons'
|
|
||||||
import { TokenSymbol } from '@/types'
|
|
||||||
import { useSWRConfig } from 'swr'
|
|
||||||
|
|
||||||
export function InputToken({
|
|
||||||
token,
|
|
||||||
setToken,
|
|
||||||
isFiat
|
|
||||||
}: {
|
|
||||||
token: TokenSymbol | string
|
|
||||||
isFiat?: boolean
|
|
||||||
setToken?: Dispatch<SetStateAction<TokenSymbol>>
|
|
||||||
}) {
|
|
||||||
const { mutate } = useSWRConfig()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className={styles.selectWrapper}>
|
|
||||||
<select
|
|
||||||
className={styles.select}
|
|
||||||
onChange={(e) => {
|
|
||||||
if (!setToken) return
|
|
||||||
|
|
||||||
setToken(e.target.value as TokenSymbol)
|
|
||||||
mutate('/api/quote')
|
|
||||||
}}
|
|
||||||
value={token}
|
|
||||||
disabled={!setToken}
|
|
||||||
style={setToken ? { paddingRight: '1.25rem' } : {}}
|
|
||||||
>
|
|
||||||
{isFiat ? (
|
|
||||||
<option value="USD">USD</option>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<option value="OCEAN">OCEAN</option>
|
|
||||||
<option value="FET">FET</option>
|
|
||||||
<option value="AGIX">AGIX</option>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
{setToken ? <CaretDownIcon className={styles.icon} /> : null}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './ResultRow'
|
|
@ -1,3 +1,7 @@
|
|||||||
|
.selectWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.select {
|
.select {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
all: unset;
|
all: unset;
|
||||||
@ -23,10 +27,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectWrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.1rem;
|
right: 0.1rem;
|
22
components/Select/Select.tsx
Normal file
22
components/Select/Select.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { SelectHTMLAttributes } from 'react'
|
||||||
|
import styles from './Select.module.css'
|
||||||
|
import { CaretDownIcon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
|
type Props = SelectHTMLAttributes<HTMLSelectElement> & {
|
||||||
|
options: { value: string; label: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Select({ options, ...rest }: Props) {
|
||||||
|
return (
|
||||||
|
<span className={styles.selectWrapper}>
|
||||||
|
<select className={styles.select} {...rest}>
|
||||||
|
{options.map((option) => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{options.length > 1 ? <CaretDownIcon className={styles.icon} /> : null}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
1
components/Select/index.tsx
Normal file
1
components/Select/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Select'
|
@ -1,66 +0,0 @@
|
|||||||
import { Result } from '@/components/ResultRow'
|
|
||||||
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
|
||||||
import { usePrices } from '@/hooks'
|
|
||||||
import { getTokenBySymbol } from '@/lib/utils'
|
|
||||||
import { TokenSymbol } from '@/types'
|
|
||||||
import { useQuote } from '@/hooks'
|
|
||||||
|
|
||||||
export function SwapResults({
|
|
||||||
tokenSymbol,
|
|
||||||
amount
|
|
||||||
}: {
|
|
||||||
tokenSymbol: TokenSymbol
|
|
||||||
amount: number
|
|
||||||
}) {
|
|
||||||
const {
|
|
||||||
prices,
|
|
||||||
isValidating: isValidatingPrices,
|
|
||||||
isLoading: isLoadingPrices
|
|
||||||
} = usePrices()
|
|
||||||
|
|
||||||
const {
|
|
||||||
amountToOcean,
|
|
||||||
amountToAgix,
|
|
||||||
amountToFet,
|
|
||||||
isValidatingToAgix,
|
|
||||||
isLoadingToAgix,
|
|
||||||
isValidatingToFet,
|
|
||||||
isLoadingToFet,
|
|
||||||
isValidatingToOcean,
|
|
||||||
isLoadingToOcean
|
|
||||||
} = useQuote(tokenSymbol, amount)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Result
|
|
||||||
token={getTokenBySymbol('OCEAN')}
|
|
||||||
amount={amountToOcean}
|
|
||||||
amountAsi={amountToOcean * ratioOceanToAsi}
|
|
||||||
amountFiat={amountToOcean * ratioOceanToAsi * prices.asi}
|
|
||||||
amountOriginalFiat={amountToOcean * prices.ocean}
|
|
||||||
isValidating={isValidatingToOcean || isValidatingPrices}
|
|
||||||
isLoading={isLoadingToOcean || isLoadingPrices}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Result
|
|
||||||
token={getTokenBySymbol('AGIX')}
|
|
||||||
amount={amountToAgix}
|
|
||||||
amountAsi={amountToAgix * ratioAgixToAsi}
|
|
||||||
amountFiat={amountToAgix * ratioAgixToAsi * prices.asi}
|
|
||||||
amountOriginalFiat={amountToAgix * prices.agix}
|
|
||||||
isValidating={isValidatingToAgix || isValidatingPrices}
|
|
||||||
isLoading={isLoadingToAgix || isLoadingPrices}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Result
|
|
||||||
token={getTokenBySymbol('FET')}
|
|
||||||
amount={amountToFet}
|
|
||||||
amountAsi={amountToFet * ratioFetToAsi}
|
|
||||||
amountFiat={amountToFet * prices.asi}
|
|
||||||
amountOriginalFiat={amountToFet * prices.asi}
|
|
||||||
isValidating={isValidatingToFet || isValidatingPrices}
|
|
||||||
isLoading={isLoadingToFet || isLoadingPrices}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
export * from './Buy'
|
|
||||||
export * from './Swap/Swap'
|
|
@ -1,4 +1,4 @@
|
|||||||
.calculationBase {
|
.marketData {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(235px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(235px, 1fr));
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -9,14 +9,14 @@
|
|||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calculationBase li {
|
.marketData li {
|
||||||
border-bottom: 1px solid rgba(var(--foreground-rgb), 0.2);
|
border-bottom: 1px solid rgba(var(--foreground-rgb), 0.2);
|
||||||
border-right: 1px solid rgba(var(--foreground-rgb), 0.2);
|
border-right: 1px solid rgba(var(--foreground-rgb), 0.2);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calculationBase p {
|
.marketData p {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
||||||
import styles from './CalculationBase.module.css'
|
import styles from './MarketData.module.css'
|
||||||
import { usePrices } from '@/hooks'
|
import { usePrices } from '@/features/prices/hooks'
|
||||||
import { Label } from '@/components/Label'
|
import { Label } from '@/components/Label'
|
||||||
|
|
||||||
export function CalculationBase() {
|
export function MarketData() {
|
||||||
const { prices, isValidating, isLoading } = usePrices()
|
const { prices, isValidating, isLoading } = usePrices()
|
||||||
|
|
||||||
const feedbackClasses = isLoading
|
const feedbackClasses = isLoading
|
||||||
@ -15,7 +15,7 @@ export function CalculationBase() {
|
|||||||
: ''
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className={styles.calculationBase}>
|
<ul className={styles.marketData}>
|
||||||
<li>
|
<li>
|
||||||
<p>1 ASI</p>
|
<p>1 ASI</p>
|
||||||
<p>
|
<p>
|
1
features/prices/components/MarketData/index.tsx
Normal file
1
features/prices/components/MarketData/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './MarketData'
|
@ -1,2 +1 @@
|
|||||||
export * from './use-prices'
|
export * from './use-prices'
|
||||||
export * from './use-quote'
|
|
@ -4,6 +4,13 @@ import useSWR from 'swr'
|
|||||||
|
|
||||||
const tokenAddresses = tokens.map((token) => token.address).toString()
|
const tokenAddresses = tokens.map((token) => token.address).toString()
|
||||||
|
|
||||||
|
export type Prices = {
|
||||||
|
ocean: number
|
||||||
|
fet: number
|
||||||
|
agix: number
|
||||||
|
asi: number
|
||||||
|
}
|
||||||
|
|
||||||
export function usePrices(): {
|
export function usePrices(): {
|
||||||
prices: { ocean: number; fet: number; agix: number; asi: number }
|
prices: { ocean: number; fet: number; agix: number; asi: number }
|
||||||
isValidating: boolean
|
isValidating: boolean
|
2
features/prices/index.tsx
Normal file
2
features/prices/index.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './components/MarketData'
|
||||||
|
export * from './hooks'
|
@ -1,13 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
|
||||||
import { Result } from '@/components/ResultRow'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useDebounce } from 'use-debounce'
|
import { useDebounce } from 'use-debounce'
|
||||||
import stylesShared from './styles.module.css'
|
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
||||||
import { usePrices } from '@/hooks'
|
import { usePrices } from '@/features/prices'
|
||||||
import { FormAmount } from '@/components/FormAmount'
|
|
||||||
import { getTokenBySymbol } from '@/lib/utils'
|
import { getTokenBySymbol } from '@/lib/utils'
|
||||||
|
import { FormAmount, Result } from '@/features/strategies/components'
|
||||||
|
import stylesShared from '@/features/strategies/styles/shared.module.css'
|
||||||
|
|
||||||
export function Buy() {
|
export function Buy() {
|
||||||
const { prices, isValidating, isLoading } = usePrices()
|
const { prices, isValidating, isLoading } = usePrices()
|
46
features/strategies/components/FormAmount/FormAmount.tsx
Normal file
46
features/strategies/components/FormAmount/FormAmount.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { InputAmount } from './Inputs/InputAmount'
|
||||||
|
import styles from './FormAmount.module.css'
|
||||||
|
import { Dispatch, SetStateAction } from 'react'
|
||||||
|
import { TokenSymbol } from '@/types'
|
||||||
|
import { Select } from '@/components/Select'
|
||||||
|
|
||||||
|
export function FormAmount({
|
||||||
|
amount,
|
||||||
|
setAmount,
|
||||||
|
token,
|
||||||
|
setToken,
|
||||||
|
isFiat
|
||||||
|
}: {
|
||||||
|
amount: number
|
||||||
|
setAmount: Dispatch<SetStateAction<number>>
|
||||||
|
token: TokenSymbol | string
|
||||||
|
setToken?: Dispatch<SetStateAction<TokenSymbol>>
|
||||||
|
isFiat?: boolean
|
||||||
|
}) {
|
||||||
|
function handleTokenChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
||||||
|
if (!setToken) return
|
||||||
|
setToken(e.target.value as TokenSymbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = isFiat
|
||||||
|
? [{ value: 'USD', label: 'USD' }]
|
||||||
|
: [
|
||||||
|
{ value: 'OCEAN', label: 'OCEAN' },
|
||||||
|
{ value: 'FET', label: 'FET' },
|
||||||
|
{ value: 'AGIX', label: 'AGIX' }
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className={styles.form}>
|
||||||
|
<InputAmount amount={amount} setAmount={setAmount} />
|
||||||
|
|
||||||
|
<Select
|
||||||
|
options={options}
|
||||||
|
value={token}
|
||||||
|
onChange={handleTokenChange}
|
||||||
|
disabled={!setToken}
|
||||||
|
style={setToken ? { paddingRight: '1.25rem' } : {}}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,4 @@
|
|||||||
'use client'
|
import { Dispatch, SetStateAction } from 'react'
|
||||||
|
|
||||||
import { Dispatch, SetStateAction, useRef, useState } from 'react'
|
|
||||||
import styles from './InputAmount.module.css'
|
import styles from './InputAmount.module.css'
|
||||||
|
|
||||||
export function InputAmount({
|
export function InputAmount({
|
@ -0,0 +1,8 @@
|
|||||||
|
.form {
|
||||||
|
display: inline-flex;
|
||||||
|
border: 1px solid rgba(var(--foreground-rgb), 0.15);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
margin: -0.15em 0.25rem 0 0.25rem;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
27
features/strategies/components/FormMarket/FormMarket.tsx
Normal file
27
features/strategies/components/FormMarket/FormMarket.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import styles from './FormMarket.module.css'
|
||||||
|
import { Dispatch, SetStateAction } from 'react'
|
||||||
|
import { Select } from '@/components/Select'
|
||||||
|
import { type Market } from '@/features/strategies'
|
||||||
|
|
||||||
|
export function FormMarket({
|
||||||
|
market,
|
||||||
|
setMarket
|
||||||
|
}: {
|
||||||
|
market: Market
|
||||||
|
setMarket: Dispatch<SetStateAction<Market>>
|
||||||
|
}) {
|
||||||
|
const options = [
|
||||||
|
{ value: 'market', label: 'All Markets' },
|
||||||
|
{ value: 'uniswap-v3', label: 'Uniswap v3' }
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<form className={styles.form}>
|
||||||
|
<Select
|
||||||
|
options={options}
|
||||||
|
value={market}
|
||||||
|
onChange={(e) => setMarket(e.target.value as Market)}
|
||||||
|
style={{ paddingRight: '1.25rem' }}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
1
features/strategies/components/FormMarket/index.tsx
Normal file
1
features/strategies/components/FormMarket/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './FormMarket'
|
@ -1,7 +1,7 @@
|
|||||||
import styles from './ResultRow.module.css'
|
import styles from './Result.module.css'
|
||||||
import { formatNumber } from '@/lib/utils'
|
import { formatNumber } from '@/lib/utils'
|
||||||
import { ArrowRightIcon } from '@radix-ui/react-icons'
|
import { ArrowRightIcon } from '@radix-ui/react-icons'
|
||||||
import { TokenLogo } from '../TokenLogo/TokenLogo'
|
import { TokenLogo } from '../../../../components/TokenLogo/TokenLogo'
|
||||||
import { Token } from '@/types'
|
import { Token } from '@/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
1
features/strategies/components/Result/index.tsx
Normal file
1
features/strategies/components/Result/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Result'
|
81
features/strategies/components/Swap/Results.tsx
Normal file
81
features/strategies/components/Swap/Results.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
||||||
|
import { usePrices, type Prices } from '@/features/prices'
|
||||||
|
import { getTokenBySymbol } from '@/lib/utils'
|
||||||
|
import { TokenSymbol } from '@/types'
|
||||||
|
import { useQuote, type Market } from '@/features/strategies'
|
||||||
|
import { Result } from '../Result'
|
||||||
|
|
||||||
|
export function SwapResults({
|
||||||
|
tokenSymbol,
|
||||||
|
amount,
|
||||||
|
market
|
||||||
|
}: {
|
||||||
|
tokenSymbol: TokenSymbol
|
||||||
|
amount: number
|
||||||
|
market: Market
|
||||||
|
}) {
|
||||||
|
const isUniswap = market === 'uniswap-v3'
|
||||||
|
|
||||||
|
const {
|
||||||
|
prices,
|
||||||
|
isValidating: isValidatingPrices,
|
||||||
|
isLoading: isLoadingPrices
|
||||||
|
} = usePrices()
|
||||||
|
|
||||||
|
const {
|
||||||
|
amountToOcean: amountToOceanUniswap,
|
||||||
|
amountToAgix: amountToAgixUniswap,
|
||||||
|
amountToFet: amountToFetUniswap,
|
||||||
|
isValidatingToAgix,
|
||||||
|
isLoadingToAgix,
|
||||||
|
isValidatingToFet,
|
||||||
|
isLoadingToFet,
|
||||||
|
isValidatingToOcean,
|
||||||
|
isLoadingToOcean
|
||||||
|
} = useQuote(tokenSymbol, amount, isUniswap)
|
||||||
|
|
||||||
|
const amountInUsd = amount * prices[tokenSymbol.toLowerCase() as keyof Prices]
|
||||||
|
const amountToOcean = amountInUsd / prices.ocean
|
||||||
|
const amountToAgix = amountInUsd / prices.agix
|
||||||
|
const amountToFet = amountInUsd / prices.fet
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Result
|
||||||
|
token={getTokenBySymbol('OCEAN')}
|
||||||
|
amount={amountToOceanUniswap || amountToOcean}
|
||||||
|
amountAsi={(amountToOceanUniswap || amountToOcean) * ratioOceanToAsi}
|
||||||
|
amountFiat={
|
||||||
|
(amountToOceanUniswap || amountToOcean) * ratioOceanToAsi * prices.asi
|
||||||
|
}
|
||||||
|
amountOriginalFiat={
|
||||||
|
(amountToOceanUniswap || amountToOcean) * prices.ocean
|
||||||
|
}
|
||||||
|
isValidating={isValidatingToOcean || isValidatingPrices}
|
||||||
|
isLoading={isLoadingToOcean || isLoadingPrices}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Result
|
||||||
|
token={getTokenBySymbol('AGIX')}
|
||||||
|
amount={amountToAgixUniswap || amountToAgix}
|
||||||
|
amountAsi={(amountToAgixUniswap || amountToAgix) * ratioAgixToAsi}
|
||||||
|
amountFiat={
|
||||||
|
(amountToAgixUniswap || amountToAgix) * ratioAgixToAsi * prices.asi
|
||||||
|
}
|
||||||
|
amountOriginalFiat={(amountToAgixUniswap || amountToAgix) * prices.agix}
|
||||||
|
isValidating={isValidatingToAgix || isValidatingPrices}
|
||||||
|
isLoading={isLoadingToAgix || isLoadingPrices}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Result
|
||||||
|
token={getTokenBySymbol('FET')}
|
||||||
|
amount={amountToFetUniswap || amountToFet}
|
||||||
|
amountAsi={(amountToFetUniswap || amountToFet) * ratioFetToAsi}
|
||||||
|
amountFiat={(amountToFetUniswap || amountToFet) * prices.asi}
|
||||||
|
amountOriginalFiat={(amountToFetUniswap || amountToFet) * prices.asi}
|
||||||
|
isValidating={isValidatingToFet || isValidatingPrices}
|
||||||
|
isLoading={isLoadingToFet || isLoadingPrices}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,16 +1,18 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import stylesShared from '../styles.module.css'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useDebounce } from 'use-debounce'
|
import { useDebounce } from 'use-debounce'
|
||||||
import { FormAmount } from '@/components/FormAmount'
|
|
||||||
import { SwapResults } from './Results'
|
import { SwapResults } from './Results'
|
||||||
import { TokenSymbol } from '@/types'
|
import { TokenSymbol } from '@/types'
|
||||||
|
import { FormAmount, FormMarket } from '@/features/strategies/components'
|
||||||
|
import stylesShared from '@/features/strategies/styles/shared.module.css'
|
||||||
|
import { type Market } from '@/features/strategies'
|
||||||
|
|
||||||
export function Swap() {
|
export function Swap() {
|
||||||
const [amount, setAmount] = useState(100)
|
const [amount, setAmount] = useState(100)
|
||||||
const [debouncedAmount] = useDebounce(amount, 500)
|
const [debouncedAmount] = useDebounce(amount, 500)
|
||||||
const [tokenSymbol, setTokenSymbol] = useState<TokenSymbol>('OCEAN')
|
const [tokenSymbol, setTokenSymbol] = useState<TokenSymbol>('OCEAN')
|
||||||
|
const [market, setMarket] = useState<Market>('all')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={stylesShared.results}>
|
<div className={stylesShared.results}>
|
||||||
@ -22,10 +24,15 @@ export function Swap() {
|
|||||||
setAmount={setAmount}
|
setAmount={setAmount}
|
||||||
setToken={setTokenSymbol}
|
setToken={setTokenSymbol}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
on Uniswap right now gets you:
|
on <FormMarket market={market} setMarket={setMarket} /> right now gets
|
||||||
|
you:
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<SwapResults tokenSymbol={tokenSymbol} amount={debouncedAmount} />
|
<SwapResults
|
||||||
|
tokenSymbol={tokenSymbol}
|
||||||
|
amount={debouncedAmount}
|
||||||
|
market={market}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
3
features/strategies/components/index.tsx
Normal file
3
features/strategies/components/index.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './FormAmount'
|
||||||
|
export * from './FormMarket'
|
||||||
|
export * from './Result'
|
1
features/strategies/hooks/index.tsx
Normal file
1
features/strategies/hooks/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './use-quote'
|
89
features/strategies/hooks/use-quote.ts
Normal file
89
features/strategies/hooks/use-quote.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { TokenSymbol } from '@/types'
|
||||||
|
import { getTokenAddressBySymbol, fetcher } from '@/lib/utils'
|
||||||
|
import useSWR from 'swr'
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
keepPreviousData: true // so loading UI can kick in properly
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useQuote(
|
||||||
|
tokenSymbol: TokenSymbol,
|
||||||
|
amount: number,
|
||||||
|
shouldFetch: boolean
|
||||||
|
) {
|
||||||
|
// -> AGIX
|
||||||
|
const {
|
||||||
|
data: dataSwapToAgix,
|
||||||
|
isValidating: isValidatingToAgix,
|
||||||
|
isLoading: isLoadingToAgix
|
||||||
|
} = useSWR(
|
||||||
|
shouldFetch
|
||||||
|
? `/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
||||||
|
tokenSymbol
|
||||||
|
)}&tokenOut=${getTokenAddressBySymbol('AGIX')}&amountIn=${amount}`
|
||||||
|
: null,
|
||||||
|
fetcher,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
// -> FET
|
||||||
|
const {
|
||||||
|
data: dataSwapToFet,
|
||||||
|
isValidating: isValidatingToFet,
|
||||||
|
isLoading: isLoadingToFet
|
||||||
|
} = useSWR(
|
||||||
|
shouldFetch
|
||||||
|
? `/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
||||||
|
tokenSymbol
|
||||||
|
)}&tokenOut=${getTokenAddressBySymbol('FET')}&amountIn=${amount}`
|
||||||
|
: null,
|
||||||
|
fetcher,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
// -> OCEAN
|
||||||
|
const {
|
||||||
|
data: dataSwapToOcean,
|
||||||
|
isValidating: isValidatingToOcean,
|
||||||
|
isLoading: isLoadingToOcean
|
||||||
|
} = useSWR(
|
||||||
|
shouldFetch
|
||||||
|
? `/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
||||||
|
tokenSymbol
|
||||||
|
)}&tokenOut=${getTokenAddressBySymbol('OCEAN')}&amountIn=${amount}`
|
||||||
|
: null,
|
||||||
|
fetcher,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
const amountToOcean =
|
||||||
|
dataSwapToOcean?.amountOut / Number(`1e${dataSwapToOcean?.decimals}`)
|
||||||
|
const amountToAgix =
|
||||||
|
dataSwapToAgix?.amountOut / Number(`1e${dataSwapToAgix?.decimals}`)
|
||||||
|
const amountToFet =
|
||||||
|
dataSwapToFet?.amountOut / Number(`1e${dataSwapToFet?.decimals}`)
|
||||||
|
|
||||||
|
return shouldFetch
|
||||||
|
? {
|
||||||
|
amountToOcean,
|
||||||
|
amountToAgix,
|
||||||
|
amountToFet,
|
||||||
|
isValidatingToAgix,
|
||||||
|
isLoadingToAgix,
|
||||||
|
isValidatingToFet,
|
||||||
|
isLoadingToFet,
|
||||||
|
isValidatingToOcean,
|
||||||
|
isLoadingToOcean
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
amountToOcean: undefined,
|
||||||
|
amountToAgix: undefined,
|
||||||
|
amountToFet: undefined,
|
||||||
|
isValidatingToAgix: false,
|
||||||
|
isLoadingToAgix: false,
|
||||||
|
isValidatingToFet: false,
|
||||||
|
isLoadingToFet: false,
|
||||||
|
isValidatingToOcean: false,
|
||||||
|
isLoadingToOcean: false
|
||||||
|
}
|
||||||
|
}
|
4
features/strategies/index.tsx
Normal file
4
features/strategies/index.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './components/Buy'
|
||||||
|
export * from './components/Swap/Swap'
|
||||||
|
export * from './hooks'
|
||||||
|
export * from './types'
|
@ -9,4 +9,5 @@
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: rgb(var(--foreground-rgb-highlight));
|
color: rgb(var(--foreground-rgb-highlight));
|
||||||
min-height: 58px;
|
min-height: 58px;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
1
features/strategies/types.ts
Normal file
1
features/strategies/types.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type Market = 'all' | 'uniswap-v3'
|
@ -1,67 +0,0 @@
|
|||||||
import { TokenSymbol } from '@/types'
|
|
||||||
import { getTokenAddressBySymbol, fetcher } from '@/lib/utils'
|
|
||||||
import useSWR from 'swr'
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
keepPreviousData: true // so loading UI can kick in properly
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useQuote(tokenSymbol: TokenSymbol, amount: number) {
|
|
||||||
// -> AGIX
|
|
||||||
const {
|
|
||||||
data: dataSwapToAgix,
|
|
||||||
isValidating: isValidatingToAgix,
|
|
||||||
isLoading: isLoadingToAgix
|
|
||||||
} = useSWR(
|
|
||||||
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
|
||||||
tokenSymbol
|
|
||||||
)}&tokenOut=${getTokenAddressBySymbol('AGIX')}&amountIn=${amount}`,
|
|
||||||
fetcher,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
|
|
||||||
// -> FET
|
|
||||||
const {
|
|
||||||
data: dataSwapToFet,
|
|
||||||
isValidating: isValidatingToFet,
|
|
||||||
isLoading: isLoadingToFet
|
|
||||||
} = useSWR(
|
|
||||||
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
|
||||||
tokenSymbol
|
|
||||||
)}&tokenOut=${getTokenAddressBySymbol('FET')}&amountIn=${amount}`,
|
|
||||||
fetcher,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
|
|
||||||
// -> OCEAN
|
|
||||||
const {
|
|
||||||
data: dataSwapToOcean,
|
|
||||||
isValidating: isValidatingToOcean,
|
|
||||||
isLoading: isLoadingToOcean
|
|
||||||
} = useSWR(
|
|
||||||
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
|
||||||
tokenSymbol
|
|
||||||
)}&tokenOut=${getTokenAddressBySymbol('OCEAN')}&amountIn=${amount}`,
|
|
||||||
fetcher,
|
|
||||||
options
|
|
||||||
)
|
|
||||||
|
|
||||||
const amountToOcean =
|
|
||||||
dataSwapToOcean?.amountOut / Number(`1e${dataSwapToOcean?.decimals}`)
|
|
||||||
const amountToAgix =
|
|
||||||
dataSwapToAgix?.amountOut / Number(`1e${dataSwapToAgix?.decimals}`)
|
|
||||||
const amountToFet =
|
|
||||||
dataSwapToFet?.amountOut / Number(`1e${dataSwapToFet?.decimals}`)
|
|
||||||
|
|
||||||
return {
|
|
||||||
amountToOcean,
|
|
||||||
amountToAgix,
|
|
||||||
amountToFet,
|
|
||||||
isValidatingToAgix,
|
|
||||||
isLoadingToAgix,
|
|
||||||
isValidatingToFet,
|
|
||||||
isLoadingToFet,
|
|
||||||
isValidatingToOcean,
|
|
||||||
isLoadingToOcean
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user