MP-2798, MP-2799, MP-2902, MP-2893, MP-2895, MP-2896, MP-2898 (#312)
@ -5,7 +5,6 @@ NEXT_PUBLIC_GQL=https://testnet-osmosis-node.marsprotocol.io/XF32UOOU55CX/osmosi
|
|||||||
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_APOLLO_APR=https://api.apollo.farm/api/vault_infos/v2/osmo-test-5
|
||||||
NEXT_PUBLIC_WALLETS=keplr,cosmostation
|
|
||||||
NEXT_PUBLIC_ACCOUNT_NFT=osmo1gmpua5rkzg6cmju73fz5a9x454nz4vwlnkgs4g8wjlyeqtmzsuhq5funky
|
NEXT_PUBLIC_ACCOUNT_NFT=osmo1gmpua5rkzg6cmju73fz5a9x454nz4vwlnkgs4g8wjlyeqtmzsuhq5funky
|
||||||
NEXT_PUBLIC_ORACLE=osmo1khe29uw3t85nmmp3mtr8dls7v2qwsfk3tndu5h4w5g2r5tzlz5qqarq2e2
|
NEXT_PUBLIC_ORACLE=osmo1khe29uw3t85nmmp3mtr8dls7v2qwsfk3tndu5h4w5g2r5tzlz5qqarq2e2
|
||||||
NEXT_PUBLIC_RED_BANK=osmo1dl4rylasnd7mtfzlkdqn2gr0ss4gvyykpvr6d7t5ylzf6z535n9s5jjt8u
|
NEXT_PUBLIC_RED_BANK=osmo1dl4rylasnd7mtfzlkdqn2gr0ss4gvyykpvr6d7t5ylzf6z535n9s5jjt8u
|
||||||
@ -21,6 +20,7 @@ CHARTING_LIBRARY_ACCESS_TOKEN="access_token_with_access_to_charting_library"
|
|||||||
CHARTING_LIBRARY_REPOSITORY="github.com/username/charting_library/"
|
CHARTING_LIBRARY_REPOSITORY="github.com/username/charting_library/"
|
||||||
NEXT_PUBLIC_PYTH_ENDPOINT=https://xc-mainnet.pyth.network/api
|
NEXT_PUBLIC_PYTH_ENDPOINT=https://xc-mainnet.pyth.network/api
|
||||||
NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
|
NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
|
||||||
|
NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
|
||||||
|
|
||||||
# MAINNET #
|
# MAINNET #
|
||||||
# NEXT_PUBLIC_NETWORK=mainnet
|
# NEXT_PUBLIC_NETWORK=mainnet
|
||||||
@ -30,7 +30,6 @@ NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmos
|
|||||||
# 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_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_ACCOUNT_NFT=osmo1450hrg6dv2l58c0rvdwx8ec2a0r6dd50hn4frk370tpvqjhy8khqw7sw09
|
# NEXT_PUBLIC_ACCOUNT_NFT=osmo1450hrg6dv2l58c0rvdwx8ec2a0r6dd50hn4frk370tpvqjhy8khqw7sw09
|
||||||
# NEXT_PUBLIC_ORACLE=osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g
|
# NEXT_PUBLIC_ORACLE=osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g
|
||||||
# NEXT_PUBLIC_RED_BANK=osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg
|
# NEXT_PUBLIC_RED_BANK=osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg
|
||||||
@ -44,3 +43,5 @@ NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmos
|
|||||||
# CHARTING_LIBRARY_REPOSITORY="username/charting_library"
|
# CHARTING_LIBRARY_REPOSITORY="username/charting_library"
|
||||||
# NEXT_PUBLIC_PYTH_ENDPOINT=https://xc-mainnet.pyth.network/api
|
# NEXT_PUBLIC_PYTH_ENDPOINT=https://xc-mainnet.pyth.network/api
|
||||||
# NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
|
# NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
|
||||||
|
# NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
|
||||||
|
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
|
|
||||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
|
||||||
import AccountDetails from 'components/Account/AccountDetails'
|
import AccountDetails from 'components/Account/AccountDetails'
|
||||||
|
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||||
|
import useStore from 'store'
|
||||||
|
|
||||||
jest.mock('hooks/useCurrentAccount', () => jest.fn(() => null))
|
jest.mock('hooks/useCurrentAccount', () => jest.fn(() => null))
|
||||||
|
|
||||||
const mockedUseCurrentAccount = useCurrentAccount as jest.Mock
|
const mockedUseCurrentAccount = useCurrentAccount as jest.Mock
|
||||||
|
|
||||||
describe('<AccountDetails />', () => {
|
describe('<AccountDetails />', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
useStore.setState({
|
||||||
|
address: 'walletAddress',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
useStore.clearState()
|
||||||
|
})
|
||||||
|
|
||||||
it('renders account details WHEN account is selected', () => {
|
it('renders account details WHEN account is selected', () => {
|
||||||
mockedUseCurrentAccount.mockReturnValue({ id: 1 })
|
mockedUseCurrentAccount.mockReturnValue({ id: 1 })
|
||||||
render(<AccountDetails />)
|
render(<AccountDetails />)
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
|
images: {
|
||||||
|
domains: [
|
||||||
|
'assets.leapwallet.io',
|
||||||
|
'raw.githubusercontent.com',
|
||||||
|
'xdefi-static.s3.eu-west-1.amazonaws.com',
|
||||||
|
],
|
||||||
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cosmjs/cosmwasm-stargate": "^0.31.0",
|
"@cosmjs/cosmwasm-stargate": "^0.31.0",
|
||||||
"@marsprotocol/wallet-connector": "^1.8.9",
|
"@delphi-labs/shuttle-react": "^3.3.0",
|
||||||
|
"@keplr-wallet/cosmos": "^0.12.16",
|
||||||
"@sentry/nextjs": "^7.60.0",
|
"@sentry/nextjs": "^7.60.0",
|
||||||
"@tanstack/react-table": "^8.9.3",
|
"@tanstack/react-table": "^8.9.3",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
@ -30,6 +31,7 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-draggable": "^4.4.5",
|
"react-draggable": "^4.4.5",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
|
"react-qr-code": "^2.0.11",
|
||||||
"react-router-dom": "^6.14.1",
|
"react-router-dom": "^6.14.1",
|
||||||
"react-spring": "^9.7.2",
|
"react-spring": "^9.7.2",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
|
BIN
public/images/bridges/gravity.png
Normal file
After Width: | Height: | Size: 872 B |
BIN
public/images/bridges/satellite.png
Normal file
After Width: | Height: | Size: 649 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
BIN
public/images/wallets/keplr.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/wallets/station.png
Normal file
After Width: | Height: | Size: 694 B |
BIN
public/images/wallets/xdelfi.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
39
src/components/Account/AccountCreateFirst.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { useCallback } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
import FullOverlayContent from 'components/FullOverlayContent'
|
||||||
|
import useToggle from 'hooks/useToggle'
|
||||||
|
import useStore from 'store'
|
||||||
|
import { hardcodedFee } from 'utils/constants'
|
||||||
|
|
||||||
|
export default function AccountCreateFirst() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const address = useStore((s) => s.address)
|
||||||
|
const createAccount = useStore((s) => s.createAccount)
|
||||||
|
const [isCreating, setIsCreating] = useToggle(false)
|
||||||
|
|
||||||
|
const handleClick = useCallback(async () => {
|
||||||
|
setIsCreating(true)
|
||||||
|
const accountId = await createAccount({ fee: hardcodedFee })
|
||||||
|
setIsCreating(false)
|
||||||
|
// TODO: set focusComponent to fund account
|
||||||
|
useStore.setState({ focusComponent: null })
|
||||||
|
accountId && navigate(`/wallets/${address}/accounts/${accountId}`)
|
||||||
|
}, [address, createAccount, navigate, setIsCreating])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullOverlayContent
|
||||||
|
title='Mint your account'
|
||||||
|
copy="We'll require you to authorise a transaction in your wallet in order to begin."
|
||||||
|
button={{
|
||||||
|
className: 'mt-4 w-full',
|
||||||
|
text: 'Approve transaction',
|
||||||
|
color: 'tertiary',
|
||||||
|
showProgressIndicator: isCreating,
|
||||||
|
onClick: handleClick,
|
||||||
|
size: 'lg',
|
||||||
|
}}
|
||||||
|
docs='account'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -2,6 +2,7 @@ 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 useCurrentAccount from 'hooks/useCurrentAccount'
|
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||||
|
import useStore from 'store'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
account: Account
|
account: Account
|
||||||
@ -9,8 +10,9 @@ interface Props {
|
|||||||
|
|
||||||
export default function AccountDetailsController() {
|
export default function AccountDetailsController() {
|
||||||
const account = useCurrentAccount()
|
const account = useCurrentAccount()
|
||||||
|
const address = useStore((s) => s.address)
|
||||||
|
|
||||||
if (!account) return null
|
if (!account || !address) return null
|
||||||
|
|
||||||
return <AccountDetails account={account} />
|
return <AccountDetails account={account} />
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ 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 useStore from 'store'
|
|
||||||
import useAccounts from 'hooks/useAccounts'
|
import useAccounts from 'hooks/useAccounts'
|
||||||
|
import useStore from 'store'
|
||||||
|
|
||||||
function Content() {
|
function Content() {
|
||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
||||||
|
@ -2,6 +2,7 @@ import classNames from 'classnames'
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||||
import AccountList from 'components/Account/AccountList'
|
import AccountList from 'components/Account/AccountList'
|
||||||
import CreateAccount from 'components/Account/CreateAccount'
|
import CreateAccount from 'components/Account/CreateAccount'
|
||||||
import FundAccount from 'components/Account/FundAccount'
|
import FundAccount from 'components/Account/FundAccount'
|
||||||
@ -10,12 +11,13 @@ import { CircularProgress } from 'components/CircularProgress'
|
|||||||
import { Account, Plus, PlusCircled } from 'components/Icons'
|
import { Account, Plus, PlusCircled } from 'components/Icons'
|
||||||
import Overlay from 'components/Overlay'
|
import Overlay from 'components/Overlay'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
|
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||||
|
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||||
import useToggle from 'hooks/useToggle'
|
import useToggle from 'hooks/useToggle'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { hardcodedFee } from 'utils/constants'
|
import { hardcodedFee } from 'utils/constants'
|
||||||
import { isNumber } from 'utils/parsers'
|
|
||||||
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
import { isNumber } from 'utils/parsers'
|
||||||
|
|
||||||
const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide'
|
const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide'
|
||||||
const ACCOUNT_MENU_BUTTON_ID = 'account-menu-button'
|
const ACCOUNT_MENU_BUTTON_ID = 'account-menu-button'
|
||||||
@ -61,11 +63,17 @@ export default function AccountMenuContent(props: Props) {
|
|||||||
}, [address, createAccount, navigate, setIsCreating, setShowMenu])
|
}, [address, createAccount, navigate, setIsCreating, setShowMenu])
|
||||||
|
|
||||||
const handleCreateAccountClick = useCallback(() => {
|
const handleCreateAccountClick = useCallback(() => {
|
||||||
setShowMenu(!showMenu)
|
if (!checkHasFunds()) {
|
||||||
if (!hasCreditAccounts && checkHasFunds()) {
|
useStore.setState({ focusComponent: <WalletBridges /> })
|
||||||
performCreateAccount()
|
return
|
||||||
}
|
}
|
||||||
}, [checkHasFunds, hasCreditAccounts, performCreateAccount, setShowMenu, showMenu])
|
if (!hasCreditAccounts) {
|
||||||
|
useStore.setState({ focusComponent: <AccountCreateFirst /> })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowMenu(!showMenu)
|
||||||
|
}, [checkHasFunds, hasCreditAccounts, setShowMenu, showMenu])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
useStore.setState({ accounts: props.accounts })
|
useStore.setState({ accounts: props.accounts })
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect } from 'react'
|
import { useCallback, useEffect } from 'react'
|
||||||
|
|
||||||
import { Cross } from 'components/Icons'
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
|
import { Cross } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -34,7 +34,7 @@ export default function EscButton(props: Props) {
|
|||||||
leftIcon={<Cross />}
|
leftIcon={<Cross />}
|
||||||
iconClassName='w-3'
|
iconClassName='w-3'
|
||||||
color='tertiary'
|
color='tertiary'
|
||||||
className='h-3'
|
className='h-3 w-13'
|
||||||
size='xs'
|
size='xs'
|
||||||
>
|
>
|
||||||
<Text size='2xs'>ESC</Text>
|
<Text size='2xs'>ESC</Text>
|
||||||
|
@ -20,27 +20,6 @@ import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
|||||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||||
import useLocalStorage from 'hooks/useLocalStorage'
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
|
|
||||||
interface Props {
|
|
||||||
children?: string | ReactNode
|
|
||||||
className?: string
|
|
||||||
color?: 'primary' | 'secondary' | 'tertiary' | 'quaternary'
|
|
||||||
disabled?: boolean
|
|
||||||
id?: string
|
|
||||||
showProgressIndicator?: boolean
|
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg'
|
|
||||||
text?: string | ReactNode
|
|
||||||
variant?: 'solid' | 'transparent' | 'round' | 'rounded'
|
|
||||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
|
||||||
leftIcon?: ReactElement
|
|
||||||
rightIcon?: ReactElement
|
|
||||||
iconClassName?: string
|
|
||||||
hasSubmenu?: boolean
|
|
||||||
hasFocus?: boolean
|
|
||||||
dataTestId?: string
|
|
||||||
tabIndex?: number
|
|
||||||
textClassNames?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const Button = React.forwardRef(function Button(
|
const Button = React.forwardRef(function Button(
|
||||||
{
|
{
|
||||||
children,
|
children,
|
||||||
@ -61,7 +40,7 @@ const Button = React.forwardRef(function Button(
|
|||||||
dataTestId,
|
dataTestId,
|
||||||
tabIndex = 0,
|
tabIndex = 0,
|
||||||
textClassNames,
|
textClassNames,
|
||||||
}: Props,
|
}: ButtonProps,
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||||
|
40
src/components/DocsLink.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { ExternalLink } from 'components/Icons'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type: 'wallet' | 'account' | 'terms'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(type: string) {
|
||||||
|
if (type === 'wallet')
|
||||||
|
return [
|
||||||
|
'New with wallets?',
|
||||||
|
'Learn more',
|
||||||
|
'https://docs.marsprotocol.io/docs/learn/workstation/basics/basics-intro',
|
||||||
|
]
|
||||||
|
if (type === 'account')
|
||||||
|
return [
|
||||||
|
'Why mint your account?',
|
||||||
|
'Learn more',
|
||||||
|
'https://docs.marsprotocol.io/docs/learn/workstation/rover/rover-intro',
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
'By continuing you accept our',
|
||||||
|
'terms of service',
|
||||||
|
'https://docs.marsprotocol.io/docs/overview/legal/terms-of-service',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocsLink(props: Props) {
|
||||||
|
const [intro, linkText, url] = getData(props.type)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text size='sm' className='w-full pt-3 text-center text-white/60'>
|
||||||
|
{`${intro} `}
|
||||||
|
<a href={url} target='_blank' className='leading-4 text-white hover:underline'>
|
||||||
|
{linkText}
|
||||||
|
<ExternalLink className='-mt-1 ml-1 inline w-3.5' />
|
||||||
|
</a>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
@ -5,7 +5,7 @@ import packageInfo from '../../package.json'
|
|||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className='flex h-6 w-full items-center justify-center'>
|
<footer className='flex h-6 w-full items-center justify-center'>
|
||||||
<div className='w-full max-w-screen-lg text-right'>
|
<div className='w-full px-4 pb-4 text-right'>
|
||||||
<Text size='xs' className='opacity-50'>
|
<Text size='xs' className='opacity-50'>
|
||||||
v{packageInfo.version}
|
v{packageInfo.version}
|
||||||
</Text>
|
</Text>
|
||||||
|
33
src/components/FullOverlayContent.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import Button from 'components/Button'
|
||||||
|
import DocsLink from 'components/DocsLink'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string
|
||||||
|
copy: string
|
||||||
|
children?: React.ReactNode
|
||||||
|
button?: ButtonProps
|
||||||
|
docs?: 'wallet' | 'account' | 'terms'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FullOverlayContent(props: Props) {
|
||||||
|
return (
|
||||||
|
<div className='min-h-[600px] w-100'>
|
||||||
|
<Text size='4xl' className='w-full pb-2 text-center'>
|
||||||
|
{props.title}
|
||||||
|
</Text>
|
||||||
|
<Text size='sm' className='min-h-14 w-full text-center text-white/60'>
|
||||||
|
{props.copy}
|
||||||
|
</Text>
|
||||||
|
{props.children && (
|
||||||
|
<div className='relative flex w-full flex-wrap justify-center pt-4'>{props.children}</div>
|
||||||
|
)}
|
||||||
|
{props.button && (
|
||||||
|
<div className='flex w-full justify-center'>
|
||||||
|
<Button {...props.button} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{props.docs && <DocsLink type={props.docs} />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -17,10 +17,10 @@ export const menuTree: { page: Page; label: string }[] = [
|
|||||||
|
|
||||||
export default function DesktopHeader() {
|
export default function DesktopHeader() {
|
||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
||||||
const isFocusMode = useStore((s) => s.isFocusMode)
|
const focusComponent = useStore((s) => s.focusComponent)
|
||||||
|
|
||||||
function handleCloseFocusMode() {
|
function handleCloseFocusMode() {
|
||||||
useStore.setState({ isFocusMode: false, showTermsOfService: false })
|
useStore.setState({ focusComponent: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -33,13 +33,17 @@ export default function DesktopHeader() {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex items-center justify-between py-3 pl-6 pr-4',
|
'flex items-center justify-between px-4 py-4',
|
||||||
!isFocusMode && ' border-b border-white/20',
|
focusComponent ? 'relative isolate' : 'border-b border-white/20',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<DesktopNavigation />
|
<DesktopNavigation />
|
||||||
{isFocusMode ? (
|
{focusComponent ? (
|
||||||
|
<div className='flex w-full justify-between'>
|
||||||
|
<div className='flex h-5 w-13' />
|
||||||
|
{address && <Wallet />}
|
||||||
<EscButton onClick={handleCloseFocusMode} />
|
<EscButton onClick={handleCloseFocusMode} />
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='flex gap-4'>
|
<div className='flex gap-4'>
|
||||||
{address && <AccountMenu />}
|
{address && <AccountMenu />}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import { menuTree } from 'components/Header/DesktopHeader'
|
import { menuTree } from 'components/Header/DesktopHeader'
|
||||||
import { Logo } from 'components/Icons'
|
import { Logo } from 'components/Icons'
|
||||||
@ -8,20 +9,24 @@ import { getRoute } from 'utils/route'
|
|||||||
|
|
||||||
export default function DesktopNavigation() {
|
export default function DesktopNavigation() {
|
||||||
const { address, accountId } = useParams()
|
const { address, accountId } = useParams()
|
||||||
const isFocusMode = useStore((s) => s.isFocusMode)
|
const focusComponent = useStore((s) => s.focusComponent)
|
||||||
|
|
||||||
function getIsActive(href: string) {
|
function getIsActive(href: string) {
|
||||||
return location.pathname.includes(href)
|
return location.pathname.includes(href)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-1 items-center'>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
focusComponent ? 'absolute left-4 top-3 z-1 block' : 'flex flex-1 items-center',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<NavLink href={getRoute('trade', address, accountId)}>
|
<NavLink href={getRoute('trade', address, accountId)}>
|
||||||
<span className='block h-10 w-10'>
|
<span className='block h-10 w-10'>
|
||||||
<Logo className='text-white' />
|
<Logo className='text-white' />
|
||||||
</span>
|
</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{!isFocusMode && (
|
{!focusComponent && (
|
||||||
<div className='flex gap-8 px-6'>
|
<div className='flex gap-8 px-6'>
|
||||||
{menuTree.map((item, index) => (
|
{menuTree.map((item, index) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useWalletManager } from '@marsprotocol/wallet-connector'
|
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import FullOverlayContent from 'components/FullOverlayContent'
|
||||||
|
import { Check } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
|
import WalletSelect from 'components/Wallet/WalletSelect'
|
||||||
import { TERMS_OF_SERVICE_KEY } from 'constants/localStore'
|
import { TERMS_OF_SERVICE_KEY } from 'constants/localStore'
|
||||||
import useLocalStorage from 'hooks/useLocalStorage'
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import Button from 'components/Button'
|
|
||||||
import { Check, ExternalLink } from 'components/Icons'
|
|
||||||
|
|
||||||
interface BenefitsProps {
|
interface BenefitsProps {
|
||||||
benefits: string[]
|
benefits: string[]
|
||||||
@ -15,9 +15,9 @@ interface BenefitsProps {
|
|||||||
|
|
||||||
function Benefits({ benefits }: BenefitsProps) {
|
function Benefits({ benefits }: BenefitsProps) {
|
||||||
return (
|
return (
|
||||||
<ul className='w-full list-none px-0 py-3'>
|
<ul className='w-full list-none px-0'>
|
||||||
{benefits.map((benefit, index) => (
|
{benefits.map((benefit, index) => (
|
||||||
<li className='relative my-6 flex h-6 w-full items-center px-0 pl-8' key={index}>
|
<li className='relative mb-6 flex h-6 w-full items-center px-0 pl-8' key={index}>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute left-0 top-0 isolate h-6 w-6 rounded-full bg-white/10',
|
'absolute left-0 top-0 isolate h-6 w-6 rounded-full bg-white/10',
|
||||||
@ -36,25 +36,26 @@ function Benefits({ benefits }: BenefitsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function TermsOfService() {
|
export default function TermsOfService() {
|
||||||
const { connect } = useWalletManager()
|
|
||||||
const [_, setHasAgreedToTerms] = useLocalStorage(TERMS_OF_SERVICE_KEY, false)
|
const [_, setHasAgreedToTerms] = useLocalStorage(TERMS_OF_SERVICE_KEY, false)
|
||||||
|
|
||||||
const handleAgreeTermsOfService = useCallback(() => {
|
const handleAgreeTermsOfService = useCallback(() => {
|
||||||
useStore.setState({ showTermsOfService: false, isFocusMode: false })
|
|
||||||
setHasAgreedToTerms(true)
|
setHasAgreedToTerms(true)
|
||||||
connect()
|
useStore.setState({ focusComponent: <WalletSelect /> })
|
||||||
}, [connect, setHasAgreedToTerms])
|
}, [setHasAgreedToTerms])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative flex h-full w-full items-center justify-center'>
|
<FullOverlayContent
|
||||||
<div className='w-100'>
|
title='Master the Red Planet'
|
||||||
<Text size='4xl' className='w-full pb-2'>
|
copy='Mars offers the easiest way to margin trade, lend & borrow and yield farm with leverage any whitelisted token'
|
||||||
Master the Red Planet
|
button={{
|
||||||
</Text>
|
className: 'w-full mt-4',
|
||||||
<Text size='sm' className='w-full text-white/60'>
|
text: 'Agree & continue',
|
||||||
Mars offers the easiest way to margin trade, lend & borrow and yield farm with leverage
|
color: 'tertiary',
|
||||||
any whitelisted token
|
onClick: handleAgreeTermsOfService,
|
||||||
</Text>
|
size: 'lg',
|
||||||
|
}}
|
||||||
|
docs='terms'
|
||||||
|
>
|
||||||
<Benefits
|
<Benefits
|
||||||
benefits={[
|
benefits={[
|
||||||
'Swap tokens with margin acress any whitelisted pair',
|
'Swap tokens with margin acress any whitelisted pair',
|
||||||
@ -62,25 +63,6 @@ export default function TermsOfService() {
|
|||||||
'Earn interest on deposited tokens',
|
'Earn interest on deposited tokens',
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Button
|
</FullOverlayContent>
|
||||||
className='w-full'
|
|
||||||
text='Agree & continue'
|
|
||||||
color='tertiary'
|
|
||||||
onClick={handleAgreeTermsOfService}
|
|
||||||
size='lg'
|
|
||||||
/>
|
|
||||||
<Text size='sm' className='w-full pt-5 text-center text-white/60'>
|
|
||||||
By continuing you accept our{' '}
|
|
||||||
<a
|
|
||||||
href='https://docs.marsprotocol.io/docs/overview/legal/terms-of-service'
|
|
||||||
target='_blank'
|
|
||||||
className='leading-4 text-white hover:underline'
|
|
||||||
>
|
|
||||||
terms of service
|
|
||||||
<ExternalLink className='-mt-1 ml-1 inline w-3.5' />
|
|
||||||
</a>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import useStore from 'store'
|
|||||||
export default function Toaster() {
|
export default function Toaster() {
|
||||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||||
const toast = useStore((s) => s.toast)
|
const toast = useStore((s) => s.toast)
|
||||||
|
const isError = toast?.isError
|
||||||
|
|
||||||
if (toast) {
|
if (toast) {
|
||||||
const Msg = () => (
|
const Msg = () => (
|
||||||
@ -19,24 +20,22 @@ export default function Toaster() {
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
'relative isolate m-0 flex w-full flex-wrap rounded-sm p-6 shadow-overlay backdrop-blur-lg',
|
'relative isolate m-0 flex w-full flex-wrap rounded-sm p-6 shadow-overlay backdrop-blur-lg',
|
||||||
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-sm before:p-[1px] before:border-glas',
|
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-sm before:p-[1px] before:border-glas',
|
||||||
toast.isError ? 'bg-error-bg/20' : 'bg-success-bg/20',
|
isError ? 'bg-error-bg/20' : 'bg-success-bg/20',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className='mb-4 flex w-full gap-2'>
|
<div className='mb-4 flex w-full gap-2'>
|
||||||
<div
|
<div className={classNames('rounded-sm p-1.5', isError ? 'bg-error' : 'bg-success')}>
|
||||||
className={classNames('rounded-sm p-1.5', toast.isError ? 'bg-error' : 'bg-success')}
|
|
||||||
>
|
|
||||||
<span className='block h-4 w-4 text-white'>
|
<span className='block h-4 w-4 text-white'>
|
||||||
{toast.isError ? <CrossCircled /> : <CheckCircled />}
|
{isError ? <CrossCircled /> : <CheckCircled />}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Text
|
<Text
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex items-center font-bold',
|
'flex items-center font-bold',
|
||||||
toast.isError ? 'text-error' : 'text-success',
|
isError ? 'text-error' : 'text-success',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{toast.isError ? 'Error' : 'Success'}
|
{toast.title ? toast.title : isError ? 'Error' : 'Success'}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -44,7 +43,7 @@ export default function Toaster() {
|
|||||||
{toast.message}
|
{toast.message}
|
||||||
</Text>
|
</Text>
|
||||||
<div className='absolute right-6 top-8 '>
|
<div className='absolute right-6 top-8 '>
|
||||||
<Cross className={classNames('h-2 w-2', toast.isError ? 'text-error' : 'text-success')} />
|
<Cross className={classNames('h-2 w-2', isError ? 'text-error' : 'text-success')} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -53,7 +52,7 @@ export default function Toaster() {
|
|||||||
icon: false,
|
icon: false,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
closeOnClick: true,
|
closeOnClick: true,
|
||||||
progressClassName: classNames('h-[1px] bg-none', toast.isError ? 'bg-error' : 'bg-success'),
|
progressClassName: classNames('h-[1px] bg-none', isError ? 'bg-error' : 'bg-success'),
|
||||||
})
|
})
|
||||||
|
|
||||||
useStore.setState({ toast: null })
|
useStore.setState({ toast: null })
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
|
|
||||||
import { ReactNode, useCallback } from 'react'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
|
||||||
import { CircularProgress } from 'components/CircularProgress'
|
|
||||||
import { Wallet } from 'components/Icons'
|
|
||||||
import { TERMS_OF_SERVICE_KEY } from 'constants/localStore'
|
|
||||||
import useLocalStorage from 'hooks/useLocalStorage'
|
|
||||||
import useStore from 'store'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
textOverride?: string | ReactNode
|
|
||||||
disabled?: boolean
|
|
||||||
status?: WalletConnectionStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ConnectButton(props: Props) {
|
|
||||||
const { connect } = useWalletManager()
|
|
||||||
const [hasAgreedToTerms] = useLocalStorage(TERMS_OF_SERVICE_KEY, false)
|
|
||||||
|
|
||||||
const handleConnect = useCallback(() => {
|
|
||||||
if (!hasAgreedToTerms) {
|
|
||||||
useStore.setState({ showTermsOfService: true, isFocusMode: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
connect()
|
|
||||||
}, [connect, hasAgreedToTerms])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='relative'>
|
|
||||||
<Button
|
|
||||||
variant='solid'
|
|
||||||
color='tertiary'
|
|
||||||
disabled={props.disabled}
|
|
||||||
onClick={handleConnect}
|
|
||||||
leftIcon={<Wallet />}
|
|
||||||
>
|
|
||||||
{props.status === WalletConnectionStatus.Connecting ? (
|
|
||||||
<span className='flex justify-center'>
|
|
||||||
<CircularProgress size={16} />
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span>{props.textOverride || 'Connect Wallet'}</span>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
66
src/components/Wallet/WalletBridges.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { useShuttle } from '@delphi-labs/shuttle-react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
import Button from 'components/Button'
|
||||||
|
import FullOverlayContent from 'components/FullOverlayContent'
|
||||||
|
import { ChevronRight } from 'components/Icons'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import WalletSelect from 'components/Wallet/WalletSelect'
|
||||||
|
import { BRIDGES } from 'constants/bridges'
|
||||||
|
import { CHAINS } from 'constants/chains'
|
||||||
|
import { ENV } from 'constants/env'
|
||||||
|
import useCurrentWallet from 'hooks/useCurrentWallet'
|
||||||
|
import useStore from 'store'
|
||||||
|
|
||||||
|
const currentChainId = ENV.CHAIN_ID
|
||||||
|
const currentChain = CHAINS[currentChainId]
|
||||||
|
|
||||||
|
function Bridge({ name, url, image }: Bridge) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
color='tertiary'
|
||||||
|
className='flex w-full px-4 py-3'
|
||||||
|
onClick={() => {
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image className='rounded-full' width={20} height={20} src={image} alt={name} />
|
||||||
|
<Text className='ml-2 flex-1 text-left'>{name}</Text>
|
||||||
|
<ChevronRight className='h-4 w-4' />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WalletBridges() {
|
||||||
|
const currentWallet = useCurrentWallet()
|
||||||
|
const { disconnectWallet } = useShuttle()
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
if (!currentWallet) return
|
||||||
|
disconnectWallet(currentWallet)
|
||||||
|
useStore.setState({ focusComponent: <WalletSelect /> })
|
||||||
|
}, [currentWallet, disconnectWallet])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullOverlayContent
|
||||||
|
title='No supported assets'
|
||||||
|
copy={`Your connected wallet has no (supported) assets. To create your account, please connect a
|
||||||
|
different ${currentChain.name} address or bridge assets.`}
|
||||||
|
button={{
|
||||||
|
className: 'w-full mt-4',
|
||||||
|
text: 'Connect different wallet',
|
||||||
|
color: 'tertiary',
|
||||||
|
onClick: handleClick,
|
||||||
|
size: 'lg',
|
||||||
|
}}
|
||||||
|
docs='wallet'
|
||||||
|
>
|
||||||
|
<div className='flex w-full flex-wrap gap-3'>
|
||||||
|
{BRIDGES.map((bridge) => (
|
||||||
|
<Bridge key={bridge.name} {...bridge} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</FullOverlayContent>
|
||||||
|
)
|
||||||
|
}
|
37
src/components/Wallet/WalletConnectButton.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ReactNode, useCallback } from 'react'
|
||||||
|
|
||||||
|
import Button from 'components/Button'
|
||||||
|
import { Wallet } from 'components/Icons'
|
||||||
|
import TermsOfService from 'components/TermsOfService'
|
||||||
|
import WalletSelect from 'components/Wallet/WalletSelect'
|
||||||
|
import { TERMS_OF_SERVICE_KEY } from 'constants/localStore'
|
||||||
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
|
import useStore from 'store'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
textOverride?: string | ReactNode
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WalletConnectButton(props: Props) {
|
||||||
|
const [hasAgreedToTerms] = useLocalStorage(TERMS_OF_SERVICE_KEY, false)
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
const focusedComponent = hasAgreedToTerms ? <WalletSelect /> : <TermsOfService />
|
||||||
|
useStore.setState({ focusComponent: focusedComponent })
|
||||||
|
}, [hasAgreedToTerms])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='relative'>
|
||||||
|
<Button
|
||||||
|
variant='solid'
|
||||||
|
color='tertiary'
|
||||||
|
disabled={props.disabled}
|
||||||
|
onClick={handleClick}
|
||||||
|
leftIcon={<Wallet />}
|
||||||
|
>
|
||||||
|
<span>{props.textOverride || 'Connect Wallet'}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,54 +1,64 @@
|
|||||||
import { ChainInfoID, WalletManagerProvider } from '@marsprotocol/wallet-connector'
|
import {
|
||||||
|
CosmostationExtensionProvider,
|
||||||
|
CosmostationMobileProvider,
|
||||||
|
KeplrExtensionProvider,
|
||||||
|
KeplrMobileProvider,
|
||||||
|
LeapCosmosExtensionProvider,
|
||||||
|
ShuttleProvider,
|
||||||
|
StationExtensionProvider,
|
||||||
|
WalletExtensionProvider,
|
||||||
|
WalletMobileProvider,
|
||||||
|
XDEFICosmosExtensionProvider,
|
||||||
|
} from '@delphi-labs/shuttle-react'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import { CHAINS } from 'constants/chains'
|
||||||
import { CircularProgress } from 'components/CircularProgress'
|
|
||||||
import { Cross } from 'components/Icons'
|
|
||||||
import { ENV } from 'constants/env'
|
import { ENV } from 'constants/env'
|
||||||
|
import { WALLETS } from 'constants/wallets'
|
||||||
|
import { WalletID } from 'types/enums/wallet'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WalletConnectProvider: FC<Props> = ({ children }) => {
|
function getSupportedChainsInfos(walletId: WalletID) {
|
||||||
const chainInfoOverrides = {
|
return WALLETS[walletId].supportedChains.map((chain) => {
|
||||||
rpc: ENV.URL_RPC,
|
const chainInfo: ChainInfo = CHAINS[chain]
|
||||||
rest: ENV.URL_REST,
|
// Check if supported chain equals current chain
|
||||||
chainID: ENV.CHAIN_ID as ChainInfoID,
|
if (ENV.CHAIN_ID !== chainInfo.chainId) return chainInfo
|
||||||
}
|
// Override rpc and rest urls for the current chain if they are defined in .env
|
||||||
const enabledWallets: string[] = ENV.WALLETS
|
if (ENV.URL_RPC) chainInfo.rpc = ENV.URL_RPC
|
||||||
|
if (ENV.URL_REST) chainInfo.rest = ENV.URL_REST
|
||||||
|
return chainInfo
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const mobileProviders: WalletMobileProvider[] = [
|
||||||
|
new KeplrMobileProvider({
|
||||||
|
networks: getSupportedChainsInfos(WalletID.KeplrMobile),
|
||||||
|
}),
|
||||||
|
new CosmostationMobileProvider({
|
||||||
|
networks: getSupportedChainsInfos(WalletID.CosmostationMobile),
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
const extensionProviders: WalletExtensionProvider[] = [
|
||||||
|
new KeplrExtensionProvider({ networks: getSupportedChainsInfos(WalletID.Keplr) }),
|
||||||
|
new StationExtensionProvider({ networks: getSupportedChainsInfos(WalletID.Station) }),
|
||||||
|
new LeapCosmosExtensionProvider({ networks: getSupportedChainsInfos(WalletID.Leap) }),
|
||||||
|
new CosmostationExtensionProvider({ networks: getSupportedChainsInfos(WalletID.Cosmostation) }),
|
||||||
|
new XDEFICosmosExtensionProvider({ networks: getSupportedChainsInfos(WalletID.Xdefi) }),
|
||||||
|
]
|
||||||
|
|
||||||
|
export const WalletConnectProvider: FC<Props> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<WalletManagerProvider
|
<ShuttleProvider
|
||||||
// TODO: handle chainIds via constants
|
walletConnectProjectId={ENV.WALLET_CONNECT_ID}
|
||||||
chainIds={[ChainInfoID.OsmosisTestnet, ChainInfoID.Osmosis1]}
|
mobileProviders={mobileProviders}
|
||||||
chainInfoOverrides={chainInfoOverrides}
|
extensionProviders={extensionProviders}
|
||||||
// closeIcon={<SVG.Close />}
|
|
||||||
defaultChainId={chainInfoOverrides.chainID}
|
|
||||||
enabledWallets={enabledWallets}
|
|
||||||
persistent
|
persistent
|
||||||
classNames={{
|
|
||||||
modalContent:
|
|
||||||
'relative z-50 w-[460px] max-w-full rounded-base border border-white/20 bg-white/5 p-6 pb-4 backdrop-blur-3xl flex flex-wrap focus-visible:outline-none',
|
|
||||||
modalOverlay:
|
|
||||||
'fixed inset-0 bg-black/60 w-full h-full z-40 flex items-center justify-center cursor-pointer m-0 backdrop-blur-sm',
|
|
||||||
modalHeader: 'text-lg text-white mb-4 flex-1',
|
|
||||||
modalCloseButton: 'inline-block',
|
|
||||||
walletList: 'w-full',
|
|
||||||
wallet:
|
|
||||||
'flex bg-transparent p-2 w-full rounded-sm cursor-pointer transition ease-in mb-2 hover:bg-primary',
|
|
||||||
walletImage: 'w-10 h-10',
|
|
||||||
walletName: 'w-full text-lg',
|
|
||||||
walletDescription: 'w-full text-xs text-white/60 break-all',
|
|
||||||
}}
|
|
||||||
closeIcon={<Button leftIcon={<Cross />} iconClassName='h-2 w-2' color='tertiary' />}
|
|
||||||
renderLoader={() => (
|
|
||||||
<div>
|
|
||||||
<CircularProgress size={30} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</WalletManagerProvider>
|
</ShuttleProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { useShuttle } from '@delphi-labs/shuttle-react'
|
||||||
ChainInfoID,
|
|
||||||
SimpleChainInfoList,
|
|
||||||
useWallet,
|
|
||||||
useWalletManager,
|
|
||||||
} from '@marsprotocol/wallet-connector'
|
|
||||||
import BigNumber from 'bignumber.js'
|
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'
|
||||||
@ -15,22 +10,26 @@ import { FormattedNumber } from 'components/FormattedNumber'
|
|||||||
import { Check, Copy, ExternalLink, Osmo } from 'components/Icons'
|
import { Check, Copy, ExternalLink, Osmo } from 'components/Icons'
|
||||||
import Overlay from 'components/Overlay'
|
import Overlay from 'components/Overlay'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
|
import { CHAINS } from 'constants/chains'
|
||||||
import { IS_TESTNET } from 'constants/env'
|
import { IS_TESTNET } from 'constants/env'
|
||||||
|
import useCurrentWallet from 'hooks/useCurrentWallet'
|
||||||
import useToggle from 'hooks/useToggle'
|
import useToggle from 'hooks/useToggle'
|
||||||
import useWalletBalances from 'hooks/useWalletBalances'
|
import useWalletBalances from 'hooks/useWalletBalances'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
import { ChainInfoID } from 'types/enums/wallet'
|
||||||
import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets'
|
import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets'
|
||||||
import { formatValue, truncate } from 'utils/formatters'
|
import { formatValue, truncate } from 'utils/formatters'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
export default function ConnectedButton() {
|
export default function WalletConnectedButton() {
|
||||||
// ---------------
|
// ---------------
|
||||||
// EXTERNAL HOOKS
|
// EXTERNAL HOOKS
|
||||||
// ---------------
|
// ---------------
|
||||||
const marketAssets = getEnabledMarketAssets()
|
const marketAssets = getEnabledMarketAssets()
|
||||||
const { disconnect } = useWallet()
|
const currentWallet = useCurrentWallet()
|
||||||
const { disconnect: terminate } = useWalletManager()
|
const { disconnectWallet } = useShuttle()
|
||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
||||||
|
const focusComponent = useStore((s) => s.focusComponent)
|
||||||
const network = useStore((s) => s.client?.connectedWallet.network)
|
const network = useStore((s) => s.client?.connectedWallet.network)
|
||||||
const baseAsset = getBaseAsset()
|
const baseAsset = getBaseAsset()
|
||||||
const { data: walletBalances, isLoading } = useWalletBalances(address)
|
const { data: walletBalances, isLoading } = useWalletBalances(address)
|
||||||
@ -46,17 +45,17 @@ export default function ConnectedButton() {
|
|||||||
// ---------------
|
// ---------------
|
||||||
// VARIABLES
|
// VARIABLES
|
||||||
// ---------------
|
// ---------------
|
||||||
const explorerName = network && SimpleChainInfoList[network.chainId as ChainInfoID].explorerName
|
const explorerName = network && CHAINS[network.chainId as ChainInfoID].explorerName
|
||||||
|
|
||||||
const viewOnFinder = useCallback(() => {
|
const viewOnFinder = useCallback(() => {
|
||||||
const explorerUrl = network && SimpleChainInfoList[network.chainId as ChainInfoID].explorer
|
const explorerUrl = network && CHAINS[network.chainId as ChainInfoID].explorer
|
||||||
|
|
||||||
window.open(`${explorerUrl}/account/${address}`, '_blank')
|
window.open(`${explorerUrl}/account/${address}`, '_blank')
|
||||||
}, [network, address])
|
}, [network, address])
|
||||||
|
|
||||||
const disconnectWallet = () => {
|
const onDisconnectWallet = () => {
|
||||||
disconnect()
|
if (!currentWallet) return
|
||||||
terminate()
|
disconnectWallet(currentWallet)
|
||||||
useStore.setState({ client: undefined, balances: [] })
|
useStore.setState({ client: undefined, balances: [] })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +107,11 @@ export default function ConnectedButton() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
<Overlay className='right-0 mt-2' show={showDetails} setShow={setShowDetails}>
|
<Overlay
|
||||||
|
className={classNames('mt-2', focusComponent ? '-left-[110px]' : 'right-0')}
|
||||||
|
show={showDetails}
|
||||||
|
setShow={setShowDetails}
|
||||||
|
>
|
||||||
<div className='flex w-[440px] flex-wrap p-6'>
|
<div className='flex w-[440px] flex-wrap p-6'>
|
||||||
<div className='flex-0 mb-4 flex w-full flex-nowrap items-start'>
|
<div className='flex-0 mb-4 flex w-full flex-nowrap items-start'>
|
||||||
<div className='flex w-auto flex-1'>
|
<div className='flex w-auto flex-1'>
|
||||||
@ -124,7 +127,7 @@ export default function ConnectedButton() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex h-[31px] w-[116px] justify-end'>
|
<div className='flex h-[31px] w-[116px] justify-end'>
|
||||||
<Button color='secondary' onClick={disconnectWallet} text='Disconnect' />
|
<Button color='secondary' onClick={onDisconnectWallet} text='Disconnect' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex w-full flex-wrap'>
|
<div className='flex w-full flex-wrap'>
|
58
src/components/Wallet/WalletFetchBalancesAndAccounts.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { Suspense, useEffect, useMemo } from 'react'
|
||||||
|
|
||||||
|
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||||
|
import { CircularProgress } from 'components/CircularProgress'
|
||||||
|
import FullOverlayContent from 'components/FullOverlayContent'
|
||||||
|
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||||
|
import useAccounts from 'hooks/useAccounts'
|
||||||
|
import useWalletBalances from 'hooks/useWalletBalances'
|
||||||
|
import useStore from 'store'
|
||||||
|
import { byDenom } from 'utils/array'
|
||||||
|
import { getBaseAsset } from 'utils/assets'
|
||||||
|
import { hardcodedFee } from 'utils/constants'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
|
function FetchLoading() {
|
||||||
|
return (
|
||||||
|
<FullOverlayContent
|
||||||
|
title='Fetching Wallet Data'
|
||||||
|
copy='Please wait, while your wallet balances and accounts are beeing analyzed'
|
||||||
|
>
|
||||||
|
<CircularProgress size={40} />
|
||||||
|
</FullOverlayContent>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content() {
|
||||||
|
const address = useStore((s) => s.address)
|
||||||
|
const { data: accounts } = useAccounts(address)
|
||||||
|
const { data: walletBalances, isLoading } = useWalletBalances(address)
|
||||||
|
const baseAsset = getBaseAsset()
|
||||||
|
|
||||||
|
const baseBalance = useMemo(
|
||||||
|
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
|
||||||
|
[walletBalances, baseAsset],
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
accounts.length !== 0 &&
|
||||||
|
BN(baseBalance).isGreaterThanOrEqualTo(hardcodedFee.amount[0].amount)
|
||||||
|
) {
|
||||||
|
useStore.setState({ accounts: accounts, balances: walletBalances, focusComponent: null })
|
||||||
|
}
|
||||||
|
}, [accounts, walletBalances, baseBalance])
|
||||||
|
|
||||||
|
if (isLoading) return <FetchLoading />
|
||||||
|
if (BN(baseBalance).isLessThan(hardcodedFee.amount[0].amount)) return <WalletBridges />
|
||||||
|
if (accounts.length === 0) return <AccountCreateFirst />
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WalletFetchBalancesAndAccounts() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<FetchLoading />}>
|
||||||
|
<Content />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
229
src/components/Wallet/WalletSelect.tsx
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
|
import { useShuttle } from '@delphi-labs/shuttle-react'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import QRCode from 'react-qr-code'
|
||||||
|
|
||||||
|
import Button from 'components/Button'
|
||||||
|
import { CircularProgress } from 'components/CircularProgress'
|
||||||
|
import FullOverlayContent from 'components/FullOverlayContent'
|
||||||
|
import { ChevronLeft, ChevronRight } from 'components/Icons'
|
||||||
|
import Text from 'components/Text'
|
||||||
|
import WalletFetchBalancesAndAccounts from 'components/Wallet/WalletFetchBalancesAndAccounts'
|
||||||
|
import { CHAINS } from 'constants/chains'
|
||||||
|
import { ENV } from 'constants/env'
|
||||||
|
import { WALLETS } from 'constants/wallets'
|
||||||
|
import useToggle from 'hooks/useToggle'
|
||||||
|
import useStore from 'store'
|
||||||
|
import { WalletID } from 'types/enums/wallet'
|
||||||
|
import { isAndroid, isIOS, isMobile } from 'utils/mobile'
|
||||||
|
|
||||||
|
interface WalletOptionProps {
|
||||||
|
name: string
|
||||||
|
imageSrc: string
|
||||||
|
handleClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentChainId = ENV.CHAIN_ID
|
||||||
|
const currentChain = CHAINS[currentChainId]
|
||||||
|
|
||||||
|
const mapErrorMessages = (providerId: string, errorMessage: string) => {
|
||||||
|
if (providerId === 'station') {
|
||||||
|
if (errorMessage.match('Wallet not connected to the network with chainId')) {
|
||||||
|
return `Your wallet is not connected to the correct network. Please switch your wallet to the ${currentChain.name} network`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
function WalletOption(props: WalletOptionProps) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
color='tertiary'
|
||||||
|
className='flex w-full !justify-start px-4 py-3'
|
||||||
|
onClick={props.handleClick}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
className='rounded-full'
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
src={props.imageSrc}
|
||||||
|
alt={props.name}
|
||||||
|
/>
|
||||||
|
<Text className='ml-2 flex-1 text-left'>{props.name}</Text>
|
||||||
|
<ChevronRight className='h-4 w-4' />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WalletSelect() {
|
||||||
|
const { extensionProviders, mobileProviders, connect, mobileConnect, simulate, sign, broadcast } =
|
||||||
|
useShuttle()
|
||||||
|
const [isConnecting, setIsConnecting] = useToggle(false)
|
||||||
|
const [qrCodeUrl, setQRCodeUrl] = useState('')
|
||||||
|
|
||||||
|
const sortedExtensionProviders = extensionProviders.sort((a, b) => +b - +a)
|
||||||
|
|
||||||
|
const handleConnectClick = async (extensionProviderId: string, chainId: string) => {
|
||||||
|
setIsConnecting(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await connect({ extensionProviderId, chainId })
|
||||||
|
const cosmClient = await CosmWasmClient.connect(response.network.rpc)
|
||||||
|
const walletClient: WalletClient = {
|
||||||
|
broadcast,
|
||||||
|
cosmWasmClient: cosmClient,
|
||||||
|
connectedWallet: response,
|
||||||
|
sign,
|
||||||
|
simulate,
|
||||||
|
}
|
||||||
|
useStore.setState({
|
||||||
|
client: walletClient,
|
||||||
|
address: response.account.address,
|
||||||
|
focusComponent: <WalletFetchBalancesAndAccounts />,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
useStore.setState({
|
||||||
|
toast: {
|
||||||
|
isError: true,
|
||||||
|
title: 'Failed to connect to wallet',
|
||||||
|
message: mapErrorMessages(extensionProviderId, error.message),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIsConnecting(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMobileConnectClick = async (mobileProviderId: string, chainId: string) => {
|
||||||
|
try {
|
||||||
|
const urls = await mobileConnect({
|
||||||
|
mobileProviderId,
|
||||||
|
chainId,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isMobile()) {
|
||||||
|
if (isAndroid()) {
|
||||||
|
window.location.href = urls.androidUrl
|
||||||
|
} else if (isIOS()) {
|
||||||
|
window.location.href = urls.iosUrl
|
||||||
|
} else {
|
||||||
|
window.location.href = urls.androidUrl
|
||||||
|
}
|
||||||
|
setIsConnecting(true)
|
||||||
|
} else {
|
||||||
|
setQRCodeUrl(urls.qrCodeUrl)
|
||||||
|
setIsConnecting(false)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
useStore.setState({
|
||||||
|
toast: {
|
||||||
|
isError: true,
|
||||||
|
title: 'Failed to connect to wallet',
|
||||||
|
message: error.message,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isConnecting)
|
||||||
|
return (
|
||||||
|
<FullOverlayContent
|
||||||
|
title={'Connecting...'}
|
||||||
|
copy={'Unlock your wallet and approve the connection'}
|
||||||
|
>
|
||||||
|
<CircularProgress size={40} />
|
||||||
|
</FullOverlayContent>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (qrCodeUrl)
|
||||||
|
return (
|
||||||
|
<FullOverlayContent
|
||||||
|
title={'Scan the QR Code'}
|
||||||
|
copy={
|
||||||
|
'Open your mobile wallet App and use the QR Scan function to connect via WalletConnect v2'
|
||||||
|
}
|
||||||
|
button={{
|
||||||
|
color: 'secondary',
|
||||||
|
leftIcon: <ChevronLeft />,
|
||||||
|
iconClassName: 'w-3',
|
||||||
|
onClick: () => setQRCodeUrl(''),
|
||||||
|
text: 'Back',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='mb-4 rounded-sm bg-white p-2'>
|
||||||
|
<QRCode value={qrCodeUrl} />
|
||||||
|
</div>
|
||||||
|
</FullOverlayContent>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullOverlayContent
|
||||||
|
title={'Connect your wallet'}
|
||||||
|
copy={`Deposit assets from your ${currentChain.name} address to your Mars credit account.`}
|
||||||
|
docs='wallet'
|
||||||
|
>
|
||||||
|
<div className='flex w-full flex-wrap gap-3'>
|
||||||
|
{!isMobile() && (
|
||||||
|
<>
|
||||||
|
{sortedExtensionProviders.map((provider) => {
|
||||||
|
const walletId = provider.id as WalletID
|
||||||
|
return (
|
||||||
|
<React.Fragment key={walletId}>
|
||||||
|
{Array.from(provider.networks.values())
|
||||||
|
.filter((network) => network.chainId === currentChainId)
|
||||||
|
.map((network) => {
|
||||||
|
if (!provider.initialized && !provider.initializing) {
|
||||||
|
return (
|
||||||
|
<WalletOption
|
||||||
|
key={`${walletId}-${network.chainId}`}
|
||||||
|
handleClick={() => {
|
||||||
|
window.open(WALLETS[walletId].installURL ?? '/', '_blank')
|
||||||
|
}}
|
||||||
|
imageSrc={WALLETS[walletId].imageURL}
|
||||||
|
name={WALLETS[walletId].install ?? 'Install Wallet'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WalletOption
|
||||||
|
key={`${walletId}-${network.chainId}`}
|
||||||
|
handleClick={() => handleConnectClick(walletId, network.chainId)}
|
||||||
|
imageSrc={WALLETS[walletId].imageURL}
|
||||||
|
name={WALLETS[walletId].name ?? 'Conenct Wallet'}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{mobileProviders.map((provider) => {
|
||||||
|
const walletId = provider.id as WalletID
|
||||||
|
return (
|
||||||
|
<React.Fragment key={walletId}>
|
||||||
|
{Array.from(provider.networks.values())
|
||||||
|
.filter((network) => network.chainId === currentChainId)
|
||||||
|
.map((network) => {
|
||||||
|
return (
|
||||||
|
<WalletOption
|
||||||
|
key={`${walletId}-${network.chainId}`}
|
||||||
|
name={WALLETS[walletId].walletConnect ?? 'WalletConnect'}
|
||||||
|
imageSrc={WALLETS[walletId].mobileImageURL ?? '/'}
|
||||||
|
handleClick={() => handleMobileConnectClick(walletId, network.chainId)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</FullOverlayContent>
|
||||||
|
)
|
||||||
|
}
|
@ -1,14 +1,11 @@
|
|||||||
import {
|
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||||
getClient,
|
import { useShuttle } from '@delphi-labs/shuttle-react'
|
||||||
useWallet,
|
|
||||||
useWalletManager,
|
|
||||||
WalletConnectionStatus,
|
|
||||||
} from '@marsprotocol/wallet-connector'
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import ConnectButton from 'components/Wallet/ConnectButton'
|
import WalletConnectButton from 'components/Wallet/WalletConnectButton'
|
||||||
import ConnectedButton from 'components/Wallet/ConnectedButton'
|
import WalletConnectedButton from 'components/Wallet/WalletConnectedButton'
|
||||||
|
import useCurrentWallet from 'hooks/useCurrentWallet'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { getPage, getRoute } from 'utils/route'
|
import { getPage, getRoute } from 'utils/route'
|
||||||
|
|
||||||
@ -16,43 +13,40 @@ export default function Wallet() {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { address: addressInUrl } = useParams()
|
const { address: addressInUrl } = useParams()
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
|
const currentWallet = useCurrentWallet()
|
||||||
const { status, connectedWallet } = useWalletManager()
|
const { simulate, sign, broadcast } = useShuttle()
|
||||||
const { simulate, sign, broadcast } = useWallet()
|
|
||||||
const address = useStore((s) => s.address)
|
const address = useStore((s) => s.address)
|
||||||
|
const client = useStore((s) => s.client)
|
||||||
|
|
||||||
// Set connection status
|
// Set connection status
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isConnected = status === WalletConnectionStatus.Connected
|
const isConnected = !!currentWallet
|
||||||
|
|
||||||
useStore.setState({ status })
|
|
||||||
useStore.setState(
|
useStore.setState(
|
||||||
isConnected
|
isConnected
|
||||||
? {
|
? {
|
||||||
address: connectedWallet?.account.address,
|
address: currentWallet.account.address,
|
||||||
}
|
}
|
||||||
: { address: undefined, accounts: null, client: undefined },
|
: { address: undefined, accounts: null, client: undefined },
|
||||||
)
|
)
|
||||||
}, [status, connectedWallet?.account.address])
|
}, [currentWallet])
|
||||||
|
|
||||||
// Set the client
|
// Set the client
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getCosmWasmClient() {
|
async function getCosmWasmClient() {
|
||||||
if (connectedWallet && connectedWallet.account.address !== address) {
|
if (client || !currentWallet) return
|
||||||
const cosmClient = await getClient(connectedWallet.network.rpc)
|
const cosmClient = await CosmWasmClient.connect(currentWallet.network.rpc)
|
||||||
const client = {
|
const walletClient: WalletClient = {
|
||||||
broadcast,
|
broadcast,
|
||||||
cosmWasmClient: cosmClient,
|
cosmWasmClient: cosmClient,
|
||||||
connectedWallet,
|
connectedWallet: currentWallet,
|
||||||
sign,
|
sign,
|
||||||
simulate,
|
simulate,
|
||||||
}
|
}
|
||||||
useStore.setState({ client })
|
useStore.setState({ client: walletClient })
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCosmWasmClient()
|
getCosmWasmClient()
|
||||||
}, [connectedWallet, address, simulate, sign, broadcast])
|
}, [currentWallet, address, simulate, sign, broadcast, client])
|
||||||
|
|
||||||
// Redirect when switching wallets or on first connection
|
// Redirect when switching wallets or on first connection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -60,5 +54,5 @@ export default function Wallet() {
|
|||||||
navigate(getRoute(getPage(pathname), address))
|
navigate(getRoute(getPage(pathname), address))
|
||||||
}, [address, addressInUrl, navigate, pathname])
|
}, [address, addressInUrl, navigate, pathname])
|
||||||
|
|
||||||
return address ? <ConnectedButton /> : <ConnectButton status={status} />
|
return address ? <WalletConnectedButton /> : <WalletConnectButton />
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ export const ASSETS: Asset[] = [
|
|||||||
color: '#9f1ab9',
|
color: '#9f1ab9',
|
||||||
decimals: 6,
|
decimals: 6,
|
||||||
hasOraclePrice: true,
|
hasOraclePrice: true,
|
||||||
logo: '/tokens/osmo.svg',
|
logo: '/images/tokens/osmo.svg',
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
isMarket: true,
|
isMarket: true,
|
||||||
isDisplayCurrency: true,
|
isDisplayCurrency: true,
|
||||||
@ -27,7 +27,7 @@ export const ASSETS: Asset[] = [
|
|||||||
? 'ibc/A8C2D23A1E6F95DA4E48BA349667E322BD7A6C996D8A4AAE8BA72E190F3D1477'
|
? 'ibc/A8C2D23A1E6F95DA4E48BA349667E322BD7A6C996D8A4AAE8BA72E190F3D1477'
|
||||||
: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2',
|
: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2',
|
||||||
color: '#6f7390',
|
color: '#6f7390',
|
||||||
logo: '/tokens/atom.svg',
|
logo: '/images/tokens/atom.svg',
|
||||||
decimals: 6,
|
decimals: 6,
|
||||||
hasOraclePrice: true,
|
hasOraclePrice: true,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
@ -43,7 +43,7 @@ export const ASSETS: Asset[] = [
|
|||||||
id: 'stATOM',
|
id: 'stATOM',
|
||||||
denom: 'ibc/C140AFD542AE77BD7DCC83F13FDD8C5E5BB8C4929785E6EC2F4C636F98F17901',
|
denom: 'ibc/C140AFD542AE77BD7DCC83F13FDD8C5E5BB8C4929785E6EC2F4C636F98F17901',
|
||||||
color: '#9f1ab9',
|
color: '#9f1ab9',
|
||||||
logo: '/tokens/statom.svg',
|
logo: '/images/tokens/statom.svg',
|
||||||
decimals: 6,
|
decimals: 6,
|
||||||
poolId: 803,
|
poolId: 803,
|
||||||
hasOraclePrice: !IS_TESTNET,
|
hasOraclePrice: !IS_TESTNET,
|
||||||
@ -57,7 +57,7 @@ export const ASSETS: Asset[] = [
|
|||||||
name: 'Axelar Wrapped Bitcoin',
|
name: 'Axelar Wrapped Bitcoin',
|
||||||
denom: 'ibc/D1542AA8762DB13087D8364F3EA6509FD6F009A34F00426AF9E4F9FA85CBBF1F',
|
denom: 'ibc/D1542AA8762DB13087D8364F3EA6509FD6F009A34F00426AF9E4F9FA85CBBF1F',
|
||||||
color: '#f09242',
|
color: '#f09242',
|
||||||
logo: '/tokens/axlwbtc.svg',
|
logo: '/images/tokens/axlwbtc.svg',
|
||||||
decimals: 8,
|
decimals: 8,
|
||||||
hasOraclePrice: true,
|
hasOraclePrice: true,
|
||||||
isEnabled: !IS_TESTNET,
|
isEnabled: !IS_TESTNET,
|
||||||
@ -72,7 +72,7 @@ export const ASSETS: Asset[] = [
|
|||||||
name: 'Axelar Wrapped Ethereum',
|
name: 'Axelar Wrapped Ethereum',
|
||||||
denom: 'ibc/EA1D43981D5C9A1C4AAEA9C23BB1D4FA126BA9BC7020A25E0AE4AA841EA25DC5',
|
denom: 'ibc/EA1D43981D5C9A1C4AAEA9C23BB1D4FA126BA9BC7020A25E0AE4AA841EA25DC5',
|
||||||
color: '#343434',
|
color: '#343434',
|
||||||
logo: '/tokens/axlweth.svg',
|
logo: '/images/tokens/axlweth.svg',
|
||||||
decimals: 18,
|
decimals: 18,
|
||||||
hasOraclePrice: true,
|
hasOraclePrice: true,
|
||||||
isEnabled: !IS_TESTNET,
|
isEnabled: !IS_TESTNET,
|
||||||
@ -89,7 +89,7 @@ export const ASSETS: Asset[] = [
|
|||||||
? 'ibc/DB9D326CF53EA07610C394D714D78F8BB4DC7E312D4213193791A9046BF45E20'
|
? 'ibc/DB9D326CF53EA07610C394D714D78F8BB4DC7E312D4213193791A9046BF45E20'
|
||||||
: MARS_MAINNET_DENOM,
|
: MARS_MAINNET_DENOM,
|
||||||
color: '#dd5b65',
|
color: '#dd5b65',
|
||||||
logo: '/tokens/mars.svg',
|
logo: '/images/tokens/mars.svg',
|
||||||
decimals: 6,
|
decimals: 6,
|
||||||
poolId: 907,
|
poolId: 907,
|
||||||
hasOraclePrice: false,
|
hasOraclePrice: false,
|
||||||
@ -105,7 +105,7 @@ export const ASSETS: Asset[] = [
|
|||||||
? 'ibc/6F34E1BD664C36CE49ACC28E60D62559A5F96C4F9A6CCE4FC5A67B2852E24CFE'
|
? 'ibc/6F34E1BD664C36CE49ACC28E60D62559A5F96C4F9A6CCE4FC5A67B2852E24CFE'
|
||||||
: 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858',
|
: 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858',
|
||||||
color: '#478edc',
|
color: '#478edc',
|
||||||
logo: '/tokens/axlusdc.svg',
|
logo: '/images/tokens/axlusdc.svg',
|
||||||
decimals: 6,
|
decimals: 6,
|
||||||
hasOraclePrice: true,
|
hasOraclePrice: true,
|
||||||
isEnabled: !IS_TESTNET,
|
isEnabled: !IS_TESTNET,
|
||||||
@ -123,7 +123,7 @@ export const ASSETS: Asset[] = [
|
|||||||
? 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4'
|
? 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4'
|
||||||
: 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4',
|
: 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4',
|
||||||
color: '#478edc',
|
color: '#478edc',
|
||||||
logo: '/tokens/nusdc.svg',
|
logo: '/images/tokens/nusdc.svg',
|
||||||
decimals: 6,
|
decimals: 6,
|
||||||
hasOraclePrice: IS_TESTNET,
|
hasOraclePrice: IS_TESTNET,
|
||||||
isEnabled: IS_TESTNET,
|
isEnabled: IS_TESTNET,
|
||||||
|
12
src/constants/bridges.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const BRIDGES: Bridge[] = [
|
||||||
|
{
|
||||||
|
name: 'Gravity bridge',
|
||||||
|
url: 'https://bridge.blockscape.network',
|
||||||
|
image: '/images/bridges/gravity.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Satellite by Axelar',
|
||||||
|
url: 'https://satellite.money',
|
||||||
|
image: '/images/bridges/satellite.png',
|
||||||
|
},
|
||||||
|
]
|
84
src/constants/chains.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { Bech32Address } from '@keplr-wallet/cosmos'
|
||||||
|
|
||||||
|
import { ChainInfoID } from 'types/enums/wallet'
|
||||||
|
|
||||||
|
export const CHAINS: ChainInfos = {
|
||||||
|
[ChainInfoID.Osmosis1]: {
|
||||||
|
rpc: 'https://rpc-osmosis.blockapsis.com',
|
||||||
|
rest: 'https://lcd-osmosis.blockapsis.com',
|
||||||
|
explorer: 'https://www.mintscan.io/osmosis',
|
||||||
|
explorerName: 'Mintscan',
|
||||||
|
chainId: ChainInfoID.Osmosis1,
|
||||||
|
name: 'Osmosis',
|
||||||
|
bip44: {
|
||||||
|
coinType: 118,
|
||||||
|
},
|
||||||
|
alternativeBIP44s: [{ coinType: 330 }],
|
||||||
|
gasPrice: '0.025uosmo',
|
||||||
|
bech32Config: Bech32Address.defaultBech32Config('osmo'),
|
||||||
|
defaultCurrency: {
|
||||||
|
coinDenom: 'OSMO',
|
||||||
|
coinMinimalDenom: 'uosmo',
|
||||||
|
coinDecimals: 6,
|
||||||
|
coinGeckoId: 'osmosis',
|
||||||
|
gasPriceStep: {
|
||||||
|
low: 0,
|
||||||
|
average: 0.025,
|
||||||
|
high: 0.04,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
features: ['ibc-transfer', 'ibc-go'],
|
||||||
|
},
|
||||||
|
[ChainInfoID.OsmosisDevnet]: {
|
||||||
|
rpc: 'https://rpc.devnet.osmosis.zone',
|
||||||
|
rest: ' https://lcd.devnet.osmosis.zone',
|
||||||
|
explorer: 'https://www.mintscan.io/osmosis',
|
||||||
|
explorerName: 'Mintscan',
|
||||||
|
chainId: ChainInfoID.OsmosisDevnet,
|
||||||
|
name: 'Osmosis Devnet',
|
||||||
|
bip44: {
|
||||||
|
coinType: 118,
|
||||||
|
},
|
||||||
|
alternativeBIP44s: [{ coinType: 330 }],
|
||||||
|
gasPrice: '0.025uosmo',
|
||||||
|
bech32Config: Bech32Address.defaultBech32Config('osmo'),
|
||||||
|
defaultCurrency: {
|
||||||
|
coinDenom: 'OSMO',
|
||||||
|
coinMinimalDenom: 'uosmo',
|
||||||
|
coinDecimals: 6,
|
||||||
|
coinGeckoId: 'osmosis',
|
||||||
|
gasPriceStep: {
|
||||||
|
low: 0,
|
||||||
|
average: 0.025,
|
||||||
|
high: 0.04,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
features: ['ibc-transfer', 'ibc-go'],
|
||||||
|
},
|
||||||
|
[ChainInfoID.OsmosisTestnet]: {
|
||||||
|
rpc: 'https://rpc.osmotest5.osmosis.zone',
|
||||||
|
rest: 'https://lcd.osmotest5.osmosis.zone',
|
||||||
|
explorer: 'https://testnet.mintscan.io/osmosis-testnet',
|
||||||
|
explorerName: 'Mintscan',
|
||||||
|
chainId: ChainInfoID.OsmosisTestnet,
|
||||||
|
name: 'Osmosis Testnet',
|
||||||
|
bip44: {
|
||||||
|
coinType: 118,
|
||||||
|
},
|
||||||
|
alternativeBIP44s: [{ coinType: 330 }],
|
||||||
|
gasPrice: '0.025uosmo',
|
||||||
|
bech32Config: Bech32Address.defaultBech32Config('osmo'),
|
||||||
|
defaultCurrency: {
|
||||||
|
coinDenom: 'OSMO',
|
||||||
|
coinMinimalDenom: 'uosmo',
|
||||||
|
coinDecimals: 6,
|
||||||
|
coinGeckoId: 'osmosis',
|
||||||
|
gasPriceStep: {
|
||||||
|
low: 0,
|
||||||
|
average: 0.025,
|
||||||
|
high: 0.04,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
features: ['ibc-transfer', 'ibc-go'],
|
||||||
|
},
|
||||||
|
}
|
@ -18,6 +18,7 @@ interface EnvironmentVariables {
|
|||||||
WALLETS: string[]
|
WALLETS: string[]
|
||||||
PYTH_ENDPOINT: string
|
PYTH_ENDPOINT: string
|
||||||
MAINNET_REST_API: string
|
MAINNET_REST_API: string
|
||||||
|
WALLET_CONNECT_ID: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ENV: EnvironmentVariables = {
|
export const ENV: EnvironmentVariables = {
|
||||||
@ -42,6 +43,7 @@ export const ENV: EnvironmentVariables = {
|
|||||||
WALLETS: process.env.NEXT_PUBLIC_WALLETS?.split(',') || [],
|
WALLETS: process.env.NEXT_PUBLIC_WALLETS?.split(',') || [],
|
||||||
PYTH_ENDPOINT: process.env.NEXT_PUBLIC_PYTH_ENDPOINT || '',
|
PYTH_ENDPOINT: process.env.NEXT_PUBLIC_PYTH_ENDPOINT || '',
|
||||||
MAINNET_REST_API: process.env.NEXT_PUBLIC_MAINNET_REST || '',
|
MAINNET_REST_API: process.env.NEXT_PUBLIC_MAINNET_REST || '',
|
||||||
|
WALLET_CONNECT_ID: process.env.NEXT_PUBLIC_WALLET_CONNECT_ID || '',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VERCEL_BYPASS = process.env.NEXT_PUBLIC_BYPASS
|
export const VERCEL_BYPASS = process.env.NEXT_PUBLIC_BYPASS
|
||||||
|
61
src/constants/wallets.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { ChainInfoID, WalletID } from 'types/enums/wallet'
|
||||||
|
|
||||||
|
export const WALLETS: WalletInfos = {
|
||||||
|
[WalletID.Cosmostation]: {
|
||||||
|
name: 'Cosmostation Wallet',
|
||||||
|
install: 'Install Cosmostation Wallet',
|
||||||
|
installURL:
|
||||||
|
'https://chrome.google.com/webstore/detail/cosmostation-wallet/fpkhgmpbidmiogeglndfbkegfdlnajnf',
|
||||||
|
imageURL:
|
||||||
|
'https://raw.githubusercontent.com/mars-protocol/wallet-connector/main/src/components/ui/images/cosmostation-wallet-extension.png',
|
||||||
|
|
||||||
|
supportedChains: [ChainInfoID.Osmosis1, ChainInfoID.OsmosisDevnet, ChainInfoID.OsmosisTestnet],
|
||||||
|
},
|
||||||
|
[WalletID.CosmostationMobile]: {
|
||||||
|
name: 'Cosmostation Wallet',
|
||||||
|
walletConnect: 'Cosmostation WalletConnect',
|
||||||
|
imageURL:
|
||||||
|
'https://raw.githubusercontent.com/mars-protocol/wallet-connector/main/src/components/ui/images/cosmostation-wallet-extension.png',
|
||||||
|
mobileImageURL:
|
||||||
|
'https://raw.githubusercontent.com/mars-protocol/wallet-connector/main/src/components/ui/images/cosmostation-wallet-connect.png',
|
||||||
|
supportedChains: [ChainInfoID.Osmosis1, ChainInfoID.OsmosisTestnet],
|
||||||
|
},
|
||||||
|
[WalletID.Keplr]: {
|
||||||
|
name: 'Keplr Wallet',
|
||||||
|
install: 'Install Keplr Wallet',
|
||||||
|
installURL: 'https://www.keplr.app/download',
|
||||||
|
imageURL: '/images/wallets/keplr.png',
|
||||||
|
supportedChains: [ChainInfoID.Osmosis1, ChainInfoID.OsmosisDevnet, ChainInfoID.OsmosisTestnet],
|
||||||
|
},
|
||||||
|
[WalletID.KeplrMobile]: {
|
||||||
|
name: 'Keplr Wallet',
|
||||||
|
walletConnect: 'Keplr WalletConnect',
|
||||||
|
imageURL: '/images/wallets/keplr.png',
|
||||||
|
mobileImageURL:
|
||||||
|
'https://raw.githubusercontent.com/mars-protocol/wallet-connector/main/src/components/ui/images/keplr-wallet-connect.png',
|
||||||
|
supportedChains: [ChainInfoID.Osmosis1],
|
||||||
|
},
|
||||||
|
[WalletID.Leap]: {
|
||||||
|
name: 'Leap Wallet',
|
||||||
|
install: 'Install Leap Wallet',
|
||||||
|
installURL:
|
||||||
|
'https://chrome.google.com/webstore/detail/leap-cosmos-wallet/fcfcfllfndlomdhbehjjcoimbgofdncg',
|
||||||
|
imageURL: 'https://assets.leapwallet.io/logos/leap-cosmos-logo.png',
|
||||||
|
supportedChains: [ChainInfoID.Osmosis1, ChainInfoID.OsmosisTestnet],
|
||||||
|
},
|
||||||
|
[WalletID.Station]: {
|
||||||
|
name: 'Station Wallet',
|
||||||
|
install: 'Install Station Wallet',
|
||||||
|
installURL:
|
||||||
|
'https://chrome.google.com/webstore/detail/station-wallet/aiifbnbfobpmeekipheeijimdpnlpgpp',
|
||||||
|
imageURL: '/images/wallets/station.png',
|
||||||
|
supportedChains: [ChainInfoID.Osmosis1],
|
||||||
|
},
|
||||||
|
[WalletID.Xdefi]: {
|
||||||
|
name: 'XDEFI Wallet',
|
||||||
|
install: 'Install XDEFI Wallet',
|
||||||
|
installURL: 'https://go.xdefi.io/mars',
|
||||||
|
imageURL: 'https://xdefi-static.s3.eu-west-1.amazonaws.com/xdefi.png',
|
||||||
|
supportedChains: [ChainInfoID.Osmosis1],
|
||||||
|
},
|
||||||
|
}
|
@ -3,8 +3,7 @@ import { useCallback, useEffect, useState } from 'react'
|
|||||||
import { ASSETS } from 'constants/assets'
|
import { ASSETS } from 'constants/assets'
|
||||||
import { FAVORITE_ASSETS_KEY } from 'constants/localStore'
|
import { FAVORITE_ASSETS_KEY } from 'constants/localStore'
|
||||||
import { getEnabledMarketAssets } from 'utils/assets'
|
import { getEnabledMarketAssets } from 'utils/assets'
|
||||||
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
import useLocalStorage from './useLocalStorage'
|
|
||||||
|
|
||||||
export default function useAssets() {
|
export default function useAssets() {
|
||||||
const [assets, setAssets] = useState<Asset[]>(ASSETS)
|
const [assets, setAssets] = useState<Asset[]>(ASSETS)
|
||||||
|
@ -7,8 +7,7 @@ import useMarketLiquidities from 'hooks/useMarketLiquidities'
|
|||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { getAssetByDenom } from 'utils/assets'
|
import { getAssetByDenom } from 'utils/assets'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
import useCurrentAccountDebts from 'hooks/useCurrentAccountDebts'
|
||||||
import useCurrentAccountDebts from './useCurrentAccountDebts'
|
|
||||||
|
|
||||||
export default function useBorrowMarketAssetsTableData(): {
|
export default function useBorrowMarketAssetsTableData(): {
|
||||||
accountBorrowedAssets: BorrowMarketTableData[]
|
accountBorrowedAssets: BorrowMarketTableData[]
|
||||||
|
15
src/hooks/useCurrentWallet.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { useShuttle } from '@delphi-labs/shuttle-react'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
import { ENV } from 'constants/env'
|
||||||
|
|
||||||
|
export default function useCurrentWallet() {
|
||||||
|
const { wallets } = useShuttle()
|
||||||
|
const chainId = ENV.CHAIN_ID
|
||||||
|
|
||||||
|
const currentWallet = useMemo(() => {
|
||||||
|
return wallets.find((wallet) => wallet.network.chainId === chainId)
|
||||||
|
}, [wallets, chainId])
|
||||||
|
|
||||||
|
return currentWallet
|
||||||
|
}
|
@ -7,13 +7,12 @@ import Footer from 'components/Footer'
|
|||||||
import DesktopHeader from 'components/Header/DesktopHeader'
|
import DesktopHeader from 'components/Header/DesktopHeader'
|
||||||
import ModalsContainer from 'components/Modals/ModalsContainer'
|
import ModalsContainer from 'components/Modals/ModalsContainer'
|
||||||
import PageMetadata from 'components/PageMetadata'
|
import PageMetadata from 'components/PageMetadata'
|
||||||
import TermsOfService from 'components/TermsOfService'
|
|
||||||
import Toaster from 'components/Toaster'
|
import Toaster from 'components/Toaster'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const showTermsOfService = useStore((s) => s.showTermsOfService)
|
const focusComponent = useStore((s) => s.focusComponent)
|
||||||
const isFullWidth = location.pathname.includes('trade') || location.pathname === '/'
|
const isFullWidth = location.pathname.includes('trade') || location.pathname === '/'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -23,13 +22,22 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
<DesktopHeader />
|
<DesktopHeader />
|
||||||
<main
|
<main
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'lg:h-[calc(100vh-89px)]',
|
'lg:min-h-[calc(100vh-89px)]',
|
||||||
'lg:mt-[65px]',
|
'lg:mt-[65px]',
|
||||||
'align-items-center grid h-full min-h-[900px] grid-cols-[auto_min-content] place-items-start gap-6 p-6',
|
'h-full min-h-[900px] gap-6 p-6',
|
||||||
|
focusComponent
|
||||||
|
? 'flex items-center justify-center'
|
||||||
|
: 'grid grid-cols-[auto_min-content] place-items-start',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={classNames('mx-auto h-full w-full', !isFullWidth && 'max-w-content')}>
|
<div className={classNames('mx-auto h-full w-full', !isFullWidth && 'max-w-content')}>
|
||||||
{showTermsOfService ? <TermsOfService /> : children}
|
{focusComponent ? (
|
||||||
|
<div className='relative flex h-full w-full items-center justify-center'>
|
||||||
|
{focusComponent}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<AccountDetails />
|
<AccountDetails />
|
||||||
</main>
|
</main>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { MsgExecuteContract } from '@marsprotocol/wallet-connector'
|
|
||||||
import { isMobile } from 'react-device-detect'
|
import { isMobile } from 'react-device-detect'
|
||||||
import { GetState, SetState } from 'zustand'
|
import { GetState, SetState } from 'zustand'
|
||||||
|
import { MsgExecuteContract } from '@delphi-labs/shuttle-react'
|
||||||
|
|
||||||
import { ENV } from 'constants/env'
|
import { ENV } from 'constants/env'
|
||||||
import { Store } from 'store'
|
import { Store } from 'store'
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { WalletConnectionStatus } from '@marsprotocol/wallet-connector'
|
|
||||||
import { GetState, SetState } from 'zustand'
|
import { GetState, SetState } from 'zustand'
|
||||||
|
|
||||||
export default function createCommonSlice(set: SetState<CommonSlice>, get: GetState<CommonSlice>) {
|
export default function createCommonSlice(set: SetState<CommonSlice>, get: GetState<CommonSlice>) {
|
||||||
@ -8,8 +7,6 @@ export default function createCommonSlice(set: SetState<CommonSlice>, get: GetSt
|
|||||||
creditAccounts: null,
|
creditAccounts: null,
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
status: WalletConnectionStatus.Unconnected,
|
focusComponent: null,
|
||||||
isFocusMode: false,
|
|
||||||
showTermsOfService: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
src/types/enums/wallet.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export enum WalletID {
|
||||||
|
Cosmostation = 'cosmostation',
|
||||||
|
CosmostationMobile = 'mobile-cosmostation',
|
||||||
|
Keplr = 'keplr',
|
||||||
|
KeplrMobile = 'mobile-keplr',
|
||||||
|
Leap = 'leap-cosmos',
|
||||||
|
Station = 'station',
|
||||||
|
Xdefi = 'xfi-cosmos',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ChainInfoID {
|
||||||
|
Osmosis1 = 'osmosis-1',
|
||||||
|
OsmosisDevnet = 'devnet',
|
||||||
|
OsmosisTestnet = 'osmo-test-5',
|
||||||
|
}
|
5
src/types/interfaces/bridges.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
interface Bridge {
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
image: string
|
||||||
|
}
|
20
src/types/interfaces/components/Button.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
interface ButtonProps {
|
||||||
|
children?: string | ReactNode
|
||||||
|
className?: string
|
||||||
|
color?: 'primary' | 'secondary' | 'tertiary' | 'quaternary'
|
||||||
|
disabled?: boolean
|
||||||
|
id?: string
|
||||||
|
showProgressIndicator?: boolean
|
||||||
|
size?: 'xs' | 'sm' | 'md' | 'lg'
|
||||||
|
text?: string | ReactNode
|
||||||
|
variant?: 'solid' | 'transparent' | 'round' | 'rounded'
|
||||||
|
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||||
|
leftIcon?: ReactElement
|
||||||
|
rightIcon?: ReactElement
|
||||||
|
iconClassName?: string
|
||||||
|
hasSubmenu?: boolean
|
||||||
|
hasFocus?: boolean
|
||||||
|
dataTestId?: string
|
||||||
|
tabIndex?: number
|
||||||
|
textClassNames?: string
|
||||||
|
}
|
23
src/types/interfaces/networkConfig.d.ts
vendored
@ -1,23 +0,0 @@
|
|||||||
interface NetworkConfig {
|
|
||||||
name: string
|
|
||||||
hiveUrl: string
|
|
||||||
rpcUrl: string
|
|
||||||
restUrl: string
|
|
||||||
contracts: {
|
|
||||||
accountNft: string
|
|
||||||
mockVault: string
|
|
||||||
marsOracleAdapter: string
|
|
||||||
swapper: string
|
|
||||||
mockZapper: string
|
|
||||||
creditManager: string
|
|
||||||
redBank: string
|
|
||||||
oracle: string
|
|
||||||
}
|
|
||||||
assets: {
|
|
||||||
base: Asset
|
|
||||||
whitelist: Asset[]
|
|
||||||
other: OtherAsset[]
|
|
||||||
}
|
|
||||||
appUrl: string
|
|
||||||
wallets: import('@marsprotocol/wallet-connector').WalletID[]
|
|
||||||
}
|
|
4
src/types/interfaces/store/broadcast.d.ts
vendored
@ -1,12 +1,12 @@
|
|||||||
const BNCoin = import('types/classes/BNCoin').BNCoin
|
const BNCoin = import('types/classes/BNCoin').BNCoin
|
||||||
|
|
||||||
interface BroadcastResult {
|
interface BroadcastResult {
|
||||||
result?: import('@marsprotocol/wallet-connector').TxBroadcastResult
|
result?: import('@delphi-labs/shuttle-react').TxBroadcastResult
|
||||||
error?: string
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BroadcastSlice {
|
interface BroadcastSlice {
|
||||||
toast: { message: string; isError?: boolean } | null
|
toast: { message: string; isError?: boolean; title?: string } | null
|
||||||
executeMsg: (options: {
|
executeMsg: (options: {
|
||||||
msg: Record<string, unknown>
|
msg: Record<string, unknown>
|
||||||
fee: StdFee
|
fee: StdFee
|
||||||
|
6
src/types/interfaces/store/common.d.ts
vendored
@ -2,10 +2,8 @@ interface CommonSlice {
|
|||||||
accounts: Account[] | null
|
accounts: Account[] | null
|
||||||
address?: string
|
address?: string
|
||||||
balances: Coin[]
|
balances: Coin[]
|
||||||
client?: import('@marsprotocol/wallet-connector').WalletClient
|
client?: WalletClient
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
selectedAccount: string | null
|
selectedAccount: string | null
|
||||||
status: import('@marsprotocol/wallet-connector').WalletConnectionStatus
|
focusComponent: ReactNode
|
||||||
isFocusMode: boolean
|
|
||||||
showTermsOfService: boolean
|
|
||||||
}
|
}
|
||||||
|
43
src/types/interfaces/wallet.d.ts
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
type WalletInfos = Record<WalletID, WalletInfo>
|
||||||
|
|
||||||
|
interface WalletInfo {
|
||||||
|
name: string
|
||||||
|
install?: string
|
||||||
|
installURL?: string
|
||||||
|
imageURL: string
|
||||||
|
mobileImageURL?: string
|
||||||
|
supportedChains: ChainInfoID[]
|
||||||
|
walletConnect?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChainInfos = Record<ChainInfoID, ChainInfo>
|
||||||
|
type Network = import('@delphi-labs/shuttle-react').Network
|
||||||
|
|
||||||
|
interface ChainInfo extends Network {
|
||||||
|
explorer: string
|
||||||
|
explorerName: string
|
||||||
|
alternativeBIP44s: BIP44[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WalletClient {
|
||||||
|
sign: (options: {
|
||||||
|
messages: import('@delphi-labs/shuttle-react').TransactionMsg<any>[]
|
||||||
|
feeAmount?: string | null | undefined
|
||||||
|
gasLimit?: string | null | undefined
|
||||||
|
memo?: string | null | undefined
|
||||||
|
wallet?: import('@delphi-labs/shuttle-react').WalletConnection | null | undefined
|
||||||
|
}) => Promise<import('@delphi-labs/shuttle-react').SigningResult>
|
||||||
|
cosmWasmClient: import('@cosmjs/cosmwasm-stargate').CosmWasmClient
|
||||||
|
connectedWallet: import('@delphi-labs/shuttle-react').WalletConnection
|
||||||
|
broadcast: (options: {
|
||||||
|
messages: import('@delphi-labs/shuttle-react').TransactionMsg<any>[]
|
||||||
|
feeAmount?: string | null | undefined
|
||||||
|
gasLimit?: string | null | undefined
|
||||||
|
memo?: string | null | undefined
|
||||||
|
wallet?: import('@delphi-labs/shuttle-react').WalletConnection | null | undefined
|
||||||
|
}) => Promise<import('@delphi-labs/shuttle-react').BroadcastResult>
|
||||||
|
simulate: (options: {
|
||||||
|
messages: import('@delphi-labs/shuttle-react').TransactionMsg<any>[]
|
||||||
|
wallet?: import('@delphi-labs/shuttle-react').WalletConnection | null | undefined
|
||||||
|
}) => Promise<import('@delphi-labs/shuttle-react').SimulateResult>
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { TxBroadcastResult } from '@marsprotocol/wallet-connector'
|
import { BroadcastResult } from '@delphi-labs/shuttle-react'
|
||||||
|
|
||||||
export function getSingleValueFromBroadcastResult(
|
export function getSingleValueFromBroadcastResult(
|
||||||
response: TxBroadcastResult,
|
response: BroadcastResult,
|
||||||
messageType: string,
|
messageType: string,
|
||||||
messageKey: string,
|
messageKey: string,
|
||||||
): string | null {
|
): string | null {
|
||||||
|
21
src/utils/mobile.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import MobileDetect from 'mobile-detect'
|
||||||
|
|
||||||
|
export const isMobile = () => {
|
||||||
|
if (typeof navigator === 'undefined') return false
|
||||||
|
|
||||||
|
const mobileDetect = new MobileDetect(navigator.userAgent)
|
||||||
|
|
||||||
|
return !!mobileDetect.mobile()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isAndroid = () => {
|
||||||
|
const mobileDetect = new MobileDetect(navigator.userAgent)
|
||||||
|
|
||||||
|
return mobileDetect.is('AndroidOS')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIOS = () => {
|
||||||
|
const mobileDetect = new MobileDetect(navigator.userAgent)
|
||||||
|
|
||||||
|
return mobileDetect.is('iOS')
|
||||||
|
}
|
@ -198,6 +198,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
4.5: '18px',
|
4.5: '18px',
|
||||||
|
13: '52px',
|
||||||
15: '60px',
|
15: '60px',
|
||||||
30: '120px',
|
30: '120px',
|
||||||
35: '140px',
|
35: '140px',
|
||||||
|