@neuraiproject/neurai-connect
v0.0.2
Published
Neurai Connect SDK for websites: login with a Neurai wallet by QR and request signatures over an encrypted session.
Maintainers
Readme
@neuraiproject/neurai-connect
The website side of Neurai Connect: let a visitor sign in with their Neurai wallet by scanning a QR code, and ask that wallet for signatures afterwards. Everything between the site and the wallet is end-to-end encrypted; the relay only forwards opaque blobs.
Specification: spec/ — auth.md for login, session.md for sessions.
Sign in with Neurai
The backend owns the login transaction (nonce, domain, expiry) with @neuraiproject/neurai-auth; the frontend only shows the QR code and forwards the CACAO.
import { NeuraiConnect } from "@neuraiproject/neurai-connect";
import { BrowserStorage, NEURAI_CHAIN_MAINNET } from "@neuraiproject/neurai-connect-core";
const nc = await NeuraiConnect.init({
relayUrl: "wss://relay.neurai.org/v1",
storage: new BrowserStorage(), // localStorage; MemoryStorage on the server or in tests
metadata: { name: "Example", url: "https://example.com", icons: ["https://example.com/icon.png"] },
});
// 1. The pairing exists before the backend transaction, so the signature is bound to this QR code.
const { pairing } = await nc.createPairing({ methods: ["wc_sessionAuthenticate"] });
const { loginId, nonce, authPayload } = await fetch("/auth/begin", {
method: "POST", credentials: "same-origin",
headers: { "content-type": "application/json" },
body: JSON.stringify({ requestId: pairing.topic }),
}).then((r) => r.json());
// 2. Show the QR code and wait for the wallet. `authPayload` is what the backend built, passed
// through unchanged; every field can also be given individually (`domain`, `aud`, `nonce`, …).
const cancel = new AbortController(); // wire it to a "Cancel" button next to the QR code
const { uri, deepLink, response } = await nc.authenticate({ pairing, authPayload, signal: cancel.signal });
renderQr(uri); // on a phone, link to `deepLink` instead
// 3. Hand the CACAO to the backend, which verifies it against the stored transaction.
const { cacaos } = await response;
await fetch("/auth/complete", {
method: "POST", credentials: "same-origin",
headers: { "content-type": "application/json" },
body: JSON.stringify({ loginId, cacao: cacaos[0] }),
});Add methods: ["signMessage", "sendTransfer"] to authenticate() to get a session in the same approval ("one-click auth"): response then also resolves with a session.
dApp session
const { uri, approval } = await nc.connect({
chains: [NEURAI_CHAIN_MAINNET],
methods: ["getAccountAddresses", "signMessage", "sendTransfer"],
});
renderQr(uri);
const session = await approval;
const addresses = await nc.request(session.topic, NEURAI_CHAIN_MAINNET, "getAccountAddresses", {});
const { signature } = await nc.request(session.topic, NEURAI_CHAIN_MAINNET, "signMessage", {
account: `${NEURAI_CHAIN_MAINNET}:${addresses[0].address}`, address: addresses[0].address, message: "hello",
});Sessions last 7 days, survive reloads and relay restarts (they live in the storage adapter), and either side can revoke them. Events: session_delete, session_event, session_update, error.
authenticate, connect and request all accept an AbortSignal, so a "Cancel" button really stops the wait instead of only hiding the QR code.
Errors
The package re-exports RpcError and the Neurai codes, so error handling is typed without importing the core package:
import { RpcError, NeuraiRpcError } from "@neuraiproject/neurai-connect";
try { await nc.request(topic, chainId, "sendTransfer", params); }
catch (e) {
if (e instanceof RpcError && e.code === NeuraiRpcError.USER_REJECTED) showRejected(); // 4001
else if (e instanceof RpcError && e.code === NeuraiRpcError.UNSUPPORTED_METHOD) showUnsupported(); // 4200
else showTimeout(e);
}Compatibility with the browser extension
installProviderShim({ client }) publishes a window.neuraiWallet object with the same surface as the Neurai Sign extension (getAddress, getPublicKey, isConnected, signMessage, getInfo, …) on top of a session, so a site already integrated with the extension works with the mobile wallet unchanged. signRawTransaction answers error 4200 until the wallet implements signPsbt.
Notes
- The relay never sees plaintext, but it does see topics, sizes and timing.
- A relayed QR code is a residual risk of every QR login: see spec/auth.md section 9 and show the user what they are approving.
- Chain identifiers are constants, never derived at runtime:
NEURAI_CHAIN_MAINNET,NEURAI_CHAIN_TESTNET.
