MP-2798, MP-2799, MP-2902, MP-2893, MP-2895, MP-2896, MP-2898 (#312)

This commit is contained in:
Linkie Link 2023-07-25 09:48:59 +02:00 committed by GitHub
parent 26c62ea2a4
commit 0aa3bb0c5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 2058 additions and 977 deletions

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

@ -0,0 +1,5 @@
interface Bridge {
name: string
url: string
image: string
}

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

View File

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

View File

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

View File

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

View File

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

View File

@ -198,6 +198,7 @@ module.exports = {
},
width: {
4.5: '18px',
13: '52px',
15: '60px',
30: '120px',
35: '140px',

1749
yarn.lock

File diff suppressed because it is too large Load Diff