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_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_ACCOUNT_NFT=osmo1gmpua5rkzg6cmju73fz5a9x454nz4vwlnkgs4g8wjlyeqtmzsuhq5funky
|
||||
NEXT_PUBLIC_ORACLE=osmo1khe29uw3t85nmmp3mtr8dls7v2qwsfk3tndu5h4w5g2r5tzlz5qqarq2e2
|
||||
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/"
|
||||
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_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
|
||||
|
||||
# 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_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_ACCOUNT_NFT=osmo1450hrg6dv2l58c0rvdwx8ec2a0r6dd50hn4frk370tpvqjhy8khqw7sw09
|
||||
# NEXT_PUBLIC_ORACLE=osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g
|
||||
# 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"
|
||||
# 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_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
|
||||
|
||||
|
@ -1,13 +1,24 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import AccountDetails from 'components/Account/AccountDetails'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useStore from 'store'
|
||||
|
||||
jest.mock('hooks/useCurrentAccount', () => jest.fn(() => null))
|
||||
|
||||
const mockedUseCurrentAccount = useCurrentAccount as jest.Mock
|
||||
|
||||
describe('<AccountDetails />', () => {
|
||||
beforeAll(() => {
|
||||
useStore.setState({
|
||||
address: 'walletAddress',
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
useStore.clearState()
|
||||
})
|
||||
|
||||
it('renders account details WHEN account is selected', () => {
|
||||
mockedUseCurrentAccount.mockReturnValue({ id: 1 })
|
||||
render(<AccountDetails />)
|
||||
|
@ -2,6 +2,13 @@
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
domains: [
|
||||
'assets.leapwallet.io',
|
||||
'raw.githubusercontent.com',
|
||||
'xdefi-static.s3.eu-west-1.amazonaws.com',
|
||||
],
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
|
@ -16,7 +16,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@tanstack/react-table": "^8.9.3",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
@ -30,6 +31,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-draggable": "^4.4.5",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-qr-code": "^2.0.11",
|
||||
"react-router-dom": "^6.14.1",
|
||||
"react-spring": "^9.7.2",
|
||||
"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 Text from 'components/Text'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
account: Account
|
||||
@ -9,8 +10,9 @@ interface Props {
|
||||
|
||||
export default function AccountDetailsController() {
|
||||
const account = useCurrentAccount()
|
||||
const address = useStore((s) => s.address)
|
||||
|
||||
if (!account) return null
|
||||
if (!account || !address) return null
|
||||
|
||||
return <AccountDetails account={account} />
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ import { Suspense } from 'react'
|
||||
|
||||
import AccountMenuContent from 'components/Account/AccountMenuContent'
|
||||
import Loading from 'components/Loading'
|
||||
import useStore from 'store'
|
||||
import useAccounts from 'hooks/useAccounts'
|
||||
import useStore from 'store'
|
||||
|
||||
function Content() {
|
||||
const address = useStore((s) => s.address)
|
||||
|
@ -2,6 +2,7 @@ import classNames from 'classnames'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import AccountCreateFirst from 'components/Account/AccountCreateFirst'
|
||||
import AccountList from 'components/Account/AccountList'
|
||||
import CreateAccount from 'components/Account/CreateAccount'
|
||||
import FundAccount from 'components/Account/FundAccount'
|
||||
@ -10,12 +11,13 @@ import { CircularProgress } from 'components/CircularProgress'
|
||||
import { Account, Plus, PlusCircled } from 'components/Icons'
|
||||
import Overlay from 'components/Overlay'
|
||||
import Text from 'components/Text'
|
||||
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
import useStore from 'store'
|
||||
import { hardcodedFee } from 'utils/constants'
|
||||
import { isNumber } from 'utils/parsers'
|
||||
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { isNumber } from 'utils/parsers'
|
||||
|
||||
const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide'
|
||||
const ACCOUNT_MENU_BUTTON_ID = 'account-menu-button'
|
||||
@ -61,11 +63,17 @@ export default function AccountMenuContent(props: Props) {
|
||||
}, [address, createAccount, navigate, setIsCreating, setShowMenu])
|
||||
|
||||
const handleCreateAccountClick = useCallback(() => {
|
||||
setShowMenu(!showMenu)
|
||||
if (!hasCreditAccounts && checkHasFunds()) {
|
||||
performCreateAccount()
|
||||
if (!checkHasFunds()) {
|
||||
useStore.setState({ focusComponent: <WalletBridges /> })
|
||||
return
|
||||
}
|
||||
}, [checkHasFunds, hasCreditAccounts, performCreateAccount, setShowMenu, showMenu])
|
||||
if (!hasCreditAccounts) {
|
||||
useStore.setState({ focusComponent: <AccountCreateFirst /> })
|
||||
return
|
||||
}
|
||||
|
||||
setShowMenu(!showMenu)
|
||||
}, [checkHasFunds, hasCreditAccounts, setShowMenu, showMenu])
|
||||
|
||||
useEffect(() => {
|
||||
useStore.setState({ accounts: props.accounts })
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
|
||||
import { Cross } from 'components/Icons'
|
||||
import Button from 'components/Button'
|
||||
import { Cross } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
|
||||
interface Props {
|
||||
@ -34,7 +34,7 @@ export default function EscButton(props: Props) {
|
||||
leftIcon={<Cross />}
|
||||
iconClassName='w-3'
|
||||
color='tertiary'
|
||||
className='h-3'
|
||||
className='h-3 w-13'
|
||||
size='xs'
|
||||
>
|
||||
<Text size='2xs'>ESC</Text>
|
||||
|
@ -20,27 +20,6 @@ import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
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(
|
||||
{
|
||||
children,
|
||||
@ -61,7 +40,7 @@ const Button = React.forwardRef(function Button(
|
||||
dataTestId,
|
||||
tabIndex = 0,
|
||||
textClassNames,
|
||||
}: Props,
|
||||
}: ButtonProps,
|
||||
ref,
|
||||
) {
|
||||
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() {
|
||||
return (
|
||||
<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'>
|
||||
v{packageInfo.version}
|
||||
</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() {
|
||||
const address = useStore((s) => s.address)
|
||||
const isFocusMode = useStore((s) => s.isFocusMode)
|
||||
const focusComponent = useStore((s) => s.focusComponent)
|
||||
|
||||
function handleCloseFocusMode() {
|
||||
useStore.setState({ isFocusMode: false, showTermsOfService: false })
|
||||
useStore.setState({ focusComponent: null })
|
||||
}
|
||||
|
||||
return (
|
||||
@ -33,13 +33,17 @@ export default function DesktopHeader() {
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex items-center justify-between py-3 pl-6 pr-4',
|
||||
!isFocusMode && ' border-b border-white/20',
|
||||
'flex items-center justify-between px-4 py-4',
|
||||
focusComponent ? 'relative isolate' : 'border-b border-white/20',
|
||||
)}
|
||||
>
|
||||
<DesktopNavigation />
|
||||
{isFocusMode ? (
|
||||
<EscButton onClick={handleCloseFocusMode} />
|
||||
{focusComponent ? (
|
||||
<div className='flex w-full justify-between'>
|
||||
<div className='flex h-5 w-13' />
|
||||
{address && <Wallet />}
|
||||
<EscButton onClick={handleCloseFocusMode} />
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex gap-4'>
|
||||
{address && <AccountMenu />}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { menuTree } from 'components/Header/DesktopHeader'
|
||||
import { Logo } from 'components/Icons'
|
||||
@ -8,20 +9,24 @@ import { getRoute } from 'utils/route'
|
||||
|
||||
export default function DesktopNavigation() {
|
||||
const { address, accountId } = useParams()
|
||||
const isFocusMode = useStore((s) => s.isFocusMode)
|
||||
const focusComponent = useStore((s) => s.focusComponent)
|
||||
|
||||
function getIsActive(href: string) {
|
||||
return location.pathname.includes(href)
|
||||
}
|
||||
|
||||
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)}>
|
||||
<span className='block h-10 w-10'>
|
||||
<Logo className='text-white' />
|
||||
</span>
|
||||
</NavLink>
|
||||
{!isFocusMode && (
|
||||
{!focusComponent && (
|
||||
<div className='flex gap-8 px-6'>
|
||||
{menuTree.map((item, index) => (
|
||||
<NavLink
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useWalletManager } from '@marsprotocol/wallet-connector'
|
||||
import classNames from 'classnames'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import FullOverlayContent from 'components/FullOverlayContent'
|
||||
import { Check } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import WalletSelect from 'components/Wallet/WalletSelect'
|
||||
import { TERMS_OF_SERVICE_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import useStore from 'store'
|
||||
import Button from 'components/Button'
|
||||
import { Check, ExternalLink } from 'components/Icons'
|
||||
|
||||
interface BenefitsProps {
|
||||
benefits: string[]
|
||||
@ -15,9 +15,9 @@ interface BenefitsProps {
|
||||
|
||||
function Benefits({ benefits }: BenefitsProps) {
|
||||
return (
|
||||
<ul className='w-full list-none px-0 py-3'>
|
||||
<ul className='w-full list-none px-0'>
|
||||
{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
|
||||
className={classNames(
|
||||
'absolute left-0 top-0 isolate h-6 w-6 rounded-full bg-white/10',
|
||||
@ -36,51 +36,33 @@ function Benefits({ benefits }: BenefitsProps) {
|
||||
}
|
||||
|
||||
export default function TermsOfService() {
|
||||
const { connect } = useWalletManager()
|
||||
const [_, setHasAgreedToTerms] = useLocalStorage(TERMS_OF_SERVICE_KEY, false)
|
||||
|
||||
const handleAgreeTermsOfService = useCallback(() => {
|
||||
useStore.setState({ showTermsOfService: false, isFocusMode: false })
|
||||
setHasAgreedToTerms(true)
|
||||
connect()
|
||||
}, [connect, setHasAgreedToTerms])
|
||||
useStore.setState({ focusComponent: <WalletSelect /> })
|
||||
}, [setHasAgreedToTerms])
|
||||
|
||||
return (
|
||||
<div className='relative flex h-full w-full items-center justify-center'>
|
||||
<div className='w-100'>
|
||||
<Text size='4xl' className='w-full pb-2'>
|
||||
Master the Red Planet
|
||||
</Text>
|
||||
<Text size='sm' className='w-full text-white/60'>
|
||||
Mars offers the easiest way to margin trade, lend & borrow and yield farm with leverage
|
||||
any whitelisted token
|
||||
</Text>
|
||||
<Benefits
|
||||
benefits={[
|
||||
'Swap tokens with margin acress any whitelisted pair',
|
||||
'Amplify your LP rewards with leveraged yield farming',
|
||||
'Earn interest on deposited tokens',
|
||||
]}
|
||||
/>
|
||||
<Button
|
||||
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>
|
||||
<FullOverlayContent
|
||||
title='Master the Red Planet'
|
||||
copy='Mars offers the easiest way to margin trade, lend & borrow and yield farm with leverage any whitelisted token'
|
||||
button={{
|
||||
className: 'w-full mt-4',
|
||||
text: 'Agree & continue',
|
||||
color: 'tertiary',
|
||||
onClick: handleAgreeTermsOfService,
|
||||
size: 'lg',
|
||||
}}
|
||||
docs='terms'
|
||||
>
|
||||
<Benefits
|
||||
benefits={[
|
||||
'Swap tokens with margin acress any whitelisted pair',
|
||||
'Amplify your LP rewards with leveraged yield farming',
|
||||
'Earn interest on deposited tokens',
|
||||
]}
|
||||
/>
|
||||
</FullOverlayContent>
|
||||
)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import useStore from 'store'
|
||||
export default function Toaster() {
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const toast = useStore((s) => s.toast)
|
||||
const isError = toast?.isError
|
||||
|
||||
if (toast) {
|
||||
const Msg = () => (
|
||||
@ -19,24 +20,22 @@ export default function Toaster() {
|
||||
className={classNames(
|
||||
'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',
|
||||
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={classNames('rounded-sm p-1.5', toast.isError ? 'bg-error' : 'bg-success')}
|
||||
>
|
||||
<div className={classNames('rounded-sm p-1.5', isError ? 'bg-error' : 'bg-success')}>
|
||||
<span className='block h-4 w-4 text-white'>
|
||||
{toast.isError ? <CrossCircled /> : <CheckCircled />}
|
||||
{isError ? <CrossCircled /> : <CheckCircled />}
|
||||
</span>
|
||||
</div>
|
||||
<Text
|
||||
className={classNames(
|
||||
'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>
|
||||
</div>
|
||||
|
||||
@ -44,7 +43,7 @@ export default function Toaster() {
|
||||
{toast.message}
|
||||
</Text>
|
||||
<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>
|
||||
)
|
||||
@ -53,7 +52,7 @@ export default function Toaster() {
|
||||
icon: false,
|
||||
draggable: false,
|
||||
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 })
|
||||
|
@ -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 Button from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { Cross } from 'components/Icons'
|
||||
import { CHAINS } from 'constants/chains'
|
||||
import { ENV } from 'constants/env'
|
||||
import { WALLETS } from 'constants/wallets'
|
||||
import { WalletID } from 'types/enums/wallet'
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const WalletConnectProvider: FC<Props> = ({ children }) => {
|
||||
const chainInfoOverrides = {
|
||||
rpc: ENV.URL_RPC,
|
||||
rest: ENV.URL_REST,
|
||||
chainID: ENV.CHAIN_ID as ChainInfoID,
|
||||
}
|
||||
const enabledWallets: string[] = ENV.WALLETS
|
||||
function getSupportedChainsInfos(walletId: WalletID) {
|
||||
return WALLETS[walletId].supportedChains.map((chain) => {
|
||||
const chainInfo: ChainInfo = CHAINS[chain]
|
||||
// Check if supported chain equals current chain
|
||||
if (ENV.CHAIN_ID !== chainInfo.chainId) return chainInfo
|
||||
// Override rpc and rest urls for the current chain if they are defined in .env
|
||||
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 (
|
||||
<WalletManagerProvider
|
||||
// TODO: handle chainIds via constants
|
||||
chainIds={[ChainInfoID.OsmosisTestnet, ChainInfoID.Osmosis1]}
|
||||
chainInfoOverrides={chainInfoOverrides}
|
||||
// closeIcon={<SVG.Close />}
|
||||
defaultChainId={chainInfoOverrides.chainID}
|
||||
enabledWallets={enabledWallets}
|
||||
<ShuttleProvider
|
||||
walletConnectProjectId={ENV.WALLET_CONNECT_ID}
|
||||
mobileProviders={mobileProviders}
|
||||
extensionProviders={extensionProviders}
|
||||
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}
|
||||
</WalletManagerProvider>
|
||||
</ShuttleProvider>
|
||||
)
|
||||
}
|
||||
|
@ -1,9 +1,4 @@
|
||||
import {
|
||||
ChainInfoID,
|
||||
SimpleChainInfoList,
|
||||
useWallet,
|
||||
useWalletManager,
|
||||
} from '@marsprotocol/wallet-connector'
|
||||
import { useShuttle } from '@delphi-labs/shuttle-react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import classNames from 'classnames'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
@ -15,22 +10,26 @@ import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Check, Copy, ExternalLink, Osmo } from 'components/Icons'
|
||||
import Overlay from 'components/Overlay'
|
||||
import Text from 'components/Text'
|
||||
import { CHAINS } from 'constants/chains'
|
||||
import { IS_TESTNET } from 'constants/env'
|
||||
import useCurrentWallet from 'hooks/useCurrentWallet'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
import useWalletBalances from 'hooks/useWalletBalances'
|
||||
import useStore from 'store'
|
||||
import { ChainInfoID } from 'types/enums/wallet'
|
||||
import { getBaseAsset, getEnabledMarketAssets } from 'utils/assets'
|
||||
import { formatValue, truncate } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export default function ConnectedButton() {
|
||||
export default function WalletConnectedButton() {
|
||||
// ---------------
|
||||
// EXTERNAL HOOKS
|
||||
// ---------------
|
||||
const marketAssets = getEnabledMarketAssets()
|
||||
const { disconnect } = useWallet()
|
||||
const { disconnect: terminate } = useWalletManager()
|
||||
const currentWallet = useCurrentWallet()
|
||||
const { disconnectWallet } = useShuttle()
|
||||
const address = useStore((s) => s.address)
|
||||
const focusComponent = useStore((s) => s.focusComponent)
|
||||
const network = useStore((s) => s.client?.connectedWallet.network)
|
||||
const baseAsset = getBaseAsset()
|
||||
const { data: walletBalances, isLoading } = useWalletBalances(address)
|
||||
@ -46,17 +45,17 @@ export default function ConnectedButton() {
|
||||
// ---------------
|
||||
// VARIABLES
|
||||
// ---------------
|
||||
const explorerName = network && SimpleChainInfoList[network.chainId as ChainInfoID].explorerName
|
||||
const explorerName = network && CHAINS[network.chainId as ChainInfoID].explorerName
|
||||
|
||||
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')
|
||||
}, [network, address])
|
||||
|
||||
const disconnectWallet = () => {
|
||||
disconnect()
|
||||
terminate()
|
||||
const onDisconnectWallet = () => {
|
||||
if (!currentWallet) return
|
||||
disconnectWallet(currentWallet)
|
||||
useStore.setState({ client: undefined, balances: [] })
|
||||
}
|
||||
|
||||
@ -108,7 +107,11 @@ export default function ConnectedButton() {
|
||||
)}
|
||||
</div>
|
||||
</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-0 mb-4 flex w-full flex-nowrap items-start'>
|
||||
<div className='flex w-auto flex-1'>
|
||||
@ -124,7 +127,7 @@ export default function ConnectedButton() {
|
||||
</div>
|
||||
</div>
|
||||
<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 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 {
|
||||
getClient,
|
||||
useWallet,
|
||||
useWalletManager,
|
||||
WalletConnectionStatus,
|
||||
} from '@marsprotocol/wallet-connector'
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { useShuttle } from '@delphi-labs/shuttle-react'
|
||||
import { useEffect } from 'react'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
import ConnectButton from 'components/Wallet/ConnectButton'
|
||||
import ConnectedButton from 'components/Wallet/ConnectedButton'
|
||||
import WalletConnectButton from 'components/Wallet/WalletConnectButton'
|
||||
import WalletConnectedButton from 'components/Wallet/WalletConnectedButton'
|
||||
import useCurrentWallet from 'hooks/useCurrentWallet'
|
||||
import useStore from 'store'
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
|
||||
@ -16,43 +13,40 @@ export default function Wallet() {
|
||||
const navigate = useNavigate()
|
||||
const { address: addressInUrl } = useParams()
|
||||
const { pathname } = useLocation()
|
||||
|
||||
const { status, connectedWallet } = useWalletManager()
|
||||
const { simulate, sign, broadcast } = useWallet()
|
||||
const currentWallet = useCurrentWallet()
|
||||
const { simulate, sign, broadcast } = useShuttle()
|
||||
const address = useStore((s) => s.address)
|
||||
const client = useStore((s) => s.client)
|
||||
|
||||
// Set connection status
|
||||
useEffect(() => {
|
||||
const isConnected = status === WalletConnectionStatus.Connected
|
||||
|
||||
useStore.setState({ status })
|
||||
const isConnected = !!currentWallet
|
||||
useStore.setState(
|
||||
isConnected
|
||||
? {
|
||||
address: connectedWallet?.account.address,
|
||||
address: currentWallet.account.address,
|
||||
}
|
||||
: { address: undefined, accounts: null, client: undefined },
|
||||
)
|
||||
}, [status, connectedWallet?.account.address])
|
||||
}, [currentWallet])
|
||||
|
||||
// Set the client
|
||||
useEffect(() => {
|
||||
async function getCosmWasmClient() {
|
||||
if (connectedWallet && connectedWallet.account.address !== address) {
|
||||
const cosmClient = await getClient(connectedWallet.network.rpc)
|
||||
const client = {
|
||||
broadcast,
|
||||
cosmWasmClient: cosmClient,
|
||||
connectedWallet,
|
||||
sign,
|
||||
simulate,
|
||||
}
|
||||
useStore.setState({ client })
|
||||
if (client || !currentWallet) return
|
||||
const cosmClient = await CosmWasmClient.connect(currentWallet.network.rpc)
|
||||
const walletClient: WalletClient = {
|
||||
broadcast,
|
||||
cosmWasmClient: cosmClient,
|
||||
connectedWallet: currentWallet,
|
||||
sign,
|
||||
simulate,
|
||||
}
|
||||
useStore.setState({ client: walletClient })
|
||||
}
|
||||
|
||||
getCosmWasmClient()
|
||||
}, [connectedWallet, address, simulate, sign, broadcast])
|
||||
}, [currentWallet, address, simulate, sign, broadcast, client])
|
||||
|
||||
// Redirect when switching wallets or on first connection
|
||||
useEffect(() => {
|
||||
@ -60,5 +54,5 @@ export default function Wallet() {
|
||||
navigate(getRoute(getPage(pathname), address))
|
||||
}, [address, addressInUrl, navigate, pathname])
|
||||
|
||||
return address ? <ConnectedButton /> : <ConnectButton status={status} />
|
||||
return address ? <WalletConnectedButton /> : <WalletConnectButton />
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export const ASSETS: Asset[] = [
|
||||
color: '#9f1ab9',
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
logo: '/tokens/osmo.svg',
|
||||
logo: '/images/tokens/osmo.svg',
|
||||
isEnabled: true,
|
||||
isMarket: true,
|
||||
isDisplayCurrency: true,
|
||||
@ -27,7 +27,7 @@ export const ASSETS: Asset[] = [
|
||||
? 'ibc/A8C2D23A1E6F95DA4E48BA349667E322BD7A6C996D8A4AAE8BA72E190F3D1477'
|
||||
: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2',
|
||||
color: '#6f7390',
|
||||
logo: '/tokens/atom.svg',
|
||||
logo: '/images/tokens/atom.svg',
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
isEnabled: true,
|
||||
@ -43,7 +43,7 @@ export const ASSETS: Asset[] = [
|
||||
id: 'stATOM',
|
||||
denom: 'ibc/C140AFD542AE77BD7DCC83F13FDD8C5E5BB8C4929785E6EC2F4C636F98F17901',
|
||||
color: '#9f1ab9',
|
||||
logo: '/tokens/statom.svg',
|
||||
logo: '/images/tokens/statom.svg',
|
||||
decimals: 6,
|
||||
poolId: 803,
|
||||
hasOraclePrice: !IS_TESTNET,
|
||||
@ -57,7 +57,7 @@ export const ASSETS: Asset[] = [
|
||||
name: 'Axelar Wrapped Bitcoin',
|
||||
denom: 'ibc/D1542AA8762DB13087D8364F3EA6509FD6F009A34F00426AF9E4F9FA85CBBF1F',
|
||||
color: '#f09242',
|
||||
logo: '/tokens/axlwbtc.svg',
|
||||
logo: '/images/tokens/axlwbtc.svg',
|
||||
decimals: 8,
|
||||
hasOraclePrice: true,
|
||||
isEnabled: !IS_TESTNET,
|
||||
@ -72,7 +72,7 @@ export const ASSETS: Asset[] = [
|
||||
name: 'Axelar Wrapped Ethereum',
|
||||
denom: 'ibc/EA1D43981D5C9A1C4AAEA9C23BB1D4FA126BA9BC7020A25E0AE4AA841EA25DC5',
|
||||
color: '#343434',
|
||||
logo: '/tokens/axlweth.svg',
|
||||
logo: '/images/tokens/axlweth.svg',
|
||||
decimals: 18,
|
||||
hasOraclePrice: true,
|
||||
isEnabled: !IS_TESTNET,
|
||||
@ -89,7 +89,7 @@ export const ASSETS: Asset[] = [
|
||||
? 'ibc/DB9D326CF53EA07610C394D714D78F8BB4DC7E312D4213193791A9046BF45E20'
|
||||
: MARS_MAINNET_DENOM,
|
||||
color: '#dd5b65',
|
||||
logo: '/tokens/mars.svg',
|
||||
logo: '/images/tokens/mars.svg',
|
||||
decimals: 6,
|
||||
poolId: 907,
|
||||
hasOraclePrice: false,
|
||||
@ -105,7 +105,7 @@ export const ASSETS: Asset[] = [
|
||||
? 'ibc/6F34E1BD664C36CE49ACC28E60D62559A5F96C4F9A6CCE4FC5A67B2852E24CFE'
|
||||
: 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858',
|
||||
color: '#478edc',
|
||||
logo: '/tokens/axlusdc.svg',
|
||||
logo: '/images/tokens/axlusdc.svg',
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
isEnabled: !IS_TESTNET,
|
||||
@ -123,7 +123,7 @@ export const ASSETS: Asset[] = [
|
||||
? 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4'
|
||||
: 'ibc/B3504E092456BA618CC28AC671A71FB08C6CA0FD0BE7C8A5B5A3E2DD933CC9E4',
|
||||
color: '#478edc',
|
||||
logo: '/tokens/nusdc.svg',
|
||||
logo: '/images/tokens/nusdc.svg',
|
||||
decimals: 6,
|
||||
hasOraclePrice: 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[]
|
||||
PYTH_ENDPOINT: string
|
||||
MAINNET_REST_API: string
|
||||
WALLET_CONNECT_ID: string
|
||||
}
|
||||
|
||||
export const ENV: EnvironmentVariables = {
|
||||
@ -42,6 +43,7 @@ export const ENV: EnvironmentVariables = {
|
||||
WALLETS: process.env.NEXT_PUBLIC_WALLETS?.split(',') || [],
|
||||
PYTH_ENDPOINT: process.env.NEXT_PUBLIC_PYTH_ENDPOINT || '',
|
||||
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
|
||||
|
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 { FAVORITE_ASSETS_KEY } from 'constants/localStore'
|
||||
import { getEnabledMarketAssets } from 'utils/assets'
|
||||
|
||||
import useLocalStorage from './useLocalStorage'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
export default function useAssets() {
|
||||
const [assets, setAssets] = useState<Asset[]>(ASSETS)
|
||||
|
@ -7,8 +7,7 @@ import useMarketLiquidities from 'hooks/useMarketLiquidities'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
import useCurrentAccountDebts from './useCurrentAccountDebts'
|
||||
import useCurrentAccountDebts from 'hooks/useCurrentAccountDebts'
|
||||
|
||||
export default function useBorrowMarketAssetsTableData(): {
|
||||
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 ModalsContainer from 'components/Modals/ModalsContainer'
|
||||
import PageMetadata from 'components/PageMetadata'
|
||||
import TermsOfService from 'components/TermsOfService'
|
||||
import Toaster from 'components/Toaster'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const location = useLocation()
|
||||
const showTermsOfService = useStore((s) => s.showTermsOfService)
|
||||
const focusComponent = useStore((s) => s.focusComponent)
|
||||
const isFullWidth = location.pathname.includes('trade') || location.pathname === '/'
|
||||
|
||||
return (
|
||||
@ -23,13 +22,22 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
<DesktopHeader />
|
||||
<main
|
||||
className={classNames(
|
||||
'lg:h-[calc(100vh-89px)]',
|
||||
'lg:min-h-[calc(100vh-89px)]',
|
||||
'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')}>
|
||||
{showTermsOfService ? <TermsOfService /> : children}
|
||||
{focusComponent ? (
|
||||
<div className='relative flex h-full w-full items-center justify-center'>
|
||||
{focusComponent}
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</div>
|
||||
<AccountDetails />
|
||||
</main>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MsgExecuteContract } from '@marsprotocol/wallet-connector'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { GetState, SetState } from 'zustand'
|
||||
import { MsgExecuteContract } from '@delphi-labs/shuttle-react'
|
||||
|
||||
import { ENV } from 'constants/env'
|
||||
import { Store } from 'store'
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { WalletConnectionStatus } from '@marsprotocol/wallet-connector'
|
||||
import { GetState, SetState } from 'zustand'
|
||||
|
||||
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,
|
||||
isOpen: true,
|
||||
selectedAccount: null,
|
||||
status: WalletConnectionStatus.Unconnected,
|
||||
isFocusMode: false,
|
||||
showTermsOfService: false,
|
||||
focusComponent: null,
|
||||
}
|
||||
}
|
||||
|
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
|
||||
|
||||
interface BroadcastResult {
|
||||
result?: import('@marsprotocol/wallet-connector').TxBroadcastResult
|
||||
result?: import('@delphi-labs/shuttle-react').TxBroadcastResult
|
||||
error?: string
|
||||
}
|
||||
|
||||
interface BroadcastSlice {
|
||||
toast: { message: string; isError?: boolean } | null
|
||||
toast: { message: string; isError?: boolean; title?: string } | null
|
||||
executeMsg: (options: {
|
||||
msg: Record<string, unknown>
|
||||
fee: StdFee
|
||||
|
6
src/types/interfaces/store/common.d.ts
vendored
@ -2,10 +2,8 @@ interface CommonSlice {
|
||||
accounts: Account[] | null
|
||||
address?: string
|
||||
balances: Coin[]
|
||||
client?: import('@marsprotocol/wallet-connector').WalletClient
|
||||
client?: WalletClient
|
||||
isOpen: boolean
|
||||
selectedAccount: string | null
|
||||
status: import('@marsprotocol/wallet-connector').WalletConnectionStatus
|
||||
isFocusMode: boolean
|
||||
showTermsOfService: boolean
|
||||
focusComponent: ReactNode
|
||||
}
|
||||
|
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(
|
||||
response: TxBroadcastResult,
|
||||
response: BroadcastResult,
|
||||
messageType: string,
|
||||
messageKey: string,
|
||||
): 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: {
|
||||
4.5: '18px',
|
||||
13: '52px',
|
||||
15: '60px',
|
||||
30: '120px',
|
||||
35: '140px',
|
||||
|