Connect a site in five minutes
This page gets a React app talking to the Zunia extension: connect, restore after a reload, and sign in. The same session object signs transactions with CosmJS.
The packages are @zunialab/sdk-web, @zunialab/sdk-react and @zunialab/sdk-core at 0.1.0. They are not on npm yet. Until the v0.1.0 tag is published, build them from zunia-sdk:
git clone https://github.com/Zunia-Lab/zunia-sdk.git
cd zunia-sdk
pnpm install
pnpm build
Then add the workspace packages to your app, or pnpm add from the built packages/*/. After publish:
pnpm add @zunialab/sdk-react @zunialab/sdk-web
You also need a Zunia extension build. Clone zunia-extension, run pnpm install && pnpm build:chrome, and load .output/chrome-mv3 as an unpacked extension. Create a wallet there before you click Connect.
1. Connect
import { ConnectWithZuniaButton, useZuniaSession } from "@zunialab/sdk-react";
const options = { chains: ["cosmoshub-4"] };
export function Connect() {
const zunia = useZuniaSession({ restore: options });
if (zunia.restoring) return null;
if (zunia.connected) {
return <p>Connected as {zunia.accounts[0]?.address}</p>;
}
return (
<ConnectWithZuniaButton
loading={zunia.connecting}
onClick={() => zunia.connect(options).catch(() => {})}
/>
);
}
useZuniaSession({ restore: options }) reattaches after a reload without opening a window. connect asks the extension for those chains. The user approves in the toolbar popup (Chrome also draws an in-page prompt when it can tell the page is visible).
2. Sign in (no transaction)
Your server issues a nonce. The wallet signs a message that names your domain. You check the result on the server.
// Browser
const { nonce } = await (await fetch("/api/nonce", { method: "POST" })).json();
const proof = await zunia.signIn({ nonce });
await fetch("/api/verify", { method: "POST", body: JSON.stringify({ nonce, ...proof }) });
// Server
import { createNonce, verifySignIn } from "@zunialab/sdk-core";
const nonce = createNonce();
// store nonce for 10 minutes, then consume it once
const { address } = verifySignIn({
message,
signature,
nonce,
domain: "app.example.com", // from your config, not from the request Host
});
The wallet refuses a message whose domain is not the site asking. See Sign in with Zunia.
3. Send with CosmJS
import { SigningStargateClient } from "@cosmjs/stargate";
const signer = zunia.session.getOfflineSigner("cosmoshub-4");
const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer);
Results are Uint8Array and bigint on every transport.
Example
examples/dapp in zunia-sdk is a Vite app with connect, verified sign-in, a CosmJS send and a live event log. It is what the end-to-end tests drive.