-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add support for Snowbridge transfers (Ethereum -> Polkadot) ❄️
- Loading branch information
1 parent
631cd55
commit 2f16524
Showing
14 changed files
with
874 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
153 changes: 153 additions & 0 deletions
153
apps/playground/src/components/eth-bridge/EthBridgeTransfer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* eslint-disable @typescript-eslint/no-misused-promises */ | ||
/* eslint-disable @typescript-eslint/no-unsafe-call */ | ||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ | ||
|
||
import { Stack, Title, Box, Button } from "@mantine/core"; | ||
import { useDisclosure, useScrollIntoView } from "@mantine/hooks"; | ||
import { useState, useEffect } from "react"; | ||
import { BrowserProvider, ethers } from "ethers"; | ||
import ErrorAlert from "../ErrorAlert"; | ||
import EthBridgeTransferForm, { FormValues } from "./EthBridgeTransferForm"; | ||
import { EvmBuilder } from "@paraspell/sdk"; | ||
|
||
const EthBridgeTransfer = () => { | ||
const [selectedAccount, setSelectedAccount] = useState<string | null>(null); | ||
const [provider, setProvider] = useState<BrowserProvider | null>(null); | ||
|
||
const [alertOpened, { open: openAlert, close: closeAlert }] = | ||
useDisclosure(false); | ||
const [error, setError] = useState<Error | null>(null); | ||
const [loading, setLoading] = useState(false); | ||
|
||
const { scrollIntoView, targetRef } = useScrollIntoView<HTMLDivElement>({ | ||
offset: 0, | ||
}); | ||
|
||
useEffect(() => { | ||
const handleAccountsChanged = (accounts: string[]) => { | ||
if (accounts.length === 0) { | ||
console.log("Please connect to MetaMask."); | ||
setSelectedAccount(null); | ||
} else { | ||
setSelectedAccount(accounts[0]); | ||
} | ||
}; | ||
|
||
if (window.ethereum) { | ||
window.ethereum.on( | ||
"accountsChanged", | ||
handleAccountsChanged as (...args: unknown[]) => void | ||
); | ||
} | ||
|
||
return () => { | ||
if (window.ethereum) { | ||
window.ethereum.removeListener( | ||
"accountsChanged", | ||
handleAccountsChanged | ||
); | ||
} | ||
}; | ||
}, []); | ||
|
||
const connectWallet = async () => { | ||
if (!window.ethereum) { | ||
alert("Please install MetaMask!"); | ||
return; | ||
} | ||
|
||
const tempProvider = new ethers.BrowserProvider(window.ethereum); | ||
setProvider(tempProvider); | ||
try { | ||
await tempProvider.send("eth_requestAccounts", []); | ||
const tempSigner = await tempProvider.getSigner(); | ||
const account = await tempSigner.getAddress(); | ||
setSelectedAccount(account); | ||
console.log("Wallet connected:", account); | ||
} catch (error) { | ||
console.error("Error connecting to MetaMask:", error); | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
if (error) { | ||
scrollIntoView(); | ||
} | ||
}, [error, scrollIntoView]); | ||
|
||
const submitEthTransaction = async ({ | ||
to, | ||
amount, | ||
currency, | ||
address, | ||
}: FormValues) => { | ||
if (!provider) { | ||
throw new Error("Provider not initialized"); | ||
} | ||
|
||
const signer = await provider.getSigner(); | ||
|
||
if (!signer) { | ||
throw new Error("Signer not initialized"); | ||
} | ||
|
||
await EvmBuilder(provider) | ||
.to(to) | ||
.amount(amount) | ||
.currency(currency) | ||
.address(address) | ||
.signer(signer) | ||
.build(); | ||
}; | ||
|
||
const onSubmit = async (formValues: FormValues) => { | ||
if (!selectedAccount) { | ||
alert("No account selected, connect wallet first"); | ||
throw new Error("No account selected!"); | ||
} | ||
|
||
setLoading(true); | ||
|
||
try { | ||
await submitEthTransaction(formValues); | ||
alert("Transaction was successful!"); | ||
} catch (e) { | ||
if (e instanceof Error) { | ||
console.error(e); | ||
setError(e); | ||
openAlert(); | ||
} | ||
} finally { | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
const onAlertCloseClick = () => { | ||
closeAlert(); | ||
}; | ||
|
||
return ( | ||
<Stack gap="xl"> | ||
<Stack w="100%" maw={400} mx="auto" gap="lg"> | ||
<Title order={3}>Ethereum Bridge Transfer</Title> | ||
<Button size="xs" variant="outline" onClick={connectWallet}> | ||
{selectedAccount | ||
? `Connected: ${selectedAccount.substring(0, 6)}...${selectedAccount.substring(selectedAccount.length - 4)}` | ||
: "Connect Ethereum Wallet"} | ||
</Button> | ||
<EthBridgeTransferForm onSubmit={onSubmit} loading={loading} /> | ||
</Stack> | ||
<Box ref={targetRef}> | ||
{alertOpened && ( | ||
<ErrorAlert onAlertCloseClick={onAlertCloseClick}> | ||
{error?.message | ||
.split("\n\n") | ||
.map((line, index) => <p key={index}>{line}</p>)}{" "} | ||
</ErrorAlert> | ||
)} | ||
</Box> | ||
</Stack> | ||
); | ||
}; | ||
|
||
export default EthBridgeTransfer; |
84 changes: 84 additions & 0 deletions
84
apps/playground/src/components/eth-bridge/EthBridgeTransferForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { useForm } from "@mantine/form"; | ||
import { FC } from "react"; | ||
import { Button, Select, Stack, TextInput } from "@mantine/core"; | ||
import { NODES_WITH_RELAY_CHAINS, TNodePolkadotKusama } from "@paraspell/sdk"; | ||
import { isValidPolkadotAddress } from "../../utils"; | ||
|
||
export type FormValues = { | ||
to: TNodePolkadotKusama; | ||
currency: string; | ||
address: string; | ||
amount: string; | ||
}; | ||
|
||
type Props = { | ||
onSubmit: (values: FormValues) => void; | ||
loading: boolean; | ||
}; | ||
|
||
const EthBridgeTransferForm: FC<Props> = ({ onSubmit, loading }) => { | ||
const form = useForm<FormValues>({ | ||
initialValues: { | ||
to: "AssetHubPolkadot", | ||
currency: "WETH", | ||
amount: "1000000000", | ||
address: "5F5586mfsnM6durWRLptYt3jSUs55KEmahdodQ5tQMr9iY96", | ||
}, | ||
|
||
validate: { | ||
address: (value) => | ||
isValidPolkadotAddress(value) ? null : "Invalid address", | ||
}, | ||
}); | ||
|
||
return ( | ||
<form onSubmit={form.onSubmit(onSubmit)}> | ||
<Stack> | ||
<Select | ||
label="From" | ||
placeholder="Pick value" | ||
data={[...NODES_WITH_RELAY_CHAINS]} | ||
searchable | ||
disabled | ||
value="Ethereum" | ||
/> | ||
|
||
<Select | ||
label="To" | ||
placeholder="Pick value" | ||
data={[...NODES_WITH_RELAY_CHAINS]} | ||
searchable | ||
required | ||
{...form.getInputProps("to")} | ||
/> | ||
|
||
<TextInput | ||
label="Currency" | ||
placeholder="WETH" | ||
required | ||
{...form.getInputProps("currency")} | ||
/> | ||
|
||
<TextInput | ||
label="Recipient address" | ||
placeholder="0x0000000" | ||
required | ||
{...form.getInputProps("address")} | ||
/> | ||
|
||
<TextInput | ||
label="Amount" | ||
placeholder="0" | ||
required | ||
{...form.getInputProps("amount")} | ||
/> | ||
|
||
<Button type="submit" loading={loading}> | ||
Submit transaction | ||
</Button> | ||
</Stack> | ||
</form> | ||
); | ||
}; | ||
|
||
export default EthBridgeTransferForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,9 @@ | ||
/// <reference types="vite/client" /> | ||
|
||
import { MetaMaskInpageProvider } from "@metamask/providers"; | ||
|
||
declare global { | ||
interface Window { | ||
ethereum?: MetaMaskInpageProvider; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.