@spectre-protocol/sdk
v0.1.4
Published
SDK for Spectre Protocol: ZK account recovery for autonomous on-chain agents
Maintainers
Readme
@spectre-protocol/sdk
TypeScript SDK for Spectre Protocol - ZK account recovery for autonomous on-chain agents.
Spectre lets an agent owner recover access to their on-chain identity through one of three modes:
- Email + World ID - prove control of a registered email address (via a Noir circuit over a DKIM-signed
.eml) plus a World ID proof of personhood. - Backup wallet - recover to a pre-designated backup address.
- Social / guardians - threshold approval from a list of guardian addresses.
All recovery paths run through a configurable timelock before they can be executed.
Install
npm install @spectre-protocol/sdk viemviem is a peer dependency. If you plan to generate proofs in the browser instead of through a hosted prover, also install:
npm install @noir-lang/noir_js @noir-lang/backend_barretenbergQuickstart
import { SpectreClient } from "@spectre-protocol/sdk";
const client = new SpectreClient({
rpcUrl: "https://sepolia.base.org",
registryAddress: "0xBe53383054Fda41A9F71b8593384144c367b01A1",
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
prover: {
type: "browser",
circuitUrl: "https://spectreprotocol.xyz/circuit/v1/spectre.json",
// SHA-256 of the circuit artifact whose VK is in the deployed Verifier.
// Required for trustless use (omit + allowUnpinnedCircuit:true only in dev).
circuitDigest: "e7243505c53a3cdc52dc0982e7a59c6f7f1330b3516f456165666a887a25bbf8",
},
});
// Register an agent with the protocol default timelock
const { hash, emailHash } = await client.register("[email protected]");
// …or arm a longer cancel window than the default
// const { hash } = await client.registerWithCustomTimelock("[email protected]", 100n);
// Read state
const record = await client.getRecord(ownerAddress);
const status = await client.getRecoveryStatus(ownerAddress);Configuration
type SpectreClientConfig = {
rpcUrl: string;
registryAddress: `0x${string}`;
privateKey: `0x${string}`;
prover:
| { type: "hosted"; url: string }
| {
type: "browser";
circuitUrl: string;
/** SHA-256 of the circuit JSON bytes whose VK is in the deployed Verifier.
* Required for trustless use; omit only with `allowUnpinnedCircuit: true`. */
circuitDigest?: string;
allowUnpinnedCircuit?: boolean;
};
/** Optional relayer base URL for off-chain helpers like email confirmation.
* Defaults to `prover.url` when using the hosted prover. */
relayerUrl?: string;
};browser(recommended) - proofs are generated locally in the user's browser via Noir.js + barretenberg, with the circuit fetched fromcircuitUrl. The user's.emlnever leaves their device. Heavier first load (multi-MB WASM) but trustless.hosted- proofs are generated by an HTTP prover service. Lighter and faster on weak hardware, but the prover operator sees the raw.eml. Useful for first-time users or constrained devices. The prover cannot forge proofs, only see them, since all proofs are verified on-chain.
Browser prover
const client = new SpectreClient({
rpcUrl: "https://sepolia.base.org",
registryAddress: "0xBe53383054Fda41A9F71b8593384144c367b01A1",
privateKey: "0x...",
prover: {
type: "browser",
// Spectre-hosted circuit artifact (versioned, immutable per version)
circuitUrl: "https://spectreprotocol.xyz/circuit/v1/spectre.json",
// SHA-256 of the circuit artifact whose VK is in the deployed Verifier.
// Required for trustless use (omit + allowUnpinnedCircuit:true only in dev).
circuitDigest: "e7243505c53a3cdc52dc0982e7a59c6f7f1330b3516f456165666a887a25bbf8",
},
});You can self-host the circuit if you'd rather not depend on the Spectre Pages deployment - just point circuitUrl at your own copy of circuits/target/spectre.json.
Live demo: https://spectreprotocol.xyz/test-browser-prover.html
Hosted prover
const client = new SpectreClient({
rpcUrl: "https://sepolia.base.org",
registryAddress: "0xBe53383054Fda41A9F71b8593384144c367b01A1",
privateKey: "0x...",
prover: { type: "hosted", url: "http://localhost:3001" },
});See relayer/src/server.ts in the main repo to run your own.
Recovery flows
Email + World ID
import { readFile } from "fs/promises";
const eml = await readFile("recovery.eml");
const worldIdProof = JSON.parse(await readFile("worldid.json", "utf-8"));
const record = await client.getRecord(agentOwner);
// Build the exact Subject line the user must put on the recovery email.
// Format: `spectre:<newOwnerAsDecimalUint256>:<nonce>`.
const subject = client.prepareRecoverySubject(newOwner, record.nonce);
// Compute the signal you need to pass into the World ID widget.
const signal = client.computeSignal(agentOwner, newOwner, record.nonce);
// Fetch a signed rp_context from the relayer for the IDKit v4 widget.
const rpContext = await client.worldId.getContext();
const { hash } = await client.initiateEmailRecovery({
eml,
agentOwner,
newOwner,
nonce: record.nonce,
worldIdProof,
});See the full walkthrough for provider-by-provider .eml download instructions and the World ID widget wiring.
Backup wallet
// One-time setup by the agent owner
await client.setBackupWallet(backupAddress);
// Later, signed by the backup wallet
await client.initiateBackupRecovery(agentOwner, newOwner);Guardians
// One-time setup: 2-of-3
await client.setGuardians([g1, g2, g3], 2);
// Each guardian calls this; once `threshold` is met, recovery becomes pending
await client.approveGuardianRecovery(agentOwner, newOwner);Finalising
After the timelock elapses:
await client.executeRecovery(agentOwner);The agent owner can also abort an in-flight recovery at any time:
await client.cancelRecovery(agentOwner);Monitoring
The cancel window is your only protection. You must watch for hostile recovery attempts so you can call cancelRecovery during the timelock. The SDK offers two ways.
In-process RPC watcher (trustless)
const unwatch = client.watchRecovery({
agentOwner: myAgentOwner, // omit to watch every agent
onInitiated: (e) => alertOnCall(e), // page someone
onCancelled: (e) => console.log("cancelled", e),
onExecuted: (e) => console.log("executed", e),
});
// later
unwatch();No third party in the path. Lives in-process; you miss events while your service is down.
Hosted webhook subscription (persistent)
// Signs with the agent owner key and registers a webhook with the relayer.
await client.notify.subscribe({
endpoint: "https://hooks.example.com/spectre",
});
await client.notify.getSubscription(myAgentOwner);
await client.notify.unsubscribe(myAgentOwner);The relayer indexes the chain and POSTs a RecoveryAlert JSON to your endpoint when a recovery is initiated. See the monitoring docs for the payload shape and the /subscribe API reference.
API reference
| Method | Purpose |
|---|---|
| register(email, timelockBlocks) | Register an agent under the caller's address. |
| setBackupWallet(addr) | Configure the backup-wallet recovery path. |
| setGuardians(addrs, threshold) | Configure the social recovery path. |
| initiateEmailRecovery(params) | Start an email + World ID recovery. |
| initiateBackupRecovery(owner, new) | Start a backup-wallet recovery. |
| approveGuardianRecovery(owner, new) | Cast a guardian approval. |
| cancelRecovery(owner) | Owner-only: abort a pending recovery. |
| executeRecovery(owner) | Finalise after the timelock. |
| getRecord(owner) | Read the agent's full record. |
| getRecoveryStatus(owner) | Read pending recovery state and mode. |
| getGuardians(owner) | List configured guardians. |
| getApprovalCount(owner, new) | Current approval count for a candidate new owner. |
| computeSignal(owner, new, nonce) | Compute the World ID signal for a recovery. |
| prepareRecoverySubject(new, nonce) | Build the exact email Subject (spectre:<bigint>:<nonce>) bound to a recovery. |
| worldId.getContext() | Fetch a signed rp_context from the relayer for the IDKit v4 widget. |
| watchRecovery({...}) | Subscribe to RecoveryInitiated / Cancelled / Executed over RPC. Returns an unwatch fn. |
| notify.subscribe({...}) | Register a webhook with the hosted relayer (signed). |
| notify.getSubscription(owner) | Look up the current webhook subscription. |
| notify.unsubscribe(owner?) | Remove the webhook subscription (signed). |
| confirmEmail(email) | Returns { challenge, verify } for the relayer's email-ownership UX gate (optional; not protocol-enforced). |
| registerWithCustomTimelock(email, timelockBlocks) | Register with a longer-than-default cancel window. |
| registerWithAdapter(email, adapter, timelockBlocks) | Register choosing a specific approved personhood adapter. |
| computeEmailHash(email) | (via registry) sha256 of the lowercased, trimmed email. |
Networks
Currently deployed on Base Sepolia. See the main repo for current addresses.
License
MIT
