Handle signing using ethereum and cosmos key (#3)

* Sign using eth

* Sign using cosmos key

* Add functionality to choose account

* Use session.topic

* Refactor cosmosSign method

* Remove logs

* Update UI for sign in with cosmos

* Remove success route

* Add modal for sign in with ethereum

* Pass data in url params

* Use material UI dropdown

* Add route for redirecting to not found page

* Remove alert

* Add snackbar

* Remove unused package

* Use notistack

* Add polyfills

---------

Co-authored-by: Adw8 <adwait@deepstacksoft.com>
Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
Adwait Gharpure 2024-03-21 13:19:17 +05:30 committed by GitHub
parent 6f6126f015
commit 5e98e2e25a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 344 additions and 39 deletions

View File

@ -5,6 +5,7 @@
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.14",
"@mui/material": "^5.15.14",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
@ -13,10 +14,13 @@
"@types/node": "^16.18.90",
"@types/react": "^18.2.67",
"@types/react-dom": "^18.2.22",
"@walletconnect/encoding": "^1.0.2",
"@walletconnect/modal": "^2.6.2",
"@walletconnect/sign-client": "^2.11.3",
"@walletconnect/types": "^2.11.3",
"assert": "^2.1.0",
"buffer": "^6.0.3",
"notistack": "^3.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.3",

View File

@ -4,7 +4,7 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ConnectWallet from "./pages/ConnectWallet";
import SignWithEthereum from "./pages/SignWithEthereum";
import SignWithCosmos from "./pages/SignWithCosmos";
import Success from "./pages/Success";
import PageNotFound from "./pages/PageNotFound";
function App() {
return (
@ -12,8 +12,8 @@ function App() {
<Routes>
<Route path="/" element={<ConnectWallet />} />
<Route path="/sign-with-ethereum" element={<SignWithEthereum />} />
<Route path="/sign-with-cosmos" element={<SignWithCosmos />} />
<Route path="/success" element={<Success />} />
<Route path="/sign-with-cosmos/:ethAddress/:cosmosAddress/:ethSignature" element={<SignWithCosmos />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</Router>
);

View File

@ -17,11 +17,13 @@ assert(PROJECT_ID, "Wallet connect project id not provided");
interface ContextValue {
connect: () => Promise<void>;
session: SessionTypes.Struct | null;
signClient: SignClient | undefined;
}
const walletConnectContext = createContext<ContextValue>({
connect: () => Promise.resolve(),
session: null,
signClient: undefined,
});
const web3Modal = new WalletConnectModal({
@ -44,8 +46,8 @@ export const WalletConnectProvider = ({
metadata: {
name: "Urbit onboarding app",
description: "Urbit onboarding app",
url: "#",
icons: ["https://walletconnect.com/walletconnect-logo.png"],
url: "localhost:3000",
icons: ["https://avatars.githubusercontent.com/u/5237680?s=200&v=4"],
},
});
@ -93,6 +95,7 @@ export const WalletConnectProvider = ({
value={{
connect,
session,
signClient,
}}
>
{children}
@ -101,10 +104,15 @@ export const WalletConnectProvider = ({
};
export const useWalletConnectContext = () => {
const { connect, session } = useContext(walletConnectContext);
const {
connect,
session,
signClient,
} = useContext(walletConnectContext);
return {
connect,
session,
signClient,
};
};

View File

@ -1,5 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Buffer } from "buffer";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
@ -8,6 +9,8 @@ import { WalletConnectProvider } from './context/WalletConnectContext';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
// Reference: https://github.com/vitejs/vite/discussions/2785#discussion-3298776
globalThis.Buffer = Buffer;
root.render(
<React.StrictMode>
<WalletConnectProvider>

View File

@ -0,0 +1,11 @@
import React from 'react';
const PageNotFound = () => {
return (
<div>
<h1>Page not found </h1>
</div>
);
};
export default PageNotFound;

View File

@ -1,10 +1,128 @@
import React from 'react';
import { Link } from 'react-router-dom'
import React, { useMemo, useState } from "react";
import { useParams } from "react-router-dom";
import { SnackbarProvider, enqueueSnackbar } from "notistack";
import { Modal, Button, Typography, Box } from "@mui/material";
import Alert from "@mui/material/Alert";
import CheckIcon from "@mui/icons-material/Check";
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 { session, signClient } = useWalletConnectContext();
const { ethAddress, cosmosAddress, ethSignature } = useParams();
const [openModal, setOpenModal] = useState(false);
const [result, setResult] = useState("");
const message = useMemo(() => {
return JSON.stringify(
{
ethAddress,
ethSignature,
text: "Attested by ethereum key",
},
null,
2
);
}, [ethAddress, ethSignature]);
const signCosmos = async () => {
try {
if (ethAddress) {
const signDoc = {
msgs: [],
fee: { amount: [], gas: "23" },
chain_id: "cosmos:cosmoshub-4",
memo: message,
account_number: "7",
sequence: "54",
};
const params = { signerAddress: cosmosAddress, signDoc };
const signedMessage = await signClient!.request<{ signature: string }>({
topic: session!.topic,
chainId: "cosmos:cosmoshub-4",
request: {
method: "cosmos_signAmino",
params,
},
});
setOpenModal(false);
setResult(signedMessage.signature);
}
} catch (error) {
console.log("err in signing ", error);
setOpenModal(false);
enqueueSnackbar("Error signing message", { variant: "error" });
}
};
return (
<div>
<Link to="/success"><button>sign with cosmos</button></Link>
<h1>Sign using Cosmos key</h1>
<p>Cosmos account: {cosmosAddress}</p>
<Button
onClick={() => {
setOpenModal(true);
}}
>
Sign with cosmos
</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 with Cosmos
</Typography>
<Typography id="modal-modal-description">Message to sign:</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
{message}
</Typography>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
}}
>
<Button
onClick={() => {
signCosmos();
}}
>
Sign
</Button>
<Button onClick={() => setOpenModal(false)}>X</Button>
</div>
</Box>
</Modal>
{result && (
<Alert icon={<CheckIcon fontSize="inherit" />} severity="success">
Signed message ({result}) will be broadcasted to the chain
</Alert>
)}
<SnackbarProvider />
</div>
);
};

View File

@ -1,28 +1,148 @@
import React from 'react';
import assert from 'assert';
import { Link } from 'react-router-dom'
import React, { useState, useMemo } from "react";
import assert from "assert";
import { useNavigate } from "react-router-dom";
import { SnackbarProvider, enqueueSnackbar } from "notistack";
import { useWalletConnectContext } from '../context/WalletConnectContext';
import {
Modal,
Button,
Typography,
Box,
Select,
MenuItem,
} from "@mui/material";
import { utf8ToHex } from "@walletconnect/encoding";
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 { session } = useWalletConnectContext();
assert(session, "Session not found")
window.Buffer = Buffer;
const { session, signClient } = useWalletConnectContext();
const navigate = useNavigate();
const [ethAddress, setEthAddress] = useState("");
const [cosmosAddress, setCosmosAddress] = useState("");
const [openModal, setOpenModal] = useState(false);
const message = useMemo(() => {
return {
cosmosAddress,
text: "Onboarding azimuth ID onto urbit chain",
};
}, [cosmosAddress]);
const signEth = async () => {
try {
const jsonMessage = JSON.stringify(message, null, 2);
const hexMsg = utf8ToHex(jsonMessage, true);
const ethSignature: string = await signClient!.request({
topic: session!.topic,
chainId: "eip155:1",
request: {
method: "personal_sign",
params: [hexMsg, ethAddress],
},
});
navigate(
`/sign-with-cosmos/${ethAddress}/${cosmosAddress}/${ethSignature}`
);
} catch (error) {
console.log("err in signing ", error);
setOpenModal(false);
enqueueSnackbar("Error signing message", { variant: "error" })
}
};
assert(session, "Session not found");
return (
<div>
<h1>Connected</h1>
<p>Session id: {session.topic}</p>
<p>Ethereum accounts: </p>
<ul>{session.namespaces.eip155.accounts.map((address, index) =>
<li key={index}>{address}</li>
)}
</ul>
<p>Cosmos accounts: </p>
<ul>{session.namespaces.cosmos.accounts.map((address, index) =>
<li key={index}>{address}</li>
)}
</ul>
<Link to="/sign-with-cosmos"><button>Sign with ethereum</button></Link>
<p>Select Cosmos accounts: </p>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={cosmosAddress}
label="Cosmos address"
onChange={(e: any) => {
setCosmosAddress(e.target.value);
}}
style={{ maxWidth: "500px", display: "block" }}
>
{session.namespaces.cosmos.accounts.map((address, index) => (
<MenuItem value={address.split(":")[2]} key={index}>
{address.split(":")[2]}
</MenuItem>
))}
</Select>
<p>Select Ethereum account: </p>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={ethAddress}
label="Ethereum address"
onChange={(e: any) => {
setEthAddress(e.target.value);
}}
style={{ maxWidth: "500px", display: "block" }}
>
{session.namespaces.eip155.accounts.map((address, index) => (
<MenuItem value={address.split(":")[2]} key={index}>
{address.split(":")[2]}
</MenuItem>
))}
</Select>
<Button
onClick={() => {
setOpenModal(true);
}}
disabled={!Boolean(ethAddress)}
>
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>
<Button onClick={() => setOpenModal(false)}>Close</Button>
</div>
</Box>
</Modal>
<SnackbarProvider />
</div>
);
};

View File

@ -1,11 +0,0 @@
import React from 'react';
const Success = () => {
return (
<div>
Success
</div>
);
};
export default Success;

View File

@ -1887,6 +1887,13 @@
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz#f7c57b261904831877220182303761c012d05046"
integrity sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==
"@mui/icons-material@^5.15.14":
version "5.15.14"
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.14.tgz#333468c94988d96203946d1cfeb8f4d7e8e7de34"
integrity sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==
dependencies:
"@babel/runtime" "^7.23.9"
"@mui/material@^5.15.14":
version "5.15.14"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.14.tgz#a40bd5eccfa9fc925535e1f4d70c6cef77fa3a75"
@ -3037,6 +3044,15 @@
lodash.isequal "4.5.0"
uint8arrays "^3.1.0"
"@walletconnect/encoding@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@walletconnect/encoding/-/encoding-1.0.2.tgz#cb3942ad038d6a6bf01158f66773062dd25724da"
integrity sha512-CrwSBrjqJ7rpGQcTL3kU+Ief+Bcuu9PH6JLOb+wM6NITX1GTxR/MfNwnQfhLKK6xpRAyj2/nM04OOH6wS8Imag==
dependencies:
is-typedarray "1.0.0"
tslib "1.14.1"
typedarray-to-buffer "3.1.5"
"@walletconnect/environment@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@walletconnect/environment/-/environment-1.0.1.tgz#1d7f82f0009ab821a2ba5ad5e5a7b8ae3b214cd7"
@ -3891,6 +3907,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
batch@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
@ -4002,6 +4023,14 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
builtin-modules@^3.1.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
@ -4187,6 +4216,11 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
clsx@^1.1.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
clsx@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
@ -6112,6 +6146,11 @@ globby@^11.0.4, globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
goober@^2.0.33:
version "2.1.14"
resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.14.tgz#4a5c94fc34dc086a8e6035360ae1800005135acd"
integrity sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@ -6415,6 +6454,11 @@ identity-obj-proxy@^3.0.0:
dependencies:
harmony-reflect "^1.4.6"
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.0, ignore@^5.2.4:
version "5.3.1"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
@ -6743,7 +6787,7 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.3:
dependencies:
which-typed-array "^1.1.14"
is-typedarray@^1.0.0:
is-typedarray@1.0.0, is-typedarray@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
@ -8127,6 +8171,14 @@ normalize-url@^6.0.1:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
notistack@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/notistack/-/notistack-3.0.1.tgz#daf59888ab7e2c30a1fa8f71f9cba2978773236e"
integrity sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==
dependencies:
clsx "^1.1.0"
goober "^2.0.33"
npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@ -10760,7 +10812,7 @@ typed-array-length@^1.0.5:
is-typed-array "^1.1.13"
possible-typed-array-names "^1.0.0"
typedarray-to-buffer@^3.1.5:
typedarray-to-buffer@3.1.5, typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==