mirror of
https://github.com/kremalicious/asi-calculator.git
synced 2024-12-22 17:33:18 +01:00
Merge pull request #2 from kremalicious/simplify
Allow market selection
This commit is contained in:
commit
fd7bbe224a
@ -3,16 +3,14 @@ import { Hanken_Grotesk } from 'next/font/google'
|
||||
import '@/styles/globals.css'
|
||||
import '@/styles/loading-ui.css'
|
||||
import Script from 'next/script'
|
||||
import { title, description } from '@/constants'
|
||||
|
||||
const hankenGrotesk = Hanken_Grotesk({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-hanken-grotesk'
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'ASI Calculator',
|
||||
description: 'See how much ASI you get for your OCEAN, AGIX, or FET.'
|
||||
}
|
||||
export const metadata: Metadata = { title, description }
|
||||
|
||||
export default function RootLayout({
|
||||
children
|
||||
|
10
app/page.tsx
10
app/page.tsx
@ -1,9 +1,7 @@
|
||||
import styles from './page.module.css'
|
||||
import { Swap, Buy } from '@/components/Strategies'
|
||||
import { Content } from '@/components/Content'
|
||||
import { CalculationBase } from '@/components/CalculationBaseOutput'
|
||||
import { Header } from '@/components/Header'
|
||||
import { Footer } from '@/components/Footer'
|
||||
import { Swap, Buy } from '@/features/strategies'
|
||||
import { MarketData } from '@/features/prices'
|
||||
import { Content, Footer, Header } from '@/components'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@ -16,7 +14,7 @@ export default function Home() {
|
||||
<Swap />
|
||||
<Buy />
|
||||
</div>
|
||||
<CalculationBase />
|
||||
<MarketData />
|
||||
</section>
|
||||
|
||||
<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,36 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { Dispatch, SetStateAction, useRef, useState } from 'react'
|
||||
import styles from './InputAmount.module.css'
|
||||
|
||||
export function InputAmount({
|
||||
amount,
|
||||
setAmount
|
||||
}: {
|
||||
amount: number
|
||||
setAmount: Dispatch<SetStateAction<number>>
|
||||
}) {
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const { value } = e.target
|
||||
|
||||
if (value === '') {
|
||||
setAmount(0)
|
||||
} else {
|
||||
setAmount(Number(value))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
className={styles.input}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
value={amount}
|
||||
onChange={handleChange}
|
||||
style={{
|
||||
width: Math.min(Math.max(amount.toString().length, 2), 50) + 'ch'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
@ -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,11 +1,11 @@
|
||||
import { metadata } from '@/app/layout'
|
||||
import { title, description } from '@/constants'
|
||||
import styles from './Header.module.css'
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<header>
|
||||
<h1 className={styles.title}>{`${metadata.title}`}</h1>
|
||||
<p className={styles.description}>{`${metadata.description}`}</p>
|
||||
<h1 className={styles.title}>{`${title}`}</h1>
|
||||
<p className={styles.description}>{`${description}`}</p>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
8
components/Input/Input.tsx
Normal file
8
components/Input/Input.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import { InputHTMLAttributes } from 'react'
|
||||
import styles from './Input.module.css'
|
||||
|
||||
type Props = InputHTMLAttributes<HTMLInputElement>
|
||||
|
||||
export function Input(props: Props) {
|
||||
return <input className={styles.input} {...props} />
|
||||
}
|
1
components/Input/index.tsx
Normal file
1
components/Input/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from './Input'
|
@ -1 +0,0 @@
|
||||
export * from './ResultRow'
|
@ -1,3 +1,7 @@
|
||||
.selectWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.select {
|
||||
display: inline-block;
|
||||
all: unset;
|
||||
@ -23,10 +27,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.selectWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
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'
|
7
components/index.tsx
Normal file
7
components/index.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
export * from './Content'
|
||||
export * from './Footer'
|
||||
export * from './Header'
|
||||
export * from './Input'
|
||||
export * from './Label'
|
||||
export * from './Select'
|
||||
export * from './TokenLogo'
|
@ -1,5 +1,9 @@
|
||||
import { Token } from '@/types'
|
||||
|
||||
export const title = 'ASI Calculator'
|
||||
export const description =
|
||||
'See how much ASI you get for your OCEAN, AGIX, or FET.'
|
||||
|
||||
export const ratioOceanToAsi = 0.433226
|
||||
export const ratioAgixToAsi = 0.43335
|
||||
export const ratioFetToAsi = 1
|
||||
|
@ -1,4 +1,6 @@
|
||||
The **→ lines** show what you would get with the displayed token amount at the moment of the ASI swap, along with the converted value based on the current market price of FET. The fiat values are fetched from [Coingecko](https://coingecko.com), and the token swap estimations directly from [Uniswap](https://uniswap.org) v3 swap routes.
|
||||
The **→ lines** show what you would get with the displayed token amount at the moment of the ASI swap, along with the converted value based on the current market price of FET.
|
||||
|
||||
The fiat values are fetched from [Coingecko](https://coingecko.com), and the token swap estimations directly from [Uniswap](https://uniswap.org) v3 swap routes.
|
||||
|
||||
All displayed values should be seen as estimates. Except for the [fixed ASI exchange rate](https://blog.oceanprotocol.com/ocean-protocol-is-joining-the-superintelligence-alliance-767c82693f24#3c8e), all other values are constantly changing based on market conditions. There is no guarantee the displayed values reflect the value of your investment once the actual ASI swap mechanism is released. Use at your own risk.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
.calculationBase {
|
||||
.marketData {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(235px, 1fr));
|
||||
justify-content: center;
|
||||
@ -9,14 +9,14 @@
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.calculationBase li {
|
||||
.marketData li {
|
||||
border-bottom: 1px solid rgba(var(--foreground-rgb), 0.2);
|
||||
border-right: 1px solid rgba(var(--foreground-rgb), 0.2);
|
||||
padding: 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.calculationBase p {
|
||||
.marketData p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
|
||||
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
||||
import styles from './CalculationBase.module.css'
|
||||
import { usePrices } from '@/hooks'
|
||||
import { Label } from '@/components/Label'
|
||||
import styles from './MarketData.module.css'
|
||||
import { usePrices } from '@/features/prices'
|
||||
import { Label } from '@/components'
|
||||
|
||||
export function CalculationBase() {
|
||||
export function MarketData() {
|
||||
const { prices, isValidating, isLoading } = usePrices()
|
||||
|
||||
const feedbackClasses = isLoading
|
||||
@ -15,7 +15,7 @@ export function CalculationBase() {
|
||||
: ''
|
||||
|
||||
return (
|
||||
<ul className={styles.calculationBase}>
|
||||
<ul className={styles.marketData}>
|
||||
<li>
|
||||
<p>1 ASI</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-quote'
|
@ -1,9 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import { tokens } from '@/constants'
|
||||
import { fetcher, getTokenAddressBySymbol } from '@/lib/utils'
|
||||
import useSWR from 'swr'
|
||||
|
||||
const tokenAddresses = tokens.map((token) => token.address).toString()
|
||||
|
||||
export type Prices = {
|
||||
ocean: number
|
||||
fet: number
|
||||
agix: number
|
||||
asi: number
|
||||
}
|
||||
|
||||
export function usePrices(): {
|
||||
prices: { ocean: number; fet: number; agix: number; asi: number }
|
||||
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'
|
||||
|
||||
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
||||
import { Result } from '@/components/ResultRow'
|
||||
import { useState } from 'react'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
import stylesShared from './styles.module.css'
|
||||
import { usePrices } from '@/hooks'
|
||||
import { FormAmount } from '@/components/FormAmount'
|
||||
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
||||
import { usePrices } from '@/features/prices'
|
||||
import { getTokenBySymbol } from '@/lib/utils'
|
||||
import { FormAmount, Result } from '@/features/strategies/components'
|
||||
import stylesShared from '@/features/strategies/styles/shared.module.css'
|
||||
|
||||
export function Buy() {
|
||||
const { prices, isValidating, isLoading } = usePrices()
|
64
features/strategies/components/FormAmount/FormAmount.tsx
Normal file
64
features/strategies/components/FormAmount/FormAmount.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import styles from './FormAmount.module.css'
|
||||
import { Dispatch, SetStateAction } from 'react'
|
||||
import { TokenSymbol } from '@/types'
|
||||
import { Select, Input } from '@/components'
|
||||
|
||||
export function FormAmount({
|
||||
amount,
|
||||
setAmount,
|
||||
token,
|
||||
setToken,
|
||||
isFiat
|
||||
}: {
|
||||
amount: number
|
||||
setAmount: Dispatch<SetStateAction<number>>
|
||||
token: TokenSymbol | string
|
||||
setToken?: Dispatch<SetStateAction<TokenSymbol>>
|
||||
isFiat?: boolean
|
||||
}) {
|
||||
function handleAmountChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const { value } = e.target
|
||||
|
||||
if (value === '') {
|
||||
setAmount(0)
|
||||
} else {
|
||||
setAmount(Number(value))
|
||||
}
|
||||
}
|
||||
|
||||
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}>
|
||||
<Input
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
value={amount}
|
||||
onChange={handleAmountChange}
|
||||
style={{
|
||||
width: Math.min(Math.max(amount.toString().length, 2), 50) + 'ch'
|
||||
}}
|
||||
/>
|
||||
|
||||
<Select
|
||||
options={options}
|
||||
value={token}
|
||||
onChange={handleTokenChange}
|
||||
disabled={!setToken}
|
||||
style={setToken ? { paddingRight: '1.25rem' } : {}}
|
||||
/>
|
||||
</form>
|
||||
)
|
||||
}
|
@ -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'
|
||||
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 { ArrowRightIcon } from '@radix-ui/react-icons'
|
||||
import { TokenLogo } from '../TokenLogo/TokenLogo'
|
||||
import { TokenLogo } from '@/components'
|
||||
import { Token } from '@/types'
|
||||
|
||||
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 { getTokenBySymbol } from '@/lib/utils'
|
||||
import { type TokenSymbol } from '@/types'
|
||||
import { usePrices, type Prices } from '@/features/prices'
|
||||
import { type Market, useQuote } 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'
|
||||
|
||||
import stylesShared from '../styles.module.css'
|
||||
import { useState } from 'react'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
import { FormAmount } from '@/components/FormAmount'
|
||||
import { SwapResults } from './Results'
|
||||
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() {
|
||||
const [amount, setAmount] = useState(100)
|
||||
const [debouncedAmount] = useDebounce(amount, 500)
|
||||
const [tokenSymbol, setTokenSymbol] = useState<TokenSymbol>('OCEAN')
|
||||
const [market, setMarket] = useState<Market>('all')
|
||||
|
||||
return (
|
||||
<div className={stylesShared.results}>
|
||||
@ -22,10 +24,15 @@ export function Swap() {
|
||||
setAmount={setAmount}
|
||||
setToken={setTokenSymbol}
|
||||
/>{' '}
|
||||
on Uniswap right now gets you:
|
||||
on <FormMarket market={market} setMarket={setMarket} /> right now gets
|
||||
you:
|
||||
</h3>
|
||||
|
||||
<SwapResults tokenSymbol={tokenSymbol} amount={debouncedAmount} />
|
||||
<SwapResults
|
||||
tokenSymbol={tokenSymbol}
|
||||
amount={debouncedAmount}
|
||||
market={market}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
1
features/strategies/components/Swap/index.tsx
Normal file
1
features/strategies/components/Swap/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from './Swap'
|
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'
|
91
features/strategies/hooks/use-quote.ts
Normal file
91
features/strategies/hooks/use-quote.ts
Normal file
@ -0,0 +1,91 @@
|
||||
'use client'
|
||||
|
||||
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'
|
||||
export * from './hooks'
|
||||
export * from './types'
|
@ -9,4 +9,5 @@
|
||||
font-size: 1.2rem;
|
||||
color: rgb(var(--foreground-rgb-highlight));
|
||||
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