Add support for onboarding to laconicd (#13)
* Change logo and app name * Get cosmos address from route * Navigate to second page on receiving eth signature * Add title to page * Use consistent formatting * Handle review changes * Remove unecessary field from onboarding message
This commit is contained in:
parent
60c95659a0
commit
359eddd385
11
src/App.tsx
11
src/App.tsx
@ -1,5 +1,5 @@
|
||||
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 SignWithEthereum from "./pages/SignWithEthereum";
|
||||
@ -14,13 +14,16 @@ function App() {
|
||||
<WalletConnectProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<ConnectWallet />} />
|
||||
<Route element={<SignPageLayout />} >
|
||||
<Route element={<SignPageLayout />}>
|
||||
<Route path="/sign-with-ethereum" element={<SignWithEthereum />} />
|
||||
<Route path="/sign-with-cosmos/:ethAddress/:ethSignature" element={<SignWithCosmos />} />
|
||||
<Route
|
||||
path="/sign-with-cosmos/:cosmosAddress/:ethSignature"
|
||||
element={<SignWithCosmos />}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="*" element={<PageNotFound />} />
|
||||
</Routes>
|
||||
</WalletConnectProvider >
|
||||
</WalletConnectProvider>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export const WalletConnectProvider = ({
|
||||
const [signClient, setSignClient] = useState<SignClient>();
|
||||
const [session, setSession] = useState<SessionTypes.Struct | null>(null);
|
||||
const isSignClientInitializing = useRef<boolean>(false);
|
||||
const navigate = useNavigate()
|
||||
const navigate = useNavigate();
|
||||
|
||||
const disconnect = useCallback(async () => {
|
||||
if (signClient && session) {
|
||||
@ -80,7 +80,7 @@ export const WalletConnectProvider = ({
|
||||
|
||||
client.on("session_delete", () => {
|
||||
setSession(null);
|
||||
navigate("/")
|
||||
navigate("/");
|
||||
});
|
||||
},
|
||||
[navigate]
|
||||
@ -91,10 +91,10 @@ export const WalletConnectProvider = ({
|
||||
const signClient = await SignClient.init({
|
||||
projectId: PROJECT_ID,
|
||||
metadata: {
|
||||
name: "Urbit onboarding app",
|
||||
description: "Urbit onboarding app",
|
||||
name: "Testnet onboarding app",
|
||||
description: "Testnet onboarding app",
|
||||
url: "localhost:3000",
|
||||
icons: ["https://avatars.githubusercontent.com/u/5237680?s=200&v=4"],
|
||||
icons: ["https://avatars.githubusercontent.com/u/92608123"],
|
||||
},
|
||||
});
|
||||
|
||||
@ -102,7 +102,7 @@ export const WalletConnectProvider = ({
|
||||
await subscribeToEvents(signClient);
|
||||
await checkPersistedState(signClient);
|
||||
isSignClientInitializing.current = false;
|
||||
}, [checkPersistedState, subscribeToEvents])
|
||||
}, [checkPersistedState, subscribeToEvents]);
|
||||
|
||||
const connect = async () => {
|
||||
if (!signClient) {
|
||||
@ -114,10 +114,12 @@ export const WalletConnectProvider = ({
|
||||
methods: ["personal_sign"],
|
||||
chains: ["eip155:1"],
|
||||
events: [],
|
||||
},cosmos: {
|
||||
methods:
|
||||
["cosmos_signDirect", "cosmos_signAmino", "cosmos_sendTransaction"],
|
||||
chains: ["cosmos:cosmoshub-4", "cosmos:laconic_9000-1"],
|
||||
},
|
||||
cosmos: {
|
||||
methods: [
|
||||
"cosmos_sendTransaction",
|
||||
],
|
||||
chains: ["cosmos:cosmoshub-4", "cosmos:laconic_9000-1"], // TODO: Get chain ID from .env
|
||||
events: [],
|
||||
},
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
Button,
|
||||
Typography,
|
||||
Container,
|
||||
Box
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
||||
@ -25,19 +25,19 @@ const SignPageLayout = () => {
|
||||
return (
|
||||
<>
|
||||
<Toolbar variant="dense">
|
||||
<Link to="/" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Link to="/" style={{ color: "inherit", textDecoration: "none" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Avatar
|
||||
alt="Urbit logo"
|
||||
src="https://avatars.githubusercontent.com/u/5237680?s=200&v=4"
|
||||
alt="Laconic logo"
|
||||
src="https://avatars.githubusercontent.com/u/92608123"
|
||||
/>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
sx={{ mr: 2 }}
|
||||
sx={{ ml: 2, mr: 2 }}
|
||||
>
|
||||
Urbit
|
||||
Testnet Onboarding
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Link>
|
||||
@ -57,7 +57,13 @@ const SignPageLayout = () => {
|
||||
<Container maxWidth="md">
|
||||
{session && (
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<div style={{ display: "flex", flexDirection: "row", alignItems: 'flex-end' }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Connected to: <b> {session.peer.metadata.name}</b>{" "}
|
||||
</Typography>
|
||||
@ -65,7 +71,12 @@ const SignPageLayout = () => {
|
||||
variant="square"
|
||||
alt="Peer logo"
|
||||
src={session.peer.metadata.icons[0]}
|
||||
sx={{ width: 20, height: 20, marginLeft: 1, paddingBottom: 0.5 }}
|
||||
sx={{
|
||||
width: 20,
|
||||
height: 20,
|
||||
marginLeft: 1,
|
||||
paddingBottom: 0.5,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Typography variant="body2">
|
||||
|
@ -1,38 +1,56 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import React, { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Typography, Button, Box, Container, Avatar } from '@mui/material';
|
||||
import { Typography, Button, Box, Container, Avatar } from "@mui/material";
|
||||
|
||||
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
||||
|
||||
const ConnectWallet = () => {
|
||||
const { connect, session } = useWalletConnectContext();
|
||||
|
||||
const navigate = useNavigate()
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (session) {
|
||||
navigate("/sign-with-ethereum")
|
||||
navigate("/sign-with-ethereum");
|
||||
}
|
||||
}, [session, navigate])
|
||||
}, [session, navigate]);
|
||||
|
||||
const handler = async () => {
|
||||
await connect();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg">
|
||||
<Box display="flex" flexDirection="column" alignItems="center" height="50vh" justifyContent="center" padding={5}>
|
||||
<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
|
||||
<Avatar
|
||||
alt="Laconic logo"
|
||||
src="https://avatars.githubusercontent.com/u/92608123"
|
||||
/>
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h6"
|
||||
style={{ marginLeft: "10px" }}
|
||||
>
|
||||
Testnet Onboarding
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="h6" component="h6" style={{ marginTop: '30px' }}>
|
||||
<Typography variant="h6" component="h6" style={{ marginTop: "30px" }}>
|
||||
Connect wallet
|
||||
</Typography>
|
||||
<Button variant="contained" onClick={handler} style={{ marginTop: '20px' }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handler}
|
||||
style={{ marginTop: "20px" }}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
</Box>
|
||||
|
@ -1,141 +1,87 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useParams, useLocation } from "react-router-dom";
|
||||
import { SnackbarProvider, enqueueSnackbar } from "notistack";
|
||||
import canonicalStringify from "canonical-json";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Box,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import LoadingButton from "@mui/lab/LoadingButton/LoadingButton";
|
||||
|
||||
// TODO: Import types exported from registry-sdk
|
||||
import {
|
||||
MsgCreateBondEncodeObject,
|
||||
} from "@cerc-io/registry-sdk/dist/types/cerc/bond/message";
|
||||
MsgOnboardParticipantEncodeObject,
|
||||
typeUrlMsgOnboardParticipant,
|
||||
} from "@cerc-io/registry-sdk";
|
||||
|
||||
import { useWalletConnectContext } from "../context/WalletConnectContext";
|
||||
|
||||
const SignWithCosmos = () => {
|
||||
const { session, signClient } = useWalletConnectContext();
|
||||
|
||||
const { ethAddress, ethSignature } = useParams();
|
||||
const { cosmosAddress, ethSignature } = useParams();
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [cosmosSignature, setCosmosSignature] = useState("");
|
||||
|
||||
const location = useLocation();
|
||||
const innerMessage = location.state;
|
||||
const cosmosAddress = innerMessage.address;
|
||||
const ethAddress = innerMessage.address;
|
||||
|
||||
const displayAttestation = useMemo(() => {
|
||||
return canonicalStringify(
|
||||
{
|
||||
payload: {
|
||||
msg: "Onboarding my Azimuth ID onto UrbitChain",
|
||||
address: ethAddress,
|
||||
payload: innerMessage,
|
||||
const onboardParticipantMsg: MsgOnboardParticipantEncodeObject =
|
||||
useMemo(() => {
|
||||
return {
|
||||
typeUrl: typeUrlMsgOnboardParticipant,
|
||||
value: {
|
||||
participant: cosmosAddress,
|
||||
ethPayload: innerMessage,
|
||||
ethSignature,
|
||||
},
|
||||
signatures: [cosmosSignature, ethSignature],
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
}, [ethAddress, cosmosSignature, ethSignature, innerMessage]);
|
||||
};
|
||||
}, [cosmosAddress, innerMessage, ethSignature]);
|
||||
|
||||
const message = useMemo(() => {
|
||||
return {
|
||||
msg: "Onboarding my Azimuth ID onto UrbitChain",
|
||||
address: ethAddress,
|
||||
payload: innerMessage,
|
||||
};
|
||||
}, [ethAddress, innerMessage]);
|
||||
|
||||
const signCosmos = async () => {
|
||||
const sendTransaction = async (
|
||||
transactionMessage: MsgOnboardParticipantEncodeObject
|
||||
) => {
|
||||
if (!ethAddress) {
|
||||
enqueueSnackbar("Set ethereum address");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const signDoc = {
|
||||
msgs: [],
|
||||
fee: { amount: [], gas: "23" },
|
||||
chain_id: "cosmos:cosmoshub-4",
|
||||
memo: canonicalStringify(message),
|
||||
account_number: "7",
|
||||
sequence: "54",
|
||||
};
|
||||
|
||||
const params = { signerAddress: cosmosAddress, signDoc };
|
||||
|
||||
const signedMessage = await signClient!.request<{ signature: string }>({
|
||||
const params = { transactionMessage, signer: cosmosAddress };
|
||||
const responseFromWallet = await signClient!.request<{
|
||||
code: number;
|
||||
}>({
|
||||
topic: session!.topic,
|
||||
chainId: "cosmos:cosmoshub-4",
|
||||
chainId: "cosmos:laconic_9000-1", // TODO: Get chain ID from .env
|
||||
request: {
|
||||
method: "cosmos_signAmino",
|
||||
method: "cosmos_sendTransaction",
|
||||
params,
|
||||
},
|
||||
});
|
||||
setIsLoading(false);
|
||||
setOpenModal(true);
|
||||
setCosmosSignature(signedMessage.signature);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
setOpenModal(false);
|
||||
enqueueSnackbar("Error signing message", { variant: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
const sendTransaction = async (
|
||||
transactionMessage: MsgCreateBondEncodeObject
|
||||
) => {
|
||||
try {
|
||||
if (ethAddress) {
|
||||
setIsLoading(true);
|
||||
|
||||
const params = { transactionMessage };
|
||||
const responseFromWallet = await signClient!.request<{
|
||||
code: number
|
||||
}>({
|
||||
topic: session!.topic,
|
||||
chainId: "cosmos:laconic_9000-1", // TODO: Get chain from WalletConnect
|
||||
request: {
|
||||
method: "cosmos_sendTransaction",
|
||||
params,
|
||||
},
|
||||
});
|
||||
if (responseFromWallet.code !== 0) {
|
||||
enqueueSnackbar("Error creating bond", { variant: "error" });
|
||||
} else {
|
||||
enqueueSnackbar("Created bond", { variant: "success" });
|
||||
}
|
||||
if (responseFromWallet.code !== 0) {
|
||||
enqueueSnackbar("Transaction not sent", { variant: "error" });
|
||||
} else {
|
||||
enqueueSnackbar("Transaction successful", { variant: "success" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
enqueueSnackbar("Error in sending transaction", { variant: "error" });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
// TODO: Add method to create attestation
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginTop: "100px",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5">Sign with cosmos key</Typography>
|
||||
<Typography variant="h5">Send transaction to chain</Typography>
|
||||
<Typography variant="body1">Cosmos account: {cosmosAddress}</Typography>
|
||||
<Typography variant="body1">
|
||||
Message: <br />
|
||||
Onboarding message: <br />
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
@ -145,49 +91,21 @@ const SignWithCosmos = () => {
|
||||
}}
|
||||
>
|
||||
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
|
||||
{canonicalStringify(message, null, 2)}{" "}
|
||||
{JSON.stringify(onboardParticipantMsg, null, 2)}{" "}
|
||||
</pre>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
signCosmos();
|
||||
onClick={async () => {
|
||||
await sendTransaction(onboardParticipantMsg);
|
||||
}}
|
||||
loading={isLoading}
|
||||
>
|
||||
Send transaction
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
<Dialog
|
||||
open={openModal}
|
||||
onClose={() => {
|
||||
setOpenModal(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
maxWidth="md"
|
||||
>
|
||||
<DialogContent>
|
||||
Response from chain
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "lightgray",
|
||||
padding: 3,
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
<pre style={{ whiteSpace: "pre-wrap", margin: 0 }}>
|
||||
{displayAttestation}{" "}
|
||||
</pre>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenModal(false)}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<SnackbarProvider />
|
||||
</Box>
|
||||
);
|
||||
|
@ -1,16 +1,11 @@
|
||||
import React, { useState, useMemo, useEffect, useCallback } from "react";
|
||||
import React, { useState, useMemo, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SnackbarProvider, enqueueSnackbar } from "notistack";
|
||||
import canonicalStringify from "canonical-json";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Select,
|
||||
MenuItem,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Box,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
@ -35,15 +30,14 @@ const SignWithEthereum = () => {
|
||||
|
||||
const [cosmosAddress, setCosmosAddress] = useState("");
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const message = useMemo(() => {
|
||||
return {
|
||||
msg: "Onboarding my cosmos validator onto UrbitChain",
|
||||
address: cosmosAddress,
|
||||
msg: "Register my account as a validator on the Laconic network",
|
||||
address: ethAddress,
|
||||
};
|
||||
}, [cosmosAddress]);
|
||||
}, [ethAddress]);
|
||||
|
||||
const signEth = async () => {
|
||||
if (session && signClient) {
|
||||
@ -51,7 +45,7 @@ const SignWithEthereum = () => {
|
||||
setIsLoading(true)
|
||||
const jsonMessage = canonicalStringify(message);
|
||||
const hexMsg = utf8ToHex(jsonMessage, true);
|
||||
const ethSignature: string = await signClient!.request({
|
||||
const receivedEthSig: string = await signClient!.request({
|
||||
topic: session!.topic,
|
||||
chainId: "eip155:1",
|
||||
request: {
|
||||
@ -61,22 +55,17 @@ const SignWithEthereum = () => {
|
||||
});
|
||||
setIsLoading(false)
|
||||
setEthSignature(ethSignature);
|
||||
setOpenModal(true);
|
||||
navigate(`/sign-with-cosmos/${cosmosAddress}/${receivedEthSig}`, {
|
||||
state: message,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("err in signing ", error);
|
||||
setIsLoading(false)
|
||||
setOpenModal(false);
|
||||
enqueueSnackbar("Error signing message", { variant: "error" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = useCallback(() => {
|
||||
navigate(`/sign-with-cosmos/${ethAddress}/${ethSignature}`, {
|
||||
state: message,
|
||||
});
|
||||
setOpenModal(false);
|
||||
}, [ethAddress, ethSignature, navigate, message]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -143,42 +132,6 @@ const SignWithEthereum = () => {
|
||||
Sign using Ethereum key
|
||||
</LoadingButton>
|
||||
</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>
|
||||
) : (
|
||||
|
Loading…
Reference in New Issue
Block a user