Skip to content

Commit

Permalink
Add example of how to verify signature in demo
Browse files Browse the repository at this point in the history
  • Loading branch information
justinbarry committed Jun 25, 2024
1 parent 8d4406f commit c7bacc6
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 34 deletions.
2 changes: 2 additions & 0 deletions apps/demo-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
},
"dependencies": {
"@burnt-labs/abstraxion": "workspace:*",
"@burnt-labs/abstraxion-core": "workspace:*",
"@burnt-labs/constants": "workspace:*",
"@burnt-labs/signers": "workspace:*",
"@burnt-labs/ui": "workspace:*",
"@cosmjs/amino": "^0.32.3",
"@cosmjs/cosmwasm-stargate": "^0.32.2",
"@heroicons/react": "^2.1.4",
"@keplr-wallet/cosmos": "^0.12.80",
"cosmjs-types": "^0.9.0",
"next": "^14.0.3",
Expand Down
38 changes: 4 additions & 34 deletions apps/demo-app/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import {
useAbstraxionSigningClient,
useModal,
} from "@burnt-labs/abstraxion";
import { Button, Input } from "@burnt-labs/ui";
import { Button } from "@burnt-labs/ui";
import "@burnt-labs/ui/dist/index.css";
import type { ExecuteResult } from "@cosmjs/cosmwasm-stargate";
import { SignArb } from "../components/sign-arb.tsx";

const seatContractAddress =
"xion1z70cvc08qv5764zeg3dykcyymj5z6nu4sqr7x8vl4zjef2gyp69s9mmdka";

type ExecuteResultOrUndefined = ExecuteResult | undefined;

export default function Page(): JSX.Element {
// Abstraxion hooks
const { data: account } = useAbstraxionAccount();
Expand All @@ -28,7 +30,6 @@ export default function Page(): JSX.Element {
const [loading, setLoading] = useState(false);
const [executeResult, setExecuteResult] =
useState<ExecuteResultOrUndefined>(undefined);
const [arbitraryMessage, setArbitraryMessage] = useState<string>("");

const blockExplorerUrl = `https://explorer.burnt.com/xion-testnet-1/tx/${executeResult?.transactionHash}`;

Expand All @@ -43,14 +44,6 @@ export default function Page(): JSX.Element {
const oneYearFromNow = new Date();
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);

async function handleSign(): Promise<void> {
if (client?.granteeAddress) {
const response = await signArb?.(client.granteeAddress, arbitraryMessage);
// eslint-disable-next-line no-console -- We log this for testing purposes.
console.log(response);
}
}

async function claimSeat(): Promise<void> {
setLoading(true);
const msg = {
Expand Down Expand Up @@ -128,30 +121,7 @@ export default function Page(): JSX.Element {
LOGOUT
</Button>
) : null}
{signArb ? (
<div className="mt-10 w-full">
<h1 className="text-lg font-normal tracking-tighter text-white">
SIGN ARBITRARY MESSAGE
</h1>
<Input
className="ui-w-full ui-mb-4"
onChange={(e) => {
setArbitraryMessage(e.target.value);
}}
placeholder="Message..."
value={arbitraryMessage}
/>
<Button
disabled={loading}
fullWidth
onClick={() => {
void handleSign();
}}
>
Sign
</Button>
</div>
) : null}
{signArb ? <SignArb /> : null}
</>
) : null}
<Abstraxion
Expand Down
43 changes: 43 additions & 0 deletions apps/demo-app/src/components/blocking-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useState } from "react";
import { ArrowPathIcon } from "@heroicons/react/24/outline";

interface BlockingButtonProps {
text: string;

/**
* Function to execute when button is pressed
*/
onExecute(): Promise<void>;
}

export function BlockingButton({ text, onExecute }: BlockingButtonProps) {
const [loading, setLoading] = useState(false);

const handleClick = async () => {
try {
setLoading(true);
// Execute the supplied function
await onExecute();
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};

return (
<button
className="hover:ui-bg-neutral-100 min-w-full rounded-md bg-white px-4 py-2 text-black shadow-md"
onClick={() => handleClick()}
disabled={loading}
>
{loading ? (
<p>
<ArrowPathIcon className="h-5 w-5 animate-spin content-center" />
</p>
) : (
text
)}
</button>
);
}
188 changes: 188 additions & 0 deletions apps/demo-app/src/components/sign-arb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { Button, Input } from "@burnt-labs/ui";
import { useState } from "react";
import {
useAbstraxionAccount,
useAbstraxionSigningClient,
} from "@burnt-labs/abstraxion";

import type { GranteeSignerClient } from "@burnt-labs/abstraxion-core";

import { BlockingButton } from "./blocking-button.tsx";

const copyToClipboard = (textToCopy: string) => async () => {
await window.navigator.clipboard.writeText(textToCopy);
};

// This component is a wizard of sorts showcasing the ability to sign and verify arbitrary ADR-036 signatures
export function SignArb() {
const [signArbResult, setSignArbResult] = useState<string | undefined>();
const [arbitraryMessage, setArbitraryMessage] = useState<string>("");

if (signArbResult) {
return (
<SignArbVerify
message={arbitraryMessage}
signature={signArbResult}
clearSigFn={async () => {
setArbitraryMessage("");
setSignArbResult(undefined);
}}
/>
);
}

return (
<SignArbSign
setResult={setSignArbResult}
arbitraryMessage={arbitraryMessage}
setArbitraryMessage={setArbitraryMessage}
/>
);
}

interface SignArbSignProps {
setResult: (signature: string) => void;
arbitraryMessage: string;
setArbitraryMessage: (signature: string) => void;
}

function SignArbSign({
setResult,
arbitraryMessage,
setArbitraryMessage,
}: SignArbSignProps) {
const { client, signArb } = useAbstraxionSigningClient();

async function handleSign(): Promise<void> {
if (client?.granteeAddress) {
const response = await signArb?.(client.granteeAddress, arbitraryMessage);
// eslint-disable-next-line no-console -- We log this for testing purposes.
if (response) setResult(response);
}
}

return (
<div className="mt-10 w-full">
<h3 className="text-sm font-normal tracking-tighter text-white">
SIGN ARBITRARY MESSAGE
</h3>
<Input
className="ui-w-full ui-mb-4"
onChange={(e) => {
setArbitraryMessage(e.target.value);
}}
placeholder="Message..."
value={arbitraryMessage}
/>
<Button
fullWidth
onClick={() => {
void handleSign();
}}
>
Sign
</Button>
</div>
);
}

interface SignArbVerifyProps {
message: string;
signature: string;
clearSigFn(): Promise<void>;
}

const verifySignatureWithApi = async (
client: GranteeSignerClient,
metaAccountAddress: string,
message: string,
signature: string,
) => {
const granteeAccountData = await client.getGranteeAccountData();

if (!granteeAccountData) return false;

const userSessionAddress = granteeAccountData.address;
const userSessionPubKey = Buffer.from(granteeAccountData.pubkey).toString(
"Base64",
);

const baseUrl = `${window.location.origin}/api/check-signature`;
const url = new URL(baseUrl);
const params = new URLSearchParams({
userSessionAddress,
userSessionPubKey,
metaAccountAddress,
message,
signature,
});
url.search = params.toString();
const data = await fetch(url, {
cache: "no-store",
})
.then(
(
response,
): Promise<{
valid: boolean;
}> => response.json(),
)
.catch((err) => {
console.error("Could not fetch grants info", err);
});

if (data && data.valid) {
return true;
}

return false;
};

function SignArbVerify({ message, signature, clearSigFn }: SignArbVerifyProps) {
const { client } = useAbstraxionSigningClient();
const { data: account } = useAbstraxionAccount();

if (!client) return <div></div>;

return (
<div className="mt-10 w-full">
<h1 className="text-lg font-normal tracking-tighter text-white">
Signature
</h1>
<p className="m-2 overflow-hidden text-ellipsis text-xs">{signature}</p>
<h1 className="text-lg font-normal tracking-tighter text-white">
User Session Address
</h1>
<p className="m-2 overflow-hidden text-ellipsis text-xs">
{client.granteeAddress}
</p>
<h1 className="text-lg font-normal tracking-tighter text-white">
Meta Account Address
</h1>
<p className="m-2 overflow-hidden text-ellipsis text-xs">
{account.bech32Address}
</p>

<div className="flex-col space-y-2">
<BlockingButton
text="Verify Signature"
onExecute={async () => {
const result = await verifySignatureWithApi(
client,
account.bech32Address,
message,
signature,
);

alert(`You message is ${result ? "valid" : "invalid"}!!!!`);
}}
/>
<BlockingButton
text="Copy Signature to Clipboard"
onExecute={copyToClipboard(signature || "")}
/>
<BlockingButton text="Reset" onExecute={clearSigFn} />
</div>
</div>
);
}
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c7bacc6

Please sign in to comment.