Implement persisting session (#5)
* Add layout to pages * Persist session * Style sign with ethereum page * Refactor modal * Remove unused styles * Implement disconnect session functionality * Add info about wallet in navbar * Remove unused imports * Use canonical JSON * Add line * Remove buffer from sign with ethereum page * Display signed message in dialog * Show modal on signature * Format cosmos signature * Add code block style for json messages * Display signature in first modal * Add urbit logo for connect wallet page * Display message for sign with ethereum * Handle review changes * Keep icon and text on same line --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com> Co-authored-by: Adw8 <adwait@deepstacksoft.com>
This commit is contained in:
parent
5e98e2e25a
commit
6bdaf60ff4
@ -20,6 +20,7 @@
|
|||||||
"@walletconnect/types": "^2.11.3",
|
"@walletconnect/types": "^2.11.3",
|
||||||
"assert": "^2.1.0",
|
"assert": "^2.1.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"canonical-json": "^0.0.4",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||||
|
|
||||||
import ConnectWallet from "./pages/ConnectWallet";
|
import ConnectWallet from "./pages/ConnectWallet";
|
||||||
import SignWithEthereum from "./pages/SignWithEthereum";
|
import SignWithEthereum from "./pages/SignWithEthereum";
|
||||||
import SignWithCosmos from "./pages/SignWithCosmos";
|
import SignWithCosmos from "./pages/SignWithCosmos";
|
||||||
import PageNotFound from "./pages/PageNotFound";
|
import PageNotFound from "./pages/PageNotFound";
|
||||||
|
import SignPageLayout from "./layout/SignPageLayout";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<ConnectWallet />} />
|
<Route path="/" element={<ConnectWallet />} />
|
||||||
<Route path="/sign-with-ethereum" element={<SignWithEthereum />} />
|
<Route element={<SignPageLayout />} >
|
||||||
<Route path="/sign-with-cosmos/:ethAddress/:cosmosAddress/:ethSignature" element={<SignWithCosmos />} />
|
<Route path="/sign-with-ethereum" element={<SignWithEthereum />} />
|
||||||
|
<Route path="/sign-with-cosmos/:ethAddress/:cosmosAddress/:ethSignature" element={<SignWithCosmos />} />
|
||||||
|
</Route>
|
||||||
<Route path="*" element={<PageNotFound />} />
|
<Route path="*" element={<PageNotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
@ -5,11 +5,13 @@ import React, {
|
|||||||
ReactNode,
|
ReactNode,
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useCallback,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
import SignClient from "@walletconnect/sign-client";
|
import SignClient from "@walletconnect/sign-client";
|
||||||
import { WalletConnectModal } from "@walletconnect/modal";
|
import { WalletConnectModal } from "@walletconnect/modal";
|
||||||
import { SessionTypes } from "@walletconnect/types";
|
import { SessionTypes } from "@walletconnect/types";
|
||||||
|
import { getSdkError } from "@walletconnect/utils";
|
||||||
|
|
||||||
const PROJECT_ID = process.env.REACT_APP_WALLET_CONNECT_ID;
|
const PROJECT_ID = process.env.REACT_APP_WALLET_CONNECT_ID;
|
||||||
assert(PROJECT_ID, "Wallet connect project id not provided");
|
assert(PROJECT_ID, "Wallet connect project id not provided");
|
||||||
@ -18,12 +20,16 @@ interface ContextValue {
|
|||||||
connect: () => Promise<void>;
|
connect: () => Promise<void>;
|
||||||
session: SessionTypes.Struct | null;
|
session: SessionTypes.Struct | null;
|
||||||
signClient: SignClient | undefined;
|
signClient: SignClient | undefined;
|
||||||
|
checkPersistedState: (client: SignClient)=> Promise<void>
|
||||||
|
disconnect: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const walletConnectContext = createContext<ContextValue>({
|
const walletConnectContext = createContext<ContextValue>({
|
||||||
connect: () => Promise.resolve(),
|
connect: () => Promise.resolve(),
|
||||||
session: null,
|
session: null,
|
||||||
signClient: undefined,
|
signClient: undefined,
|
||||||
|
checkPersistedState: ()=> Promise.resolve(),
|
||||||
|
disconnect: () => Promise.resolve(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const web3Modal = new WalletConnectModal({
|
const web3Modal = new WalletConnectModal({
|
||||||
@ -40,7 +46,28 @@ export const WalletConnectProvider = ({
|
|||||||
const [signClient, setSignClient] = useState<SignClient>();
|
const [signClient, setSignClient] = useState<SignClient>();
|
||||||
const [session, setSession] = useState<SessionTypes.Struct | null>(null);
|
const [session, setSession] = useState<SessionTypes.Struct | null>(null);
|
||||||
|
|
||||||
const createClient = async () => {
|
const disconnect = useCallback(async () => {
|
||||||
|
if (signClient && session) {
|
||||||
|
await signClient.disconnect({
|
||||||
|
topic: session.topic,
|
||||||
|
reason: getSdkError("USER_DISCONNECTED"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setSession(null)
|
||||||
|
}, [signClient, session]);
|
||||||
|
|
||||||
|
const checkPersistedState = useCallback(async(client: SignClient)=>{
|
||||||
|
if (client.session.length) {
|
||||||
|
const lastKeyIndex = client.session.keys.length - 1;
|
||||||
|
const session = client.session.get(
|
||||||
|
client.session.keys[lastKeyIndex]
|
||||||
|
);
|
||||||
|
setSession(session)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const createClient = useCallback(async () => {
|
||||||
const signClient = await SignClient.init({
|
const signClient = await SignClient.init({
|
||||||
projectId: PROJECT_ID,
|
projectId: PROJECT_ID,
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -52,7 +79,8 @@ export const WalletConnectProvider = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
setSignClient(signClient);
|
setSignClient(signClient);
|
||||||
};
|
await checkPersistedState(signClient)
|
||||||
|
}, [checkPersistedState])
|
||||||
|
|
||||||
const connect = async () => {
|
const connect = async () => {
|
||||||
if (!signClient) {
|
if (!signClient) {
|
||||||
@ -88,7 +116,7 @@ export const WalletConnectProvider = ({
|
|||||||
if (!signClient) {
|
if (!signClient) {
|
||||||
createClient();
|
createClient();
|
||||||
}
|
}
|
||||||
}, [signClient]);
|
}, [signClient, createClient]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<walletConnectContext.Provider
|
<walletConnectContext.Provider
|
||||||
@ -96,6 +124,8 @@ export const WalletConnectProvider = ({
|
|||||||
connect,
|
connect,
|
||||||
session,
|
session,
|
||||||
signClient,
|
signClient,
|
||||||
|
checkPersistedState,
|
||||||
|
disconnect
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@ -108,11 +138,15 @@ export const useWalletConnectContext = () => {
|
|||||||
connect,
|
connect,
|
||||||
session,
|
session,
|
||||||
signClient,
|
signClient,
|
||||||
|
checkPersistedState,
|
||||||
|
disconnect
|
||||||
} = useContext(walletConnectContext);
|
} = useContext(walletConnectContext);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connect,
|
connect,
|
||||||
session,
|
session,
|
||||||
signClient,
|
signClient,
|
||||||
|
checkPersistedState,
|
||||||
|
disconnect
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
1
src/decs.d.ts
vendored
Normal file
1
src/decs.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module 'canonical-json'
|
76
src/layout/SignPageLayout.tsx
Normal file
76
src/layout/SignPageLayout.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Outlet, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Toolbar,
|
||||||
|
IconButton,
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Container,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
||||||
|
|
||||||
|
const SignPageLayout = () => {
|
||||||
|
const { disconnect, session } = useWalletConnectContext();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const disconnectHandler = async () => {
|
||||||
|
await disconnect();
|
||||||
|
navigate("/");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Toolbar variant="dense">
|
||||||
|
<Avatar
|
||||||
|
alt="Urbit logo"
|
||||||
|
src="https://avatars.githubusercontent.com/u/5237680?s=200&v=4"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
edge="start"
|
||||||
|
color="inherit"
|
||||||
|
aria-label="menu"
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
>
|
||||||
|
Urbit
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
style={{
|
||||||
|
marginLeft: "auto",
|
||||||
|
}}
|
||||||
|
onClick={disconnectHandler}
|
||||||
|
>
|
||||||
|
Disconnect
|
||||||
|
</Button>
|
||||||
|
</Toolbar>
|
||||||
|
|
||||||
|
<Container maxWidth="md">
|
||||||
|
{session && (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "row", alignItems: 'flex-end' }}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Connected to: <b> {session.peer.metadata.name}</b>{" "}
|
||||||
|
</Typography>
|
||||||
|
<Avatar
|
||||||
|
variant="square"
|
||||||
|
alt="Peer logo"
|
||||||
|
src={session.peer.metadata.icons[0]}
|
||||||
|
sx={{ width: 20, height: 20, marginLeft: 1, paddingBottom: 0.5 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Session ID: <b>{session.topic} </b>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Outlet />
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignPageLayout;
|
@ -1,22 +1,46 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { Typography, Button, Box, Grid, Avatar } from '@mui/material';
|
||||||
|
|
||||||
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
||||||
|
|
||||||
// TODO: Connect wallet should not be accessible if session already exists
|
|
||||||
const ConnectWallet = () => {
|
const ConnectWallet = () => {
|
||||||
const { connect } = useWalletConnectContext();
|
const { connect, session } = useWalletConnectContext();
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
navigate("/sign-with-ethereum")
|
||||||
|
}
|
||||||
const handler = async () => {
|
const handler = async () => {
|
||||||
await connect();
|
await connect();
|
||||||
navigate("/sign-with-ethereum")
|
navigate("/sign-with-ethereum")
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Grid container spacing={2}>
|
||||||
<h1>Connect wallet </h1>
|
<Grid item xs={2}>
|
||||||
<button onClick={handler}>Connect</button>
|
</Grid>
|
||||||
</div>
|
<Grid item xs={8}>
|
||||||
|
<Box display="flex" flexDirection="column" alignItems="center" height="50vh" justifyContent="center" padding={5}>
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Avatar alt="Urbit logo" src="https://avatars.githubusercontent.com/u/5237680?s=200&v=4" />
|
||||||
|
<Typography variant="h4" component="h6">
|
||||||
|
Urbit Onboarding
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Typography variant="h6" component="h6" style={{ marginTop: '30px' }}>
|
||||||
|
Connect wallet
|
||||||
|
</Typography>
|
||||||
|
<Button variant="contained" onClick={handler} style={{ marginTop: '20px' }}>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,44 +1,59 @@
|
|||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { SnackbarProvider, enqueueSnackbar } from "notistack";
|
import { SnackbarProvider, enqueueSnackbar } from "notistack";
|
||||||
|
import canonicalStringify from "canonical-json";
|
||||||
|
|
||||||
import { Modal, Button, Typography, Box } from "@mui/material";
|
import {
|
||||||
import Alert from "@mui/material/Alert";
|
Button,
|
||||||
import CheckIcon from "@mui/icons-material/Check";
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
||||||
|
|
||||||
const style = {
|
|
||||||
position: "absolute" as const,
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
overflow: "scroll",
|
|
||||||
bgcolor: "background.paper",
|
|
||||||
border: "2px solid #000",
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SignWithCosmos = () => {
|
const SignWithCosmos = () => {
|
||||||
const { session, signClient } = useWalletConnectContext();
|
const { session, signClient } = useWalletConnectContext();
|
||||||
|
|
||||||
const { ethAddress, cosmosAddress, ethSignature } = useParams();
|
const { ethAddress, cosmosAddress, ethSignature } = useParams();
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const [result, setResult] = useState("");
|
const [cosmosSignature, setCosmosSignature] = useState("");
|
||||||
|
|
||||||
const message = useMemo(() => {
|
const displayAttestation = useMemo(() => {
|
||||||
return JSON.stringify(
|
return canonicalStringify(
|
||||||
{
|
{
|
||||||
ethAddress,
|
payload: {
|
||||||
ethSignature,
|
msg: "Onboarding my Azimuth ID onto UrbitChain",
|
||||||
text: "Attested by ethereum key",
|
address: ethAddress,
|
||||||
|
payload: {
|
||||||
|
msg: "Onboarding my cosmos validator onto UrbitChain",
|
||||||
|
address: cosmosAddress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
signatures: [cosmosSignature, ethSignature],
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
}, [ethAddress, ethSignature]);
|
}, [ethAddress, cosmosAddress, cosmosSignature, ethSignature]);
|
||||||
|
|
||||||
|
const message = useMemo(() => {
|
||||||
|
return canonicalStringify(
|
||||||
|
{
|
||||||
|
msg: "Onboarding my Azimuth ID onto UrbitChain",
|
||||||
|
address: ethAddress,
|
||||||
|
payload: {
|
||||||
|
msg: "Onboarding my cosmos validator onto UrbitChain",
|
||||||
|
address: cosmosAddress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}, [ethAddress, cosmosAddress]);
|
||||||
|
|
||||||
const signCosmos = async () => {
|
const signCosmos = async () => {
|
||||||
try {
|
try {
|
||||||
@ -62,8 +77,8 @@ const SignWithCosmos = () => {
|
|||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
setOpenModal(false);
|
setOpenModal(true);
|
||||||
setResult(signedMessage.signature);
|
setCosmosSignature(signedMessage.signature);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("err in signing ", error);
|
console.log("err in signing ", error);
|
||||||
@ -73,57 +88,69 @@ const SignWithCosmos = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box
|
||||||
<h1>Sign using Cosmos key</h1>
|
style={{
|
||||||
<p>Cosmos account: {cosmosAddress}</p>
|
display: "flex",
|
||||||
<Button
|
flexDirection: "column",
|
||||||
onClick={() => {
|
marginTop: "100px",
|
||||||
setOpenModal(true);
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h5">Sign with cosmos key</Typography>
|
||||||
|
<Typography variant="body1">Cosmos account: {cosmosAddress}</Typography>
|
||||||
|
<Typography variant="body1">
|
||||||
|
Message: <br />
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "lightgray",
|
||||||
|
padding: 3,
|
||||||
|
wordWrap: "break-word",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Sign with cosmos
|
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>{message} </pre>
|
||||||
</Button>
|
</Box>
|
||||||
<Modal
|
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
signCosmos();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign with cosmos
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Dialog
|
||||||
open={openModal}
|
open={openModal}
|
||||||
onClose={() => setOpenModal(false)}
|
onClose={() => {
|
||||||
aria-labelledby="modal-modal-title"
|
setOpenModal(false);
|
||||||
aria-describedby="modal-modal-description"
|
}}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
maxWidth="md"
|
||||||
>
|
>
|
||||||
<Box sx={style}>
|
<DialogContent>
|
||||||
<Typography id="modal-modal-title" variant="h6" component="h2">
|
Attested message to be broadcasted on chain
|
||||||
Sign with Cosmos
|
<Box
|
||||||
</Typography>
|
sx={{
|
||||||
<Typography id="modal-modal-description">Message to sign:</Typography>
|
backgroundColor: "lightgray",
|
||||||
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
|
padding: 3,
|
||||||
{message}
|
wordWrap: "break-word",
|
||||||
</Typography>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
|
||||||
onClick={() => {
|
{displayAttestation}{" "}
|
||||||
signCosmos();
|
</pre>
|
||||||
}}
|
</Box>
|
||||||
>
|
</DialogContent>
|
||||||
Sign
|
<DialogActions>
|
||||||
</Button>
|
<Button onClick={() => setOpenModal(false)}>Close</Button>
|
||||||
<Button onClick={() => setOpenModal(false)}>X</Button>
|
</DialogActions>
|
||||||
</div>
|
</Dialog>
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
{result && (
|
|
||||||
<Alert icon={<CheckIcon fontSize="inherit" />} severity="success">
|
|
||||||
Signed message ({result}) will be broadcasted to the chain
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
<SnackbarProvider />
|
<SnackbarProvider />
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,148 +1,185 @@
|
|||||||
import React, { useState, useMemo } from "react";
|
import React, { useState, useMemo, useEffect, useCallback } from "react";
|
||||||
import assert from "assert";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { SnackbarProvider, enqueueSnackbar } from "notistack";
|
import { SnackbarProvider, enqueueSnackbar } from "notistack";
|
||||||
|
import canonicalStringify from "canonical-json";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Modal,
|
|
||||||
Button,
|
Button,
|
||||||
Typography,
|
|
||||||
Box,
|
|
||||||
Select,
|
Select,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { utf8ToHex } from "@walletconnect/encoding";
|
import { utf8ToHex } from "@walletconnect/encoding";
|
||||||
|
|
||||||
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
||||||
|
|
||||||
const style = {
|
|
||||||
position: "absolute" as const,
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
overflow: "scroll",
|
|
||||||
bgcolor: "background.paper",
|
|
||||||
border: "2px solid #000",
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SignWithEthereum = () => {
|
const SignWithEthereum = () => {
|
||||||
window.Buffer = Buffer;
|
const { session, signClient, checkPersistedState } =
|
||||||
|
useWalletConnectContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (signClient && !session) {
|
||||||
|
checkPersistedState(signClient);
|
||||||
|
}
|
||||||
|
}, [session, signClient, checkPersistedState]);
|
||||||
|
|
||||||
const { session, signClient } = useWalletConnectContext();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [ethAddress, setEthAddress] = useState("");
|
const [ethAddress, setEthAddress] = useState("");
|
||||||
|
const [ethSignature, setEthSignature] = useState("");
|
||||||
|
|
||||||
const [cosmosAddress, setCosmosAddress] = useState("");
|
const [cosmosAddress, setCosmosAddress] = useState("");
|
||||||
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
||||||
const message = useMemo(() => {
|
const message = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
cosmosAddress,
|
msg: "Onboarding my cosmos validator onto UrbitChain",
|
||||||
text: "Onboarding azimuth ID onto urbit chain",
|
address: cosmosAddress,
|
||||||
};
|
};
|
||||||
}, [cosmosAddress]);
|
}, [cosmosAddress]);
|
||||||
|
|
||||||
const signEth = async () => {
|
const signEth = async () => {
|
||||||
try {
|
if (session && signClient) {
|
||||||
const jsonMessage = JSON.stringify(message, null, 2);
|
try {
|
||||||
const hexMsg = utf8ToHex(jsonMessage, true);
|
const jsonMessage = canonicalStringify(message);
|
||||||
const ethSignature: string = await signClient!.request({
|
const hexMsg = utf8ToHex(jsonMessage, true);
|
||||||
topic: session!.topic,
|
const ethSignature: string = await signClient!.request({
|
||||||
chainId: "eip155:1",
|
topic: session!.topic,
|
||||||
request: {
|
chainId: "eip155:1",
|
||||||
method: "personal_sign",
|
request: {
|
||||||
params: [hexMsg, ethAddress],
|
method: "personal_sign",
|
||||||
},
|
params: [hexMsg, ethAddress],
|
||||||
});
|
},
|
||||||
navigate(
|
});
|
||||||
`/sign-with-cosmos/${ethAddress}/${cosmosAddress}/${ethSignature}`
|
setEthSignature(ethSignature);
|
||||||
);
|
setOpenModal(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("err in signing ", error);
|
console.log("err in signing ", error);
|
||||||
setOpenModal(false);
|
setOpenModal(false);
|
||||||
enqueueSnackbar("Error signing message", { variant: "error" })
|
enqueueSnackbar("Error signing message", { variant: "error" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(session, "Session not found");
|
const submitHandler = useCallback(() => {
|
||||||
|
navigate(
|
||||||
|
`/sign-with-cosmos/${ethAddress}/${cosmosAddress}/${ethSignature}`
|
||||||
|
);
|
||||||
|
setOpenModal(false);
|
||||||
|
}, [ethAddress, cosmosAddress, ethSignature, navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Connected</h1>
|
{session ? (
|
||||||
<p>Session id: {session.topic}</p>
|
<Box
|
||||||
<p>Select Cosmos accounts: </p>
|
style={{
|
||||||
<Select
|
display: "flex",
|
||||||
labelId="demo-simple-select-label"
|
flexDirection: "column",
|
||||||
id="demo-simple-select"
|
marginTop: "100px",
|
||||||
value={cosmosAddress}
|
gap: "10px",
|
||||||
label="Cosmos address"
|
}}
|
||||||
onChange={(e: any) => {
|
>
|
||||||
setCosmosAddress(e.target.value);
|
<Typography variant="h5">Sign with ethereum key</Typography>
|
||||||
}}
|
<Typography variant="body1">Select Cosmos account:</Typography>
|
||||||
style={{ maxWidth: "500px", display: "block" }}
|
<Select
|
||||||
>
|
labelId="demo-simple-select-label"
|
||||||
{session.namespaces.cosmos.accounts.map((address, index) => (
|
id="demo-simple-select"
|
||||||
<MenuItem value={address.split(":")[2]} key={index}>
|
value={cosmosAddress}
|
||||||
{address.split(":")[2]}
|
label="Cosmos address"
|
||||||
</MenuItem>
|
onChange={(e: any) => {
|
||||||
))}
|
setCosmosAddress(e.target.value);
|
||||||
</Select>
|
}}
|
||||||
<p>Select Ethereum account: </p>
|
style={{ maxWidth: "600px", display: "block" }}
|
||||||
<Select
|
>
|
||||||
labelId="demo-simple-select-label"
|
{session?.namespaces.cosmos.accounts.map((address, index) => (
|
||||||
id="demo-simple-select"
|
<MenuItem value={address.split(":")[2]} key={index}>
|
||||||
value={ethAddress}
|
{address.split(":")[2]}
|
||||||
label="Ethereum address"
|
</MenuItem>
|
||||||
onChange={(e: any) => {
|
))}
|
||||||
setEthAddress(e.target.value);
|
</Select>
|
||||||
}}
|
<Typography variant="body1">Select Ethereum account: </Typography>
|
||||||
style={{ maxWidth: "500px", display: "block" }}
|
<Select
|
||||||
>
|
labelId="demo-simple-select-label"
|
||||||
{session.namespaces.eip155.accounts.map((address, index) => (
|
id="demo-simple-select"
|
||||||
<MenuItem value={address.split(":")[2]} key={index}>
|
value={ethAddress}
|
||||||
{address.split(":")[2]}
|
label="Ethereum address"
|
||||||
</MenuItem>
|
onChange={(e: any) => {
|
||||||
))}
|
setEthAddress(e.target.value);
|
||||||
</Select>
|
}}
|
||||||
|
style={{ maxWidth: "600px", display: "block" }}
|
||||||
|
>
|
||||||
|
{session?.namespaces.eip155.accounts.map((address, index) => (
|
||||||
|
<MenuItem value={address.split(":")[2]} key={index}>
|
||||||
|
{address.split(":")[2]}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
|
||||||
<Button
|
{ Boolean(ethAddress) && (<Box
|
||||||
onClick={() => {
|
sx={{
|
||||||
setOpenModal(true);
|
backgroundColor: "lightgray",
|
||||||
}}
|
padding: 3,
|
||||||
disabled={!Boolean(ethAddress)}
|
wordWrap: "break-word",
|
||||||
>
|
|
||||||
Sign using Ethereum key
|
|
||||||
</Button>
|
|
||||||
<Modal
|
|
||||||
open={openModal}
|
|
||||||
onClose={() => setOpenModal(false)}
|
|
||||||
aria-labelledby="modal-modal-title"
|
|
||||||
aria-describedby="modal-modal-description"
|
|
||||||
>
|
|
||||||
<Box sx={style}>
|
|
||||||
<Typography id="modal-modal-title" variant="h6" component="h2">
|
|
||||||
Sign using ethereum ({ethAddress})
|
|
||||||
</Typography>
|
|
||||||
<Typography id="modal-modal-description">Message to sign:</Typography>
|
|
||||||
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
|
|
||||||
{JSON.stringify(message, null, 2)}
|
|
||||||
</Typography>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button onClick={signEth}>Sign</Button>
|
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>{JSON.stringify(message, null, 2)} </pre>
|
||||||
<Button onClick={() => setOpenModal(false)}>Close</Button>
|
</Box>)}
|
||||||
</div>
|
<Box>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={signEth}
|
||||||
|
disabled={!Boolean(ethAddress)}
|
||||||
|
style={{ marginTop: "20px" }}
|
||||||
|
>
|
||||||
|
Sign using Ethereum key
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Dialog
|
||||||
|
open={openModal}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenModal(false);
|
||||||
|
}}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
maxWidth="md"
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
{"Signed message with ethereum key"}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography variant="body1" style={{ wordWrap: "break-word" }}>
|
||||||
|
Signature: {ethSignature}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "lightgray",
|
||||||
|
padding: 3,
|
||||||
|
wordWrap: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
|
||||||
|
{canonicalStringify(message, null, 2)}{" "}
|
||||||
|
</pre>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={submitHandler} autoFocus>
|
||||||
|
Sign with cosmos
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setOpenModal(false)}>Close</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
<SnackbarProvider />
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
) : (
|
||||||
<SnackbarProvider />
|
<>Loading...</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4100,6 +4100,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001591:
|
|||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce"
|
||||||
integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==
|
integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==
|
||||||
|
|
||||||
|
canonical-json@^0.0.4:
|
||||||
|
version "0.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/canonical-json/-/canonical-json-0.0.4.tgz#6579c072c3db5c477ec41dc978fbf2b8f41074a3"
|
||||||
|
integrity sha512-2sW7x0m/P7dqEnO0O87U7RTVQAaa7MELcd+Jd9FA6CYgYtwJ1TlDWIYMD8nuMkH1KoThsJogqgLyklrt9d/Azw==
|
||||||
|
|
||||||
case-sensitive-paths-webpack-plugin@^2.4.0:
|
case-sensitive-paths-webpack-plugin@^2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
|
resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
|
||||||
|
Loading…
Reference in New Issue
Block a user