Upgrade next (#100)
* upgrade to next 13 * WIP: adjust to app dir * add docker + wallet connector * fix: update the wallet connect component * tidy: format * wip: make the wallet balance fetcher work * fix balance retrieval * MP-2258: added estimateFee hook (#94) * Mp 2259 queries to api (#96) * update next config for build errors * Convert queries to API + remove config * tidy: save some bytes by adding constants/env.ts * tidy: added URL_ prefix to REST, RPC and GQL --------- Co-authored-by: Linkie Link <linkielink.dev@gmail.com> * MP-2261: created useBroadcast hook for transactions (#95) * tidy: remove unneeded wallet images * Mp 2264 convert store (#97) * Merge stores into 1 * refactor codebase to use new store * fiex build and rename whitelisted to marketassets * tidy: import refactor * updated account navigation basics * feat: added loading component and fixed the disconnect button * fix: format * update new routing system * update config and dependencies * feat: create and delete credit account are restored * tidy: format * fix: fixed the deployment * update route structure (#98) * fix: creditAccountDeposit works again * fix: bugfixes * add apis, remove allowedCoins, get basic borrow tables (#99) Co-authored-by: bwvdhelm <34470358+bobthebuidlr@users.noreply.github.com> --------- Co-authored-by: bwvdhelm <34470358+bobthebuidlr@users.noreply.github.com>
This commit is contained in:
parent
2b91adf438
commit
b5c097d661
13
.env.example
Normal file
13
.env.example
Normal file
@ -0,0 +1,13 @@
|
||||
NEXT_PUBLIC_NETWORK=testnet
|
||||
NEXT_PUBLIC_CHAIN_ID=osmo-test-4
|
||||
NEXT_PUBLIC_RPC=https://testnet-osmosis-node.marsprotocol.io/XF32UOOU55CX/osmosis-rpc-front/
|
||||
NEXT_PUBLIC_GQL=https://testnet-osmosis-node.marsprotocol.io/XF32UOOU55CX/osmosis-hive-front/graphql
|
||||
NEXT_PUBLIC_REST=https://testnet-osmosis-node.marsprotocol.io/XF32UOOU55CX/osmosis-lcd-front/
|
||||
NEXT_PUBLIC_SWAP=https://testnet.osmosis.zone
|
||||
NEXT_PUBLIC_WALLETS=keplr,cosmostation
|
||||
NEXT_PUBLIC_ACCOUNT_NFT=osmo1l8c3g6zy7kjhuh8d2kqyvxkw0myn4puxv0tzcdf9nwxd386r9l7s3vlhzq
|
||||
NEXT_PUBLIC_ORACLE=osmo1dqz2u3c8rs5e7w5fnchsr2mpzzsxew69wtdy0aq4jsd76w7upmsstqe0s8
|
||||
NEXT_PUBLIC_RED_BANK=osmo1g30recyv8pfy3qd4qn3dn7plc0rn5z68y5gn32j39e96tjhthzxsw3uvvu
|
||||
NEXT_PUBLIC_CREDIT_MANAGER=osmo12hgn4jec4tftahm7spf7c2aqsqrxzzk50hkq60e89atumyu0zvys7vzxdc
|
||||
NEXT_PUBLIC_ZAPPER=osmo1ua8dwc9v8qjh7n3qf8kg6xvrwjm5yu9xxln7yjvgmrvfzaxvzsuqfcdnjq
|
||||
NEXT_PUBLIC_SWAPPER=osmo1uj6r9tu440wwp2mhtagh48yvmeyeaqt2xa7kdnlhyrqcuthlj4ss7ghg6n
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -37,3 +37,7 @@ next-env.d.ts
|
||||
|
||||
# Sentry
|
||||
.sentryclirc
|
||||
|
||||
# Environment variables
|
||||
.env.local
|
||||
.env.production
|
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
59
Dockerfile
Normal file
59
Dockerfile
Normal file
@ -0,0 +1,59 @@
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN yarn build
|
||||
|
||||
# If using npm comment out above and use below instead
|
||||
# RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
|
||||
CMD ["node", "server.js"]
|
@ -1,11 +1,11 @@
|
||||
const { withSentryConfig } = require('@sentry/nextjs')
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
sentry: {
|
||||
hideSourceMaps: true,
|
||||
output: 'standalone',
|
||||
experimental: {
|
||||
appDir: true,
|
||||
},
|
||||
reactStrictMode: true,
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
@ -13,9 +13,34 @@ const nextConfig = {
|
||||
destination: '/trade',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/wallets',
|
||||
destination: '/trade',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/wallets/:wallet',
|
||||
destination: '/wallets/:wallet/trade',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/wallets/:wallet/accounts',
|
||||
destination: '/wallets/:wallet/accounts/trade',
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
webpack(config) {
|
||||
webpack(config, {isServer}) {
|
||||
if (isServer) {
|
||||
config.resolve.fallback = {
|
||||
...config.resolve.fallback,
|
||||
'utf-8-validate': false,
|
||||
bufferutil: false,
|
||||
'./build/Release/ecdh': false,
|
||||
eccrypto: false,
|
||||
}
|
||||
}
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
@ -40,4 +65,4 @@ const sentryWebpackPluginOptions = {
|
||||
|
||||
// Make sure adding Sentry options is the last code to run before exporting, to
|
||||
// ensure that your source maps include changes from all other Webpack plugins
|
||||
module.exports = withSentryConfig(nextConfig, sentryWebpackPluginOptions)
|
||||
module.exports = nextConfig
|
||||
|
24
package.json
24
package.json
@ -6,17 +6,16 @@
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
"lint": "eslint ./src/ && yarn prettier-check",
|
||||
"index": "vscode-generate-index-standalone src/",
|
||||
"format": "yarn index && eslint ./src/ --fix && prettier --write ./src/",
|
||||
"prettier-check": "prettier --ignore-path .gitignore --check ./src/",
|
||||
"start": "next start",
|
||||
"index": "vscode-generate-index-standalone src/"
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/cosmwasm-stargate": "^0.29.4",
|
||||
"@cosmjs/stargate": "^0.29.5",
|
||||
"@headlessui/react": "^1.7.0",
|
||||
"@marsprotocol/wallet-connector": "^0.9.5",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@marsprotocol/wallet-connector": "^1.4.2",
|
||||
"@radix-ui/react-slider": "^1.0.0",
|
||||
"@sentry/nextjs": "^7.12.1",
|
||||
"@tanstack/react-query": "^4.3.4",
|
||||
@ -30,14 +29,16 @@
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^5.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"next": "12.3.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"next": "^13.1.6",
|
||||
"react": "^18.2.0",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-number-format": "^5.1.0",
|
||||
"react-spring": "^9.5.5",
|
||||
"react-toastify": "^9.0.8",
|
||||
"react-use-clipboard": "^1.0.9",
|
||||
"recharts": "^2.2.0",
|
||||
"swr": "^2.0.3",
|
||||
"tailwindcss-border-gradient-radius": "^3.0.1",
|
||||
"use-local-storage-state": "^18.1.1",
|
||||
"zustand": "^4.1.4"
|
||||
@ -49,13 +50,16 @@
|
||||
"@types/react-dom": "18.0.9",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "8.30.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"eslint-config-next": "^13.1.6",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"postcss": "^8.4.16",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-tailwindcss": "^0.1.13",
|
||||
"tailwindcss": "^3.2.1",
|
||||
"typescript": "4.9.4",
|
||||
"vscode-generate-index-standalone": "^1.6.0"
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.9"
|
||||
}
|
||||
}
|
||||
|
3
src/app/borrow/loading.tsx
Normal file
3
src/app/borrow/loading.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function laoding() {
|
||||
return '...isLoading'
|
||||
}
|
7
src/app/borrow/page.tsx
Normal file
7
src/app/borrow/page.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import { getBorrowData } from 'utils/api'
|
||||
|
||||
export default async function page() {
|
||||
const borrowData = await getBorrowData()
|
||||
|
||||
return `You are a guest`
|
||||
}
|
3
src/app/council/page.tsx
Normal file
3
src/app/council/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a guest`
|
||||
}
|
3
src/app/earn/page.tsx
Normal file
3
src/app/earn/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a guest`
|
||||
}
|
30
src/app/head.tsx
Normal file
30
src/app/head.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
export default function Head() {
|
||||
return (
|
||||
<>
|
||||
<title>Mars Protocol V2</title>
|
||||
<meta charSet='utf-8' />
|
||||
<link href='/favicon.svg' rel='icon' />
|
||||
<link href='/apple-touch-icon.png' rel='apple-touch-icon' sizes='180x180' />
|
||||
<link href='/site.webmanifest' rel='manifest' />
|
||||
<link color='#dd5b65' href='/safari-pinned-tab.svg' rel='mask-icon' />
|
||||
<meta content='index,follow' name='robots' />
|
||||
<meta
|
||||
content="Lend, borrow and earn on the galaxy's most powerful credit protocol or enter the Fields of Mars for advanced DeFi strategies."
|
||||
name='description'
|
||||
/>
|
||||
<meta content='summary_large_image' name='twitter:card' />
|
||||
<meta content='@mars_protocol' name='twitter:site' />
|
||||
<meta content='@mars_protocol' name='twitter:creator' />
|
||||
<meta content='https://osmosis.marsprotocol.io' property='og:url' />
|
||||
<meta content='Mars Protocol V2 - Powered by Osmosis' property='og:title' />
|
||||
<meta
|
||||
content="Lend, borrow and earn on the galaxy's most powerful credit protocol or enter the Fields of Mars for advanced DeFi strategies."
|
||||
property='og:description'
|
||||
/>
|
||||
<meta content='https://osmosis.marsprotocol.io/banner.png' property='og:image' />
|
||||
<meta content='Mars Protocol V2' property='og:site_name' />
|
||||
<meta content='#ffffff' name='msapplication-TileColor' />
|
||||
<meta content='#ffffff' name='theme-color' />
|
||||
</>
|
||||
)
|
||||
}
|
29
src/app/layout.tsx
Normal file
29
src/app/layout.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import Background from 'components/Background'
|
||||
import { Modals } from 'components/Modals'
|
||||
import DesktopNavigation from 'components/Navigation/DesktopNavigation'
|
||||
import Toaster from 'components/Toaster'
|
||||
import { WalletConnectProvider } from 'components/Wallet/WalletConnectProvider'
|
||||
import 'react-toastify/dist/ReactToastify.min.css'
|
||||
import 'styles/globals.css'
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang='en'>
|
||||
<head />
|
||||
|
||||
<body>
|
||||
<div className='relative min-h-screen w-full'>
|
||||
<WalletConnectProvider>
|
||||
<Background />
|
||||
<DesktopNavigation />
|
||||
</WalletConnectProvider>
|
||||
<Modals />
|
||||
<Toaster />
|
||||
<main className='relative flex lg:min-h-[calc(100vh-120px)]'>
|
||||
<div className='flex flex-grow flex-col flex-wrap'>{children}</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
3
src/app/page.tsx
Normal file
3
src/app/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return 'Connect to your wallet'
|
||||
}
|
3
src/app/portfolio/page.tsx
Normal file
3
src/app/portfolio/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a guest`
|
||||
}
|
3
src/app/trade/page.tsx
Normal file
3
src/app/trade/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a guest`
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export default function Loading() {
|
||||
return '...isLoading'
|
||||
}
|
31
src/app/wallets/[wallet]/accounts/[account]/borrow/page.tsx
Normal file
31
src/app/wallets/[wallet]/accounts/[account]/borrow/page.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { BorrowTable } from 'components/BorrowTable'
|
||||
import { AccountDebtTable } from 'components/AccountDebtTable'
|
||||
import { Card } from 'components/Card'
|
||||
import Loading from 'components/Loading'
|
||||
import { Text } from 'components/Text'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default function page({ params }: { params: PageParams }) {
|
||||
return (
|
||||
<div className='flex w-full flex-col'>
|
||||
<Card className='mb-4'>
|
||||
<Text size='lg' uppercase>
|
||||
Debt data
|
||||
</Text>
|
||||
<Suspense fallback={<Loading className='h-full w-full' />}>
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<AccountDebtTable account={params.account} />
|
||||
</Suspense>
|
||||
</Card>
|
||||
<Card>
|
||||
<Text size='lg' uppercase>
|
||||
Borrow data
|
||||
</Text>
|
||||
<Suspense fallback={<Loading className='h-full w-full' />}>
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<BorrowTable />
|
||||
</Suspense>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export default function loading() {
|
||||
return '...isLoading'
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { Card, Text } from 'components'
|
||||
import { Card } from 'components/Card'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
const Council = () => {
|
||||
export default function page() {
|
||||
return (
|
||||
<div className='flex w-full'>
|
||||
<Card>
|
||||
@ -11,5 +12,3 @@ const Council = () => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Council
|
@ -0,0 +1,3 @@
|
||||
export default function loading() {
|
||||
return '...isLoading'
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { Card, Text } from 'components'
|
||||
import { Card } from 'components/Card'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
const Earn = () => {
|
||||
export default function page() {
|
||||
return (
|
||||
<div className='flex w-full gap-4'>
|
||||
<Card>
|
||||
@ -18,5 +19,3 @@ const Earn = () => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Earn
|
3
src/app/wallets/[wallet]/accounts/[account]/page.tsx
Normal file
3
src/app/wallets/[wallet]/accounts/[account]/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return 'Trade page'
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
'use client'
|
||||
|
||||
export default function page({ params }: { params: PageParams }) {
|
||||
return 'error!'
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { getCreditAccounts } from 'utils/api'
|
||||
|
||||
export default async function page({ params }: { params: PageParams }) {
|
||||
const creditAccounts = await getCreditAccounts(params.wallet)
|
||||
|
||||
return (
|
||||
<div className='flex w-full items-start gap-4'>
|
||||
<ul>
|
||||
{creditAccounts.map((account: string, index: number) => (
|
||||
<li key={index}>{account}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export default function loading() {
|
||||
return '...isLoading'
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { Card, Text } from 'components'
|
||||
import { TradeActionModule } from 'components/Trade'
|
||||
import { Card } from 'components/Card'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
const Trade = () => {
|
||||
export default function page() {
|
||||
return (
|
||||
<div className='flex w-full flex-wrap'>
|
||||
<div className='mb-4 flex flex-grow gap-4'>
|
||||
@ -11,9 +11,7 @@ const Trade = () => {
|
||||
</Text>
|
||||
</Card>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Card>
|
||||
<TradeActionModule />
|
||||
</Card>
|
||||
<Card>{/* <TradeActionModule /> */}</Card>
|
||||
<Card>
|
||||
<Text size='lg' uppercase>
|
||||
Orderbook module (optional)
|
||||
@ -29,5 +27,3 @@ const Trade = () => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Trade
|
3
src/app/wallets/[wallet]/borrow/council/page.tsx
Normal file
3
src/app/wallets/[wallet]/borrow/council/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
3
src/app/wallets/[wallet]/borrow/earn/page.tsx
Normal file
3
src/app/wallets/[wallet]/borrow/earn/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
3
src/app/wallets/[wallet]/borrow/page.tsx
Normal file
3
src/app/wallets/[wallet]/borrow/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
3
src/app/wallets/[wallet]/borrow/portfolio/page.tsx
Normal file
3
src/app/wallets/[wallet]/borrow/portfolio/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
3
src/app/wallets/[wallet]/borrow/trade/page.tsx
Normal file
3
src/app/wallets/[wallet]/borrow/trade/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
3
src/app/wallets/[wallet]/council/page.tsx
Normal file
3
src/app/wallets/[wallet]/council/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
3
src/app/wallets/[wallet]/earn/page.tsx
Normal file
3
src/app/wallets/[wallet]/earn/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
21
src/app/wallets/[wallet]/layout.tsx
Normal file
21
src/app/wallets/[wallet]/layout.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { AccountNavigation } from 'components/Account/AccountNavigation'
|
||||
import { getCreditAccounts } from 'utils/api'
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: PageParams
|
||||
}) {
|
||||
const creditAccounts = await getCreditAccounts(params.wallet)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='relative hidden bg-header lg:block'>
|
||||
<AccountNavigation creditAccounts={creditAccounts} />
|
||||
</div>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
3
src/app/wallets/[wallet]/portfolio/page.tsx
Normal file
3
src/app/wallets/[wallet]/portfolio/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
3
src/app/wallets/[wallet]/trade/page.tsx
Normal file
3
src/app/wallets/[wallet]/trade/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
@ -1,20 +1,27 @@
|
||||
'use client'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { Button, LabelValuePair, PositionsList } from 'components'
|
||||
import { AccountManageOverlay, RiskChart } from 'components/Account'
|
||||
import { AccountManageOverlay } from 'components/Account/AccountManageOverlay'
|
||||
import { RiskChart } from 'components/Account/RiskChart'
|
||||
import { Button } from 'components/Button'
|
||||
import { ArrowRightLine, ChevronDown, ChevronLeft } from 'components/Icons'
|
||||
import { useAccountStats, useBalances } from 'hooks/data'
|
||||
import { useAccountDetailsStore, useNetworkConfigStore, useSettingsStore } from 'stores'
|
||||
import { lookup } from 'utils/formatters'
|
||||
import { LabelValuePair } from 'components/LabelValuePair'
|
||||
import { PositionsList } from 'components/PositionsList'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { useBalances } from 'hooks/data/useBalances'
|
||||
import { convertFromGwei } from 'utils/formatters'
|
||||
import { createRiskData } from 'utils/risk'
|
||||
import useStore from 'store'
|
||||
import { getBaseAsset, getMarketAssets } from 'utils/assets'
|
||||
|
||||
export const AccountDetails = () => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const isOpen = useAccountDetailsStore((s) => s.isOpen)
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const baseAsset = useNetworkConfigStore((s) => s.assets.base)
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const isOpen = useStore((s) => s.isOpen)
|
||||
const marketAssets = getMarketAssets()
|
||||
const baseAsset = getBaseAsset()
|
||||
|
||||
const balances = useBalances()
|
||||
const accountStats = useAccountStats()
|
||||
@ -36,7 +43,7 @@ export const AccountDetails = () => {
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
useAccountDetailsStore.setState({ isOpen: true })
|
||||
useStore.setState({ isOpen: true })
|
||||
}}
|
||||
variant='text'
|
||||
className={classNames(
|
||||
@ -75,7 +82,7 @@ export const AccountDetails = () => {
|
||||
enableAnimations && 'transition-[color]',
|
||||
)}
|
||||
onClick={() => {
|
||||
useAccountDetailsStore.setState({ isOpen: false })
|
||||
useStore.setState({ isOpen: false })
|
||||
}}
|
||||
>
|
||||
<ArrowRightLine />
|
||||
@ -93,7 +100,11 @@ export const AccountDetails = () => {
|
||||
label='Total Position:'
|
||||
value={{
|
||||
format: 'number',
|
||||
amount: lookup(accountStats?.totalPosition ?? 0, baseAsset.denom, whitelistedAssets),
|
||||
amount: convertFromGwei(
|
||||
accountStats?.totalPosition ?? 0,
|
||||
baseAsset.denom,
|
||||
marketAssets,
|
||||
),
|
||||
prefix: '$',
|
||||
}}
|
||||
/>
|
||||
@ -101,7 +112,7 @@ export const AccountDetails = () => {
|
||||
label='Total Liabilities:'
|
||||
value={{
|
||||
format: 'number',
|
||||
amount: lookup(accountStats?.totalDebt ?? 0, baseAsset.denom, whitelistedAssets),
|
||||
amount: convertFromGwei(accountStats?.totalDebt ?? 0, baseAsset.denom, marketAssets),
|
||||
prefix: '$',
|
||||
}}
|
||||
/>
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { useEffect } from 'react'
|
||||
'use client'
|
||||
|
||||
import { Button, Text } from 'components'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
import { Button } from 'components/Button'
|
||||
import { Add, ArrowDown, ArrowsLeftRight, ArrowUp, Rubbish } from 'components/Icons'
|
||||
import { Overlay, OverlayAction } from 'components/Overlay'
|
||||
import { useCreateCreditAccount, useDeleteCreditAccount } from 'hooks/mutations'
|
||||
import { useAccountDetailsStore, useModalStore } from 'stores'
|
||||
import { Overlay } from 'components/Overlay/Overlay'
|
||||
import { OverlayAction } from 'components/Overlay/OverlayAction'
|
||||
import { Text } from 'components/Text'
|
||||
import useParams from 'hooks/useParams'
|
||||
import useStore from 'store'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
@ -13,20 +18,22 @@ interface Props {
|
||||
}
|
||||
|
||||
export const AccountManageOverlay = ({ className, setShow, show }: Props) => {
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const createCreditAccount = useStore((s) => s.createCreditAccount)
|
||||
const deleteCreditAccount = useStore((s) => s.deleteCreditAccount)
|
||||
|
||||
const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount()
|
||||
const { mutate: deleteCreditAccount, isLoading: isLoadingDelete } = useDeleteCreditAccount(
|
||||
selectedAccount || '',
|
||||
)
|
||||
async function createAccount() {
|
||||
const newAccountId = await createCreditAccount({ fee: hardcodedFee })
|
||||
router.push(`/wallets/${params.wallet}/accounts/${newAccountId}`)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
useModalStore.setState({ createAccountModal: isLoadingCreate })
|
||||
}, [isLoadingCreate])
|
||||
|
||||
useEffect(() => {
|
||||
useModalStore.setState({ deleteAccountModal: isLoadingDelete })
|
||||
}, [isLoadingDelete])
|
||||
async function deleteAccountHandler() {
|
||||
const isSuccess = await deleteCreditAccount({ fee: hardcodedFee, accountId: params.account })
|
||||
if (isSuccess) {
|
||||
router.push(`/wallets/${params.wallet}/accounts`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Overlay className={className} show={show} setShow={setShow}>
|
||||
@ -38,7 +45,7 @@ export const AccountManageOverlay = ({ className, setShow, show }: Props) => {
|
||||
<Button
|
||||
className='flex w-[115px] items-center justify-center pl-0 pr-2'
|
||||
onClick={() => {
|
||||
useModalStore.setState({ fundAccountModal: true })
|
||||
useStore.setState({ fundAccountModal: true })
|
||||
setShow(false)
|
||||
}}
|
||||
>
|
||||
@ -51,7 +58,7 @@ export const AccountManageOverlay = ({ className, setShow, show }: Props) => {
|
||||
className='flex w-[115px] items-center justify-center pl-0 pr-2'
|
||||
color='secondary'
|
||||
onClick={() => {
|
||||
useModalStore.setState({ withdrawModal: true })
|
||||
useStore.setState({ withdrawModal: true })
|
||||
setShow(false)
|
||||
}}
|
||||
>
|
||||
@ -65,13 +72,13 @@ export const AccountManageOverlay = ({ className, setShow, show }: Props) => {
|
||||
<OverlayAction
|
||||
setShow={setShow}
|
||||
text='Create New Account'
|
||||
onClick={createCreditAccount}
|
||||
onClick={createAccount}
|
||||
icon={<Add />}
|
||||
/>
|
||||
<OverlayAction
|
||||
setShow={setShow}
|
||||
text='Close Account'
|
||||
onClick={deleteCreditAccount}
|
||||
onClick={deleteAccountHandler}
|
||||
icon={<Rubbish />}
|
||||
/>
|
||||
<OverlayAction
|
||||
|
@ -1,106 +1,130 @@
|
||||
'use client'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { Button } from 'components'
|
||||
import { AccountManageOverlay } from 'components/Account/AccountManageOverlay'
|
||||
import { Button } from 'components/Button'
|
||||
import { ChevronDown } from 'components/Icons'
|
||||
import { Overlay } from 'components/Overlay'
|
||||
import { useAccountDetailsStore } from 'stores'
|
||||
import { AccountManageOverlay } from 'components/Account'
|
||||
|
||||
interface Props {
|
||||
creditAccountsList: string[]
|
||||
selectedAccount: string | null
|
||||
}
|
||||
import { Overlay } from 'components/Overlay/Overlay'
|
||||
import useParams from 'hooks/useParams'
|
||||
import useStore from 'store'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
|
||||
const MAX_VISIBLE_CREDIT_ACCOUNTS = 5
|
||||
|
||||
export const AccountNavigation = ({ creditAccountsList, selectedAccount }: Props) => {
|
||||
const { firstCreditAccounts, restCreditAccounts } = useMemo(() => {
|
||||
return {
|
||||
firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [],
|
||||
restCreditAccounts: creditAccountsList?.slice(MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [],
|
||||
}
|
||||
}, [creditAccountsList])
|
||||
interface Props {
|
||||
creditAccounts: string[]
|
||||
}
|
||||
|
||||
export const AccountNavigation = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const address = useStore((s) => s.client?.recentWallet.account?.address) || ''
|
||||
const selectedAccount = params.account
|
||||
const createCreditAccount = useStore((s) => s.createCreditAccount)
|
||||
|
||||
const hasCreditAccounts = !!props.creditAccounts?.length
|
||||
const firstCreditAccounts = props.creditAccounts?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? []
|
||||
const restCreditAccounts = props.creditAccounts?.slice(MAX_VISIBLE_CREDIT_ACCOUNTS) ?? []
|
||||
|
||||
const [showManageMenu, setShowManageMenu] = useState(false)
|
||||
const [showMoreMenu, setShowMoreMenu] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
{firstCreditAccounts.map((account) => (
|
||||
<Button
|
||||
key={account}
|
||||
className={classNames(
|
||||
'cursor-pointer whitespace-nowrap px-4 text-base hover:text-white',
|
||||
selectedAccount === account ? 'text-white' : 'text-white/40',
|
||||
)}
|
||||
variant='text'
|
||||
onClick={() => {
|
||||
useAccountDetailsStore.setState({ selectedAccount: account, isOpen: true })
|
||||
}}
|
||||
>
|
||||
Account {account}
|
||||
</Button>
|
||||
))}
|
||||
<div className='relative'>
|
||||
{restCreditAccounts.length > 0 && (
|
||||
<>
|
||||
<Button
|
||||
className='flex items-center px-3 py-3 text-base hover:text-white'
|
||||
variant='text'
|
||||
onClick={() => setShowMoreMenu(!showMoreMenu)}
|
||||
>
|
||||
More
|
||||
<span className='ml-1 inline-block w-3'>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</Button>
|
||||
<Overlay show={showMoreMenu} setShow={setShowMoreMenu}>
|
||||
<div className='flex w-[120px] flex-wrap p-4'>
|
||||
{restCreditAccounts.map((account) => (
|
||||
<Button
|
||||
key={account}
|
||||
variant='text'
|
||||
className={classNames(
|
||||
'w-full whitespace-nowrap py-2 text-sm',
|
||||
selectedAccount === account
|
||||
? 'text-secondary'
|
||||
: 'cursor-pointer text-accent-dark hover:text-secondary',
|
||||
)}
|
||||
onClick={() => {
|
||||
setShowMoreMenu(!showMoreMenu)
|
||||
useAccountDetailsStore.setState({ selectedAccount: account, isOpen: true })
|
||||
}}
|
||||
>
|
||||
Account {account}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</Overlay>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='relative'>
|
||||
<Button
|
||||
className={classNames(
|
||||
'flex items-center px-3 py-3 text-base hover:text-white',
|
||||
showManageMenu ? 'text-white' : 'text-white/40',
|
||||
)}
|
||||
onClick={() => setShowManageMenu(!showManageMenu)}
|
||||
variant='text'
|
||||
>
|
||||
Manage
|
||||
<span className='ml-1 inline-block w-3'>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</Button>
|
||||
async function createAccountHandler() {
|
||||
const accountId = await createCreditAccount({ fee: hardcodedFee })
|
||||
if (!accountId) return
|
||||
router.push(`/wallets/${params.wallet}/accounts/${accountId}`)
|
||||
}
|
||||
|
||||
<AccountManageOverlay
|
||||
className='-left-[86px]'
|
||||
show={showManageMenu}
|
||||
setShow={setShowManageMenu}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
return (
|
||||
<section
|
||||
role='navigation'
|
||||
className='flex h-11 w-full items-center gap-6 border-b border-white/20 px-6 text-sm text-white/40'
|
||||
>
|
||||
<>
|
||||
{hasCreditAccounts ? (
|
||||
<>
|
||||
{firstCreditAccounts.map((account) => (
|
||||
<Button
|
||||
key={account}
|
||||
className={classNames(
|
||||
'cursor-pointer whitespace-nowrap px-4 text-base hover:text-white',
|
||||
selectedAccount === account ? 'text-white' : 'text-white/40',
|
||||
)}
|
||||
variant='text'
|
||||
onClick={() => {
|
||||
router.push(`/wallets/${params.wallet}/accounts/${account}/${params.page}`)
|
||||
}}
|
||||
>
|
||||
Account {account}
|
||||
</Button>
|
||||
))}
|
||||
<div className='relative'>
|
||||
{restCreditAccounts.length > 0 && (
|
||||
<>
|
||||
<Button
|
||||
className='flex items-center px-3 py-3 text-base hover:text-white'
|
||||
variant='text'
|
||||
onClick={() => setShowMoreMenu(!showMoreMenu)}
|
||||
>
|
||||
More
|
||||
<span className='ml-1 inline-block w-3'>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</Button>
|
||||
<Overlay show={showMoreMenu} setShow={setShowMoreMenu}>
|
||||
<div className='flex w-[120px] flex-wrap p-4'>
|
||||
{restCreditAccounts.map((account) => (
|
||||
<Button
|
||||
key={account}
|
||||
variant='text'
|
||||
className={classNames(
|
||||
'w-full whitespace-nowrap py-2 text-sm',
|
||||
selectedAccount === account
|
||||
? 'text-secondary'
|
||||
: 'cursor-pointer text-accent-dark hover:text-secondary',
|
||||
)}
|
||||
onClick={() => {
|
||||
setShowMoreMenu(!showMoreMenu)
|
||||
router.push(`/wallets/${params.wallet}/accounts/${account}`)
|
||||
}}
|
||||
>
|
||||
Account {account}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</Overlay>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='relative'>
|
||||
<Button
|
||||
className={classNames(
|
||||
'flex items-center px-3 py-3 text-base hover:text-white',
|
||||
showManageMenu ? 'text-white' : 'text-white/40',
|
||||
)}
|
||||
onClick={() => setShowManageMenu(!showManageMenu)}
|
||||
variant='text'
|
||||
>
|
||||
Manage
|
||||
<span className='ml-1 inline-block w-3'>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<AccountManageOverlay
|
||||
className='-left-[86px]'
|
||||
show={showManageMenu}
|
||||
setShow={setShowManageMenu}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>{address ? <Button onClick={createAccountHandler}>Create Account</Button> : ''}</>
|
||||
)}
|
||||
</>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
@ -1,22 +1,25 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useEffect } from 'react'
|
||||
'use client'
|
||||
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
import { Button, FormattedNumber, Gauge, Text } from 'components'
|
||||
import { BorrowCapacity } from 'components/BorrowCapacity'
|
||||
import { useAccountStats } from 'hooks/data'
|
||||
import { useCreateCreditAccount } from 'hooks/mutations'
|
||||
import { useCreditAccounts } from 'hooks/queries'
|
||||
import { useModalStore, useNetworkConfigStore } from 'stores'
|
||||
import { Button } from 'components/Button'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import { Text } from 'components/Text'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { useCreditAccounts } from 'hooks/queries/useCreditAccounts'
|
||||
import { getBaseAsset } from 'utils/assets'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
|
||||
export const AccountStatus = () => {
|
||||
const baseAsset = useNetworkConfigStore((s) => s.assets.base)
|
||||
const baseAsset = getBaseAsset()
|
||||
const accountStats = useAccountStats()
|
||||
const { data: creditAccountsList } = useCreditAccounts()
|
||||
const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount()
|
||||
useEffect(() => {
|
||||
useModalStore.setState({ createAccountModal: isLoadingCreate })
|
||||
}, [isLoadingCreate])
|
||||
|
||||
const createCreditAccount = () => {
|
||||
console.log('create credit account')
|
||||
}
|
||||
|
||||
const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0
|
||||
|
||||
|
@ -1,12 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { CircularProgress, Modal, Text } from 'components'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { MarsProtocol } from 'components/Icons'
|
||||
import { useModalStore } from 'stores'
|
||||
import { Modal } from 'components/Modal'
|
||||
import { Text } from 'components/Text'
|
||||
import useStore from 'store'
|
||||
|
||||
export const ConfirmModal = () => {
|
||||
const createOpen = useModalStore((s) => s.createAccountModal)
|
||||
const deleteOpen = useModalStore((s) => s.deleteAccountModal)
|
||||
const createOpen = useStore((s) => s.createAccountModal)
|
||||
const deleteOpen = useStore((s) => s.deleteAccountModal)
|
||||
|
||||
return (
|
||||
<Modal open={createOpen || deleteOpen}>
|
||||
@ -23,7 +27,7 @@ export const ConfirmModal = () => {
|
||||
</div>
|
||||
<Text size='2xl' uppercase={true} className='w-full text-center'>
|
||||
{createOpen &&
|
||||
'A small step for a Smart Contracts but a big leap for your financial freedom.'}
|
||||
'A small step for a Smart Contract but a big leap for your financial freedom.'}
|
||||
{deleteOpen && 'Some rovers have to be recycled once in a while...'}
|
||||
</Text>
|
||||
</div>
|
||||
|
@ -1,28 +1,36 @@
|
||||
import { Switch } from '@headlessui/react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import useLocalStorageState from 'use-local-storage-state'
|
||||
'use client'
|
||||
|
||||
import { Button, CircularProgress, Modal, Slider, Text } from 'components'
|
||||
import { Switch } from '@headlessui/react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
|
||||
import { Button } from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { MarsProtocol } from 'components/Icons'
|
||||
import { useDepositCreditAccount } from 'hooks/mutations'
|
||||
import { useAllBalances, useAllowedCoins } from 'hooks/queries'
|
||||
import { useAccountDetailsStore, useModalStore, useNetworkConfigStore } from 'stores'
|
||||
import { Modal } from 'components/Modal'
|
||||
import { Slider } from 'components/Slider'
|
||||
import { Text } from 'components/Text'
|
||||
import useParams from 'hooks/useParams'
|
||||
import useStore from 'store'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
import { convertFromGwei, convertToGwei } from 'utils/formatters'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import { getAccountDeposits } from 'utils/api'
|
||||
|
||||
export const FundAccountModal = () => {
|
||||
// ---------------
|
||||
// STORE
|
||||
// ---------------
|
||||
const open = useModalStore((s) => s.fundAccountModal)
|
||||
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const [lendAssets, setLendAssets] = useLocalStorageState(`lendAssets_${selectedAccount}`, {
|
||||
defaultValue: false,
|
||||
})
|
||||
const open = useStore((s) => s.fundAccountModal)
|
||||
const params = useParams()
|
||||
const depositCreditAccount = useStore((s) => s.depositCreditAccount)
|
||||
const address = useStore((s) => s.client?.recentWallet.account?.address)
|
||||
const { data: balancesData, isLoading: balanceIsLoading } = useSWR(address, getAccountDeposits)
|
||||
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const marketAssets = getMarketAssets()
|
||||
const [lendAssets, setLendAssets] = useState(false)
|
||||
// ---------------
|
||||
// LOCAL STATE
|
||||
// ---------------
|
||||
@ -30,41 +38,45 @@ export const FundAccountModal = () => {
|
||||
const [selectedToken, setSelectedToken] = useState('')
|
||||
|
||||
// ---------------
|
||||
// EXTERNAL HOOKS
|
||||
// FUNCTIONS
|
||||
// ---------------
|
||||
const { data: balancesData } = useAllBalances()
|
||||
const { data: allowedCoinsData, isLoading: isLoadingAllowedCoins } = useAllowedCoins()
|
||||
const { mutate, isLoading } = useDepositCreditAccount(
|
||||
selectedAccount || '',
|
||||
selectedToken,
|
||||
BigNumber(amount)
|
||||
.times(10 ** getTokenDecimals(selectedToken, whitelistedAssets))
|
||||
.toNumber(),
|
||||
{
|
||||
onSuccess: () => {
|
||||
setAmount(0)
|
||||
toast.success(
|
||||
`${amount} ${getTokenSymbol(selectedToken, whitelistedAssets)} successfully Deposited`,
|
||||
)
|
||||
useModalStore.setState({ fundAccountModal: false })
|
||||
},
|
||||
},
|
||||
)
|
||||
async function depositAccountHandler() {
|
||||
if (!selectedToken) return
|
||||
const deposit = {
|
||||
amount: convertToGwei(amount, selectedToken, marketAssets).toString(),
|
||||
denom: selectedToken,
|
||||
}
|
||||
const isSuccess = await depositCreditAccount({
|
||||
fee: hardcodedFee,
|
||||
accountId: params.account,
|
||||
deposit,
|
||||
})
|
||||
if (isSuccess) {
|
||||
useStore.setState({ fundAccountModal: false })
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (allowedCoinsData && allowedCoinsData.length > 0) {
|
||||
// initialize selected token when allowedCoins fetch data is available
|
||||
setSelectedToken(allowedCoinsData[0])
|
||||
}
|
||||
}, [allowedCoinsData])
|
||||
if (!marketAssets || !balancesData || selectedToken !== '') return
|
||||
let found = false
|
||||
marketAssets.map((asset) => {
|
||||
if (found) return
|
||||
if (balancesData?.find((balance) => balance.denom === asset.denom)?.amount ?? 0 > 0) {
|
||||
setSelectedToken(asset.denom)
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}, [marketAssets, balancesData])
|
||||
|
||||
// ---------------
|
||||
// VARIABLES
|
||||
// ---------------
|
||||
const walletAmount = useMemo(() => {
|
||||
if (!selectedToken) return 0
|
||||
|
||||
return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0)
|
||||
.div(10 ** getTokenDecimals(selectedToken, whitelistedAssets))
|
||||
.toNumber()
|
||||
}, [balancesData, selectedToken, whitelistedAssets])
|
||||
const walletAmount =
|
||||
balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0
|
||||
return convertFromGwei(walletAmount, selectedToken, marketAssets)
|
||||
}, [balancesData, selectedToken, marketAssets])
|
||||
|
||||
const handleValueChange = (value: number) => {
|
||||
if (value > walletAmount) {
|
||||
@ -76,16 +88,15 @@ export const FundAccountModal = () => {
|
||||
}
|
||||
|
||||
const setOpen = (open: boolean) => {
|
||||
useModalStore.setState({ fundAccountModal: open })
|
||||
useStore.setState({ fundAccountModal: open })
|
||||
}
|
||||
|
||||
const maxValue = walletAmount
|
||||
const percentageValue = isNaN(amount) ? 0 : (amount * 100) / maxValue
|
||||
const percentageValue = isNaN(amount) ? 0 : (amount * 100) / walletAmount
|
||||
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen}>
|
||||
<div className='flex min-h-[520px] w-full'>
|
||||
{isLoading && (
|
||||
{balanceIsLoading && (
|
||||
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
@ -118,68 +129,69 @@ export const FundAccountModal = () => {
|
||||
have any assets in your osmosis wallet use the osmosis bridge to transfer funds to
|
||||
your osmosis wallet.
|
||||
</Text>
|
||||
{isLoadingAllowedCoins ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
<>
|
||||
<div className='mb-4 rounded-md border border-white/20'>
|
||||
<div className='mb-1 flex justify-between border-b border-white/20 p-2'>
|
||||
<Text size='sm' className='text-white'>
|
||||
Asset:
|
||||
</Text>
|
||||
<select
|
||||
className='bg-transparent text-white outline-0'
|
||||
onChange={(e) => {
|
||||
setSelectedToken(e.target.value)
|
||||
<>
|
||||
<div className='mb-4 rounded-md border border-white/20'>
|
||||
<div className='mb-1 flex justify-between border-b border-white/20 p-2'>
|
||||
<Text size='sm' className='text-white'>
|
||||
Asset:
|
||||
</Text>
|
||||
<select
|
||||
className='bg-transparent text-white outline-0'
|
||||
onChange={(e) => {
|
||||
setSelectedToken(e.target.value)
|
||||
|
||||
if (e.target.value !== selectedToken) setAmount(0)
|
||||
}}
|
||||
value={selectedToken}
|
||||
>
|
||||
{allowedCoinsData?.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
{getTokenSymbol(entry, whitelistedAssets)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className='flex justify-between p-2'>
|
||||
<Text size='sm' className='text-white'>
|
||||
Amount:
|
||||
</Text>
|
||||
<input
|
||||
type='number'
|
||||
className='appearance-none bg-transparent text-right text-white'
|
||||
value={amount}
|
||||
min='0'
|
||||
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === '') setAmount(0)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
if (e.target.value !== selectedToken) setAmount(0)
|
||||
}}
|
||||
value={selectedToken}
|
||||
>
|
||||
{/* {marketAssets?.map((entry) => {
|
||||
const entrySymbol = getTokenSymbol(entry, marketAssets)
|
||||
return (
|
||||
entrySymbol !== '' && (
|
||||
<option key={entry} value={entry}>
|
||||
{getTokenSymbol(entry, marketAssets)}
|
||||
</option>
|
||||
)
|
||||
) */}
|
||||
{/* })} */}
|
||||
</select>
|
||||
</div>
|
||||
<Text size='xs' uppercase className='mb-2 text-white/60'>
|
||||
{`In wallet: ${walletAmount.toLocaleString()} ${getTokenSymbol(
|
||||
selectedToken,
|
||||
whitelistedAssets,
|
||||
)}`}
|
||||
</Text>
|
||||
<Slider
|
||||
className='mb-6'
|
||||
value={percentageValue}
|
||||
onChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(selectedToken, whitelistedAssets)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
<div className='flex justify-between p-2'>
|
||||
<Text size='sm' className='text-white'>
|
||||
Amount:
|
||||
</Text>
|
||||
<input
|
||||
type='number'
|
||||
className='appearance-none bg-transparent text-right text-white'
|
||||
value={amount}
|
||||
min='0'
|
||||
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
|
||||
onBlur={(e) => {
|
||||
if (e.target.value === '') setAmount(0)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<Text size='xs' uppercase className='mb-2 text-white/60'>
|
||||
{`In wallet: ${walletAmount.toLocaleString()} ${getTokenSymbol(
|
||||
selectedToken,
|
||||
marketAssets,
|
||||
)}`}
|
||||
</Text>
|
||||
<Slider
|
||||
className='mb-6'
|
||||
value={percentageValue}
|
||||
onChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(selectedToken, marketAssets)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * walletAmount).toFixed(tokenDecimals))
|
||||
|
||||
setAmount(newAmount)
|
||||
}}
|
||||
onMaxClick={() => setAmount(maxValue)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
setAmount(newAmount)
|
||||
}}
|
||||
onMaxClick={() => setAmount(walletAmount)}
|
||||
/>
|
||||
</div>
|
||||
<div className='mb-2 flex items-center justify-between'>
|
||||
<div>
|
||||
@ -207,7 +219,7 @@ export const FundAccountModal = () => {
|
||||
</div>
|
||||
<Button
|
||||
className='mt-auto w-full'
|
||||
onClick={() => mutate()}
|
||||
onClick={depositAccountHandler}
|
||||
disabled={amount === 0 || !amount}
|
||||
>
|
||||
Fund Account
|
||||
|
@ -9,13 +9,14 @@ import {
|
||||
YAxis,
|
||||
} from 'recharts'
|
||||
|
||||
import { FormattedNumber, Text } from 'components'
|
||||
import { useAccountStats } from 'hooks/data'
|
||||
import { useSettingsStore } from 'stores'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Text } from 'components/Text'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import useStore from 'store'
|
||||
|
||||
export const RiskChart = ({ data }: RiskChartProps) => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const accountStats = useAccountStats()
|
||||
const currentRisk = accountStats?.risk ?? 0
|
||||
|
||||
|
@ -4,40 +4,36 @@ import classNames from 'classnames'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
FormattedNumber,
|
||||
Gauge,
|
||||
LabelValuePair,
|
||||
Modal,
|
||||
PositionsList,
|
||||
Slider,
|
||||
Text,
|
||||
} from 'components'
|
||||
import { BorrowCapacity } from 'components/BorrowCapacity'
|
||||
import { useAccountStats, useBalances, useCalculateMaxWithdrawAmount } from 'hooks/data'
|
||||
import { useWithdrawFunds } from 'hooks/mutations'
|
||||
import { useCreditAccountPositions, useTokenPrices } from 'hooks/queries'
|
||||
import {
|
||||
useAccountDetailsStore,
|
||||
useModalStore,
|
||||
useNetworkConfigStore,
|
||||
useWalletStore,
|
||||
} from 'stores'
|
||||
import { formatValue, lookup } from 'utils/formatters'
|
||||
import { convertFromGwei, formatValue } from 'utils/formatters'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { Button } from 'components/Button'
|
||||
import { Text } from 'components/Text'
|
||||
import { Slider } from 'components/Slider'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import { LabelValuePair } from 'components/LabelValuePair'
|
||||
import { Modal } from 'components/Modal'
|
||||
import { PositionsList } from 'components/PositionsList'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { useBalances } from 'hooks/data/useBalances'
|
||||
import { useCalculateMaxWithdrawAmount } from 'hooks/data/useCalculateMaxWithdrawAmount'
|
||||
import { useWithdrawFunds } from 'hooks/mutations/useWithdrawFunds'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import useStore from 'store'
|
||||
import { getBaseAsset, getMarketAssets } from 'utils/assets'
|
||||
|
||||
export const WithdrawModal = () => {
|
||||
// ---------------
|
||||
// STORE
|
||||
// ---------------
|
||||
const open = useModalStore((s) => s.withdrawModal)
|
||||
const chainInfo = useWalletStore((s) => s.chainInfo)
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const open = useStore((s) => s.withdrawModal)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const baseAsset = useNetworkConfigStore((s) => s.assets.base)
|
||||
const marketAssets = getMarketAssets()
|
||||
const baseAsset = getBaseAsset()
|
||||
|
||||
// ---------------
|
||||
// LOCAL STATE
|
||||
@ -52,8 +48,8 @@ export const WithdrawModal = () => {
|
||||
const { data: tokenPrices } = useTokenPrices()
|
||||
const balances = useBalances()
|
||||
|
||||
const selectedTokenSymbol = getTokenSymbol(selectedToken, whitelistedAssets)
|
||||
const selectedTokenDecimals = getTokenDecimals(selectedToken, whitelistedAssets)
|
||||
const selectedTokenSymbol = getTokenSymbol(selectedToken, marketAssets)
|
||||
const selectedTokenDecimals = getTokenDecimals(selectedToken, marketAssets)
|
||||
|
||||
const tokenAmountInCreditAccount = useMemo(() => {
|
||||
return BigNumber(positionsData?.coins.find((coin) => coin.denom === selectedToken)?.amount ?? 0)
|
||||
@ -96,7 +92,7 @@ export const WithdrawModal = () => {
|
||||
|
||||
const { mutate, isLoading } = useWithdrawFunds(withdrawAmount, borrowAmount, selectedToken, {
|
||||
onSuccess: () => {
|
||||
useModalStore.setState({ withdrawModal: false })
|
||||
useStore.setState({ withdrawModal: false })
|
||||
toast.success(`${amount} ${selectedTokenSymbol} successfully withdrawn`)
|
||||
},
|
||||
})
|
||||
@ -131,13 +127,13 @@ export const WithdrawModal = () => {
|
||||
setAmount(0)
|
||||
}
|
||||
|
||||
const getTokenTotalUSDValue = (amount: string, denom: string, whitelistedAssets: Asset[]) => {
|
||||
const getTokenTotalUSDValue = (amount: string, denom: string, marketAssets: Asset[]) => {
|
||||
// early return if prices are not fetched yet
|
||||
if (!tokenPrices) return 0
|
||||
|
||||
return (
|
||||
BigNumber(amount)
|
||||
.div(10 ** getTokenDecimals(denom, whitelistedAssets))
|
||||
.div(10 ** getTokenDecimals(denom, marketAssets))
|
||||
.toNumber() * tokenPrices[denom]
|
||||
)
|
||||
}
|
||||
@ -149,7 +145,7 @@ export const WithdrawModal = () => {
|
||||
}, [amount, maxWithdrawAmount])
|
||||
|
||||
const setOpen = (open: boolean) => {
|
||||
useModalStore.setState({ withdrawModal: open })
|
||||
useStore.setState({ withdrawModal: open })
|
||||
}
|
||||
|
||||
return (
|
||||
@ -183,7 +179,7 @@ export const WithdrawModal = () => {
|
||||
>
|
||||
{positionsData?.coins?.map((coin) => (
|
||||
<option key={coin.denom} value={coin.denom}>
|
||||
{getTokenSymbol(coin.denom, whitelistedAssets)}
|
||||
{getTokenSymbol(coin.denom, marketAssets)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@ -310,10 +306,10 @@ export const WithdrawModal = () => {
|
||||
label='Total Position:'
|
||||
value={{
|
||||
format: 'number',
|
||||
amount: lookup(
|
||||
amount: convertFromGwei(
|
||||
accountStats?.totalPosition ?? 0,
|
||||
baseAsset.denom,
|
||||
whitelistedAssets,
|
||||
marketAssets,
|
||||
),
|
||||
prefix: '$',
|
||||
}}
|
||||
@ -322,7 +318,11 @@ export const WithdrawModal = () => {
|
||||
label='Total Liabilities:'
|
||||
value={{
|
||||
format: 'number',
|
||||
amount: lookup(accountStats?.totalDebt ?? 0, baseAsset.denom, whitelistedAssets),
|
||||
amount: convertFromGwei(
|
||||
accountStats?.totalDebt ?? 0,
|
||||
baseAsset.denom,
|
||||
marketAssets,
|
||||
),
|
||||
prefix: '$',
|
||||
}}
|
||||
/>
|
||||
|
@ -1,10 +0,0 @@
|
||||
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
|
||||
export { AccountDetails } from './AccountDetails'
|
||||
export { AccountManageOverlay } from './AccountManageOverlay'
|
||||
export { AccountNavigation } from './AccountNavigation'
|
||||
export { AccountStatus } from './AccountStatus'
|
||||
export { ConfirmModal } from './ConfirmModal'
|
||||
export { FundAccountModal } from './FundAccountModal'
|
||||
export { RiskChart } from './RiskChart'
|
||||
export { WithdrawModal } from './WithdrawModal'
|
||||
// @endindex
|
17
src/components/AccountDebtTable.tsx
Normal file
17
src/components/AccountDebtTable.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { getAccountDebts } from 'utils/api'
|
||||
|
||||
interface Props {
|
||||
account: string
|
||||
}
|
||||
|
||||
export async function AccountDebtTable(props: Props) {
|
||||
const debtData = await getAccountDebts(props.account)
|
||||
|
||||
return debtData.map((debt) => {
|
||||
return (
|
||||
<p key={debt.denom}>
|
||||
{debt.denom} {debt.amount}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
}
|
21
src/components/Background.tsx
Normal file
21
src/components/Background.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
'use client'
|
||||
|
||||
import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const filter = {
|
||||
day: 'brightness-100 hue-rotate-0',
|
||||
night: '-hue-rotate-82 brightness-30',
|
||||
}
|
||||
|
||||
export default function Background() {
|
||||
const { status } = useWalletManager()
|
||||
|
||||
const backgroundClasses = classNames(
|
||||
status === WalletConnectionStatus.Connected ? filter.day : filter.night,
|
||||
'top-0 left-0 absolute block h-full w-full flex-col bg-body bg-mars bg-desktop bg-top bg-no-repeat filter',
|
||||
true && 'transition-background duration-3000 ease-linear',
|
||||
)
|
||||
|
||||
return <div className={backgroundClasses} />
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import Image from 'next/image'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import { Button } from 'components'
|
||||
import { ChevronDown, ChevronUp } from 'components/Icons'
|
||||
import { formatCurrency } from 'utils/formatters'
|
||||
import { Button } from 'components/Button'
|
||||
|
||||
type AssetRowProps = {
|
||||
data: {
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
|
||||
import { AssetRow } from 'components/Borrow'
|
||||
import { AssetRow } from 'components/Borrow/AssetRow'
|
||||
import { ChevronDown, ChevronUp } from 'components/Icons'
|
||||
import { formatCurrency } from 'utils/formatters'
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
// @index(['./**/*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
|
||||
export { AssetRow } from './AssetRow'
|
||||
export { BorrowTable } from './BorrowTable'
|
||||
// @endindex
|
@ -1,8 +1,10 @@
|
||||
import classNames from 'classnames'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { FormattedNumber, Text, Tooltip } from 'components'
|
||||
import { useSettingsStore } from 'stores'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Text } from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
balance: number
|
||||
@ -27,7 +29,7 @@ export const BorrowCapacity = ({
|
||||
hideValues,
|
||||
decimals = 2,
|
||||
}: Props) => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
const [percentOfMaxRound, setPercentOfMaxRound] = useState(0)
|
||||
const [percentOfMaxRange, setPercentOfMaxRange] = useState(0)
|
||||
|
@ -4,23 +4,26 @@ import React, { useMemo, useState } from 'react'
|
||||
import { NumericFormat } from 'react-number-format'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
ContainerSecondary,
|
||||
Gauge,
|
||||
PositionsList,
|
||||
ProgressBar,
|
||||
Slider,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from 'components'
|
||||
import { useAccountStats, useBalances, useCalculateMaxBorrowAmount } from 'hooks/data'
|
||||
import { useBorrowFunds } from 'hooks/mutations'
|
||||
import { useAllBalances, useMarkets, useTokenPrices } from 'hooks/queries'
|
||||
import { useAccountDetailsStore, useNetworkConfigStore } from 'stores'
|
||||
import { Button } from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { ContainerSecondary } from 'components/ContainerSecondary'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import { PositionsList } from 'components/PositionsList'
|
||||
import { ProgressBar } from 'components/ProgressBar'
|
||||
import { Slider } from 'components/Slider'
|
||||
import { Text } from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { useBalances } from 'hooks/data/useBalances'
|
||||
import { useCalculateMaxBorrowAmount } from 'hooks/data/useCalculateMaxBorrowAmount'
|
||||
import { useBorrowFunds } from 'hooks/mutations/useBorrowFunds'
|
||||
import { useAllBalances } from 'hooks/queries/useAllBalances'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { formatCurrency, formatValue } from 'utils/formatters'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import useStore from 'store'
|
||||
import { getBaseAsset, getMarketAssets } from 'utils/assets'
|
||||
|
||||
type Props = {
|
||||
show: boolean
|
||||
@ -32,15 +35,15 @@ export const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
const [amount, setAmount] = useState(0)
|
||||
const [isBorrowToCreditAccount, setIsBorrowToCreditAccount] = useState(false)
|
||||
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const baseAsset = useNetworkConfigStore((s) => s.assets.base)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const marketAssets = getMarketAssets()
|
||||
const baseAsset = getBaseAsset()
|
||||
|
||||
const balances = useBalances()
|
||||
|
||||
const { actions, borrowAmount } = useMemo(() => {
|
||||
const borrowAmount = BigNumber(amount)
|
||||
.times(10 ** getTokenDecimals(tokenDenom, whitelistedAssets))
|
||||
.times(10 ** getTokenDecimals(tokenDenom, marketAssets))
|
||||
.toNumber()
|
||||
|
||||
const withdrawAmount = isBorrowToCreditAccount ? 0 : borrowAmount
|
||||
@ -61,11 +64,11 @@ export const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
},
|
||||
] as AccountStatsAction[],
|
||||
}
|
||||
}, [amount, isBorrowToCreditAccount, tokenDenom, whitelistedAssets])
|
||||
}, [amount, isBorrowToCreditAccount, tokenDenom, marketAssets])
|
||||
|
||||
const accountStats = useAccountStats(actions)
|
||||
|
||||
const tokenSymbol = getTokenSymbol(tokenDenom, whitelistedAssets)
|
||||
const tokenSymbol = getTokenSymbol(tokenDenom, marketAssets)
|
||||
|
||||
const { mutate, isLoading } = useBorrowFunds(borrowAmount, tokenDenom, !isBorrowToCreditAccount, {
|
||||
onSuccess: () => {
|
||||
@ -84,9 +87,9 @@ export const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
|
||||
const walletAmount = useMemo(() => {
|
||||
return BigNumber(balancesData?.find((balance) => balance.denom === tokenDenom)?.amount ?? 0)
|
||||
.div(10 ** getTokenDecimals(tokenDenom, whitelistedAssets))
|
||||
.div(10 ** getTokenDecimals(tokenDenom, marketAssets))
|
||||
.toNumber()
|
||||
}, [balancesData, tokenDenom, whitelistedAssets])
|
||||
}, [balancesData, tokenDenom, marketAssets])
|
||||
|
||||
const tokenPrice = tokenPrices?.[tokenDenom] ?? 0
|
||||
const borrowRate = Number(marketsData?.[tokenDenom]?.borrow_rate)
|
||||
@ -110,7 +113,7 @@ export const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
|
||||
const handleSliderValueChange = (value: number[]) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(tokenDenom, whitelistedAssets)
|
||||
const tokenDecimals = getTokenDecimals(tokenDenom, marketAssets)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
|
||||
@ -175,7 +178,7 @@ export const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
allowNegative={false}
|
||||
onValueChange={(v) => handleValueChange(v.floatValue || 0)}
|
||||
suffix={` ${tokenSymbol}`}
|
||||
decimalScale={getTokenDecimals(tokenDenom, whitelistedAssets)}
|
||||
decimalScale={getTokenDecimals(tokenDenom, marketAssets)}
|
||||
/>
|
||||
<div className='flex justify-between text-xs tracking-widest'>
|
||||
<div>
|
||||
|
13
src/components/BorrowTable.tsx
Normal file
13
src/components/BorrowTable.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { getBorrowData } from 'utils/api'
|
||||
|
||||
export async function BorrowTable() {
|
||||
const borrowData = await getBorrowData()
|
||||
|
||||
return borrowData.map((borrow) => {
|
||||
return (
|
||||
<p key={borrow.denom}>
|
||||
{borrow.denom} {borrow.borrowRate} {borrow.marketLiquidity}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import classNames from 'classnames'
|
||||
import React, { LegacyRef, ReactNode } from 'react'
|
||||
|
||||
import { CircularProgress } from 'components'
|
||||
import { useSettingsStore } from 'stores'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
children?: string | ReactNode
|
||||
@ -74,7 +74,7 @@ export const Button = React.forwardRef(function Button(
|
||||
ref,
|
||||
) {
|
||||
const buttonClasses = []
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
switch (variant) {
|
||||
case 'round':
|
||||
|
@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { Text } from 'components'
|
||||
import { useSettingsStore } from 'stores'
|
||||
import { Text } from 'components/Text'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
color?: string
|
||||
@ -10,7 +10,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const CircularProgress = ({ color = '#FFFFFF', size = 20, className }: Props) => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
const borderWidth = `${size / 10}px`
|
||||
const borderColor = `${color} transparent transparent transparent`
|
||||
|
@ -1,60 +0,0 @@
|
||||
import classNames from 'classnames'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { DocURL } from 'types/enums/docURL'
|
||||
|
||||
import { Button } from './Button'
|
||||
|
||||
export const CookieConsent = () => {
|
||||
const [cookieConsent, setCookieConsent] = useState<boolean>(true)
|
||||
|
||||
const createCookie = () => {
|
||||
setCookieConsent(true)
|
||||
document.cookie = 'viewed_cookie_policy=yes; path=/'
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setCookieConsent(!!document.cookie.match(new RegExp('(^| )viewed_cookie_policy=([^;]+)')))
|
||||
}, [])
|
||||
|
||||
return cookieConsent ? null : (
|
||||
<section
|
||||
className={classNames(
|
||||
'fixed bottom-0 left-0 z-50 flex w-full bg-black/90 p-4',
|
||||
'md:bg-black/70',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'mx-auto my-0 flex max-w-screen-xl flex-wrap items-center justify-center gap-4',
|
||||
'md:flex-nowrap',
|
||||
)}
|
||||
>
|
||||
<p className='basis-full text-sm'>
|
||||
This website uses cookies to improve its functionality and optimize content delivery. By
|
||||
using this website, you agree to the use of cookies for these purposes. Learn more,
|
||||
including how to modify your cookie settings, in Marsprotocol.io's{' '}
|
||||
<a
|
||||
href={DocURL.PRIVACY_POLICY_URL}
|
||||
target='_blank'
|
||||
rel='nofollow noreferrer'
|
||||
title='Privacy Policy'
|
||||
>
|
||||
privacy
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a
|
||||
href={DocURL.COOKIE_POLICY_URL}
|
||||
target='_blank'
|
||||
rel='nofollow noreferrer'
|
||||
title='Cookie Policy'
|
||||
>
|
||||
cookie
|
||||
</a>{' '}
|
||||
policies.
|
||||
</p>
|
||||
<Button onClick={createCookie} text='Understood' />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
|
||||
import { useSettingsStore } from 'stores'
|
||||
import useStore from 'store'
|
||||
import { formatValue } from 'utils/formatters'
|
||||
|
||||
export const FormattedNumber = React.memo(
|
||||
@ -18,7 +20,7 @@ export const FormattedNumber = React.memo(
|
||||
rounded = false,
|
||||
abbreviated = false,
|
||||
}: FormattedNumberProps) => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const prevAmountRef = useRef<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { Tooltip } from 'components'
|
||||
import { useSettingsStore } from 'stores'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
tooltip: string | ReactNode
|
||||
@ -20,7 +20,7 @@ export const Gauge = ({
|
||||
label,
|
||||
tooltip,
|
||||
}: Props) => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
const percentage = value * 100
|
||||
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
|
||||
|
@ -1,48 +1,48 @@
|
||||
// @index(['./*.svg'], f => `export { default as ${f.name} } from '${f.path}.svg'`)
|
||||
export { default as Add } from './Add.svg'
|
||||
export { default as ArrowBack } from './ArrowBack.svg'
|
||||
export { default as ArrowDown } from './ArrowDown.svg'
|
||||
export { default as ArrowLeftLine } from './ArrowLeftLine.svg'
|
||||
export { default as ArrowRightLine } from './ArrowRightLine.svg'
|
||||
export { default as ArrowsLeftRight } from './ArrowsLeftRight.svg'
|
||||
export { default as ArrowsUpDown } from './ArrowsUpDown.svg'
|
||||
export { default as ArrowUp } from './ArrowUp.svg'
|
||||
export { default as BurgerMenu } from './BurgerMenu.svg'
|
||||
export { default as Check } from './Check.svg'
|
||||
export { default as ChevronDown } from './ChevronDown.svg'
|
||||
export { default as ChevronLeft } from './ChevronLeft.svg'
|
||||
export { default as ChevronRight } from './ChevronRight.svg'
|
||||
export { default as ChevronUp } from './ChevronUp.svg'
|
||||
export { default as Close } from './Close.svg'
|
||||
export { default as Copy } from './Copy.svg'
|
||||
export { default as Deposit } from './Deposit.svg'
|
||||
export { default as Discord } from './Discord.svg'
|
||||
export { default as Edit } from './Edit.svg'
|
||||
export { default as Ellipsis } from './Ellipsis.svg'
|
||||
export { default as ExternalLink } from './ExternalLink.svg'
|
||||
export { default as Failed } from './Failed.svg'
|
||||
export { default as Github } from './Github.svg'
|
||||
export { default as Info } from './Info.svg'
|
||||
export { default as Logo } from './Logo.svg'
|
||||
export { default as MarsProtocol } from './MarsProtocol.svg'
|
||||
export { default as Medium } from './Medium.svg'
|
||||
export { default as Osmo } from './Osmo.svg'
|
||||
export { default as Questionmark } from './Questionmark.svg'
|
||||
export { default as Reddit } from './Reddit.svg'
|
||||
export { default as Rubbish } from './Rubbish.svg'
|
||||
export { default as Search } from './Search.svg'
|
||||
export { default as SmallClose } from './SmallClose.svg'
|
||||
export { default as SortAsc } from './SortAsc.svg'
|
||||
export { default as SortDesc } from './SortDesc.svg'
|
||||
export { default as SortNone } from './SortNone.svg'
|
||||
export { default as Subtract } from './Subtract.svg'
|
||||
export { default as Success } from './Success.svg'
|
||||
export { default as Telegram } from './Telegram.svg'
|
||||
export { default as TriangleDown } from './TriangleDown.svg'
|
||||
export { default as Twitter } from './Twitter.svg'
|
||||
export { default as Wallet } from './Wallet.svg'
|
||||
export { default as WalletConnect } from './WalletConnect.svg'
|
||||
export { default as Warning } from './Warning.svg'
|
||||
export { default as Withdraw } from './Withdraw.svg'
|
||||
export { default as YouTube } from './YouTube.svg'
|
||||
// @index(['./*.svg'], f => `export { default as ${f.name} } from 'components/Icons/${f.name}.svg'`)
|
||||
export { default as Add } from 'components/Icons/Add.svg'
|
||||
export { default as ArrowBack } from 'components/Icons/ArrowBack.svg'
|
||||
export { default as ArrowDown } from 'components/Icons/ArrowDown.svg'
|
||||
export { default as ArrowLeftLine } from 'components/Icons/ArrowLeftLine.svg'
|
||||
export { default as ArrowRightLine } from 'components/Icons/ArrowRightLine.svg'
|
||||
export { default as ArrowsLeftRight } from 'components/Icons/ArrowsLeftRight.svg'
|
||||
export { default as ArrowsUpDown } from 'components/Icons/ArrowsUpDown.svg'
|
||||
export { default as ArrowUp } from 'components/Icons/ArrowUp.svg'
|
||||
export { default as BurgerMenu } from 'components/Icons/BurgerMenu.svg'
|
||||
export { default as Check } from 'components/Icons/Check.svg'
|
||||
export { default as ChevronDown } from 'components/Icons/ChevronDown.svg'
|
||||
export { default as ChevronLeft } from 'components/Icons/ChevronLeft.svg'
|
||||
export { default as ChevronRight } from 'components/Icons/ChevronRight.svg'
|
||||
export { default as ChevronUp } from 'components/Icons/ChevronUp.svg'
|
||||
export { default as Close } from 'components/Icons/Close.svg'
|
||||
export { default as Copy } from 'components/Icons/Copy.svg'
|
||||
export { default as Deposit } from 'components/Icons/Deposit.svg'
|
||||
export { default as Discord } from 'components/Icons/Discord.svg'
|
||||
export { default as Edit } from 'components/Icons/Edit.svg'
|
||||
export { default as Ellipsis } from 'components/Icons/Ellipsis.svg'
|
||||
export { default as ExternalLink } from 'components/Icons/ExternalLink.svg'
|
||||
export { default as Failed } from 'components/Icons/Failed.svg'
|
||||
export { default as Github } from 'components/Icons/Github.svg'
|
||||
export { default as Info } from 'components/Icons/Info.svg'
|
||||
export { default as Logo } from 'components/Icons/Logo.svg'
|
||||
export { default as MarsProtocol } from 'components/Icons/MarsProtocol.svg'
|
||||
export { default as Medium } from 'components/Icons/Medium.svg'
|
||||
export { default as Osmo } from 'components/Icons/Osmo.svg'
|
||||
export { default as Questionmark } from 'components/Icons/Questionmark.svg'
|
||||
export { default as Reddit } from 'components/Icons/Reddit.svg'
|
||||
export { default as Rubbish } from 'components/Icons/Rubbish.svg'
|
||||
export { default as Search } from 'components/Icons/Search.svg'
|
||||
export { default as SmallClose } from 'components/Icons/SmallClose.svg'
|
||||
export { default as SortAsc } from 'components/Icons/SortAsc.svg'
|
||||
export { default as SortDesc } from 'components/Icons/SortDesc.svg'
|
||||
export { default as SortNone } from 'components/Icons/SortNone.svg'
|
||||
export { default as Subtract } from 'components/Icons/Subtract.svg'
|
||||
export { default as Success } from 'components/Icons/Success.svg'
|
||||
export { default as Telegram } from 'components/Icons/Telegram.svg'
|
||||
export { default as TriangleDown } from 'components/Icons/TriangleDown.svg'
|
||||
export { default as Twitter } from 'components/Icons/Twitter.svg'
|
||||
export { default as Wallet } from 'components/Icons/Wallet.svg'
|
||||
export { default as WalletConnect } from 'components/Icons/WalletConnect.svg'
|
||||
export { default as Warning } from 'components/Icons/Warning.svg'
|
||||
export { default as Withdraw } from 'components/Icons/Withdraw.svg'
|
||||
export { default as YouTube } from 'components/Icons/YouTube.svg'
|
||||
// @endindex
|
||||
|
@ -1,6 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { FormattedNumber, Text } from 'components'
|
||||
import { Text } from 'components/Text'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
|
||||
interface ValueData extends FormattedNumberProps {
|
||||
format?: 'number' | 'string'
|
||||
|
@ -1,49 +0,0 @@
|
||||
import { useWallet, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
|
||||
import classNames from 'classnames'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import { AccountDetails } from 'components/Account'
|
||||
import { DesktopNavigation } from 'components/Navigation'
|
||||
import { useCreditAccounts } from 'hooks/queries'
|
||||
import { useSettingsStore, useWalletStore } from 'stores'
|
||||
|
||||
import { CookieConsent } from './CookieConsent'
|
||||
|
||||
const filter = {
|
||||
day: 'brightness-100 hue-rotate-0',
|
||||
night: '-hue-rotate-82 brightness-30',
|
||||
}
|
||||
|
||||
export const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
|
||||
const { data: creditAccountsList } = useCreditAccounts()
|
||||
const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0
|
||||
|
||||
const { status, signingCosmWasmClient, chainInfo, address, name } = useWallet()
|
||||
const initialize = useWalletStore((s) => s.actions.initialize)
|
||||
|
||||
useEffect(() => {
|
||||
initialize(status, signingCosmWasmClient, address, name, chainInfo)
|
||||
}, [status, signingCosmWasmClient, chainInfo, address, name, initialize])
|
||||
|
||||
const isConnected = status === WalletConnectionStatus.Connected
|
||||
|
||||
const backgroundClasses = classNames(
|
||||
isConnected ? filter.day : filter.night,
|
||||
'top-0 left-0 absolute block h-full w-full flex-col bg-body bg-mars bg-desktop bg-top bg-no-repeat filter',
|
||||
enableAnimations && 'transition-background duration-3000 ease-linear',
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='relative min-h-screen w-full'>
|
||||
<div className={backgroundClasses} />
|
||||
<DesktopNavigation />
|
||||
<main className='relative flex lg:min-h-[calc(100vh-120px)]'>
|
||||
<div className='flex flex-grow flex-wrap p-6'>{children}</div>
|
||||
{hasCreditAccounts && <AccountDetails />}
|
||||
</main>
|
||||
<CookieConsent />
|
||||
</div>
|
||||
)
|
||||
}
|
28
src/components/Loading.tsx
Normal file
28
src/components/Loading.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
count?: number
|
||||
height?: number
|
||||
width?: number
|
||||
}
|
||||
|
||||
export default function Loading(props: Props) {
|
||||
return (
|
||||
<>
|
||||
{Array.from({ length: props.count ?? 1 }, (_, i) => (
|
||||
<div
|
||||
role='status'
|
||||
className={classNames(
|
||||
'animate-pulse rounded-full bg-white/40',
|
||||
props.className,
|
||||
props.height ? `h-${props.height}` : 'h-3',
|
||||
props.width ? `w-${props.width}` : 'w-full',
|
||||
)}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
<span className='sr-only'>Loading...</span>
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { Card } from 'components'
|
||||
import { Close } from 'components/Icons'
|
||||
import { Card } from 'components/Card'
|
||||
|
||||
interface Props {
|
||||
children?: ReactNode | string
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { ConfirmModal, FundAccountModal, WithdrawModal } from './Account'
|
||||
'use client'
|
||||
|
||||
import { ConfirmModal } from 'components/Account/ConfirmModal'
|
||||
import { FundAccountModal } from 'components/Account/FundAccountModal'
|
||||
|
||||
export const Modals = () => (
|
||||
<>
|
||||
<FundAccountModal />
|
||||
<WithdrawModal />
|
||||
{/* <WithdrawModal /> */}
|
||||
<ConfirmModal />
|
||||
</>
|
||||
)
|
||||
|
@ -1,33 +1,36 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
import { AccountNavigation, AccountStatus } from 'components/Account'
|
||||
import { Logo } from 'components/Icons'
|
||||
import { menuTree, NavLink, SearchInput } from 'components/Navigation'
|
||||
import { Wallet } from 'components/Wallet'
|
||||
import { useCreditAccounts } from 'hooks/queries'
|
||||
import { useAccountDetailsStore, useWalletStore } from 'stores'
|
||||
import { NavLink } from 'components/Navigation/NavLink'
|
||||
import Wallet from 'components/Wallet/Wallet'
|
||||
import { getRoute } from 'utils/route'
|
||||
|
||||
export const DesktopNavigation = () => {
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
export const menuTree: { href: RouteSegment; label: string }[] = [
|
||||
{ href: 'trade', label: 'Trade' },
|
||||
{ href: 'earn', label: 'Earn' },
|
||||
{ href: 'borrow', label: 'Borrow' },
|
||||
{ href: 'portfolio', label: 'Portfolio' },
|
||||
{ href: 'council', label: 'Council' },
|
||||
]
|
||||
|
||||
const { data: creditAccountsList } = useCreditAccounts()
|
||||
|
||||
const isConnected = !!address
|
||||
const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0
|
||||
export default function DesktopNavigation() {
|
||||
const pathname = usePathname() || ''
|
||||
|
||||
return (
|
||||
<div className='relative hidden bg-header lg:block'>
|
||||
<div className='flex items-center justify-between border-b border-white/20 px-6 py-3'>
|
||||
<div className='flex flex-grow items-center'>
|
||||
<Link href='/trade' passHref>
|
||||
<span className='h-10 w-10'>
|
||||
<Link href={getRoute(pathname, { page: 'trade' })}>
|
||||
<span className='block h-10 w-10'>
|
||||
<Logo />
|
||||
</span>
|
||||
</Link>
|
||||
<div className='flex gap-8 px-6'>
|
||||
{menuTree.map((item, index) => (
|
||||
<NavLink key={index} href={item.href}>
|
||||
<NavLink key={index} href={getRoute(pathname, { page: item.href })}>
|
||||
{item.label}
|
||||
</NavLink>
|
||||
))}
|
||||
@ -35,19 +38,6 @@ export const DesktopNavigation = () => {
|
||||
</div>
|
||||
<Wallet />
|
||||
</div>
|
||||
{/* Sub navigation bar */}
|
||||
<div className='flex items-center justify-between border-b border-white/20 pl-6 text-sm text-white/40'>
|
||||
<div className='flex items-center'>
|
||||
<SearchInput />
|
||||
{isConnected && hasCreditAccounts && (
|
||||
<AccountNavigation
|
||||
selectedAccount={selectedAccount}
|
||||
creditAccountsList={creditAccountsList}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isConnected && <AccountStatus />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { ReactNode } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
@ -9,19 +11,18 @@ interface Props {
|
||||
}
|
||||
|
||||
export const NavLink = ({ href, children }: Props) => {
|
||||
const router = useRouter()
|
||||
const isActive = router.pathname === href
|
||||
const pathname = usePathname()
|
||||
const isActive = pathname === href
|
||||
|
||||
return (
|
||||
<Link href={href} passHref>
|
||||
<a
|
||||
className={classNames(
|
||||
'text-lg-caps hover:text-white active:text-white',
|
||||
isActive ? 'pointer-events-none text-white' : 'text-white/60',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
<Link
|
||||
href={href}
|
||||
className={classNames(
|
||||
'text-lg-caps hover:text-white active:text-white',
|
||||
isActive ? 'pointer-events-none text-white' : 'text-white/60',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
import { Search } from 'components/Icons'
|
||||
export const SearchInput = () => (
|
||||
<div className='relative mt-[1px] py-2 text-white'>
|
||||
<span className='absolute top-1/2 left-0 flex h-6 w-8 -translate-y-1/2 items-center pl-2'>
|
||||
<Search />
|
||||
</span>
|
||||
<input
|
||||
className='w-[280px] rounded-md border border-white/20 bg-black/30 py-2 pl-10 text-sm text-white placeholder:text-white/40 focus:border-white/60 focus:outline-none'
|
||||
placeholder='Search'
|
||||
/>
|
||||
</div>
|
||||
)
|
@ -1,6 +0,0 @@
|
||||
// @index(['./*.ts*'], f => `export { ${f.name} } from '${f.path}'`)
|
||||
export { DesktopNavigation } from './DesktopNavigation'
|
||||
export { menuTree } from './menuTree'
|
||||
export { NavLink } from './NavLink'
|
||||
export { SearchInput } from './SearchInput'
|
||||
// @endindex
|
@ -1,7 +0,0 @@
|
||||
export const menuTree = [
|
||||
{ href: '/trade', label: 'Trade' },
|
||||
{ href: '/earn', label: 'Earn' },
|
||||
{ href: '/borrow', label: 'Borrow' },
|
||||
{ href: '/portfolio', label: 'Portfolio' },
|
||||
{ href: '/council', label: 'Council' },
|
||||
]
|
@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { Button } from 'components'
|
||||
import { Button } from 'components/Button'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
|
@ -1,4 +0,0 @@
|
||||
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
|
||||
export { Overlay } from './Overlay'
|
||||
export { OverlayAction } from './OverlayAction'
|
||||
// @endindex
|
@ -1,6 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { FormattedNumber, Text } from 'components'
|
||||
import { Text } from 'components/Text'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
|
@ -5,12 +5,18 @@ import React, { useMemo, useState } from 'react'
|
||||
import { NumericFormat } from 'react-number-format'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { Button, CircularProgress, ContainerSecondary, Slider } from 'components'
|
||||
import { useRepayFunds } from 'hooks/mutations'
|
||||
import { useAllBalances, useCreditAccountPositions, useTokenPrices } from 'hooks/queries'
|
||||
import { useAccountDetailsStore, useNetworkConfigStore } from 'stores'
|
||||
import { Button } from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { ContainerSecondary } from 'components/ContainerSecondary'
|
||||
import { Slider } from 'components/Slider'
|
||||
import { useRepayFunds } from 'hooks/mutations/useRepayFunds'
|
||||
import { useAllBalances } from 'hooks/queries/useAllBalances'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { formatCurrency } from 'utils/formatters'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import useStore from 'store'
|
||||
|
||||
// 0.001% buffer / slippage to avoid repay action from not fully repaying the debt amount
|
||||
const REPAY_BUFFER = 1.00001
|
||||
@ -24,11 +30,11 @@ type Props = {
|
||||
export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
const [amount, setAmount] = useState(0)
|
||||
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const tokenSymbol = getTokenSymbol(tokenDenom, whitelistedAssets)
|
||||
const tokenSymbol = getTokenSymbol(tokenDenom, marketAssets)
|
||||
|
||||
const maxRepayAmount = useMemo(() => {
|
||||
const tokenDebtAmount =
|
||||
@ -37,13 +43,13 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
return BigNumber(tokenDebtAmount)
|
||||
.times(REPAY_BUFFER)
|
||||
.decimalPlaces(0)
|
||||
.div(10 ** getTokenDecimals(tokenDenom, whitelistedAssets))
|
||||
.div(10 ** getTokenDecimals(tokenDenom, marketAssets))
|
||||
.toNumber()
|
||||
}, [positionsData, tokenDenom, whitelistedAssets])
|
||||
}, [positionsData, tokenDenom, marketAssets])
|
||||
|
||||
const { mutate, isLoading } = useRepayFunds(
|
||||
BigNumber(amount)
|
||||
.times(10 ** getTokenDecimals(tokenDenom, whitelistedAssets))
|
||||
.times(10 ** getTokenDecimals(tokenDenom, marketAssets))
|
||||
.toNumber(),
|
||||
tokenDenom,
|
||||
{
|
||||
@ -63,9 +69,9 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
|
||||
const walletAmount = useMemo(() => {
|
||||
return BigNumber(balancesData?.find((balance) => balance.denom === tokenDenom)?.amount ?? 0)
|
||||
.div(10 ** getTokenDecimals(tokenDenom, whitelistedAssets))
|
||||
.div(10 ** getTokenDecimals(tokenDenom, marketAssets))
|
||||
.toNumber()
|
||||
}, [balancesData, tokenDenom, whitelistedAssets])
|
||||
}, [balancesData, tokenDenom, marketAssets])
|
||||
|
||||
const tokenPrice = tokenPrices?.[tokenDenom] ?? 0
|
||||
|
||||
@ -143,7 +149,7 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
allowNegative={false}
|
||||
onValueChange={(v) => handleValueChange(v.floatValue || 0)}
|
||||
suffix={` ${tokenSymbol}`}
|
||||
decimalScale={getTokenDecimals(tokenDenom, whitelistedAssets)}
|
||||
decimalScale={getTokenDecimals(tokenDenom, marketAssets)}
|
||||
/>
|
||||
<div className='flex justify-between text-xs tracking-widest'>
|
||||
<div>
|
||||
@ -158,7 +164,7 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
value={percentageValue}
|
||||
onChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(tokenDenom, whitelistedAssets)
|
||||
const tokenDecimals = getTokenDecimals(tokenDenom, marketAssets)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals))
|
||||
|
||||
|
18
src/components/Toaster.tsx
Normal file
18
src/components/Toaster.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
'use client'
|
||||
import { Slide, ToastContainer } from 'react-toastify'
|
||||
|
||||
import useStore from 'store'
|
||||
|
||||
export default function Toaster() {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
return (
|
||||
<ToastContainer
|
||||
autoClose={3000}
|
||||
closeButton={false}
|
||||
position='bottom-right'
|
||||
newestOnTop
|
||||
transition={enableAnimations ? Slide : undefined}
|
||||
/>
|
||||
)
|
||||
}
|
@ -3,7 +3,7 @@ import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { Questionmark } from 'components/Icons'
|
||||
import { useSettingsStore } from 'stores'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
children?: ReactNode | string
|
||||
@ -22,7 +22,7 @@ export const Tooltip = ({
|
||||
inderactive = false,
|
||||
underline = false,
|
||||
}: Props) => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
return (
|
||||
<Tippy
|
||||
|
@ -1,21 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import { Switch } from '@headlessui/react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { Button, CircularProgress, Slider } from 'components'
|
||||
import { ArrowsUpDown } from 'components/Icons'
|
||||
import { useCalculateMaxTradeAmount } from 'hooks/data'
|
||||
import { useTradeAsset } from 'hooks/mutations'
|
||||
import {
|
||||
useAllBalances,
|
||||
useAllowedCoins,
|
||||
useCreditAccountPositions,
|
||||
useMarkets,
|
||||
useTokenPrices,
|
||||
} from 'hooks/queries'
|
||||
import { useAccountDetailsStore, useNetworkConfigStore } from 'stores'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import { Slider } from 'components/Slider'
|
||||
import { Button } from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { useCalculateMaxTradeAmount } from 'hooks/data/useCalculateMaxTradeAmount'
|
||||
import { useTradeAsset } from 'hooks/mutations/useTradeAsset'
|
||||
import { useAllBalances } from 'hooks/queries/useAllBalances'
|
||||
import { useAllowedCoins } from 'hooks/queries/useAllowedCoins'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import useStore from 'store'
|
||||
|
||||
enum FundingMode {
|
||||
Account = 'Account',
|
||||
@ -23,7 +26,7 @@ enum FundingMode {
|
||||
}
|
||||
|
||||
export const TradeActionModule = () => {
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const [selectedTokenIn, setSelectedTokenIn] = useState('')
|
||||
const [selectedTokenOut, setSelectedTokenOut] = useState('')
|
||||
@ -34,7 +37,7 @@ export const TradeActionModule = () => {
|
||||
|
||||
const [isMarginEnabled, setIsMarginEnabled] = React.useState(false)
|
||||
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
|
||||
const { data: allowedCoinsData } = useAllowedCoins()
|
||||
const { data: balancesData } = useAllBalances()
|
||||
@ -96,8 +99,8 @@ export const TradeActionModule = () => {
|
||||
toast.success(
|
||||
`${amountIn} ${getTokenSymbol(
|
||||
selectedTokenIn,
|
||||
whitelistedAssets,
|
||||
)} swapped for ${amountOut} ${getTokenSymbol(selectedTokenOut, whitelistedAssets)}`,
|
||||
marketAssets,
|
||||
)} swapped for ${amountOut} ${getTokenSymbol(selectedTokenOut, marketAssets)}`,
|
||||
)
|
||||
resetAmounts()
|
||||
},
|
||||
@ -192,20 +195,20 @@ export const TradeActionModule = () => {
|
||||
>
|
||||
{allowedCoinsData?.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
{getTokenSymbol(entry, whitelistedAssets)}
|
||||
{getTokenSymbol(entry, marketAssets)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type='number'
|
||||
className='h-8 flex-1 px-2 text-black outline-0'
|
||||
value={amountIn / 10 ** getTokenDecimals(selectedTokenIn, whitelistedAssets)}
|
||||
value={amountIn / 10 ** getTokenDecimals(selectedTokenIn, marketAssets)}
|
||||
min='0'
|
||||
placeholder='0.00'
|
||||
onChange={(e) => {
|
||||
const valueAsNumber = e.target.valueAsNumber
|
||||
const valueWithDecimals =
|
||||
valueAsNumber * 10 ** getTokenDecimals(selectedTokenIn, whitelistedAssets)
|
||||
valueAsNumber * 10 ** getTokenDecimals(selectedTokenIn, marketAssets)
|
||||
|
||||
handleAmountChange(valueWithDecimals, 'in')
|
||||
}}
|
||||
@ -232,20 +235,20 @@ export const TradeActionModule = () => {
|
||||
>
|
||||
{allowedCoinsData?.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
{getTokenSymbol(entry, whitelistedAssets)}
|
||||
{getTokenSymbol(entry, marketAssets)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type='number'
|
||||
className='h-8 flex-1 px-2 text-black outline-0'
|
||||
value={amountOut / 10 ** getTokenDecimals(selectedTokenOut, whitelistedAssets)}
|
||||
value={amountOut / 10 ** getTokenDecimals(selectedTokenOut, marketAssets)}
|
||||
min='0'
|
||||
placeholder='0.00'
|
||||
onChange={(e) => {
|
||||
const valueAsNumber = e.target.valueAsNumber
|
||||
const valueWithDecimals =
|
||||
valueAsNumber * 10 ** getTokenDecimals(selectedTokenOut, whitelistedAssets)
|
||||
valueAsNumber * 10 ** getTokenDecimals(selectedTokenOut, marketAssets)
|
||||
|
||||
handleAmountChange(valueWithDecimals, 'out')
|
||||
}}
|
||||
@ -255,29 +258,29 @@ export const TradeActionModule = () => {
|
||||
<div className='mb-1'>
|
||||
In Wallet:{' '}
|
||||
{BigNumber(walletAmount)
|
||||
.dividedBy(10 ** getTokenDecimals(selectedTokenIn, whitelistedAssets))
|
||||
.dividedBy(10 ** getTokenDecimals(selectedTokenIn, marketAssets))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getTokenDecimals(selectedTokenIn, whitelistedAssets),
|
||||
maximumFractionDigits: getTokenDecimals(selectedTokenIn, marketAssets),
|
||||
})}{' '}
|
||||
<span>{getTokenSymbol(selectedTokenIn, whitelistedAssets)}</span>
|
||||
<span>{getTokenSymbol(selectedTokenIn, marketAssets)}</span>
|
||||
</div>
|
||||
<div className='mb-4'>
|
||||
In Account:{' '}
|
||||
{BigNumber(accountAmount)
|
||||
.dividedBy(10 ** getTokenDecimals(selectedTokenIn, whitelistedAssets))
|
||||
.dividedBy(10 ** getTokenDecimals(selectedTokenIn, marketAssets))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getTokenDecimals(selectedTokenIn, whitelistedAssets),
|
||||
maximumFractionDigits: getTokenDecimals(selectedTokenIn, marketAssets),
|
||||
})}{' '}
|
||||
<span>{getTokenSymbol(selectedTokenIn, whitelistedAssets)}</span>
|
||||
<span>{getTokenSymbol(selectedTokenIn, marketAssets)}</span>
|
||||
</div>
|
||||
<Slider
|
||||
className='mb-6'
|
||||
value={percentageValue}
|
||||
onChange={(value) => {
|
||||
const decimal = value[0] / 100
|
||||
const tokenDecimals = getTokenDecimals(selectedTokenIn, whitelistedAssets)
|
||||
const tokenDecimals = getTokenDecimals(selectedTokenIn, marketAssets)
|
||||
// limit decimal precision based on token contract decimals
|
||||
const newAmount = Number((decimal * maxAmount).toFixed(0))
|
||||
|
||||
@ -313,10 +316,10 @@ export const TradeActionModule = () => {
|
||||
<p>
|
||||
{isMarginEnabled
|
||||
? BigNumber(borrowAmount)
|
||||
.dividedBy(10 ** getTokenDecimals(selectedTokenIn, whitelistedAssets))
|
||||
.dividedBy(10 ** getTokenDecimals(selectedTokenIn, marketAssets))
|
||||
.toNumber()
|
||||
.toLocaleString(undefined, {
|
||||
maximumFractionDigits: getTokenDecimals(selectedTokenIn, whitelistedAssets),
|
||||
maximumFractionDigits: getTokenDecimals(selectedTokenIn, marketAssets),
|
||||
})
|
||||
: '-'}
|
||||
</p>
|
||||
|
@ -1,3 +0,0 @@
|
||||
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
|
||||
export { TradeActionModule } from './TradeActionModule'
|
||||
// @endindex
|
@ -1,7 +1,7 @@
|
||||
import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { CircularProgress } from 'components'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { Wallet } from 'components/Icons'
|
||||
|
||||
interface Props {
|
||||
@ -10,17 +10,17 @@ interface Props {
|
||||
status?: WalletConnectionStatus
|
||||
}
|
||||
|
||||
export const ConnectButton = ({ textOverride, disabled = false, status }: Props) => {
|
||||
export default function ConnectButton(props: Props) {
|
||||
const { connect } = useWalletManager()
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<button
|
||||
disabled={disabled}
|
||||
disabled={props.disabled}
|
||||
className='flex h-[31px] min-w-[186px] flex-1 flex-nowrap content-center items-center justify-center rounded-2xl border border-white/60 bg-black/10 px-4 pt-0.5 text-white text-2xs-caps hover:border-white hover:bg-white/60'
|
||||
onClick={connect}
|
||||
>
|
||||
{status === WalletConnectionStatus.Connecting ? (
|
||||
{props.status === WalletConnectionStatus.Connecting ? (
|
||||
<span className='flex justify-center'>
|
||||
<CircularProgress size={16} />
|
||||
</span>
|
||||
@ -29,7 +29,7 @@ export const ConnectButton = ({ textOverride, disabled = false, status }: Props)
|
||||
<span className='flex h-4 w-4 items-center justify-center'>
|
||||
<Wallet />
|
||||
</span>
|
||||
<span className='ml-2 mt-0.5'>{textOverride || 'Connect Wallet'}</span>
|
||||
<span className='ml-2 mt-0.5'>{props.textOverride || 'Connect Wallet'}</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
@ -1,61 +1,69 @@
|
||||
import { ChainInfoID, SimpleChainInfoList, useWalletManager } from '@marsprotocol/wallet-connector'
|
||||
import { Coin } from '@cosmjs/stargate'
|
||||
import {
|
||||
ChainInfoID,
|
||||
SimpleChainInfoList,
|
||||
useWallet,
|
||||
useWalletManager,
|
||||
} from '@marsprotocol/wallet-connector'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import classNames from 'classnames'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import useClipboard from 'react-use-clipboard'
|
||||
import useSWR from 'swr'
|
||||
|
||||
import { Button, CircularProgress, FormattedNumber, Text } from 'components'
|
||||
import { Check, Copy, ExternalLink, Osmo, Wallet } from 'components/Icons'
|
||||
import { Overlay } from 'components/Overlay'
|
||||
import { useAllBalances } from 'hooks/queries'
|
||||
import { useNetworkConfigStore, useWalletStore } from 'stores'
|
||||
import { Button } from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Check, Copy, ExternalLink, Osmo } from 'components/Icons'
|
||||
import { Overlay } from 'components/Overlay/Overlay'
|
||||
import { Text } from 'components/Text'
|
||||
import useStore from 'store'
|
||||
import { getBaseAsset } from 'utils/assets'
|
||||
import { formatValue, truncate } from 'utils/formatters'
|
||||
import { getWalletBalances } from 'utils/api'
|
||||
|
||||
export const ConnectedButton = () => {
|
||||
export default function ConnectedButton() {
|
||||
// ---------------
|
||||
// EXTERNAL HOOKS
|
||||
// ---------------
|
||||
const { disconnect } = useWalletManager()
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const chainInfo = useWalletStore((s) => s.chainInfo)
|
||||
const name = useWalletStore((s) => s.name)
|
||||
const baseAsset = useNetworkConfigStore((s) => s.assets.base)
|
||||
const { disconnect } = useWallet()
|
||||
const { disconnect: terminate } = useWalletManager()
|
||||
const address = useStore((s) => s.client?.recentWallet.account?.address)
|
||||
const network = useStore((s) => s.client?.recentWallet.network)
|
||||
const name = useStore((s) => s.name)
|
||||
const baseAsset = getBaseAsset()
|
||||
|
||||
// ---------------
|
||||
// LOCAL HOOKS
|
||||
// ---------------
|
||||
const { data } = useAllBalances()
|
||||
const { data, isLoading } = useSWR(address, getWalletBalances)
|
||||
|
||||
// ---------------
|
||||
// LOCAL STATE
|
||||
// ---------------
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [showDetails, setShowDetails] = useState(false)
|
||||
const [walletAmount, setWalletAmount] = useState(0)
|
||||
const [isCopied, setCopied] = useClipboard(address || '', {
|
||||
successDuration: 1000 * 5,
|
||||
})
|
||||
|
||||
// ---------------
|
||||
// VARIABLES
|
||||
// ---------------
|
||||
const explorerName =
|
||||
chainInfo && SimpleChainInfoList[chainInfo.chainId as ChainInfoID].explorerName
|
||||
const explorerName = network && SimpleChainInfoList[network.chainId as ChainInfoID].explorerName
|
||||
|
||||
const viewOnFinder = useCallback(() => {
|
||||
const explorerUrl = chainInfo && SimpleChainInfoList[chainInfo.chainId as ChainInfoID].explorer
|
||||
const explorerUrl = network && SimpleChainInfoList[network.chainId as ChainInfoID].explorer
|
||||
|
||||
window.open(`${explorerUrl}account/${address}`, '_blank')
|
||||
}, [chainInfo, address])
|
||||
|
||||
useEffect(() => {
|
||||
const loading = !(address && name && chainInfo)
|
||||
setIsLoading(loading)
|
||||
}, [address, name, chainInfo])
|
||||
window.open(`${explorerUrl}/account/${address}`, '_blank')
|
||||
}, [network, address])
|
||||
|
||||
const disconnectWallet = () => {
|
||||
disconnect()
|
||||
terminate()
|
||||
useStore.setState({ client: undefined })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || data.length === 0) return
|
||||
setWalletAmount(
|
||||
BigNumber(data?.find((balance) => balance.denom === baseAsset.denom)?.amount ?? 0)
|
||||
BigNumber(data?.find((coin: Coin) => coin.denom === baseAsset.denom)?.amount ?? 0)
|
||||
.div(10 ** baseAsset.decimals)
|
||||
.toNumber(),
|
||||
)
|
||||
@ -63,13 +71,13 @@ export const ConnectedButton = () => {
|
||||
|
||||
return (
|
||||
<div className={'relative'}>
|
||||
{chainInfo?.chainId !== ChainInfoID.Osmosis1 && (
|
||||
{network?.chainId !== ChainInfoID.Osmosis1 && (
|
||||
<Text
|
||||
className='absolute -right-2 -top-2.5 rounded-lg bg-secondary-highlight p-0.5 px-2'
|
||||
size='3xs'
|
||||
uppercase
|
||||
>
|
||||
{chainInfo?.chainId}
|
||||
{network?.chainId}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@ -84,12 +92,7 @@ export const ConnectedButton = () => {
|
||||
}}
|
||||
>
|
||||
<span className='flex h-4 w-4 items-center justify-center'>
|
||||
{chainInfo?.chainId === ChainInfoID.Osmosis1 ||
|
||||
chainInfo?.chainId === ChainInfoID.OsmosisTestnet ? (
|
||||
<Osmo />
|
||||
) : (
|
||||
<Wallet />
|
||||
)}
|
||||
<Osmo />
|
||||
</span>
|
||||
<span className='ml-2'>{name ? name : truncate(address, [2, 4])}</span>
|
||||
<div
|
||||
@ -98,10 +101,10 @@ export const ConnectedButton = () => {
|
||||
'before:content-[" "] before:absolute before:top-1.5 before:bottom-1.5 before:left-0 before:h-[calc(100%-12px)] before:border-l before:border-white',
|
||||
)}
|
||||
>
|
||||
{!isLoading ? (
|
||||
`${formatValue(walletAmount, 2, 2, true, false, ` ${baseAsset.symbol}`)}`
|
||||
) : (
|
||||
{isLoading ? (
|
||||
<CircularProgress size={12} />
|
||||
) : (
|
||||
`${formatValue(walletAmount, 2, 2, true, false, ` ${baseAsset.symbol}`)}`
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
@ -121,7 +124,7 @@ export const ConnectedButton = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex h-[31px] w-[116px] justify-end'>
|
||||
<Button color='secondary' onClick={disconnect} text='Disconnect' />
|
||||
<Button color='secondary' onClick={disconnectWallet} text='Disconnect' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex w-full flex-wrap'>
|
||||
|
@ -1,18 +1,65 @@
|
||||
import { useWallet, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
|
||||
'use client'
|
||||
|
||||
import {
|
||||
getClient,
|
||||
useWallet,
|
||||
useWalletManager,
|
||||
WalletConnectionStatus,
|
||||
} from '@marsprotocol/wallet-connector'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
import { ConnectButton, ConnectedButton } from 'components/Wallet'
|
||||
import ConnectButton from 'components/Wallet/ConnectButton'
|
||||
import ConnectedButton from 'components/Wallet/ConnectedButton'
|
||||
import useParams from 'hooks/useParams'
|
||||
import useStore from 'store'
|
||||
|
||||
export const Wallet = () => {
|
||||
const { status } = useWallet()
|
||||
export default function Wallet() {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const { status } = useWalletManager()
|
||||
const [isConnected, setIsConnected] = useState(false)
|
||||
const { recentWallet, simulate, sign, broadcast } = useWallet()
|
||||
const client = useStore((s) => s.client)
|
||||
|
||||
useEffect(() => {
|
||||
const connectedStatus = status === WalletConnectionStatus.Connected
|
||||
if (connectedStatus !== isConnected) {
|
||||
setIsConnected(connectedStatus)
|
||||
}
|
||||
if (connectedStatus === isConnected) return
|
||||
setIsConnected(connectedStatus)
|
||||
}, [status, isConnected])
|
||||
|
||||
return !isConnected ? <ConnectButton status={status} /> : <ConnectedButton />
|
||||
useEffect(() => {
|
||||
if (!isConnected && !params.wallet) {
|
||||
router.push('/')
|
||||
return
|
||||
}
|
||||
|
||||
const address = client?.recentWallet.account.address
|
||||
if (!address || address === params.wallet) return
|
||||
|
||||
router.push(`/wallets/${client.recentWallet.account.address}`)
|
||||
}, [client, params, isConnected])
|
||||
|
||||
useEffect(() => {
|
||||
if (!recentWallet) return
|
||||
if (!client) {
|
||||
const getCosmWasmClient = async () => {
|
||||
const cosmClient = await getClient(recentWallet.network.rpc)
|
||||
|
||||
const client = {
|
||||
broadcast,
|
||||
cosmWasmClient: cosmClient,
|
||||
recentWallet,
|
||||
sign,
|
||||
simulate,
|
||||
}
|
||||
useStore.setState({ client })
|
||||
}
|
||||
|
||||
getCosmWasmClient()
|
||||
|
||||
return
|
||||
}
|
||||
}, [simulate, sign, recentWallet, broadcast])
|
||||
return isConnected ? <ConnectedButton /> : <ConnectButton status={status} />
|
||||
}
|
||||
|
@ -1,89 +1,40 @@
|
||||
import { ChainInfoID, WalletManagerProvider, WalletType } from '@marsprotocol/wallet-connector'
|
||||
import classNames from 'classnames'
|
||||
'use client'
|
||||
|
||||
import { WalletManagerProvider } from '@marsprotocol/wallet-connector'
|
||||
import { FC } from 'react'
|
||||
|
||||
import { CircularProgress } from 'components'
|
||||
import { buttonColorClasses, buttonSizeClasses, buttonVariantClasses } from 'components/Button'
|
||||
import { Close } from 'components/Icons'
|
||||
// TODO: get networkConfig source dynamically
|
||||
import { networkConfig } from 'config/osmo-test-4'
|
||||
import KeplrImage from 'images/wallets/keplr-wallet-extension.png'
|
||||
import WalletConnectImage from 'images/wallets/walletconnect-keplr.png'
|
||||
import { useSettingsStore } from 'stores'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { CHAIN_ID, ENV_MISSING_MESSAGE, URL_REST, URL_RPC, WALLETS } from 'constants/env'
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const WalletConnectProvider: FC<Props> = ({ children }) => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
if (!CHAIN_ID || !URL_REST || !URL_RPC || !WALLETS) {
|
||||
console.error(ENV_MISSING_MESSAGE)
|
||||
return null
|
||||
}
|
||||
|
||||
const chainInfoOverrides = {
|
||||
rpc: URL_RPC,
|
||||
rest: URL_REST,
|
||||
chainID: CHAIN_ID,
|
||||
}
|
||||
const enabledWallets: string[] = WALLETS
|
||||
|
||||
return (
|
||||
<WalletManagerProvider
|
||||
chainInfoOverrides={{
|
||||
[ChainInfoID.OsmosisTestnet]: {
|
||||
rpc: networkConfig.rpcUrl,
|
||||
rest: networkConfig.restUrl,
|
||||
},
|
||||
}}
|
||||
classNames={{
|
||||
modalContent:
|
||||
'flex h-fit w-[500px] max-w-full overflow-hidden rounded-xl border-[7px] border-accent-highlight p-4 gradient-card flex-col outline-none relative',
|
||||
modalOverlay:
|
||||
'bg-black/60 fixed top-0 left-0 w-screen h-screen z-50 flex items-center justify-center cursor-pointer backdrop-blur',
|
||||
modalHeader: 'text-2xl-caps text-center text-white mb-4',
|
||||
walletList: 'flex flex-col gap-4 py-2',
|
||||
wallet:
|
||||
'bg-transparent rounded-base p-2 shadow-none flex items-center appearance-none border-none w-full no-underline cursor-pointer hover:bg-white/10 disabled:pointer-events-none disabled:opacity-50',
|
||||
walletImage: 'h-15 w-15',
|
||||
walletInfo: 'flex flex-col ml-5',
|
||||
walletName: 'text-lg-caps text-white',
|
||||
walletDescription: 'mt-1 text-white/40 text-base text-left',
|
||||
textContent: 'block w-full text-center text-base text-white',
|
||||
}}
|
||||
closeIcon={
|
||||
<span className='flex w-8 text-white/70 hover:text-white'>
|
||||
<Close />
|
||||
</span>
|
||||
}
|
||||
defaultChainId={ChainInfoID.OsmosisTestnet}
|
||||
enabledWalletTypes={[WalletType.Keplr, WalletType.WalletConnectKeplr]}
|
||||
enablingMeta={{
|
||||
text: 'If nothing shows up in your wallet try to connect again, by clicking on the button below. Refresh the page if the problem persists.',
|
||||
textClassName: 'block w-full text-center text-base text-white',
|
||||
buttonText: 'Retry the Connection',
|
||||
buttonClassName: classNames(
|
||||
'cursor-pointer appearance-none break-normal rounded-3xl outline-none',
|
||||
enableAnimations && 'transition-colors',
|
||||
buttonColorClasses.primary,
|
||||
buttonSizeClasses.small,
|
||||
buttonVariantClasses.solid,
|
||||
),
|
||||
contentClassName: 'flex flex-wrap w-full justify-center',
|
||||
}}
|
||||
enablingStringOverride='connecting to wallet'
|
||||
localStorageKey='walletConnection'
|
||||
chainInfoOverrides={chainInfoOverrides}
|
||||
// closeIcon={<SVG.Close />}
|
||||
defaultChainId={chainInfoOverrides.chainID}
|
||||
enabledWallets={enabledWallets}
|
||||
persistent
|
||||
renderLoader={() => (
|
||||
<div className='my-4 flex w-full justify-center'>
|
||||
<div>
|
||||
<CircularProgress size={30} />
|
||||
</div>
|
||||
)}
|
||||
walletConnectClientMeta={{
|
||||
name: 'Mars Protocol',
|
||||
description: 'Mars V2 Description',
|
||||
url: 'https://marsprotocol.io',
|
||||
icons: ['https://marsprotocol.io/favicon.svg'],
|
||||
}}
|
||||
walletMetaOverride={{
|
||||
[WalletType.Keplr]: {
|
||||
description: 'Keplr browser extension',
|
||||
imageUrl: KeplrImage.src,
|
||||
},
|
||||
[WalletType.WalletConnectKeplr]: {
|
||||
name: 'Wallet Connect',
|
||||
description: 'Keplr mobile WalletConnect',
|
||||
imageUrl: WalletConnectImage.src,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</WalletManagerProvider>
|
||||
|
@ -1,6 +0,0 @@
|
||||
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
|
||||
export { ConnectButton } from './ConnectButton'
|
||||
export { ConnectedButton } from './ConnectedButton'
|
||||
export { Wallet } from './Wallet'
|
||||
export { WalletConnectProvider } from './WalletConnectProvider'
|
||||
// @endindex
|
@ -1,17 +1,19 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
|
||||
import { BorrowModal, Card, RepayModal, Text } from 'components'
|
||||
import { BorrowTable } from 'components/Borrow'
|
||||
import {
|
||||
useAllowedCoins,
|
||||
useCreditAccountPositions,
|
||||
useMarkets,
|
||||
useRedbankBalances,
|
||||
useTokenPrices,
|
||||
} from 'hooks/queries'
|
||||
import { useAccountDetailsStore, useNetworkConfigStore } from 'stores'
|
||||
import { BorrowTable } from 'components/Borrow/BorrowTable'
|
||||
import { BorrowModal } from 'components/BorrowModal'
|
||||
import { Card } from 'components/Card'
|
||||
import { RepayModal } from 'components/RepayModal'
|
||||
import { useAllowedCoins } from 'hooks/queries/useAllowedCoins'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useRedbankBalances } from 'hooks/queries/useRedbankBalances'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { Text } from 'components/Text'
|
||||
import { getTokenDecimals, getTokenInfo } from 'utils/tokens'
|
||||
import useStore from 'store'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
type ModalState = {
|
||||
show: 'borrow' | 'repay' | false
|
||||
@ -21,14 +23,14 @@ type ModalState = {
|
||||
}
|
||||
|
||||
const Borrow = () => {
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const [modalState, setModalState] = useState<ModalState>({
|
||||
show: false,
|
||||
data: { tokenDenom: '' },
|
||||
})
|
||||
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
|
||||
const { data: allowedCoinsData } = useAllowedCoins()
|
||||
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||
@ -55,17 +57,17 @@ const Borrow = () => {
|
||||
allowedCoinsData
|
||||
?.filter((denom) => borrowedAssetsMap.has(denom))
|
||||
.map((denom) => {
|
||||
const { symbol, name, logo } = getTokenInfo(denom, whitelistedAssets)
|
||||
const { symbol, name, logo } = getTokenInfo(denom, marketAssets)
|
||||
const borrowRate = Number(marketsData?.[denom].borrow_rate) || 0
|
||||
const marketLiquidity = BigNumber(
|
||||
redbankBalances?.find((asset) => asset.denom.toLowerCase() === denom.toLowerCase())
|
||||
?.amount || 0,
|
||||
)
|
||||
.div(10 ** getTokenDecimals(denom, whitelistedAssets))
|
||||
.div(10 ** getTokenDecimals(denom, marketAssets))
|
||||
.toNumber()
|
||||
|
||||
const borrowAmount = BigNumber(borrowedAssetsMap.get(denom) as string)
|
||||
.div(10 ** getTokenDecimals(denom, whitelistedAssets))
|
||||
.div(10 ** getTokenDecimals(denom, marketAssets))
|
||||
.toNumber()
|
||||
const borrowValue = borrowAmount * (tokenPrices?.[denom] ?? 0)
|
||||
|
||||
@ -88,13 +90,13 @@ const Borrow = () => {
|
||||
allowedCoinsData
|
||||
?.filter((denom) => !borrowedAssetsMap.has(denom))
|
||||
.map((denom) => {
|
||||
const { symbol, name, logo } = getTokenInfo(denom, whitelistedAssets)
|
||||
const { symbol, name, logo } = getTokenInfo(denom, marketAssets)
|
||||
const borrowRate = Number(marketsData?.[denom].borrow_rate) || 0
|
||||
const marketLiquidity = BigNumber(
|
||||
redbankBalances?.find((asset) => asset.denom.toLowerCase() === denom.toLowerCase())
|
||||
?.amount || 0,
|
||||
)
|
||||
.div(10 ** getTokenDecimals(denom, whitelistedAssets))
|
||||
.div(10 ** getTokenDecimals(denom, marketAssets))
|
||||
.toNumber()
|
||||
|
||||
const rowData = {
|
||||
@ -110,14 +112,7 @@ const Borrow = () => {
|
||||
return rowData
|
||||
}) ?? [],
|
||||
}
|
||||
}, [
|
||||
allowedCoinsData,
|
||||
borrowedAssetsMap,
|
||||
marketsData,
|
||||
redbankBalances,
|
||||
tokenPrices,
|
||||
whitelistedAssets,
|
||||
])
|
||||
}, [allowedCoinsData, borrowedAssetsMap, marketsData, redbankBalances, tokenPrices, marketAssets])
|
||||
|
||||
const handleBorrowClick = (denom: string) => {
|
||||
setModalState({ show: 'borrow', data: { tokenDenom: denom } })
|
@ -1,22 +0,0 @@
|
||||
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
|
||||
export { BorrowCapacity } from './BorrowCapacity'
|
||||
export { BorrowModal } from './BorrowModal'
|
||||
export { Button } from './Button'
|
||||
export { Card } from './Card'
|
||||
export { CircularProgress } from './CircularProgress'
|
||||
export { ContainerSecondary } from './ContainerSecondary'
|
||||
export { CookieConsent } from './CookieConsent'
|
||||
export { FormattedNumber } from './FormattedNumber'
|
||||
export { Gauge } from './Gauge'
|
||||
export { LabelValuePair } from './LabelValuePair'
|
||||
export { Layout } from './Layout'
|
||||
export { Modal } from './Modal'
|
||||
export { Modals } from './Modals'
|
||||
export { PositionsList } from './PositionsList'
|
||||
export { ProgressBar } from './ProgressBar'
|
||||
export { RepayModal } from './RepayModal'
|
||||
export { Slider } from './Slider'
|
||||
export { Text } from './Text'
|
||||
export { TextLink } from './TextLink'
|
||||
export { Tooltip } from './Tooltip'
|
||||
// @endindex
|
@ -1,68 +0,0 @@
|
||||
import { ChainInfoID } from '@marsprotocol/wallet-connector'
|
||||
|
||||
const Assets: { [key: string]: Asset } = {
|
||||
osmo: {
|
||||
symbol: 'OSMO',
|
||||
name: 'Osmosis',
|
||||
denom: 'uosmo',
|
||||
color: '#9f1ab9',
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
logo: '/tokens/osmo.svg',
|
||||
},
|
||||
atom: {
|
||||
symbol: 'ATOM',
|
||||
name: 'Atom',
|
||||
denom: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2',
|
||||
color: '#6f7390',
|
||||
logo: '/tokens/atom.svg',
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
},
|
||||
cro: {
|
||||
symbol: 'CRO',
|
||||
name: 'Cronos',
|
||||
denom: 'ibc/E6931F78057F7CC5DA0FD6CEF82FF39373A6E0452BF1FD76910B93292CF356C1',
|
||||
color: '#002D74',
|
||||
logo: '/tokens/cro.svg',
|
||||
decimals: 8,
|
||||
hasOraclePrice: true,
|
||||
},
|
||||
}
|
||||
|
||||
const OtherAssets: { [key: string]: OtherAsset } = {
|
||||
mars: {
|
||||
symbol: 'MARS',
|
||||
name: 'Mars',
|
||||
denom: 'ibc/EA3E1640F9B1532AB129A571203A0B9F789A7F14BB66E350DCBFA18E1A1931F0',
|
||||
//denom: 'ibc/1BF910A3C8A30C8E3331764FA0113B920AE14B913F487DF7E1989FD75EFE61FD'
|
||||
color: '#a03b45',
|
||||
logo: '/tokens/mars.svg',
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
poolId: 601,
|
||||
},
|
||||
}
|
||||
|
||||
export const networkConfig: NetworkConfig = {
|
||||
name: ChainInfoID.OsmosisTestnet,
|
||||
hiveUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-hive/graphql',
|
||||
rpcUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-rpc',
|
||||
restUrl: 'https://osmosis-delphi-testnet-1.simply-vc.com.mt/XF32UOOU55CX/osmosis-lcd',
|
||||
contracts: {
|
||||
accountNft: 'osmo1xvne7u9svgy9vtqtqnaet4nvn8zcpp984zzrlezfzgk4798tps8srkf5wa',
|
||||
mockVault: 'osmo1yqgjaehalz0pv5j22fdnaaekuprlggd7hth8m66jmdxe58ztqs4sjqtrlk',
|
||||
marsOracleAdapter: 'osmo1tlad2hj9rm7az7atx2qq8pdpl2007hrhpzua42j8wgxr0kc0ct4sahuyh7',
|
||||
swapper: 'osmo15kxcpvjaqlrj8ezecnghf2qs2x87veqx0fcemye0jpdr8jq7qkvsnyvuuf',
|
||||
mockZapper: 'osmo1axad429tgnvzvfax08s4ytmf7ndg0f9z4jy355zyh4m6nasgtnzs5aw8u7',
|
||||
creditManager: 'osmo1krz37p6xkkyu0f240enyt4ccxk7ds69kfgc5pnldsmpmmuvn3vpsnmpjaf',
|
||||
redBank: 'osmo1g30recyv8pfy3qd4qn3dn7plc0rn5z68y5gn32j39e96tjhthzxsw3uvvu',
|
||||
oracle: 'osmo1hkkx42777dyfz7wc8acjjhfdh9x2ugcjvdt7shtft6ha9cn420cquz3u3j',
|
||||
},
|
||||
assets: {
|
||||
base: Assets.osmo,
|
||||
whitelist: [Assets.osmo, Assets.atom, Assets.cro],
|
||||
other: [OtherAssets.mars],
|
||||
},
|
||||
appUrl: 'https://testnet.osmosis.zone',
|
||||
}
|
62
src/constants/assets.ts
Normal file
62
src/constants/assets.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { IS_TESTNET } from 'constants/env'
|
||||
|
||||
export const ASSETS: Asset[] = [
|
||||
{
|
||||
symbol: 'OSMO',
|
||||
name: 'Osmosis',
|
||||
denom: 'uosmo',
|
||||
color: '#9f1ab9',
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
logo: '/tokens/osmo.svg',
|
||||
isEnabled: true,
|
||||
isMarket: true,
|
||||
},
|
||||
{
|
||||
symbol: 'ATOM',
|
||||
name: 'Atom',
|
||||
denom: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2',
|
||||
color: '#6f7390',
|
||||
logo: '/tokens/atom.svg',
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
isEnabled: IS_TESTNET ? true : false,
|
||||
isMarket: true,
|
||||
},
|
||||
{
|
||||
symbol: 'CRO',
|
||||
name: 'Cronos',
|
||||
denom: 'ibc/E6931F78057F7CC5DA0FD6CEF82FF39373A6E0452BF1FD76910B93292CF356C1',
|
||||
color: '#002D74',
|
||||
logo: '/tokens/cro.svg',
|
||||
decimals: 8,
|
||||
hasOraclePrice: true,
|
||||
isEnabled: false,
|
||||
isMarket: true,
|
||||
},
|
||||
{
|
||||
symbol: 'MARS',
|
||||
name: 'Mars',
|
||||
denom: IS_TESTNET
|
||||
? 'ibc/ACA4C8A815A053CC027DB90D15915ADA31939FA331CE745862CDD00A2904FA17'
|
||||
: 'ibc/573FCD90FACEE750F55A8864EF7D38265F07E5A9273FA0E8DAFD39951332B580',
|
||||
color: '#dd5b65',
|
||||
logo: '/tokens/mars.svg',
|
||||
decimals: 6,
|
||||
poolId: IS_TESTNET ? 768 : 907,
|
||||
hasOraclePrice: true,
|
||||
isMarket: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
symbol: 'JUNO',
|
||||
name: 'Juno',
|
||||
denom: 'ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED',
|
||||
color: 'black',
|
||||
logo: '/tokens/juno.svg',
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
isMarket: IS_TESTNET,
|
||||
isEnabled: false,
|
||||
},
|
||||
]
|
18
src/constants/env.ts
Normal file
18
src/constants/env.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export const ADDRESS_ACCOUNT_NFT = process.env.NEXT_PUBLIC_ACCOUNT_NFT
|
||||
export const ADDRESS_CREDIT_MANAGER = process.env.NEXT_PUBLIC_CREDIT_MANAGER
|
||||
export const ADDRESS_INCENTIVES = process.env.NEXT_PUBLIC_INCENTIVES
|
||||
export const ADDRESS_ORACLE = process.env.NEXT_PUBLIC_ORACLE
|
||||
export const ADDRESS_RED_BANK = process.env.NEXT_PUBLIC_RED_BANK
|
||||
export const ADDRESS_SWAPPER = process.env.NEXT_PUBLIC_SWAPPER
|
||||
export const ADDRESS_ZAPPER = process.env.NEXT_PUBLIC_ZAPPER
|
||||
|
||||
export const CHAIN_ID = process.env.NEXT_PUBLIC_CHAIN_ID
|
||||
export const NETWORK = process.env.NEXT_PUBLIC_NETWORK
|
||||
export const IS_TESTNET = NETWORK !== 'mainnet'
|
||||
export const URL_GQL = process.env.NEXT_PUBLIC_GQL
|
||||
export const URL_REST = process.env.NEXT_PUBLIC_REST
|
||||
export const URL_RPC = process.env.NEXT_PUBLIC_RPC
|
||||
export const URL_API = process.env.NEXT_PUBLIC_API
|
||||
export const WALLETS = process.env.NEXT_PUBLIC_WALLETS?.split(',') ?? []
|
||||
|
||||
export const ENV_MISSING_MESSAGE = 'Environment variable missing'
|
2
src/constants/gas.ts
Normal file
2
src/constants/gas.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const GAS_ADJUSTMENT = 1.3
|
||||
export const GAS_PRICE = '0.025uosmo'
|
@ -1,8 +0,0 @@
|
||||
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
|
||||
export { useAccountStats } from './useAccountStats'
|
||||
export { useAnimations } from './useAnimations'
|
||||
export { useBalances } from './useBalances'
|
||||
export { useCalculateMaxBorrowAmount } from './useCalculateMaxBorrowAmount'
|
||||
export { useCalculateMaxTradeAmount } from './useCalculateMaxTradeAmount'
|
||||
export { useCalculateMaxWithdrawAmount } from './useCalculateMaxWithdrawAmount'
|
||||
// @endindex
|
@ -1,8 +1,10 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { useCreditAccountPositions, useMarkets, useTokenPrices } from 'hooks/queries'
|
||||
import { useAccountDetailsStore } from 'stores'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import useStore from 'store'
|
||||
|
||||
// displaying 3 levels of risk based on the weighted average of liquidation LTVs
|
||||
// 0.85 -> 25% risk
|
||||
@ -80,7 +82,7 @@ const calculateStatsFromAccountPositions = (assets: Asset[], debts: Debt[]) => {
|
||||
}
|
||||
|
||||
export const useAccountStats = (actions?: AccountStatsAction[]) => {
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
|
||||
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||
const { data: marketsData } = useMarkets()
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useSettingsStore } from 'stores'
|
||||
import useStore from 'store'
|
||||
|
||||
export const useAnimations = () => {
|
||||
const enableAnimations = useSettingsStore((s) => s.enableAnimations)
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
const queryChangeHandler = (event: MediaQueryListEvent) => {
|
||||
useSettingsStore.setState({ enableAnimations: !event?.matches ?? true })
|
||||
useStore.setState({ enableAnimations: !event?.matches ?? true })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery: MediaQueryList = window.matchMedia('(prefers-reduced-motion: reduce)')
|
||||
|
||||
if (mediaQuery) {
|
||||
useSettingsStore.setState({ enableAnimations: !mediaQuery.matches })
|
||||
useStore.setState({ enableAnimations: !mediaQuery.matches })
|
||||
mediaQuery.addEventListener('change', queryChangeHandler)
|
||||
return () => mediaQuery.removeEventListener('change', queryChangeHandler)
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { useCreditAccountPositions, useMarkets, useTokenPrices } from 'hooks/queries'
|
||||
import { useAccountDetailsStore, useNetworkConfigStore } from 'stores'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { formatBalances } from 'utils/balances'
|
||||
import useStore from 'store'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
export const useBalances = () => {
|
||||
const [balanceData, setBalanceData] = useState<PositionsData[]>()
|
||||
|
||||
const { data: marketsData } = useMarkets()
|
||||
const { data: tokenPrices } = useTokenPrices()
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
|
||||
selectedAccount ?? '',
|
||||
@ -19,15 +22,15 @@ export const useBalances = () => {
|
||||
useEffect(() => {
|
||||
const balances =
|
||||
positionsData?.coins && tokenPrices
|
||||
? formatBalances(positionsData.coins, tokenPrices, false, whitelistedAssets)
|
||||
? formatBalances(positionsData.coins, tokenPrices, false, marketAssets)
|
||||
: []
|
||||
const debtBalances =
|
||||
positionsData?.debts && tokenPrices
|
||||
? formatBalances(positionsData.debts, tokenPrices, true, whitelistedAssets, marketsData)
|
||||
? formatBalances(positionsData.debts, tokenPrices, true, marketAssets, marketsData)
|
||||
: []
|
||||
|
||||
setBalanceData([...balances, ...debtBalances])
|
||||
}, [positionsData, marketsData, tokenPrices, whitelistedAssets])
|
||||
}, [positionsData, marketsData, tokenPrices, marketAssets])
|
||||
|
||||
return balanceData
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import {
|
||||
useCreditAccountPositions,
|
||||
useMarkets,
|
||||
useRedbankBalances,
|
||||
useTokenPrices,
|
||||
} from 'hooks/queries'
|
||||
import { useAccountDetailsStore, useNetworkConfigStore } from 'stores'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useRedbankBalances } from 'hooks/queries/useRedbankBalances'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { getTokenDecimals } from 'utils/tokens'
|
||||
import useStore from 'store'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
|
||||
const hourlyAPY = BigNumber(borrowAPY).div(24 * 365)
|
||||
@ -17,8 +16,8 @@ const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
|
||||
}
|
||||
|
||||
export const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boolean) => {
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||
const { data: marketsData } = useMarkets()
|
||||
@ -62,7 +61,7 @@ export const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized
|
||||
}, 0)
|
||||
|
||||
const borrowTokenPrice = tokenPrices[denom]
|
||||
const tokenDecimals = getTokenDecimals(denom, whitelistedAssets)
|
||||
const tokenDecimals = getTokenDecimals(denom, marketAssets)
|
||||
|
||||
let maxAmountCapacity
|
||||
if (isUnderCollateralized) {
|
||||
@ -100,6 +99,6 @@ export const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized
|
||||
positionsData,
|
||||
redbankBalances,
|
||||
tokenPrices,
|
||||
whitelistedAssets,
|
||||
marketAssets,
|
||||
])
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import {
|
||||
useCreditAccountPositions,
|
||||
useMarkets,
|
||||
useRedbankBalances,
|
||||
useTokenPrices,
|
||||
} from 'hooks/queries'
|
||||
import { useAccountDetailsStore } from 'stores'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useRedbankBalances } from 'hooks/queries/useRedbankBalances'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import useStore from 'store'
|
||||
|
||||
const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
|
||||
const hourlyAPY = BigNumber(borrowAPY).div(24 * 365)
|
||||
@ -22,7 +20,7 @@ export const useCalculateMaxTradeAmount = (
|
||||
tokenOut: string,
|
||||
isMargin: boolean,
|
||||
) => {
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
|
||||
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||
const { data: marketsData } = useMarkets()
|
||||
|
@ -1,14 +1,13 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
import {
|
||||
useCreditAccountPositions,
|
||||
useMarkets,
|
||||
useRedbankBalances,
|
||||
useTokenPrices,
|
||||
} from 'hooks/queries'
|
||||
import { useAccountDetailsStore, useNetworkConfigStore } from 'stores'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useMarkets } from 'hooks/queries/useMarkets'
|
||||
import { useRedbankBalances } from 'hooks/queries/useRedbankBalances'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { getTokenDecimals } from 'utils/tokens'
|
||||
import useStore from 'store'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
|
||||
const hourlyAPY = BigNumber(borrowAPY).div(24 * 365)
|
||||
@ -17,15 +16,15 @@ const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
|
||||
}
|
||||
|
||||
export const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
|
||||
const whitelistedAssets = useNetworkConfigStore((s) => s.assets.whitelist)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
||||
const { data: marketsData } = useMarkets()
|
||||
const { data: tokenPrices } = useTokenPrices()
|
||||
const { data: redbankBalances } = useRedbankBalances()
|
||||
|
||||
const tokenDecimals = getTokenDecimals(denom, whitelistedAssets)
|
||||
const tokenDecimals = getTokenDecimals(denom, marketAssets)
|
||||
|
||||
const getTokenValue = useCallback(
|
||||
(amount: string, denom: string) => {
|
||||
|
@ -1,9 +0,0 @@
|
||||
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
|
||||
export { useBorrowFunds } from './useBorrowFunds'
|
||||
export { useCreateCreditAccount } from './useCreateCreditAccount'
|
||||
export { useDeleteCreditAccount } from './useDeleteCreditAccount'
|
||||
export { useDepositCreditAccount } from './useDepositCreditAccount'
|
||||
export { useRepayFunds } from './useRepayFunds'
|
||||
export { useTradeAsset } from './useTradeAsset'
|
||||
export { useWithdrawFunds } from './useWithdrawFunds'
|
||||
// @endindex
|
@ -2,7 +2,7 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react
|
||||
import { useMemo } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { useAccountDetailsStore, useWalletStore } from 'stores'
|
||||
import useStore from 'store'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
|
||||
@ -12,12 +12,10 @@ export const useBorrowFunds = (
|
||||
withdraw = false,
|
||||
options: Omit<UseMutationOptions, 'onError'>,
|
||||
) => {
|
||||
const creditManagerClient = useWalletStore((s) => s.clients.creditManager)
|
||||
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount ?? '')
|
||||
const address = useWalletStore((s) => s.address)
|
||||
|
||||
const creditManagerClient = useStore((s) => s.clients.creditManager)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount ?? '')
|
||||
const address = useStore((s) => s.address)
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const actions = useMemo(() => {
|
||||
if (!withdraw) {
|
||||
return [
|
||||
@ -29,7 +27,6 @@ export const useBorrowFunds = (
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
borrow: {
|
||||
@ -45,7 +42,6 @@ export const useBorrowFunds = (
|
||||
},
|
||||
]
|
||||
}, [withdraw, denom, amount])
|
||||
|
||||
return useMutation(
|
||||
async () =>
|
||||
await creditManagerClient?.updateCreditAccount(
|
||||
@ -56,7 +52,6 @@ export const useBorrowFunds = (
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount))
|
||||
queryClient.invalidateQueries(queryKeys.redbankBalances())
|
||||
|
||||
// if withdrawing to wallet, need to explicility invalidate balances queries
|
||||
if (withdraw) {
|
||||
queryClient.invalidateQueries(queryKeys.tokenBalance(address ?? '', denom))
|
||||
|
@ -1,45 +0,0 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import {
|
||||
useAccountDetailsStore,
|
||||
useModalStore,
|
||||
useNetworkConfigStore,
|
||||
useWalletStore,
|
||||
} from 'stores'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
|
||||
// 200000 gas used
|
||||
const executeMsg = {
|
||||
create_credit_account: {},
|
||||
}
|
||||
|
||||
export const useCreateCreditAccount = () => {
|
||||
const signingClient = useWalletStore((s) => s.signingClient)
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const creditManagerAddress = useNetworkConfigStore((s) => s.contracts.creditManager)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation(
|
||||
async () =>
|
||||
await signingClient?.execute(address ?? '', creditManagerAddress, executeMsg, hardcodedFee),
|
||||
{
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries(queryKeys.creditAccounts(address ?? ''))
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
toast.error(err.message)
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (!data) return
|
||||
|
||||
// TODO: is there some better way to parse response to extract token id???
|
||||
const createdID = data.logs[0].events[2].attributes[6].value
|
||||
useAccountDetailsStore.setState({ selectedAccount: createdID })
|
||||
useModalStore.setState({ fundAccountModal: true })
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { useNetworkConfigStore, useWalletStore } from 'stores'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
|
||||
export const useDeleteCreditAccount = (accountId: string) => {
|
||||
const signingClient = useWalletStore((s) => s.signingClient)
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const accountNftAddress = useNetworkConfigStore((s) => s.contracts.accountNft)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation(
|
||||
async () =>
|
||||
await signingClient?.execute(
|
||||
address ?? '',
|
||||
accountNftAddress,
|
||||
{
|
||||
burn: {
|
||||
token_id: accountId,
|
||||
},
|
||||
},
|
||||
hardcodedFee,
|
||||
),
|
||||
{
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries(queryKeys.creditAccounts(address ?? ''))
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
toast.error(err.message)
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('Credit Account Deleted')
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { useNetworkConfigStore, useWalletStore } from 'stores'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
|
||||
export const useDepositCreditAccount = (
|
||||
accountId: string,
|
||||
denom: string,
|
||||
amount: number,
|
||||
options?: {
|
||||
onSuccess?: () => void
|
||||
},
|
||||
) => {
|
||||
const signingClient = useWalletStore((s) => s.signingClient)
|
||||
const address = useWalletStore((s) => s.address)
|
||||
const creditManagerAddress = useNetworkConfigStore((s) => s.contracts.creditManager)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation(
|
||||
async () =>
|
||||
await signingClient?.execute(
|
||||
address ?? '',
|
||||
creditManagerAddress,
|
||||
{
|
||||
update_credit_account: {
|
||||
account_id: accountId,
|
||||
actions: [
|
||||
{
|
||||
deposit: {
|
||||
denom,
|
||||
amount: String(amount),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
hardcodedFee,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
denom,
|
||||
amount: String(amount),
|
||||
},
|
||||
],
|
||||
),
|
||||
{
|
||||
onError: (err: Error) => {
|
||||
toast.error(err.message)
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(queryKeys.allBalances(address ?? ''))
|
||||
queryClient.invalidateQueries(queryKeys.tokenBalance(address ?? '', denom))
|
||||
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(accountId))
|
||||
|
||||
options?.onSuccess && options.onSuccess()
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user