New architecture (#196)

* feat: move app to pages features

* feat: route changes

* Use React Router, Remove SSR

* Fix account menu

* Remove app folder

* remove old useParams

* Moved pages back to pages and refactor names

* add layout to route

* clean up

* create hooks for api fetching

* fix refetch of all data on tx complete

* formatting

* fix: fixed the wallet-connector race condition

* remove cosmjs/stargate (#202)

* remove cosmjs/stargate

* add Yusuf as code-orwner

* Singleton client (#203)

* remove cosmjs/stargate

* add Yusuf as code-orwner

* create signleton client and refactor vaults api

* update client name, add apollo apr env

* Setup validate-env and remove checking from apis

* uncomment vaults

* Resolve comments

* fix: html templating, add checks for hydration&window object, reduce bundle size (#204)

* fix: tests

* Fix routing and wallet client (#205)

* Add header to router (as layout)

* Refactor Wallet component

* Remove server fallback packages webpack

* add missing dependency for useeffect

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
This commit is contained in:
Yusuf Seyrek 2023-05-16 13:39:52 +03:00 committed by GitHub
parent a59e880559
commit b24bbb3376
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
137 changed files with 1039 additions and 1211 deletions

View File

@ -4,6 +4,7 @@ NEXT_PUBLIC_RPC=https://testnet-osmosis-node.marsprotocol.io/XF32UOOU55CX/osmosi
NEXT_PUBLIC_GQL=https://testnet-osmosis-node.marsprotocol.io/XF32UOOU55CX/osmosis-hive-front/graphql 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_REST=https://testnet-osmosis-node.marsprotocol.io/XF32UOOU55CX/osmosis-lcd-front/
NEXT_PUBLIC_SWAP=https://testnet.osmosis.zone NEXT_PUBLIC_SWAP=https://testnet.osmosis.zone
NEXT_PUBLIC_APOLLO_APR=https://api.apollo.farm/api/vault_infos/v2/osmo-test-5
NEXT_PUBLIC_WALLETS=keplr,cosmostation NEXT_PUBLIC_WALLETS=keplr,cosmostation
NEXT_PUBLIC_ACCOUNT_NFT=osmo1ye2rntzz9qmxgv7eg09supww6k6xs0y0sekcr3x5clp087fymn4q3y33s4 NEXT_PUBLIC_ACCOUNT_NFT=osmo1ye2rntzz9qmxgv7eg09supww6k6xs0y0sekcr3x5clp087fymn4q3y33s4
NEXT_PUBLIC_ORACLE=osmo1khe29uw3t85nmmp3mtr8dls7v2qwsfk3tndu5h4w5g2r5tzlz5qqarq2e2 NEXT_PUBLIC_ORACLE=osmo1khe29uw3t85nmmp3mtr8dls7v2qwsfk3tndu5h4w5g2r5tzlz5qqarq2e2
@ -21,6 +22,7 @@ NEXT_PUBLIC_API=http://localhost:3000/api
# NEXT_PUBLIC_GQL=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-hive-front/graphql # NEXT_PUBLIC_GQL=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-hive-front/graphql
# NEXT_PUBLIC_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/ # NEXT_PUBLIC_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
# NEXT_PUBLIC_SWAP=https://app.osmosis.zone # NEXT_PUBLIC_SWAP=https://app.osmosis.zone
# NEXT_PUBLIC_APOLLO_APR=https://api.apollo.farm/api/vault_infos/v2/osmosis-1
# NEXT_PUBLIC_WALLETS=keplr,xfi-cosmos,leap-cosmos,cosmostation,mobile-keplr,mobile-cosmostation # NEXT_PUBLIC_WALLETS=keplr,xfi-cosmos,leap-cosmos,cosmostation,mobile-keplr,mobile-cosmostation
# NEXT_PUBLIC_ACCOUNT_NFT=osmo1450hrg6dv2l58c0rvdwx8ec2a0r6dd50hn4frk370tpvqjhy8khqw7sw09 # NEXT_PUBLIC_ACCOUNT_NFT=osmo1450hrg6dv2l58c0rvdwx8ec2a0r6dd50hn4frk370tpvqjhy8khqw7sw09
# NEXT_PUBLIC_ORACLE=osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g # NEXT_PUBLIC_ORACLE=osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g

2
.github/CODEOWNERS vendored
View File

@ -1 +1 @@
* @bobthebuidlr @linkielink * @bobthebuidlr @linkielink @yusufseyrek

View File

@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import * as rrd from 'react-router-dom'
import * as useParams from 'utils/route'
import AccountDetails from 'components/Account/AccountDetails' import AccountDetails from 'components/Account/AccountDetails'
jest.mock('utils/route') jest.mock('react-router-dom')
const mockedUseParams = useParams.default as jest.Mock const mockedUseParams = rrd.useParams as jest.Mock
describe('<AccountDetails />', () => { describe('<AccountDetails />', () => {
afterAll(() => { afterAll(() => {

View File

@ -1,46 +1,16 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: 'standalone',
experimental: {
appDir: true,
},
reactStrictMode: true, reactStrictMode: true,
async redirects() { async rewrites() {
return [ return [
{ {
source: '/', source: '/:any*',
destination: '/trade', destination: '/',
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, { isServer }) { webpack(config) {
if (isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
'utf-8-validate': false,
bufferutil: false,
'./build/Release/ecdh': false,
eccrypto: false,
}
}
config.module.rules.push({ config.module.rules.push({
test: /\.svg$/i, test: /\.svg$/i,
issuer: /\.[jt]sx?$/, issuer: /\.[jt]sx?$/,

84
package-lock.json generated
View File

@ -19152,6 +19152,66 @@
"optional": true "optional": true
} }
} }
},
"node_modules/@next/swc-android-arm-eabi": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz",
"integrity": "sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-android-arm64": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz",
"integrity": "sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-freebsd-x64": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz",
"integrity": "sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm-gnueabihf": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz",
"integrity": "sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
} }
}, },
"dependencies": { "dependencies": {
@ -33215,6 +33275,30 @@
"requires": { "requires": {
"use-sync-external-store": "1.2.0" "use-sync-external-store": "1.2.0"
} }
},
"@next/swc-android-arm-eabi": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.2.4.tgz",
"integrity": "sha512-DWlalTSkLjDU11MY11jg17O1gGQzpRccM9Oes2yTqj2DpHndajrXHGxj9HGtJ+idq2k7ImUdJVWS2h2l/EDJOw==",
"optional": true
},
"@next/swc-android-arm64": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.2.4.tgz",
"integrity": "sha512-sRavmUImUCf332Gy+PjIfLkMhiRX1Ez4SI+3vFDRs1N5eXp+uNzjFUK/oLMMOzk6KFSkbiK/3Wt8+dHQR/flNg==",
"optional": true
},
"@next/swc-freebsd-x64": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.2.4.tgz",
"integrity": "sha512-kkbzKVZGPaXRBPisoAQkh3xh22r+TD+5HwoC5bOkALraJ0dsOQgSMAvzMXKsN3tMzJUPS0tjtRf1cTzrQ0I5vQ==",
"optional": true
},
"@next/swc-linux-arm-gnueabihf": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.2.4.tgz",
"integrity": "sha512-7qA1++UY0fjprqtjBZaOA6cas/7GekpjVsZn/0uHvquuITFCdKGFCsKNBx3S0Rpxmx6WYo0GcmhNRM9ru08BGg==",
"optional": true
} }
} }
} }

View File

@ -3,18 +3,18 @@
"version": "2.0.0", "version": "2.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "next build", "build": "yarn validate-env && next build",
"dev": "next dev", "dev": "next dev",
"test": "jest", "test": "jest",
"lint": "eslint ./src/ && yarn prettier-check", "lint": "eslint ./src/ && yarn prettier-check",
"format": "eslint ./src/ --fix && prettier --write ./src/", "format": "eslint ./src/ --fix && prettier --write ./src/",
"prettier-check": "prettier --ignore-path .gitignore --check ./src/", "prettier-check": "prettier --ignore-path .gitignore --check ./src/",
"start": "next start" "start": "next start",
"validate-env": "node ./validate-env"
}, },
"dependencies": { "dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.30.1", "@cosmjs/cosmwasm-stargate": "^0.30.1",
"@cosmjs/stargate": "^0.30.1", "@marsprotocol/wallet-connector": "^1.5.9",
"@marsprotocol/wallet-connector": "^1.5.8",
"@sentry/nextjs": "^7.51.2", "@sentry/nextjs": "^7.51.2",
"@tanstack/react-table": "^8.9.1", "@tanstack/react-table": "^8.9.1",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
@ -27,6 +27,7 @@
"react-device-detect": "^2.2.3", "react-device-detect": "^2.2.3",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.5", "react-draggable": "^4.4.5",
"react-router-dom": "^6.11.1",
"react-spring": "^9.7.1", "react-spring": "^9.7.1",
"react-toastify": "^9.1.2", "react-toastify": "^9.1.2",
"react-use-clipboard": "^1.0.9", "react-use-clipboard": "^1.0.9",
@ -37,15 +38,16 @@
}, },
"devDependencies": { "devDependencies": {
"@svgr/webpack": "^8.0.1", "@svgr/webpack": "^8.0.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@types/node": "^20.1.1", "@types/node": "^20.1.1",
"@types/react": "18.2.6", "@types/react": "18.2.6",
"@types/react-dom": "18.2.4", "@types/react-dom": "18.2.4",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"babel-jest": "^29.5.0",
"dotenv": "^16.0.3",
"eslint": "8.40.0", "eslint": "8.40.0",
"eslint-config-next": "^13.4.1", "eslint-config-next": "^13.4.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"babel-jest": "^29.5.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^29.5.0", "jest": "^29.5.0",

View File

@ -0,0 +1,18 @@
import { getClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env'
export default async function getAccount(accountId: string): Promise<AccountResponse> {
const client = await getClient()
const account: AccountResponse = await client.queryContractSmart(ENV.ADDRESS_CREDIT_MANAGER, {
positions: {
account_id: accountId,
},
})
if (account) {
return account
}
return new Promise((_, reject) => reject('No account found'))
}

View File

@ -0,0 +1,11 @@
import getAccount from 'api/accounts/getAccount'
export default async function getAccountDebts(accountId: string): Promise<Coin[]> {
const account = await getAccount(accountId)
if (account) {
return account.debts
}
return new Promise((_, reject) => reject('Account not found'))
}

View File

@ -0,0 +1,11 @@
import getAccount from 'api/accounts/getAccount'
export default async function getAccountDeposits(accountId: string) {
const account = await getAccount(accountId)
if (account) {
return account.deposits
}
return new Promise((_, reject) => reject('Account not found'))
}

View File

@ -0,0 +1,11 @@
import getAccount from 'api/accounts/getAccount'
export default async function getAccountDeposits(accountId: string) {
const account = await getAccount(accountId)
if (account) {
return account.vaults
}
return new Promise((_, reject) => reject('Account not found'))
}

View File

@ -0,0 +1,19 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { ENV } from 'constants/env'
let _cosmWasmClient: CosmWasmClient
const getClient = async () => {
try {
if (!_cosmWasmClient) {
_cosmWasmClient = await CosmWasmClient.connect(ENV.URL_RPC || '')
}
return _cosmWasmClient
} catch (error) {
throw error
}
}
export { getClient }

View File

@ -1,13 +1,8 @@
import { gql, request as gqlRequest } from 'graphql-request' import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next'
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env' import { ENV } from 'constants/env'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!ENV.URL_GQL || !ENV.ADDRESS_RED_BANK) {
return res.status(404).json(ENV_MISSING_MESSAGE)
}
export default async function getBalances() {
const result = await gqlRequest<Result>( const result = await gqlRequest<Result>(
ENV.URL_GQL, ENV.URL_GQL,
gql` gql`
@ -24,7 +19,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
`, `,
) )
return res.status(200).json(result.bank.balance) return result.bank.balance
} }
interface Result { interface Result {

View File

@ -0,0 +1,29 @@
import { BN } from 'utils/helpers'
import getPrices from 'api/prices/getPrices'
import getMarkets from 'api/markets/getMarkets'
import getMarketLiquidity from 'api/markets/getMarketLiquidity'
export default async function getMarketBorrowings(): Promise<BorrowAsset[]> {
const liquidity = await getMarketLiquidity()
const borrowEnabledMarkets = (await getMarkets()).filter((market: Market) => market.borrowEnabled)
const prices = await getPrices()
const borrow: BorrowAsset[] = borrowEnabledMarkets.map((market) => {
const price = prices.find((coin) => coin.denom === market.denom)?.amount ?? '1'
const amount = liquidity.find((coin) => coin.denom === market.denom)?.amount ?? '0'
return {
denom: market.denom,
borrowRate: market.borrowRate ?? 0,
liquidity: {
amount: amount,
value: BN(amount).times(price).toString(),
},
}
})
if (borrow) {
return borrow
}
return new Promise((_, reject) => reject('No data'))
}

View File

@ -1,15 +1,11 @@
import { gql, request as gqlRequest } from 'graphql-request' import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next'
import { ENV, ENV_MISSING_MESSAGE, VERCEL_BYPASS } from 'constants/env' import { ENV } from 'constants/env'
import { denomToKey, getContractQuery, keyToDenom } from 'utils/query' import { denomToKey, getContractQuery, keyToDenom } from 'utils/query'
import getMarkets from 'api/markets/getMarkets'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function getMarketDebts(): Promise<Coin[]> {
if (!ENV.URL_API || !ENV.ADDRESS_RED_BANK || !ENV.URL_GQL) { const markets: Market[] = await getMarkets()
return res.status(404).json(ENV_MISSING_MESSAGE)
}
const markets: Market[] = await (await fetch(`${ENV.URL_API}/markets${VERCEL_BYPASS}`)).json()
let query = '' let query = ''
@ -45,10 +41,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
amount: result.debts[key], amount: result.debts[key],
} }
}) })
return res.status(200).json(debts) return debts
} }
return res.status(404) return new Promise((_, reject) => reject('No data'))
} }
interface DebtsQuery { interface DebtsQuery {

View File

@ -1,15 +1,11 @@
import { gql, request as gqlRequest } from 'graphql-request' import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next'
import { ENV, ENV_MISSING_MESSAGE, VERCEL_BYPASS } from 'constants/env' import { ENV } from 'constants/env'
import { denomToKey, getContractQuery, keyToDenom } from 'utils/query' import { denomToKey, getContractQuery, keyToDenom } from 'utils/query'
import getMarkets from 'api/markets/getMarkets'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function getMarketDeposits(): Promise<Coin[]> {
if (!ENV.URL_RPC || !ENV.ADDRESS_RED_BANK || !ENV.URL_GQL || !ENV.URL_API) { const markets = await getMarkets()
return res.status(404).json(ENV_MISSING_MESSAGE)
}
const markets = await (await fetch(`${ENV.URL_API}/markets${VERCEL_BYPASS}`)).json()
let query = '' let query = ''
@ -45,10 +41,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
amount: result.deposits[key], amount: result.deposits[key],
} }
}) })
return res.status(200).json(deposits) return deposits
} }
return res.status(404) return new Promise((_, reject) => reject('No data'))
} }
interface DepositsQuery { interface DepositsQuery {

View File

@ -0,0 +1,30 @@
import { BN } from 'utils/helpers'
import getMarketDeposits from 'api/markets/getMarketDeposits'
import getMarketDebts from 'api/markets/getMarketDebts'
export default async function getMarketLiquidity(): Promise<Coin[]> {
const deposits = await getMarketDeposits()
const debts = await getMarketDebts()
const liquidity: Coin[] = deposits.map((deposit) => {
const debt = debts.find((debt) => debt.denom === deposit.denom)
if (debt) {
return {
denom: deposit.denom,
amount: BN(deposit.amount).minus(debt.amount).toString(),
}
}
return {
denom: deposit.denom,
amount: '0',
}
})
if (liquidity) {
return liquidity
}
return new Promise((_, reject) => reject('No data'))
}

View File

@ -1,16 +1,11 @@
import { gql, request as gqlRequest } from 'graphql-request' import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next'
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env' import { ENV } from 'constants/env'
import { getMarketAssets } from 'utils/assets' import { getMarketAssets } from 'utils/assets'
import { denomToKey } from 'utils/query' import { denomToKey } from 'utils/query'
import { resolveMarketResponses } from 'utils/resolvers' import { resolveMarketResponses } from 'utils/resolvers'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function getMarkets(): Promise<Market[]> {
if (!ENV.URL_GQL || !ENV.ADDRESS_RED_BANK || !ENV.ADDRESS_INCENTIVES) {
return res.status(404).json(ENV_MISSING_MESSAGE)
}
const marketAssets = getMarketAssets() const marketAssets = getMarketAssets()
const marketQueries = marketAssets.map( const marketQueries = marketAssets.map(
@ -36,7 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const market = result.rbwasmkey[`${denomToKey(asset.denom)}`] const market = result.rbwasmkey[`${denomToKey(asset.denom)}`]
return market return market
}) })
return res.status(200).json(resolveMarketResponses(markets)) return resolveMarketResponses(markets)
} }
interface RedBankData { interface RedBankData {

View File

@ -1,16 +1,11 @@
import { gql, request as gqlRequest } from 'graphql-request' import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next'
import { ASSETS } from 'constants/assets' import { ASSETS } from 'constants/assets'
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env' import { ENV } from 'constants/env'
import { getMarketAssets } from 'utils/assets' import { getMarketAssets } from 'utils/assets'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function getPrices(): Promise<Coin[]> {
if (!ENV.URL_GQL || !ENV.ADDRESS_ORACLE) {
return res.status(404).json(ENV_MISSING_MESSAGE)
}
const marketAssets = getMarketAssets() const marketAssets = getMarketAssets()
const baseCurrency = ASSETS[0] const baseCurrency = ASSETS[0]
@ -51,7 +46,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
] as Coin[] ] as Coin[]
}, []) }, [])
return res.status(200).json(data) return data
} }
interface TokenPricesResult { interface TokenPricesResult {

View File

@ -0,0 +1,50 @@
import { ENV } from 'constants/env'
export default async function getAprs() {
try {
const response = await fetch(ENV.URL_APOLLO_APR)
if (response.ok) {
const data: FlatApr[] | NestedApr[] = await response.json()
const newAprs = data.map((aprData) => {
try {
const apr = aprData as FlatApr
const aprTotal = apr.apr.reduce((prev, curr) => Number(curr.value) + prev, 0)
const feeTotal = apr.fees.reduce((prev, curr) => Number(curr.value) + prev, 0)
const finalApr = aprTotal + feeTotal
return { address: aprData.contract_address, apr: finalApr }
} catch {
const apr = aprData as NestedApr
const aprTotal = apr.apr.aprs.reduce((prev, curr) => Number(curr.value) + prev, 0)
const feeTotal = apr.apr.fees.reduce((prev, curr) => Number(curr.value) + prev, 0)
const finalApr = aprTotal + feeTotal
return { address: aprData.contract_address, apr: finalApr }
}
})
return newAprs
}
return []
} catch {
return []
}
}
interface FlatApr {
contract_address: string
apr: { type: string; value: number | string }[]
fees: { type: string; value: number | string }[]
}
interface NestedApr {
contract_address: string
apr: {
aprs: { type: string; value: number | string }[]
fees: { type: string; value: number | string }[]
}
}

View File

@ -0,0 +1,64 @@
import { getClient } from 'api/cosmwasm-client'
import { ENV, IS_TESTNET } from 'constants/env'
import { TESTNET_VAULTS, VAULTS } from 'constants/vaults'
import {
ArrayOfVaultInfoResponse,
VaultBaseForString,
} from 'types/generated/mars-credit-manager/MarsCreditManager.types'
export default async function getVaultConfigs(): Promise<VaultConfig[]> {
const vaultInfos: VaultInfo[] = await getVaultInfos([])
const vaults = IS_TESTNET ? TESTNET_VAULTS : VAULTS
return vaults.map((vaultMetaData) => {
const vaultConfig = vaultInfos.find((vaultInfo) => vaultInfo.address === vaultMetaData.address)
return {
...vaultMetaData,
...vaultConfig,
} as VaultConfig
})
}
const getVaultInfos = async (
vaultInfos: VaultInfo[],
startAfter?: VaultBaseForString,
): Promise<VaultInfo[]> => {
if (!ENV.ADDRESS_CREDIT_MANAGER) return []
const client = await getClient()
try {
const batch: ArrayOfVaultInfoResponse = await client.queryContractSmart(
ENV.ADDRESS_CREDIT_MANAGER,
{
vaults_info: { limit: 4, start_after: startAfter },
},
)
const batchProcessed = batch?.map((vaultInfo) => {
return {
address: vaultInfo.vault.address,
cap: {
denom: vaultInfo.config.deposit_cap.denom,
used: Number(vaultInfo.utilization.amount),
max: Number(vaultInfo.config.deposit_cap.amount),
},
ltv: {
max: Number(vaultInfo.config.max_ltv),
liq: Number(vaultInfo.config.liquidation_threshold),
},
} as VaultConfig
})
vaultInfos.push(...batchProcessed)
if (batch.length === 4) {
return await getVaultInfos(vaultInfos, {
address: batchProcessed[batchProcessed.length - 1].address,
} as VaultBaseForString)
}
return vaultInfos
} catch {
return vaultInfos
}
}

View File

@ -0,0 +1,29 @@
import { convertAprToApy } from 'utils/parsers'
import getVaultConfigs from 'api/vaults/getVaultConfigs'
import getAprs from 'api/vaults/getVaultAprs'
export default async function getVaults(): Promise<Vault[]> {
const $vaultConfigs = getVaultConfigs()
const $aprs = getAprs()
const vaults: Vault[] = await Promise.all([$vaultConfigs, $aprs]).then(([vaultConfigs, aprs]) => {
return vaultConfigs.map((vaultConfig) => {
const apr = aprs.find((apr) => apr.address === vaultConfig.address)
if (apr) {
return {
...vaultConfig,
apy: convertAprToApy(apr.apr, 365),
}
}
return {
...vaultConfig,
apy: null,
}
})
})
if (vaults) {
return vaults
}
return new Promise((_, reject) => reject('No data'))
}

View File

@ -0,0 +1,18 @@
import { getClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env'
export default async function getAccountIds(address: string) {
const client = await getClient()
const data = await client.queryContractSmart(ENV.ADDRESS_ACCOUNT_NFT, {
tokens: {
owner: address,
},
})
if (data.tokens) {
return data.tokens
}
return new Promise((_, reject) => reject('No data'))
}

View File

@ -0,0 +1,26 @@
import { ENV } from 'constants/env'
import { resolvePositionResponses } from 'utils/resolvers'
import getWalletAccountIds from 'api/wallets/getAccountIds'
import { getClient } from 'api/cosmwasm-client'
export default async function getAccounts(address: string): Promise<Account[]> {
const accountIds: string[] = await getWalletAccountIds(address)
const client = await getClient()
const $accounts: Promise<AccountResponse>[] = accountIds.map((accountId) =>
client.queryContractSmart(ENV.ADDRESS_CREDIT_MANAGER!, {
positions: {
account_id: `${accountId}`,
},
}),
)
const accounts = await Promise.all($accounts).then((accounts) => accounts)
if (accounts) {
return resolvePositionResponses(accounts)
}
return new Promise((_, reject) => reject('No data'))
}

View File

@ -0,0 +1,14 @@
import { ENV } from 'constants/env'
export default async function getWalletBalances(address: string): Promise<Coin[]> {
const uri = '/cosmos/bank/v1beta1/balances/'
const response = await fetch(`${ENV.URL_REST}${uri}${address}`)
if (response.ok) {
const data = await response.json()
return data.balances
}
return new Promise((_, reject) => reject('No data'))
}

View File

@ -1,5 +0,0 @@
import BorrowPage from 'components/pages/borrow'
export default async function page({ params }: PageProps) {
return <BorrowPage params={params} />
}

View File

@ -1,5 +0,0 @@
import CouncilPage from 'components/pages/council'
export default async function page({ params }: PageProps) {
return <CouncilPage params={params} />
}

View File

@ -1,5 +0,0 @@
import FarmPage from 'components/pages/farm'
export default async function page({ params }: { params: PageParams }) {
return <FarmPage params={params} />
}

View File

@ -1,5 +0,0 @@
import LendPage from 'components/pages/lend'
export default function page({ params }: { params: PageParams }) {
return <LendPage params={params} />
}

View File

@ -1,30 +0,0 @@
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' />
</>
)
}

View File

@ -1,42 +0,0 @@
import classNames from 'classnames'
import { headers } from 'next/headers'
import AccountDetails from 'components/Account/AccountDetails'
import Background from 'components/Background'
import FetchPrices from 'components/FetchPrices'
import Footer from 'components/Footer'
import DesktopHeader from 'components/Header/DesktopHeader'
import ModalsContainer from 'components/Modals/ModalsContainer'
import Toaster from 'components/Toaster'
import 'react-toastify/dist/ReactToastify.min.css'
import 'styles/globals.css'
import { getRouteParams } from 'utils/route'
export default function RootLayout(props: { children: React.ReactNode }) {
const href = headers().get('x-url') || ''
const params = getRouteParams(href)
return (
<html className='m-0 p-0' lang='en'>
<head />
<body className='m-0 cursor-default bg-body p-0 font-sans text-white'>
<Background />
<DesktopHeader params={params} />
<FetchPrices />
<main
className={classNames(
'relative flex justify-center pt-6',
'lg:mt-[65px] lg:h-[calc(100vh-89px)]',
)}
>
<div className='flex w-full max-w-content flex-grow flex-wrap content-start'>
{props.children}
</div>
<AccountDetails />
</main>
<Footer />
<ModalsContainer />
<Toaster />
</body>
</html>
)
}

View File

@ -1,5 +0,0 @@
import TradePage from 'components/pages/trade'
export default async function page({ params }: PageProps) {
return <TradePage params={params} />
}

View File

@ -1,5 +0,0 @@
import PortfolioPage from 'components/pages/portfolio'
export default async function page({ params }: PageProps) {
return <PortfolioPage params={params} />
}

View File

@ -1,5 +0,0 @@
import TradePage from 'components/pages/trade'
export default async function page({ params }: PageProps) {
return <TradePage params={params} />
}

View File

@ -1,5 +0,0 @@
import BorrowPage from 'components/pages/borrow'
export default async function page({ params }: PageProps) {
return <BorrowPage params={params} />
}

View File

@ -1,5 +0,0 @@
import CouncilPage from 'components/pages/council'
export default async function page({ params }: PageProps) {
return <CouncilPage params={params} />
}

View File

@ -1,5 +0,0 @@
import FarmPage from 'components/pages/farm'
export default async function page({ params }: { params: PageParams }) {
return <FarmPage params={params} />
}

View File

@ -1,5 +0,0 @@
import LendPage from 'components/pages/lend'
export default function page({ params }: { params: PageParams }) {
return <LendPage params={params} />
}

View File

@ -1,5 +0,0 @@
import TradePage from 'components/pages/trade'
export default async function page({ params }: PageProps) {
return <TradePage params={params} />
}

View File

@ -1,5 +0,0 @@
import PortfolioPage from 'components/pages/portfolio'
export default async function page({ params }: PageProps) {
return <PortfolioPage params={params} />
}

View File

@ -1,5 +0,0 @@
import TradePage from 'components/pages/trade'
export default async function page({ params }: PageProps) {
return <TradePage params={params} />
}

View File

@ -1,5 +0,0 @@
import BorrowPage from 'components/pages/borrow'
export default async function page({ params }: PageProps) {
return <BorrowPage params={params} />
}

View File

@ -1,5 +0,0 @@
import CouncilPage from 'components/pages/council'
export default async function page({ params }: PageProps) {
return <CouncilPage params={params} />
}

View File

@ -1,5 +0,0 @@
import FarmPage from 'components/pages/farm'
export default async function page({ params }: { params: PageParams }) {
return <FarmPage params={params} />
}

View File

@ -1,5 +0,0 @@
import LendPage from 'components/pages/lend'
export default function page({ params }: { params: PageParams }) {
return <LendPage params={params} />
}

View File

@ -1,3 +0,0 @@
export default function RootLayout({ children }: { children: React.ReactNode }) {
return children
}

View File

@ -1,5 +0,0 @@
import PortfolioPage from 'components/pages/portfolio'
export default async function page({ params }: PageProps) {
return <PortfolioPage params={params} />
}

View File

@ -1,5 +0,0 @@
import TradePage from 'components/pages/trade'
export default async function page({ params }: PageProps) {
return <TradePage params={params} />
}

View File

@ -1,5 +1,3 @@
'use client'
import { import {
ColumnDef, ColumnDef,
flexRender, flexRender,
@ -142,7 +140,6 @@ export const AcccountBalancesTable = (props: Props) => {
onSortingChange: setSorting, onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
debugTable: true,
}) })
return ( return (

View File

@ -1,4 +1,3 @@
'use client'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classNames from 'classnames' import classNames from 'classnames'

View File

@ -1,13 +1,13 @@
'use client' import { useParams } from 'react-router-dom'
import { Gauge } from 'components/Gauge' import { Gauge } from 'components/Gauge'
import { Heart } from 'components/Icons' import { Heart } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import { isNumber } from 'utils/parsers' import { isNumber } from 'utils/parsers'
import useParams from 'utils/route'
export default function AccountDetails() { export default function AccountDetails() {
const params = useParams() const { accountId } = useParams()
const hasAccount = isNumber(params.accountId) const hasAccount = isNumber(accountId)
return hasAccount ? ( return hasAccount ? (
<div <div

View File

@ -1,4 +1,3 @@
'use client'
import classNames from 'classnames' import classNames from 'classnames'
import { Heart } from 'components/Icons' import { Heart } from 'components/Icons'

View File

@ -1,8 +1,6 @@
'use client'
import classNames from 'classnames' import classNames from 'classnames'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import AccountStats from 'components/Account/AccountStats' import AccountStats from 'components/Account/AccountStats'
import { Button } from 'components/Button' import { Button } from 'components/Button'
@ -16,7 +14,8 @@ import useStore from 'store'
import { calculateAccountDeposits } from 'utils/accounts' import { calculateAccountDeposits } from 'utils/accounts'
import { hardcodedFee } from 'utils/contants' import { hardcodedFee } from 'utils/contants'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import useParams, { getRoute } from 'utils/route' import { getPage, getRoute } from 'utils/route'
import usePrices from 'hooks/usePrices'
interface Props { interface Props {
setShowFundAccount: (showFundAccount: boolean) => void setShowFundAccount: (showFundAccount: boolean) => void
@ -29,26 +28,25 @@ const accountCardHeaderClasses = classNames(
) )
export default function AccountList(props: Props) { export default function AccountList(props: Props) {
const router = useRouter() const navigate = useNavigate()
const params = useParams() const { pathname } = useLocation()
const { accountId, address } = useParams()
const selectedAccount = params.accountId const { data: prices } = usePrices()
const prices = useStore((s) => s.prices)
const deleteAccount = useStore((s) => s.deleteAccount) const deleteAccount = useStore((s) => s.deleteAccount)
const [isLending, setIsLending] = useToggle() const [isLending, setIsLending] = useToggle()
const accountSelected = !!selectedAccount && !isNaN(Number(selectedAccount)) const accountSelected = !!accountId && !isNaN(Number(accountId))
const selectedAccountDetails = props.accounts.find((account) => account.id === selectedAccount) const selectedAccountDetails = props.accounts.find((account) => account.id === accountId)
const selectedAccountBalance = selectedAccountDetails const selectedAccountBalance = selectedAccountDetails
? calculateAccountDeposits(selectedAccountDetails, prices) ? calculateAccountDeposits(selectedAccountDetails, prices)
: BN(0) : BN(0)
async function deleteAccountHandler() { async function deleteAccountHandler() {
if (!accountSelected) return if (!accountSelected) return
const isSuccess = await deleteAccount({ fee: hardcodedFee, accountId: selectedAccount }) const isSuccess = await deleteAccount({ fee: hardcodedFee, accountId: accountId })
if (isSuccess) { if (isSuccess) {
router.push(`/wallets/${params.address}/accounts`) navigate(`/wallets/${address}/accounts`)
} }
} }
@ -58,11 +56,11 @@ export default function AccountList(props: Props) {
} }
useEffect(() => { useEffect(() => {
const element = document.getElementById(`account-${selectedAccount}`) const element = document.getElementById(`account-${accountId}`)
if (element) { if (element) {
element.scrollIntoView({ behavior: 'smooth' }) element.scrollIntoView({ behavior: 'smooth' })
} }
}, [selectedAccount]) }, [accountId])
if (!props.accounts?.length) return null if (!props.accounts?.length) return null
@ -70,7 +68,7 @@ export default function AccountList(props: Props) {
<div className='flex w-full flex-wrap p-4'> <div className='flex w-full flex-wrap p-4'>
{props.accounts.map((account) => { {props.accounts.map((account) => {
const positionBalance = calculateAccountDeposits(account, prices) const positionBalance = calculateAccountDeposits(account, prices)
const isActive = selectedAccount === account.id const isActive = accountId === account.id
return ( return (
<div key={account.id} id={`account-${account.id}`} className='w-full pt-4'> <div key={account.id} id={`account-${account.id}`} className='w-full pt-4'>
<Card <Card
@ -87,7 +85,7 @@ export default function AccountList(props: Props) {
role={!isActive ? 'button' : undefined} role={!isActive ? 'button' : undefined}
onClick={() => { onClick={() => {
if (isActive) return if (isActive) return
router.push(getRoute(params, { accountId: account.id })) navigate(getRoute(getPage(pathname), address, account.id))
}} }}
> >
<Text size='xs' className='flex flex-1'> <Text size='xs' className='flex flex-1'>

View File

@ -2,23 +2,20 @@ import { Suspense } from 'react'
import AccountMenuContent from 'components/Account/AccountMenuContent' import AccountMenuContent from 'components/Account/AccountMenuContent'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import { getAccounts } from 'utils/api' import useStore from 'store'
import useAccounts from 'hooks/useAccounts'
interface Props { function Content() {
params: PageParams const address = useStore((s) => s.address)
} const { data: accounts } = useAccounts(address)
if (!accounts) return null
async function Content(props: Props) {
if (props.params.address === undefined) return null
const accounts = await getAccounts(props.params.address)
return <AccountMenuContent accounts={accounts} /> return <AccountMenuContent accounts={accounts} />
} }
export default function AccountMenu(props: Props) { export default function AccountMenu() {
return ( return (
<Suspense fallback={<Loading className='h-8 w-35' />}> <Suspense fallback={<Loading className='h-8 w-35' />}>
{/* @ts-expect-error Server Component */} <Content />
<Content params={props.params} />
</Suspense> </Suspense>
) )
} }

View File

@ -1,8 +1,6 @@
'use client'
import classNames from 'classnames' import classNames from 'classnames'
import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import AccountList from 'components/Account/AccountList' import AccountList from 'components/Account/AccountList'
import CreateAccount from 'components/Account/CreateAccount' import CreateAccount from 'components/Account/CreateAccount'
@ -16,7 +14,6 @@ import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
import { hardcodedFee } from 'utils/contants' import { hardcodedFee } from 'utils/contants'
import { isNumber } from 'utils/parsers' import { isNumber } from 'utils/parsers'
import useParams from 'utils/route'
const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide' const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide'
@ -25,22 +22,21 @@ interface Props {
} }
export default function AccountMenuContent(props: Props) { export default function AccountMenuContent(props: Props) {
const router = useRouter() const navigate = useNavigate()
const params = useParams() const { accountId, address } = useParams()
const createAccount = useStore((s) => s.createAccount) const createAccount = useStore((s) => s.createAccount)
const [showMenu, setShowMenu] = useToggle() const [showMenu, setShowMenu] = useToggle()
const [isCreating, setIsCreating] = useToggle() const [isCreating, setIsCreating] = useToggle()
const selectedAccountId = params.accountId
const hasCreditAccounts = !!props.accounts.length const hasCreditAccounts = !!props.accounts.length
const isAccountSelected = isNumber(selectedAccountId) const isAccountSelected = isNumber(accountId)
const selectedAccountDetails = props.accounts.find((account) => account.id === selectedAccountId) const selectedAccountDetails = props.accounts.find((account) => account.id === accountId)
const [showFundAccount, setShowFundAccount] = useState<boolean>( const [showFundAccount, setShowFundAccount] = useState<boolean>(
isAccountSelected && !selectedAccountDetails?.deposits?.length, isAccountSelected && !selectedAccountDetails?.deposits?.length,
) )
const isLoadingAccount = isAccountSelected && selectedAccountDetails?.id !== selectedAccountId const isLoadingAccount = isAccountSelected && selectedAccountDetails?.id !== accountId
const showCreateAccount = !hasCreditAccounts || isCreating const showCreateAccount = !hasCreditAccounts || isCreating
async function createAccountHandler() { async function createAccountHandler() {
@ -49,14 +45,14 @@ export default function AccountMenuContent(props: Props) {
const accountId = await createAccount({ fee: hardcodedFee }) const accountId = await createAccount({ fee: hardcodedFee })
setIsCreating(false) setIsCreating(false)
if (!accountId) return if (!accountId) return
router.push(`/wallets/${params.address}/accounts/${accountId}`) navigate(`/wallets/${address}/accounts/${accountId}`)
} }
useEffect(() => { useEffect(() => {
useStore.setState({ accounts: props.accounts }) useStore.setState({ accounts: props.accounts })
}, [props.accounts]) }, [props.accounts])
if (!params.address) return null if (!address) return null
return ( return (
<div className='relative'> <div className='relative'>
@ -69,7 +65,7 @@ export default function AccountMenuContent(props: Props) {
> >
{hasCreditAccounts {hasCreditAccounts
? isAccountSelected ? isAccountSelected
? `Account ${selectedAccountId}` ? `Account ${accountId}`
: 'Select Account' : 'Select Account'
: 'Create Account'} : 'Create Account'}
</Button> </Button>

View File

@ -1,4 +1,3 @@
'use client'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import AccountHealth from 'components/Account/AccountHealth' import AccountHealth from 'components/Account/AccountHealth'

View File

@ -1,5 +1,3 @@
'use client'
import Accordion from 'components/Accordion' import Accordion from 'components/Accordion'
import { AcccountBalancesTable } from 'components/Account/AccountBalancesTable' import { AcccountBalancesTable } from 'components/Account/AccountBalancesTable'
import AccountComposition from 'components/Account/AccountComposition' import AccountComposition from 'components/Account/AccountComposition'

View File

@ -1,5 +1,3 @@
'use client'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'

View File

@ -1,7 +1,6 @@
'use client'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { useParams } from 'react-router-dom'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { ArrowRight, Cross } from 'components/Icons' import { ArrowRight, Cross } from 'components/Icons'
@ -14,7 +13,6 @@ import useStore from 'store'
import { getAmount } from 'utils/accounts' import { getAmount } from 'utils/accounts'
import { hardcodedFee } from 'utils/contants' import { hardcodedFee } from 'utils/contants'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import useParams from 'utils/route'
interface Props { interface Props {
setShowFundAccount: (show: boolean) => void setShowFundAccount: (show: boolean) => void
@ -22,7 +20,7 @@ interface Props {
} }
export default function FundAccount(props: Props) { export default function FundAccount(props: Props) {
const params = useParams() const { accountId } = useParams()
const deposit = useStore((s) => s.deposit) const deposit = useStore((s) => s.deposit)
const balances = useStore((s) => s.balances) const balances = useStore((s) => s.balances)
@ -50,10 +48,11 @@ export default function FundAccount(props: Props) {
) )
async function onDeposit() { async function onDeposit() {
if (!accountId) return
setIsFunding(true) setIsFunding(true)
const result = await deposit({ const result = await deposit({
fee: hardcodedFee, fee: hardcodedFee,
accountId: params.accountId, accountId,
coin: { coin: {
denom: asset.denom, denom: asset.denom,
amount: amount.toString(), amount: amount.toString(),
@ -79,7 +78,7 @@ export default function FundAccount(props: Props) {
</div> </div>
<div className='relative z-10 w-full p-4'> <div className='relative z-10 w-full p-4'>
<Text size='lg' className='mb-2 font-bold'> <Text size='lg' className='mb-2 font-bold'>
{`Fund Account ${params.accountId}`} {`Fund Account ${accountId}`}
</Text> </Text>
<Text className='mb-4 text-white/70'> <Text className='mb-4 text-white/70'>
Deposit assets from your Osmosis address to your Mars credit account. Bridge assets if Deposit assets from your Osmosis address to your Mars credit account. Bridge assets if

View File

@ -1,4 +1,4 @@
import { getAccountDebts } from 'utils/api' import getAccountDebts from 'api/accounts/getAccountDebts'
interface Props { interface Props {
accountId: string accountId: string

View File

@ -1,5 +1,3 @@
'use client'
import classNames from 'classnames' import classNames from 'classnames'
import useStore from 'store' import useStore from 'store'

View File

@ -1,5 +1,3 @@
'use client'
import { Row } from '@tanstack/react-table' import { Row } from '@tanstack/react-table'
import { Button } from 'components/Button' import { Button } from 'components/Button'

View File

View File

@ -1,5 +1,3 @@
'use client'
import { import {
ColumnDef, ColumnDef,
flexRender, flexRender,
@ -124,7 +122,6 @@ export const BorrowTable = (props: Props) => {
onSortingChange: setSorting, onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
debugTable: true,
}) })
return ( return (

View File

@ -1,17 +1,20 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import { useParams } from 'react-router-dom'
import Card from 'components/Card' import Card from 'components/Card'
import { getAccountDebts, getBorrowData } from 'utils/api'
import { getMarketAssets } from 'utils/assets' import { getMarketAssets } from 'utils/assets'
import { BorrowTable } from 'components/Borrow/BorrowTable' import { BorrowTable } from 'components/Borrow/BorrowTable'
import useAccountDebts from 'hooks/useAccountDebts'
import useMarketBorrowings from 'hooks/useMarketBorrowings'
interface Props extends PageProps { interface Props {
type: 'active' | 'available' type: 'active' | 'available'
} }
async function Content(props: Props) { function Content(props: Props) {
const debtData = await getAccountDebts(props.params?.accountId) const { accountId } = useParams()
const borrowData = await getBorrowData() const { data: debtData } = useAccountDebts(accountId)
const { data: borrowData } = useMarketBorrowings()
const marketAssets = getMarketAssets() const marketAssets = getMarketAssets()
@ -20,7 +23,7 @@ async function Content(props: Props) {
(prev: { available: BorrowAsset[]; active: BorrowAssetActive[] }, curr) => { (prev: { available: BorrowAsset[]; active: BorrowAssetActive[] }, curr) => {
const borrow = borrowData.find((borrow) => borrow.denom === curr.denom) const borrow = borrowData.find((borrow) => borrow.denom === curr.denom)
if (borrow) { if (borrow) {
const debt = debtData.find((debt) => debt.denom === curr.denom) const debt = debtData?.find((debt) => debt.denom === curr.denom)
if (debt) { if (debt) {
prev.active.push({ prev.active.push({
...borrow, ...borrow,
@ -68,22 +71,20 @@ function Fallback() {
return <BorrowTable data={available} /> return <BorrowTable data={available} />
} }
export function AvailableBorrowings(props: PageProps) { export function AvailableBorrowings() {
return ( return (
<Card className='h-fit w-full bg-white/5' title={'Available to borrow'}> <Card className='h-fit w-full bg-white/5' title={'Available to borrow'}>
<Suspense fallback={<Fallback />}> <Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */} <Content type='available' />
<Content params={props.params} type='available' />
</Suspense> </Suspense>
</Card> </Card>
) )
} }
export function ActiveBorrowings(props: PageProps) { export function ActiveBorrowings() {
return ( return (
<Suspense fallback={null}> <Suspense fallback={null}>
{/* @ts-expect-error Server Component */} <Content type='active' />
<Content params={props.params} type='active' />
</Suspense> </Suspense>
) )
} }

View File

@ -1,11 +1,12 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import { useParams } from 'react-router-dom'
import Card from 'components/Card' import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import Text from 'components/Text' import Text from 'components/Text'
async function Content(props: PageProps) { function Content() {
const address = props.params.address const address = useParams().address || ''
return address ? ( return address ? (
<Text size='sm'>{`Council page for ${address}`}</Text> <Text size='sm'>{`Council page for ${address}`}</Text>
@ -18,7 +19,7 @@ function Fallback() {
return <Loading className='h-4 w-50' /> return <Loading className='h-4 w-50' />
} }
export default function Overview(props: PageProps) { export default function Overview() {
return ( return (
<Card <Card
className='h-fit w-full justify-center bg-white/5' className='h-fit w-full justify-center bg-white/5'
@ -26,8 +27,7 @@ export default function Overview(props: PageProps) {
contentClassName='px-4 py-6' contentClassName='px-4 py-6'
> >
<Suspense fallback={<Fallback />}> <Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */} <Content />
<Content params={props.params} />
</Suspense> </Suspense>
</Card> </Card>
) )

View File

@ -1,5 +1,3 @@
import { Coin } from '@cosmjs/stargate'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import useStore from 'store' import useStore from 'store'
import { convertToDisplayAmount } from 'utils/formatters' import { convertToDisplayAmount } from 'utils/formatters'

View File

@ -1,5 +1,5 @@
import classNames from 'classnames' import classNames from 'classnames'
import Link from 'next/link' import { NavLink, useParams } from 'react-router-dom'
import { getRoute } from 'utils/route' import { getRoute } from 'utils/route'
@ -7,34 +7,35 @@ const underlineClasses =
'relative before:absolute before:h-[2px] before:-bottom-1 before:left-0 before:right-0 before:gradient-active-tab' 'relative before:absolute before:h-[2px] before:-bottom-1 before:left-0 before:right-0 before:gradient-active-tab'
interface Props { interface Props {
params: PageParams
isFarm?: boolean isFarm?: boolean
} }
export default function Tab(props: Props) { export default function Tab(props: Props) {
const { address, accountId } = useParams()
return ( return (
<div className='mb-8 w-full'> <div className='mb-8 w-full'>
<div className='flex gap-2'> <div className='flex gap-2'>
<div className='relative'> <div className='relative'>
<Link <NavLink
href={getRoute(props.params, { page: 'earn/farm' })} to={getRoute('farm', address, accountId)}
className={classNames( className={classNames(
!props.isFarm ? 'text-white/20' : underlineClasses, !props.isFarm ? 'text-white/20' : underlineClasses,
'relative mr-8 text-xl', 'relative mr-8 text-xl',
)} )}
> >
Farm Farm
</Link> </NavLink>
</div> </div>
<Link <NavLink
href={getRoute(props.params, { page: 'earn/lend' })} to={getRoute('lend', address, accountId)}
className={classNames( className={classNames(
props.isFarm ? 'text-white/20' : underlineClasses, props.isFarm ? 'text-white/20' : underlineClasses,
'relative text-xl', 'relative text-xl',
)} )}
> >
Lend Lend
</Link> </NavLink>
</div> </div>
</div> </div>
) )

View File

@ -5,10 +5,10 @@ import { VaultTable } from 'components/Earn/vault/VaultTable'
import Text from 'components/Text' import Text from 'components/Text'
import { IS_TESTNET } from 'constants/env' import { IS_TESTNET } from 'constants/env'
import { TESTNET_VAULTS, VAULTS } from 'constants/vaults' import { TESTNET_VAULTS, VAULTS } from 'constants/vaults'
import { getVaults } from 'utils/api' import useVaults from 'hooks/useVaults'
async function Content() { function Content() {
const vaults = await getVaults() const { data: vaults } = useVaults()
if (!vaults.length) return null if (!vaults.length) return null
@ -19,7 +19,6 @@ export default function AvailableVaults() {
return ( return (
<Card title='Available vaults' className='mb-4 h-fit w-full bg-white/5'> <Card title='Available vaults' className='mb-4 h-fit w-full bg-white/5'>
<Suspense fallback={<Fallback />}> <Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */}
<Content /> <Content />
</Suspense> </Suspense>
</Card> </Card>

View File

@ -2,10 +2,10 @@ import { Suspense } from 'react'
import Card from 'components/Card' import Card from 'components/Card'
import VaultCard from 'components/Earn/vault/VaultCard' import VaultCard from 'components/Earn/vault/VaultCard'
import { getVaults } from 'utils/api' import useVaults from 'hooks/useVaults'
async function Content() { function Content() {
const vaults = await getVaults() const { data: vaults } = useVaults()
const featuredVaults = vaults.filter((vault) => vault.isFeatured) const featuredVaults = vaults.filter((vault) => vault.isFeatured)
@ -33,7 +33,6 @@ async function Content() {
export default function FeaturedVaults() { export default function FeaturedVaults() {
return ( return (
<Suspense fallback={null}> <Suspense fallback={null}>
{/* @ts-expect-error Server Component */}
<Content /> <Content />
</Suspense> </Suspense>
) )

View File

@ -1,5 +1,3 @@
'use client'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import VaultLogo from 'components/Earn/vault/VaultLogo' import VaultLogo from 'components/Earn/vault/VaultLogo'
import Text from 'components/Text' import Text from 'components/Text'

View File

@ -1,5 +1,3 @@
'use client'
import { import {
ColumnDef, ColumnDef,
flexRender, flexRender,
@ -103,7 +101,6 @@ export const VaultTable = (props: Props) => {
onSortingChange: setSorting, onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
debugTable: true,
}) })
return ( return (

View File

@ -1,5 +1,3 @@
'use client'
import classNames from 'classnames' import classNames from 'classnames'
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
import { animated, useSpring } from 'react-spring' import { animated, useSpring } from 'react-spring'

View File

@ -4,21 +4,18 @@ import AccountMenu from 'components/Account/AccountMenu'
import DesktopNavigation from 'components/Navigation/DesktopNavigation' import DesktopNavigation from 'components/Navigation/DesktopNavigation'
import Settings from 'components/Settings' import Settings from 'components/Settings'
import Wallet from 'components/Wallet/Wallet' import Wallet from 'components/Wallet/Wallet'
import { WalletConnectProvider } from 'components/Wallet/WalletConnectProvider' import useStore from 'store'
export const menuTree: { href: RouteSegment; label: string }[] = [ export const menuTree: { page: Page; label: string }[] = [
{ href: 'trade', label: 'Trade' }, { page: 'trade', label: 'Trade' },
{ href: 'earn/farm', label: 'Earn' }, { page: 'farm', label: 'Earn' },
{ href: 'borrow', label: 'Borrow' }, { page: 'borrow', label: 'Borrow' },
{ href: 'portfolio', label: 'Portfolio' }, { page: 'portfolio', label: 'Portfolio' },
{ href: 'council', label: 'Council' }, { page: 'council', label: 'Council' },
] ]
interface Props { export default function DesktopHeader() {
params: PageParams const address = useStore((s) => s.address)
}
export default function DesktopHeader(props: Props) {
return ( return (
<header <header
className={classNames( className={classNames(
@ -30,10 +27,8 @@ export default function DesktopHeader(props: Props) {
<div className='flex items-center justify-between border-b border-white/20 py-3 pl-6 pr-4'> <div className='flex items-center justify-between border-b border-white/20 py-3 pl-6 pr-4'>
<DesktopNavigation /> <DesktopNavigation />
<div className='flex gap-4'> <div className='flex gap-4'>
<AccountMenu params={props.params} /> {address && <AccountMenu />}
<WalletConnectProvider> <Wallet />
<Wallet />
</WalletConnectProvider>
<Settings /> <Settings />
</div> </div>
</div> </div>

View File

@ -1,5 +1,3 @@
'use client'
import classNames from 'classnames' import classNames from 'classnames'
import { ReactNode, useEffect, useRef } from 'react' import { ReactNode, useEffect, useRef } from 'react'

View File

@ -1,5 +1,3 @@
'use client'
import Image from 'next/image' import Image from 'next/image'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'

View File

@ -1,5 +1,3 @@
'use client'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import AccountSummary from 'components/Account/AccountSummary' import AccountSummary from 'components/Account/AccountSummary'

View File

@ -1,5 +1,3 @@
'use client'
import BorrowModal from 'components/Modals/BorrowModal' import BorrowModal from 'components/Modals/BorrowModal'
import FundAndWithdrawModal from 'components/Modals/FundAndWithdrawModal' import FundAndWithdrawModal from 'components/Modals/FundAndWithdrawModal'
import VaultModal from 'components/Modals/VaultModal' import VaultModal from 'components/Modals/VaultModal'

View File

@ -1,33 +1,30 @@
'use client' import { useParams } from 'react-router-dom'
import Link from 'next/link'
import { menuTree } from 'components/Header/DesktopHeader' import { menuTree } from 'components/Header/DesktopHeader'
import { Logo } from 'components/Icons' import { Logo } from 'components/Icons'
import { NavLink } from 'components/Navigation/NavLink' import { NavLink } from 'components/Navigation/NavLink'
import useParams, { getRoute } from 'utils/route' import { getRoute } from 'utils/route'
export default function DesktopNavigation() { export default function DesktopNavigation() {
const params = useParams() const { address, accountId } = useParams()
function getIsActive(href: string) { function getIsActive(href: string) {
if (params.page.includes('earn') && href.includes('earn')) return true return location.pathname.includes(href)
return params.page === href
} }
return ( return (
<div className='flex flex-grow items-center'> <div className='flex flex-grow items-center'>
<Link href={getRoute(params, { page: 'trade' })}> <NavLink href={getRoute('trade', address, accountId)} isActive={false}>
<span className='block h-10 w-10'> <span className='block h-10 w-10'>
<Logo /> <Logo />
</span> </span>
</Link> </NavLink>
<div className='flex gap-8 px-6'> <div className='flex gap-8 px-6'>
{menuTree.map((item, index) => ( {menuTree.map((item, index) => (
<NavLink <NavLink
key={index} key={index}
href={getRoute(params, { page: item.href })} href={getRoute(item.page, address, accountId)}
isActive={getIsActive(item.href)} isActive={getIsActive(item.page)}
> >
{item.label} {item.label}
</NavLink> </NavLink>

View File

@ -1,6 +1,6 @@
import classNames from 'classnames' import classNames from 'classnames'
import Link from 'next/link'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { NavLink as Link } from 'react-router-dom'
interface Props { interface Props {
href: string href: string
@ -11,11 +11,13 @@ interface Props {
export const NavLink = (props: Props) => { export const NavLink = (props: Props) => {
return ( return (
<Link <Link
href={props.href} to={props.href}
className={classNames( className={({ isActive }) =>
'text-sm font-semibold hover:text-white active:text-white', classNames(
props.isActive ? 'pointer-events-none text-white' : 'text-white/60', 'text-sm font-semibold hover:text-white active:text-white',
)} isActive ? 'pointer-events-none text-white' : 'text-white/60',
)
}
> >
{props.children} {props.children}
</Link> </Link>

View File

@ -1,5 +1,3 @@
'use client'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classNames from 'classnames' import classNames from 'classnames'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'

View File

@ -1,16 +1,17 @@
import classNames from 'classnames' import classNames from 'classnames'
import { Suspense } from 'react' import { Suspense } from 'react'
import { useParams } from 'react-router-dom'
import { AcccountBalancesTable } from 'components/Account/AccountBalancesTable' import { AcccountBalancesTable } from 'components/Account/AccountBalancesTable'
import AccountComposition from 'components/Account/AccountComposition' import AccountComposition from 'components/Account/AccountComposition'
import Card from 'components/Card' import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import Text from 'components/Text' import Text from 'components/Text'
import { getAccounts } from 'utils/api' import useAccounts from 'hooks/useAccounts'
async function Content(props: PageProps) { function Content() {
const address = props.params.address const address = useParams().address || ''
const account = await getAccounts(address) const { data: account } = useAccounts(address)
if (!address) { if (!address) {
return ( return (
@ -60,11 +61,10 @@ function Fallback() {
) )
} }
export default function AccountOverview(props: PageProps) { export default function AccountOverview() {
return ( return (
<Suspense fallback={<Fallback />}> <Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */} <Content />
<Content params={props.params} />
</Suspense> </Suspense>
) )
} }

49
src/components/Routes.tsx Normal file
View File

@ -0,0 +1,49 @@
import { Outlet, Route, Routes as RoutesWrapper } from 'react-router-dom'
import BorrowPage from 'pages/BorrowPage'
import CouncilPage from 'pages/CouncilPage'
import FarmPage from 'pages/FarmPage'
import LendPage from 'pages/LendPage'
import PortfolioPage from 'pages/PortfolioPage'
import TradePage from 'pages/TradePage'
import Layout from 'pages/_layout'
export default function Routes() {
return (
<RoutesWrapper>
<Route
element={
<Layout>
<Outlet />
</Layout>
}
>
<Route path='/trade' element={<TradePage />} />
<Route path='/farm' element={<FarmPage />} />
<Route path='/lend' element={<LendPage />} />
<Route path='/borrow' element={<BorrowPage />} />
<Route path='/portfolio' element={<PortfolioPage />} />
<Route path='/council' element={<CouncilPage />} />
<Route path='/' element={<TradePage />} />
<Route path='/wallets/:address'>
<Route path='accounts/:accountId'>
<Route path='trade' element={<TradePage />} />
<Route path='farm' element={<FarmPage />} />
<Route path='lend' element={<LendPage />} />
<Route path='borrow' element={<BorrowPage />} />
<Route path='portfolio' element={<PortfolioPage />} />
<Route path='council' element={<CouncilPage />} />
<Route path='' element={<TradePage />} />
</Route>
<Route path='trade' element={<TradePage />} />
<Route path='farm' element={<FarmPage />} />
<Route path='lend' element={<LendPage />} />
<Route path='borrow' element={<BorrowPage />} />
<Route path='portfolio' element={<PortfolioPage />} />
<Route path='council' element={<CouncilPage />} />
<Route path='' element={<TradePage />} />
</Route>
</Route>
</RoutesWrapper>
)
}

View File

@ -1,5 +1,3 @@
'use client'
import classNames from 'classnames' import classNames from 'classnames'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'

View File

@ -1,5 +1,3 @@
'use client'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { Gear } from 'components/Icons' import { Gear } from 'components/Icons'
import Overlay from 'components/Overlay' import Overlay from 'components/Overlay'

View File

@ -1,5 +1,3 @@
'use client'
import classNames from 'classnames' import classNames from 'classnames'
import { ChangeEvent, useRef, useState } from 'react' import { ChangeEvent, useRef, useState } from 'react'
import Draggable from 'react-draggable' import Draggable from 'react-draggable'

View File

@ -1,7 +1,7 @@
'use client'
import classNames from 'classnames' import classNames from 'classnames'
import { useRouter } from 'next/navigation'
import { toast as createToast, Slide, ToastContainer } from 'react-toastify' import { toast as createToast, Slide, ToastContainer } from 'react-toastify'
import { useNavigate } from 'react-router-dom'
import { mutate } from 'swr'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { CheckCircled, Cross, CrossCircled } from 'components/Icons' import { CheckCircled, Cross, CrossCircled } from 'components/Icons'
@ -11,7 +11,6 @@ import useStore from 'store'
export default function Toaster() { export default function Toaster() {
const enableAnimations = useStore((s) => s.enableAnimations) const enableAnimations = useStore((s) => s.enableAnimations)
const toast = useStore((s) => s.toast) const toast = useStore((s) => s.toast)
const router = useRouter()
if (toast) { if (toast) {
const Msg = () => ( const Msg = () => (
@ -62,7 +61,7 @@ export default function Toaster() {
}) })
useStore.setState({ toast: null }) useStore.setState({ toast: null })
router.refresh() mutate(() => true)
} }
return ( return (

View File

@ -1,5 +1,3 @@
'use client'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classNames from 'classnames' import classNames from 'classnames'
import Image from 'next/image' import Image from 'next/image'

View File

@ -1,5 +1,3 @@
'use client'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'

View File

@ -1,11 +1,13 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import { useParams } from 'react-router-dom'
import Card from 'components/Card' import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import Text from 'components/Text' import Text from 'components/Text'
async function Content(props: PageProps) { function Content() {
const address = props.params.address const params = useParams()
const address = params.address
return address ? ( return address ? (
<Text size='sm'>{`Order book for ${address}`}</Text> <Text size='sm'>{`Order book for ${address}`}</Text>
@ -20,12 +22,11 @@ function Fallback() {
return <Loading className='h-4 w-50' /> return <Loading className='h-4 w-50' />
} }
export default function OrderBook(props: PageProps) { export default function OrderBook() {
return ( return (
<Card className='col-span-3 bg-white/5' title='Order Book' contentClassName='px-4 py-6'> <Card className='col-span-3 bg-white/5' title='Order Book' contentClassName='px-4 py-6'>
<Suspense fallback={<Fallback />}> <Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */} <Content />
<Content params={props.params} />
</Suspense> </Suspense>
</Card> </Card>
) )

View File

@ -1,12 +1,14 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import { useParams } from 'react-router-dom'
import Card from 'components/Card' import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import Text from 'components/Text' import Text from 'components/Text'
async function Content(props: PageProps) { function Content() {
const address = props.params.address const params = useParams()
const currentAccount = props.params.accountId const address = params.address
const currentAccount = params.accountId
const hasAccount = !isNaN(Number(currentAccount)) const hasAccount = !isNaN(Number(currentAccount))
if (!address) return <Text size='sm'>You need to be connected to trade</Text> if (!address) return <Text size='sm'>You need to be connected to trade</Text>
@ -20,12 +22,11 @@ function Fallback() {
return <Loading className='h-4 w-50' /> return <Loading className='h-4 w-50' />
} }
export default function Trade(props: PageProps) { export default function Trade() {
return ( return (
<Card className='h-full w-full bg-white/5' title='Trade Module' contentClassName='px-4 py-6'> <Card className='h-full w-full bg-white/5' title='Trade Module' contentClassName='px-4 py-6'>
<Suspense fallback={<Fallback />}> <Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */} <Content />
<Content params={props.params} />
</Suspense> </Suspense>
</Card> </Card>
) )

View File

@ -4,7 +4,7 @@ import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import Text from 'components/Text' import Text from 'components/Text'
async function Content(props: PageProps) { function Content() {
return <Text size='sm'>Chart view</Text> return <Text size='sm'>Chart view</Text>
} }
@ -12,7 +12,7 @@ function Fallback() {
return <Loading className='h-4 w-50' /> return <Loading className='h-4 w-50' />
} }
export default function TradingView(props: PageProps) { export default function TradingView() {
return ( return (
<Card <Card
className='col-span-2 h-full bg-white/5' className='col-span-2 h-full bg-white/5'
@ -20,8 +20,7 @@ export default function TradingView(props: PageProps) {
contentClassName='px-4 py-6' contentClassName='px-4 py-6'
> >
<Suspense fallback={<Fallback />}> <Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */} <Content />
<Content params={props.params} />
</Suspense> </Suspense>
</Card> </Card>
) )

View File

@ -1,5 +1,3 @@
'use client'
import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector' import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
import { ReactNode } from 'react' import { ReactNode } from 'react'

View File

@ -1,5 +1,3 @@
'use client'
import { import {
ChainInfoID, ChainInfoID,
SimpleChainInfoList, SimpleChainInfoList,
@ -10,7 +8,6 @@ import BigNumber from 'bignumber.js'
import classNames from 'classnames' import classNames from 'classnames'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import useClipboard from 'react-use-clipboard' import useClipboard from 'react-use-clipboard'
import useSWR from 'swr'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
@ -21,9 +18,9 @@ import Text from 'components/Text'
import { IS_TESTNET } from 'constants/env' import { IS_TESTNET } from 'constants/env'
import useToggle from 'hooks/useToggle' import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
import { Endpoints, getEndpoint, getWalletBalancesSWR } from 'utils/api'
import { getBaseAsset, getMarketAssets } from 'utils/assets' import { getBaseAsset, getMarketAssets } from 'utils/assets'
import { formatValue, truncate } from 'utils/formatters' import { formatValue, truncate } from 'utils/formatters'
import useWalletBalances from 'hooks/useWalletBalances'
export default function ConnectedButton() { export default function ConnectedButton() {
// --------------- // ---------------
@ -35,10 +32,7 @@ export default function ConnectedButton() {
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
const network = useStore((s) => s.client?.recentWallet.network) const network = useStore((s) => s.client?.recentWallet.network)
const baseAsset = getBaseAsset() const baseAsset = getBaseAsset()
const { data, isLoading } = useSWR( const { data: walletBalances, isLoading } = useWalletBalances(address)
getEndpoint(Endpoints.WALLET_BALANCES, { address }),
getWalletBalancesSWR,
)
// --------------- // ---------------
// LOCAL STATE // LOCAL STATE
@ -66,17 +60,17 @@ export default function ConnectedButton() {
} }
useEffect(() => { useEffect(() => {
if (!data || data.length === 0) return if (!walletBalances || walletBalances.length === 0) return
setWalletAmount( setWalletAmount(
BigNumber(data?.find((coin: Coin) => coin.denom === baseAsset.denom)?.amount ?? 0) BigNumber(walletBalances?.find((coin: Coin) => coin.denom === baseAsset.denom)?.amount ?? 0)
.div(10 ** baseAsset.decimals) .div(10 ** baseAsset.decimals)
.toNumber(), .toNumber(),
) )
const assetDenoms = marketAssets.map((asset) => asset.denom) const assetDenoms = marketAssets.map((asset) => asset.denom)
const balances = data.filter((coin) => assetDenoms.includes(coin.denom)) const balances = walletBalances.filter((coin) => assetDenoms.includes(coin.denom))
useStore.setState({ balances }) useStore.setState({ balances })
}, [data, baseAsset.denom, baseAsset.decimals, marketAssets]) }, [walletBalances, baseAsset.denom, baseAsset.decimals, marketAssets])
return ( return (
<div className={'relative'}> <div className={'relative'}>

View File

@ -1,28 +1,28 @@
'use client'
import { import {
getClient, getClient,
useWallet, useWallet,
useWalletManager, useWalletManager,
WalletConnectionStatus, WalletConnectionStatus,
} from '@marsprotocol/wallet-connector' } from '@marsprotocol/wallet-connector'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import ConnectButton from 'components/Wallet/ConnectButton' import ConnectButton from 'components/Wallet/ConnectButton'
import ConnectedButton from 'components/Wallet/ConnectedButton' import ConnectedButton from 'components/Wallet/ConnectedButton'
import useParams from 'utils/route'
import useStore from 'store' import useStore from 'store'
import { getPage, getRoute } from 'utils/route'
export default function Wallet() { export default function Wallet() {
const router = useRouter() const navigate = useNavigate()
const params = useParams() const { address: addressInUrl } = useParams()
const { pathname } = useLocation()
const { status } = useWalletManager() const { status } = useWalletManager()
const { recentWallet, simulate, sign, broadcast } = useWallet() const { recentWallet, simulate, sign, broadcast } = useWallet()
const client = useStore((s) => s.client) const client = useStore((s) => s.client)
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
// Set connection status
useEffect(() => { useEffect(() => {
const isConnected = status === WalletConnectionStatus.Connected const isConnected = status === WalletConnectionStatus.Connected
@ -34,29 +34,33 @@ export default function Wallet() {
} }
: { address: undefined, accounts: null, client: undefined }, : { address: undefined, accounts: null, client: undefined },
) )
}, [status, recentWallet?.account.address])
if (!isConnected || !recentWallet) return // Set the client
useEffect(() => {
if (!recentWallet || client) return
async function getCosmWasmClient() {
if (!recentWallet) return
if (!client) { const cosmClient = await getClient(recentWallet.network.rpc)
const getCosmWasmClient = async () => { const client = {
const cosmClient = await getClient(recentWallet.network.rpc) broadcast,
cosmWasmClient: cosmClient,
const client = { recentWallet,
broadcast, sign,
cosmWasmClient: cosmClient, simulate,
recentWallet,
sign,
simulate,
}
useStore.setState({ client })
} }
useStore.setState({ client })
getCosmWasmClient()
} }
if (!address || address === params.address) return getCosmWasmClient()
router.push(`/wallets/${address}`) }, [recentWallet, client, simulate, sign, broadcast])
}, [address, broadcast, client, params, recentWallet, router, simulate, sign, status])
// Redirect when switching wallets or on first connection
useEffect(() => {
if (!address || address === addressInUrl) return
navigate(getRoute(getPage(pathname), address))
}, [address, addressInUrl, navigate, pathname])
return address ? <ConnectedButton /> : <ConnectButton status={status} /> return address ? <ConnectedButton /> : <ConnectButton status={status} />
} }

View File

@ -1,23 +1,16 @@
'use client'
import { ChainInfoID, WalletManagerProvider } from '@marsprotocol/wallet-connector' import { ChainInfoID, WalletManagerProvider } from '@marsprotocol/wallet-connector'
import { FC } from 'react' import { FC } from 'react'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import { Cross } from 'components/Icons' import { Cross } from 'components/Icons'
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env' import { ENV } from 'constants/env'
type Props = { type Props = {
children?: React.ReactNode children?: React.ReactNode
} }
export const WalletConnectProvider: FC<Props> = ({ children }) => { export const WalletConnectProvider: FC<Props> = ({ children }) => {
if (!ENV.CHAIN_ID || !ENV.URL_REST || !ENV.URL_RPC || !ENV.WALLETS) {
console.error(ENV_MISSING_MESSAGE)
return null
}
const chainInfoOverrides = { const chainInfoOverrides = {
rpc: ENV.URL_RPC, rpc: ENV.URL_RPC,
rest: ENV.URL_REST, rest: ENV.URL_REST,

View File

@ -1,9 +0,0 @@
import Overview from 'components/Council/Overview'
interface Props {
params: PageParams
}
export default function Councilpage(props: Props) {
return <Overview params={props.params} />
}

View File

@ -1,9 +0,0 @@
import AccountOverview from 'components/Portfolio/AccountOverview'
interface Props {
params: PageParams
}
export default function Portfoliopage(props: Props) {
return <AccountOverview params={props.params} />
}

View File

@ -1,37 +1,39 @@
interface EnvironmentVariables { interface EnvironmentVariables {
ADDRESS_ACCOUNT_NFT: string | undefined ADDRESS_ACCOUNT_NFT: string
ADDRESS_CREDIT_MANAGER: string | undefined ADDRESS_CREDIT_MANAGER: string
ADDRESS_INCENTIVES: string | undefined ADDRESS_INCENTIVES: string
ADDRESS_ORACLE: string | undefined ADDRESS_ORACLE: string
ADDRESS_RED_BANK: string | undefined ADDRESS_RED_BANK: string
ADDRESS_SWAPPER: string | undefined ADDRESS_SWAPPER: string
ADDRESS_ZAPPER: string | undefined ADDRESS_ZAPPER: string
CHAIN_ID: string | undefined CHAIN_ID: string
NETWORK: string | undefined NETWORK: string
URL_GQL: string | undefined URL_GQL: string
URL_REST: string | undefined URL_REST: string
URL_RPC: string | undefined URL_RPC: string
URL_API: string | undefined URL_API: string
WALLETS: string[] | undefined URL_APOLLO_APR: string
WALLETS: string[]
} }
export const ENV: EnvironmentVariables = { export const ENV: EnvironmentVariables = {
ADDRESS_ACCOUNT_NFT: process.env.NEXT_PUBLIC_ACCOUNT_NFT, ADDRESS_ACCOUNT_NFT: process.env.NEXT_PUBLIC_ACCOUNT_NFT || '',
ADDRESS_CREDIT_MANAGER: process.env.NEXT_PUBLIC_CREDIT_MANAGER, ADDRESS_CREDIT_MANAGER: process.env.NEXT_PUBLIC_CREDIT_MANAGER || '',
ADDRESS_INCENTIVES: process.env.NEXT_PUBLIC_INCENTIVES, ADDRESS_INCENTIVES: process.env.NEXT_PUBLIC_INCENTIVES || '',
ADDRESS_ORACLE: process.env.NEXT_PUBLIC_ORACLE, ADDRESS_ORACLE: process.env.NEXT_PUBLIC_ORACLE || '',
ADDRESS_RED_BANK: process.env.NEXT_PUBLIC_RED_BANK, ADDRESS_RED_BANK: process.env.NEXT_PUBLIC_RED_BANK || '',
ADDRESS_SWAPPER: process.env.NEXT_PUBLIC_SWAPPER, ADDRESS_SWAPPER: process.env.NEXT_PUBLIC_SWAPPER || '',
ADDRESS_ZAPPER: process.env.NEXT_PUBLIC_ZAPPER, ADDRESS_ZAPPER: process.env.NEXT_PUBLIC_ZAPPER || '',
CHAIN_ID: process.env.NEXT_PUBLIC_CHAIN_ID, CHAIN_ID: process.env.NEXT_PUBLIC_CHAIN_ID || '',
NETWORK: process.env.NEXT_PUBLIC_NETWORK, NETWORK: process.env.NEXT_PUBLIC_NETWORK || '',
URL_GQL: process.env.NEXT_PUBLIC_GQL, URL_GQL: process.env.NEXT_PUBLIC_GQL || '',
URL_REST: process.env.NEXT_PUBLIC_REST, URL_REST: process.env.NEXT_PUBLIC_REST || '',
URL_RPC: process.env.NEXT_PUBLIC_RPC, URL_RPC: process.env.NEXT_PUBLIC_RPC || '',
URL_API: process.env.NEXT_PUBLIC_VERCEL_URL URL_API: process.env.NEXT_PUBLIC_VERCEL_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}/api` ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}/api`
: process.env.NEXT_PUBLIC_API, : process.env.NEXT_PUBLIC_API || '',
WALLETS: process.env.NEXT_PUBLIC_WALLETS?.split(','), URL_APOLLO_APR: process.env.NEXT_PUBLIC_APOLLO_APR || '',
WALLETS: process.env.NEXT_PUBLIC_WALLETS?.split(',') || [],
} }
export const VERCEL_BYPASS = process.env.NEXT_PUBLIC_BYPASS export const VERCEL_BYPASS = process.env.NEXT_PUBLIC_BYPASS
@ -39,14 +41,3 @@ export const VERCEL_BYPASS = process.env.NEXT_PUBLIC_BYPASS
: '' : ''
export const IS_TESTNET = ENV.NETWORK !== 'mainnet' export const IS_TESTNET = ENV.NETWORK !== 'mainnet'
export const ENV_MISSING_MESSAGE = () => {
const missing: string[] = []
Object.keys(ENV).forEach((key) => {
if (!ENV[key as keyof EnvironmentVariables]) {
missing.push(key)
}
})
return `Environment variable(s) missing for: ${missing.join(', ')}`
}

View File

@ -0,0 +1,10 @@
import useSWR from 'swr'
import getAccountDebts from 'api/accounts/getAccountDebts'
export default function useAccountDebts(accountId?: string) {
return useSWR(`accountDebts${accountId}`, () => getAccountDebts(accountId || ''), {
suspense: true,
isPaused: () => !accountId,
})
}

10
src/hooks/useAccounts.tsx Normal file
View File

@ -0,0 +1,10 @@
import useSWR from 'swr'
import getAccounts from 'api/wallets/getAccounts'
export default function useAccounts(address?: string) {
return useSWR(`accounts${address}`, () => getAccounts(address || ''), {
suspense: true,
isPaused: () => !address,
})
}

Some files were not shown because too many files have changed in this diff Show More