Skip to content

Commit e5de910

Browse files
authored
Merge pull request #346 from ourzora/bridge-modal
Add inline bridging for EOA accounts
2 parents 99fd6e9 + 4daa4ea commit e5de910

File tree

11 files changed

+530
-22
lines changed

11 files changed

+530
-22
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as yup from 'yup'
2+
3+
export interface BridgeFormValues {
4+
amount?: number
5+
}
6+
7+
const bridgeFormSchema = (userL1Balance: number) =>
8+
yup.object({
9+
amount: yup
10+
.number()
11+
.required()
12+
.max(userL1Balance, 'Must bridge less than L1 balance.')
13+
.test(
14+
'is-greater-than-0',
15+
'Must bridge more than 0 ETH',
16+
(value) => !!value && value > 0
17+
),
18+
})
19+
20+
export default bridgeFormSchema
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { style } from '@vanilla-extract/css'
2+
import { vars } from '@zoralabs/zord'
3+
4+
export const chainPopUpButton = style({
5+
backgroundColor: 'white',
6+
selectors: {
7+
'&:hover': {
8+
backgroundColor: vars.color.background2,
9+
},
10+
},
11+
})
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import { useConnectModal } from '@rainbow-me/rainbowkit'
2+
import { sendTransaction } from '@wagmi/core'
3+
import { Box, Button, Flex, Heading, Text } from '@zoralabs/zord'
4+
import { parseEther } from 'ethers/lib/utils.js'
5+
import { Formik } from 'formik'
6+
import Image from 'next/image'
7+
import Link from 'next/link'
8+
import { useState } from 'react'
9+
import { useAccount, useBalance, useNetwork, useSwitchNetwork } from 'wagmi'
10+
11+
import Input from 'src/components/Input/Input'
12+
import { L2ChainType, PUBLIC_L1_BRIDGE_ADDRESS } from 'src/constants/addresses'
13+
import { PUBLIC_DEFAULT_CHAINS } from 'src/constants/defaultChains'
14+
import { useBridgeModal } from 'src/hooks/useBridgeModal'
15+
import { useIsContract } from 'src/hooks/useIsContract'
16+
import { useChainStore } from 'src/stores/useChainStore'
17+
import { formatCryptoVal } from 'src/utils/numbers'
18+
19+
import { Icon } from '../Icon'
20+
import bridgeFormSchema, { BridgeFormValues } from './BridgeForm.schema'
21+
import { NetworkSelector } from './NetworkSelector'
22+
23+
export const BridgeForm = () => {
24+
const { address } = useAccount()
25+
const { chain: userChain } = useNetwork()
26+
const { switchNetwork } = useSwitchNetwork()
27+
const { closeBridgeModal } = useBridgeModal()
28+
const { openConnectModal } = useConnectModal()
29+
const { data: isContractWallet } = useIsContract({ address })
30+
const [loading, setLoading] = useState(false)
31+
32+
const { chain: appChain } = useChainStore()
33+
34+
const l1Chain = PUBLIC_DEFAULT_CHAINS[0]
35+
const [l2Chain, setL2Chain] = useState(
36+
appChain.id !== l1Chain.id ? appChain : PUBLIC_DEFAULT_CHAINS[1]
37+
)
38+
39+
const isWalletOnL1 = userChain?.id === l1Chain.id
40+
41+
const { data: userL1Balance } = useBalance({
42+
address,
43+
chainId: l1Chain.id,
44+
})
45+
46+
const { data: userL2Balance } = useBalance({
47+
address,
48+
chainId: l2Chain.id,
49+
})
50+
51+
const initialValues: BridgeFormValues = {
52+
amount: 0,
53+
}
54+
55+
const handleSubmit = async (values: BridgeFormValues) => {
56+
const bridge = PUBLIC_L1_BRIDGE_ADDRESS[l2Chain.id as L2ChainType]
57+
58+
if (!values.amount || !bridge) return
59+
60+
setLoading(true)
61+
try {
62+
const { wait } = await sendTransaction({
63+
request: {
64+
to: PUBLIC_L1_BRIDGE_ADDRESS[l2Chain.id as L2ChainType],
65+
value: parseEther(values.amount.toString()),
66+
},
67+
mode: 'recklesslyUnprepared',
68+
})
69+
await wait()
70+
} catch (err) {
71+
console.log('err', err)
72+
} finally {
73+
setLoading(false)
74+
}
75+
}
76+
77+
const formattedL1Balance = userL1Balance ? parseFloat(userL1Balance.formatted) : 0
78+
const formattedL2Balance = userL2Balance ? parseFloat(userL2Balance.formatted) : 0
79+
80+
const getButtonText = (isAmountInvalid: boolean) => {
81+
if (isContractWallet) return 'Contract wallets are not supported'
82+
if (loading) return 'Bridging...'
83+
if (isAmountInvalid) return 'Insufficent ETH balance'
84+
return 'Bridge'
85+
}
86+
87+
return (
88+
<Box position={'relative'}>
89+
<Box
90+
onClick={closeBridgeModal}
91+
cursor={'pointer'}
92+
position={'absolute'}
93+
top="x0"
94+
right="x3"
95+
>
96+
<Icon id="cross" fill="text3" />
97+
</Box>
98+
<Heading size="xs" fontWeight="display">
99+
Bridge
100+
</Heading>
101+
102+
<Formik
103+
initialValues={initialValues}
104+
validationSchema={bridgeFormSchema(formattedL1Balance)}
105+
onSubmit={handleSubmit}
106+
validateOnBlur
107+
validateOnMount={false}
108+
validateOnChange={false}
109+
>
110+
{({ errors, touched, isValid, submitForm }) => {
111+
const isAmountInvalid = !!errors.amount && touched.amount
112+
113+
return (
114+
<Box>
115+
<Box
116+
mt="x5"
117+
p="x4"
118+
borderColor="border"
119+
borderStyle="solid"
120+
borderRadius="curved"
121+
borderWidth="normal"
122+
>
123+
<Input
124+
name={'amount'}
125+
label={
126+
<Flex>
127+
<Text mr="x2" fontWeight="heading">
128+
From
129+
</Text>
130+
<Box mr="x1">
131+
<Image
132+
alt="L1 Chain"
133+
style={{
134+
height: 20,
135+
width: 20,
136+
}}
137+
src={l1Chain.icon}
138+
/>
139+
</Box>
140+
<Text fontWeight="heading">{l1Chain.name}</Text>
141+
</Flex>
142+
}
143+
secondaryLabel={'ETH'}
144+
autoComplete={'off'}
145+
type={'number'}
146+
placeholder={0}
147+
min={0}
148+
max={userL1Balance?.formatted}
149+
step={'any'}
150+
/>
151+
<Text mt="x3" color="text3">
152+
Balance: {formatCryptoVal(formattedL1Balance)} ETH
153+
</Text>
154+
</Box>
155+
156+
<Box
157+
mt="x5"
158+
p="x4"
159+
borderColor="border"
160+
borderStyle="solid"
161+
borderRadius="curved"
162+
borderWidth="normal"
163+
>
164+
<Input
165+
name={'amount'}
166+
label={
167+
<Flex align={'center'}>
168+
<Text mr="x2" fontWeight="heading">
169+
To
170+
</Text>
171+
<NetworkSelector
172+
selectedChain={l2Chain}
173+
setSelectedChain={setL2Chain}
174+
/>
175+
</Flex>
176+
}
177+
secondaryLabel={'ETH'}
178+
autoComplete={'off'}
179+
type={'number'}
180+
placeholder={0}
181+
min={0}
182+
max={userL2Balance?.formatted}
183+
step={'any'}
184+
/>
185+
<Text mt="x3" color="text3">
186+
Balance: {formatCryptoVal(formattedL2Balance)} ETH
187+
</Text>
188+
</Box>
189+
190+
{!address ? (
191+
<Button onClick={openConnectModal} type="button" w="100%" mt="x5">
192+
Connect wallet
193+
</Button>
194+
) : isWalletOnL1 ? (
195+
<Button
196+
disabled={!isValid || isContractWallet || loading}
197+
onClick={submitForm}
198+
type="submit"
199+
w="100%"
200+
mt="x5"
201+
>
202+
{getButtonText(isAmountInvalid || false)}
203+
</Button>
204+
) : (
205+
<Button
206+
onClick={() => switchNetwork?.(l1Chain.id)}
207+
type="button"
208+
w="100%"
209+
mt="x5"
210+
>
211+
Switch network
212+
</Button>
213+
)}
214+
215+
<Box fontSize={12} color="text3" mt="x4">
216+
By proceeding, you agree to Nouns Builder's{' '}
217+
<Link href="/legal" style={{ textDecoration: 'underline' }}>
218+
terms
219+
</Link>
220+
. THIS BRIDGE IS DEPOSIT ONLY. YOU MUST USE ANOTHER BRIDGE TO WITHDRAW.
221+
Learn more about{' '}
222+
<Box
223+
as="a"
224+
rel="noopener noreferrer"
225+
target="_blank"
226+
style={{ textDecoration: 'underline' }}
227+
href="https://docs.zora.co/docs/guides/builder-bridging"
228+
>
229+
bridging
230+
</Box>
231+
.
232+
</Box>
233+
</Box>
234+
)
235+
}}
236+
</Formik>
237+
</Box>
238+
)
239+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useRouter } from 'next/router'
2+
import { useEffect } from 'react'
3+
4+
import AnimatedModal from '../Modal/AnimatedModal'
5+
import { BridgeForm } from './BridgeForm'
6+
7+
export const BridgeModal = () => {
8+
const {
9+
query: { bridge },
10+
} = useRouter()
11+
12+
useEffect(() => {
13+
document.body.style.overflow = !!bridge ? 'hidden' : 'unset'
14+
}, [bridge])
15+
16+
return (
17+
<AnimatedModal open={!!bridge}>
18+
<BridgeForm />
19+
</AnimatedModal>
20+
)
21+
}

0 commit comments

Comments
 (0)