Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more wallets support #1241

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d0b11a6
add auto-xdm dependencies
marc-aurele-besner Feb 18, 2025
c5d02a0
add transfer and swap routes
marc-aurele-besner Feb 18, 2025
4103c8a
add swap header
marc-aurele-besner Feb 18, 2025
1380e8f
add basic new components
marc-aurele-besner Feb 18, 2025
3ed8133
add main swap/transfer component
marc-aurele-besner Feb 18, 2025
5296972
add maxAmount (wallet balance)
marc-aurele-besner Feb 18, 2025
34286dc
delete receiver parameter if switch to swap
marc-aurele-besner Feb 18, 2025
87fb2ae
Merge branch 'main' into feat/add-xdm-support
marc-aurele-besner Feb 18, 2025
422da4c
switch to use useIndexers for tokenSymbol
marc-aurele-besner Feb 18, 2025
69560d3
use domain api
marc-aurele-besner Feb 18, 2025
86b553d
add fee
marc-aurele-besner Feb 18, 2025
aaa23ce
add support for domain wallet balance
marc-aurele-besner Feb 18, 2025
10889a7
collide swap and transfer into one route
marc-aurele-besner Feb 21, 2025
04817ea
simplify logic since we always show receiver field now
marc-aurele-besner Feb 21, 2025
8728f3d
add address book selector in receiver field
marc-aurele-besner Feb 21, 2025
c0f4b4b
improve address validation
marc-aurele-besner Feb 21, 2025
f21d53f
Merge branch 'main' into feat/add-xdm-support
marc-aurele-besner Feb 21, 2025
b2be05b
Merge branch 'main' into feat/add-xdm-support
marc-aurele-besner Feb 21, 2025
c5fdffe
Merge branch 'main' into feat/add-xdm-support
marc-aurele-besner Feb 23, 2025
ce0e7af
add blocknative web-onboard for web3 wallets support
marc-aurele-besner Feb 24, 2025
e588902
add mm icon
marc-aurele-besner Feb 24, 2025
ee47bb9
add web3 wallet provider
marc-aurele-besner Feb 24, 2025
21c006d
separate wallets by type
marc-aurele-besner Feb 24, 2025
12bd785
add wallets
marc-aurele-besner Feb 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions explorer/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ NEXT_PUBLIC_GOOGLE_ANALYTICS_ID='G-'
# Next Telemetry
NEXT_PUBLIC_TELEMETRY_URL="wss://telemetry.subspace.network/feed"

# Wallet Connect Project ID
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="abc123..."

# Discord integration for authentication
DISCORD_CLIENT_ID="<discord-client-id>"
DISCORD_CLIENT_SECRET="<discord-client-secret>"
Expand Down
7 changes: 7 additions & 0 deletions explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@autonomys/auto-consensus": "^1.3.1",
"@autonomys/auto-drive": "^1.3.1",
"@autonomys/auto-utils": "^1.3.1",
"@autonomys/auto-xdm": "^1.3.1",
"@floating-ui/react": "^0.27.4",
"@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0",
Expand All @@ -55,6 +56,12 @@
"@testing-library/react": "^14.2.1",
"@testing-library/user-event": "^14.5.2",
"@vercel/og": "^0.6.2",
"@web3-onboard/coinbase": "^2.4.2",
"@web3-onboard/core": "^2.24.0",
"@web3-onboard/injected-wallets": "^2.11.3",
"@web3-onboard/metamask": "^2.2.1",
"@web3-onboard/react": "^2.11.0",
"@web3-onboard/walletconnect": "^2.6.2",
"clsx": "^2.1.1",
"d3": "^7.9.0",
"dayjs": "^1.11.10",
Expand Down
12 changes: 12 additions & 0 deletions explorer/public/images/wallets/coinbase.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions explorer/public/images/wallets/metamask.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions explorer/public/images/wallets/other-wallets.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions explorer/public/images/wallets/walletConnect.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions explorer/src/app/[chain]/transfer/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { MainLayout } from 'components/layout/Layout'
import { TransferHeader } from 'components/layout/TransferHeader'
import type { ChildrenPageProps } from 'types/app'

export default async function Layout({ children }: ChildrenPageProps) {
return <MainLayout subHeader={<TransferHeader />}>{children}</MainLayout>
}
16 changes: 16 additions & 0 deletions explorer/src/app/[chain]/transfer/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Transfer } from 'components/Transfer'
import { NotFound } from 'components/layout/NotFound'
import { Routes } from 'constants/routes'
import { Metadata } from 'next'
import { FC } from 'react'
import type { ChainPageProps } from 'types/app'
import { getMetadata } from 'utils/metadata/basic'
import { isRouteSupportingNetwork } from 'utils/route'

export const generateMetadata = ({ params: { chain } }: ChainPageProps): Metadata =>
getMetadata(chain, 'Transfer', undefined)

const Page: FC<ChainPageProps> = ({ params: { chain } }) =>
isRouteSupportingNetwork(chain, Routes.transfer) ? <Transfer /> : <NotFound />

export default Page
120 changes: 120 additions & 0 deletions explorer/src/components/Transfer/AmountField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Field, Form, Formik } from 'formik'
import { useIndexers } from 'hooks/useIndexers'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { FC, useCallback, useMemo } from 'react'
import { limitNumberDecimals } from 'utils/number'
import * as Yup from 'yup'

interface AmountFieldProps {
maxAmount?: number
disabled?: boolean
}

interface FormValues {
amount: number
}

export const AmountField: FC<AmountFieldProps> = ({ maxAmount, disabled }) => {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const { tokenSymbol } = useIndexers()

const initialValues: FormValues = useMemo(() => {
try {
const _amount = searchParams.get('amount')
return _amount
? {
amount: parseFloat(_amount),
}
: {
amount: 0,
}
} catch {
return {
amount: 0,
}
}
}, [searchParams])

const formValidationSchema = Yup.object().shape({
amount: Yup.number().min(0, 'Amount need to be greater than 0').required('Amount is required'),
})

const setAmount = useCallback(
(amount: string) => {
const params = new URLSearchParams(searchParams.toString())
params.set('amount', amount)
router.push(`${pathname}?${params.toString()}`)
},
[pathname, router, searchParams],
)

const handleSetAmount = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => setAmount(e.target.value),
[setAmount],
)

return (
<div className='bg-grayLighter dark:border-purpleDeepAccent dark:bg-purpleUndertone flex flex-col space-y-1 rounded-xl border-grayDarker'>
<div className='text-grayText flex items-center justify-between text-sm dark:text-white'>
<span className='w-2/3' />
{maxAmount && (
<div
role='button'
tabIndex={0}
onClick={() => setAmount(maxAmount.toString())}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setAmount(maxAmount.toString())
}
}}
>
<span className='cursor-pointer font-bold hover:underline'>
Max: {limitNumberDecimals(maxAmount)} {tokenSymbol}
</span>{' '}
</div>
)}
</div>

<div className='text-grayText flex items-center justify-between text-sm dark:text-white'>
<Formik
initialValues={initialValues}
validationSchema={formValidationSchema}
enableReinitialize
onSubmit={() => {}}
>
{({ errors, touched, handleSubmit }) => (
<Form className='w-full' onSubmit={handleSubmit} data-testid='testOperatorStakeForm'>
<div className='flex items-center'>
<Field
name='amount'
placeholder='0'
type='number'
disabled={disabled}
onChange={handleSetAmount}
className={`mt-4 block w-full rounded-full bg-white from-primaryAccent to-blueUndertone px-4 py-[10px] text-sm text-gray-900 shadow-lg dark:bg-gradient-to-r dark:text-white
${
errors.amount &&
touched.amount &&
'block w-full rounded-full bg-white px-4 py-[10px] text-sm text-gray-900 shadow-lg dark:bg-blueDarkAccent'
}
`}
/>
<span className='ml-4 mt-4 text-lg font-bold'>{tokenSymbol}</span>
</div>
{errors.amount && touched.amount ? (
<div className='text-md mt-2 h-8 text-red-500' data-testid='errorMessage'>
{errors.amount}
</div>
) : (
<div className='text-md mt-2 h-8' data-testid='placeHolder' />
)}
</Form>
)}
</Formik>
</div>
</div>
)
}
114 changes: 114 additions & 0 deletions explorer/src/components/Transfer/NetworkSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { networks } from '@autonomys/auto-utils'
import { Listbox, Transition } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { AutonomysSymbol } from 'components/icons/AutonomysSymbol'
import { SwapDirection } from 'constants/transaction'
import useIndexers from 'hooks/useIndexers'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { FC, Fragment, useCallback, useMemo } from 'react'

interface NetworkSelectorProps {
direction: SwapDirection
}

export const NetworkSelector: FC<NetworkSelectorProps> = ({ direction }) => {
const { network } = useIndexers()
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()

const consensusNetwork = useMemo(() => networks.find((n) => n.id === network), [network])
const domains = useMemo(() => consensusNetwork?.domains, [consensusNetwork])
const chainId = searchParams.get(direction.toLowerCase())

const networkList = useMemo(() => {
if (!consensusNetwork) return []
const list = [{ id: 'consensus', name: 'Consensus' }]
if (domains) domains.forEach((d) => list.push({ id: 'domainId' + d.domainId, name: d.name }))
return list
}, [consensusNetwork, domains])

const selectedNetwork = useMemo(
() => networkList.find((n) => n.id === chainId),
[networkList, chainId],
)

const setChainId = useCallback(
(chainId: string) => {
const params = new URLSearchParams(searchParams.toString())
params.set(direction.toLowerCase(), chainId)
router.push(`${pathname}?${params.toString()}`)
},
[direction, pathname, router, searchParams],
)

if (!networkList) return null

return (
<Listbox value={selectedNetwork}>
<div className='relative mt-2'>
<Listbox.Button
className={
'relative mt-4 flex items-center rounded-full border-2 border-grayDark px-2 py-2 text-sm font-medium text-grayDarker dark:border-blueLight'
}
style={{ right: '10px', top: '50%', transform: 'translateY(-50%)' }}
>
<>
{selectedNetwork ? (
<>
<span className='text-grayDark dark:text-white'>
<AutonomysSymbol />
</span>
<span className='ml-2 hidden w-5 truncate pr-6 text-sm dark:text-white sm:block md:w-full'>
{selectedNetwork?.name}
</span>
</>
) : (
<span className='pr-6 text-grayDark dark:text-white'>Select Network</span>
)}
<span className='pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 dark:text-white'>
<ChevronDownIcon
className='size-5 text-gray-400 ui-open:rotate-180'
aria-hidden='true'
/>
</span>
</>
</Listbox.Button>
<Transition
as={Fragment}
leave='transition ease-in duration-100'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<Listbox.Options className='absolute right-0 z-50 mt-1 max-h-40 w-auto overflow-auto rounded-md bg-white py-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-blueAccent dark:text-white sm:text-sm md:w-full'>
{networkList.map((network) => (
<Listbox.Option
key={`domain-book-saved-${network.id}-label-${network.name}`}
className={({ active }) =>
`relative z-50 cursor-default select-none py-2 pr-4 text-gray-900 dark:text-white ${
active && 'bg-gray-100 dark:bg-blueDarkAccent'
}`
}
value={network.name}
onClick={() => setChainId(network.id)}
>
{({ selected }) => {
return (
<div className={`px-2 ${selected ? 'bg-grayLighter' : 'bg-transparent'}`}>
<div className='flex items-center'>
<AutonomysSymbol />
<span className='ml-2 hidden w-5 truncate text-sm sm:block md:w-full '>
{network.name}
</span>
</div>
</div>
)
}}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
)
}
Loading