1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-11-22 01:46:51 +01:00

automatic network switching

This commit is contained in:
Matthias Kretschmann 2024-03-14 13:18:21 +00:00
parent d1315e7912
commit cbbb81e391
Signed by: m
GPG Key ID: 606EEEF3C479A91F
12 changed files with 84 additions and 48 deletions

View File

@ -32,8 +32,15 @@ table[aria-disabled='true'] {
align-items: center;
}
.table :global(.TokenLogo),
.table :global(.TokenLogo) img {
.table :global(.TokenLogo) {
width: 18px;
height: 18px;
border-radius: 50%;
border: 1px solid var(--border-color);
}
.table :global(.TokenLogo) img {
width: 100%;
height: 100%;
border-radius: 50%;
}

View File

@ -1,4 +1,4 @@
import { useAccount, useEnsName } from 'wagmi'
import { useAccount, useChains, useEnsName } from 'wagmi'
import styles from './Data.module.css'
import { useStore } from '@nanostores/react'
import { $amount, $selectedToken } from '@features/Web3/stores'
@ -13,11 +13,17 @@ export function Data({
ensResolved: string | null | undefined
isDisabled: boolean
}) {
const { address: from, chain } = useAccount()
const chains = useChains()
const { address: from } = useAccount()
const { data: ensFrom } = useEnsName({ address: from, chainId: 1 })
const selectedToken = useStore($selectedToken)
const amount = useStore($amount)
const networkName = chains.filter(
(chain) => chain.id === selectedToken?.chainId
)[0].name
return (
<table className={styles.table} aria-disabled={isDisabled}>
<tbody>
@ -54,7 +60,10 @@ export function Data({
<tr>
<td className={styles.label}>on</td>
<td>
<span className={styles.network}>{chain?.name}</span>
<div className="TokenLogo">
<img src={selectedToken?.chainLogo || ''} />
</div>
<span className={styles.network}>{networkName}</span>
</td>
</tr>

View File

@ -18,12 +18,6 @@ export function Preview() {
const { handleSend, isLoading, error } = useSend()
// TODO: Cancel flow if chain changes in preview as this can mess with token selection
// useEffect(() => {
// if (!chain?.id || $isInitSend.get() === false) return
// $isInitSend.set(false)
// }, [chain?.id])
return (
<>
<Data to={to} ensResolved={ensResolved} isDisabled={isLoading} />

View File

@ -3,18 +3,12 @@
}
.rainbowkit > div:first-child {
display: flex;
/* display: flex;
flex-direction: row-reverse;
justify-content: space-between;
justify-content: space-between; */
font-size: var(--font-size-small);
}
/* hide the account icon, and hope nothing else */
/* .rainbowkit button [aria-hidden] {
display: none;
} */
.rainbowkit [aria-label='Chain Selector'],
.rainbowkit [data-testid='rk-account-button'] div {
font-weight: var(--font-weight-base);

View File

@ -4,7 +4,7 @@ import styles from './RainbowKit.module.css'
export function RainbowKit() {
return (
<div className={styles.rainbowkit}>
<ConnectButton chainStatus="full" showBalance={false} />
<ConnectButton chainStatus="none" showBalance={false} />
</div>
)
}

View File

@ -21,8 +21,8 @@
}
.TokenLogo {
width: 28px;
height: 28px;
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: calc(var(--spacer) / 4);
border: 1px solid var(--border-color);
@ -30,15 +30,24 @@
align-items: center;
justify-content: center;
font-size: var(--font-size-mini);
position: relative;
}
.TokenLogo img {
.TokenLogoImage {
margin: 0;
width: 26px;
height: 26px;
border-radius: 50%;
}
.TokenChainLogo {
width: 16px;
height: 16px;
position: absolute;
bottom: -5px;
right: -5px;
border-radius: 50%;
border: 2px solid var(--border-color);
}
.TokenName,
.TokenBalance {
margin: 0;

View File

@ -41,10 +41,21 @@ export const Token = forwardRef<HTMLDivElement, SelectItemProps>(
<Select.ItemText>
<span className="TokenLogo">
{token?.logo ? (
<img src={token.logo} width="32" height="32" />
<img
src={token.logo}
width="32"
height="32"
className="TokenLogoImage"
/>
) : (
token?.symbol?.substring(0, 3)
)}
<img
src={token?.chainLogo}
width="20"
height="20"
className="TokenChainLogo"
/>
</span>
</Select.ItemText>
<div>

View File

@ -17,7 +17,7 @@ export function TokenSelect() {
const selectedToken = useStore($selectedToken)
const items = tokens?.map((token) => (
<Token key={token.address} token={token} />
<Token key={`${token.address}-${token.chainId}`} token={token} />
))
function handleValueChange(value: `0x${string}`) {
@ -32,12 +32,12 @@ export function TokenSelect() {
useEffect(() => {
if (selectedToken?.address || !tokens || !tokens?.length) return
handleValueChange('0x0')
// select ETH mainnet token
handleValueChange('0x0-1')
}, [tokens, selectedToken])
return tokens && address ? (
<Select.Root
// defaultValue={selectedToken?.address || tokens[0].address}
value={selectedToken?.address}
onValueChange={(value: `0x${string}`) => handleValueChange(value)}
disabled={isLoading}

View File

@ -2,12 +2,13 @@ export type GetToken = {
address: `0x${string}`
balance: number | undefined
chainId: number
name: string | null
symbol: string | null
decimals: number | null
logo: string | null
chainLogo: string | undefined
name: string | undefined
symbol: string | undefined
decimals: number | undefined
logo: string | undefined
price: {
usd: number | null
eur: number | null
usd: number | undefined
eur: number | undefined
}
}

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import useSWR, { type SWRResponse } from 'swr'
import { useChainId, useAccount } from 'wagmi'
import { useAccount, useChains } from 'wagmi'
import type { GetToken } from './types'
const fetcher = (url: string) => fetch(url).then((res) => res.json())
@ -10,26 +10,23 @@ const apiUrl = import.meta.env.PUBLIC_WEB3_API_URL
// Wrapper for fetching user tokens with swr.
//
export function useFetchTokens(): SWRResponse<GetToken[] | undefined, Error> {
const chainId = useChainId()
const { address } = useAccount()
// const { chains } = useConfig()
const chains = useChains()
const [url, setUrl] = useState<string | undefined>()
const fetchResults = useSWR<GetToken[] | undefined>(url, fetcher)
// Set url only after we have all data loaded on client,
// preventing initial fetch.
useEffect(() => {
if (!address || !chainId) {
if (!address || !chains) {
setUrl(undefined)
return
}
// const chainIds = chains.map((chain) => chain.id).join(',')
const url = `${apiUrl}/balance?address=${address}&chainIds=${chainId}`
const chainIds = chains.map((chain) => chain.id).join(',')
const url = `${apiUrl}/balance?address=${address}&chainIds=${chainIds}`
setUrl(url)
}, [address, chainId])
}, [address, chains])
return fetchResults
}

View File

@ -13,7 +13,7 @@ export async function send(
) {
if (!selectedToken?.decimals || !amount || !to) return
const isNative = selectedToken.address === '0x0'
const isNative = selectedToken.address.startsWith('0x0')
const requestNative = { chainId, to, value: parseEther(amount) }
const requestErc20 = {
chainId,

View File

@ -3,7 +3,7 @@ import { useStore } from '@nanostores/react'
import { useState } from 'react'
import { send } from './send'
import { isUnhelpfulErrorMessage } from './isUnhelpfulErrorMessage'
import { useAccount, useConfig, useEnsAddress } from 'wagmi'
import { useAccount, useConfig, useEnsAddress, useSwitchChain } from 'wagmi'
import siteConfig from '@config/blog.config'
export function useSend() {
@ -13,17 +13,31 @@ export function useSend() {
const { chainId } = useAccount()
const { ens } = siteConfig.author.ether
const { data: to } = useEnsAddress({ name: ens, chainId: 1 })
const { switchChain } = useSwitchChain()
const [isLoading, setIsLoading] = useState(false)
const [isError, setIsError] = useState(false)
const [error, setError] = useState<string | undefined>()
async function handleSend() {
if (!selectedToken || !amount || !to) return
// switch chains first
if (chainId !== selectedToken.chainId) {
switchChain({ chainId: selectedToken.chainId })
}
try {
setIsError(false)
setError(undefined)
setIsLoading(true)
const hash = await send(config, selectedToken, amount, to, chainId)
const hash = await send(
config,
selectedToken,
amount,
to,
selectedToken.chainId
)
if (hash) $txHash.set(hash)
} catch (error: unknown) {
const errorMessage = (error as Error).message