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:
Linkie Link 2023-02-24 09:47:27 +01:00 committed by GitHub
parent 2b91adf438
commit b5c097d661
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
215 changed files with 7613 additions and 3357 deletions

13
.env.example Normal file
View 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
View File

@ -37,3 +37,7 @@ next-env.d.ts
# Sentry
.sentryclirc
# Environment variables
.env.local
.env.production

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

59
Dockerfile Normal file
View 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"]

View File

@ -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

View File

@ -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"
}
}

View File

@ -0,0 +1,3 @@
export default function laoding() {
return '...isLoading'
}

7
src/app/borrow/page.tsx Normal file
View 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
View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a guest`
}

3
src/app/earn/page.tsx Normal file
View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a guest`
}

30
src/app/head.tsx Normal file
View 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
View 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
View File

@ -0,0 +1,3 @@
export default function page() {
return 'Connect to your wallet'
}

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a guest`
}

3
src/app/trade/page.tsx Normal file
View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a guest`
}

View File

@ -0,0 +1,3 @@
export default function Loading() {
return '...isLoading'
}

View 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>
)
}

View File

@ -0,0 +1,3 @@
export default function loading() {
return '...isLoading'
}

View File

@ -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

View File

@ -0,0 +1,3 @@
export default function loading() {
return '...isLoading'
}

View File

@ -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

View File

@ -0,0 +1,3 @@
export default function page() {
return 'Trade page'
}

View File

@ -0,0 +1,5 @@
'use client'
export default function page({ params }: { params: PageParams }) {
return 'error!'
}

View File

@ -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>
)
}

View File

@ -0,0 +1,3 @@
export default function loading() {
return '...isLoading'
}

View File

@ -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

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a viewer or a user`
}

View 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}
</>
)
}

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -0,0 +1,3 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -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: '$',
}}
/>

View File

@ -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

View File

@ -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>
)
}

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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: '$',
}}
/>

View File

@ -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

View 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>
)
})
}

View 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} />
}

View File

@ -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: {

View File

@ -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'

View File

@ -1,4 +0,0 @@
// @index(['./**/*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
export { AssetRow } from './AssetRow'
export { BorrowTable } from './BorrowTable'
// @endindex

View File

@ -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)

View File

@ -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>

View 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>
)
})
}

View File

@ -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':

View File

@ -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`

View File

@ -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&apos;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>
)
}

View File

@ -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(() => {

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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>
)
}

View 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>
</>
)
}

View File

@ -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

View File

@ -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 />
</>
)

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -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>
)

View File

@ -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

View File

@ -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' },
]

View File

@ -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

View File

@ -1,4 +0,0 @@
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
export { Overlay } from './Overlay'
export { OverlayAction } from './OverlayAction'
// @endindex

View File

@ -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

View File

@ -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))

View 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}
/>
)
}

View File

@ -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

View File

@ -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>

View File

@ -1,3 +0,0 @@
// @index(['./*.tsx'], f => `export { ${f.name} } from '${f.path}'`)
export { TradeActionModule } from './TradeActionModule'
// @endindex

View File

@ -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>

View File

@ -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'>

View File

@ -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} />
}

View File

@ -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>

View File

@ -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

View File

@ -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 } })

View File

@ -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

View File

@ -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
View 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
View 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
View File

@ -0,0 +1,2 @@
export const GAS_ADJUSTMENT = 1.3
export const GAS_PRICE = '0.025uosmo'

View File

@ -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

View File

@ -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()

View File

@ -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)
}

View File

@ -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
}

View File

@ -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,
])
}

View File

@ -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()

View File

@ -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) => {

View File

@ -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

View File

@ -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))

View File

@ -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 })
},
},
)
}

View File

@ -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')
},
},
)
}

View File

@ -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