Every entry in this catalog compiles down to the same shape: a TanStack server function calling a Hyperledger Identus Cloud Agent, plus one client surface. It's the only pattern that ships a working self-sovereign identity demo in one shot, inside the 5-credit budget.
Identus is identity infrastructure, not accounts. The issuer signs a fact once; the holder carries it in their own wallet; any verifier checks it cryptographically without calling the issuer. Nothing about it is a token, a coin, or a speculation.
Start simulated — an in-app mock with the agent's exact response shapes and zero setup. Swap in a local Docker stack or a hosted Fly.io deployment later by changing two environment variables. The UI never has to change.
// src/lib/identus.functions.ts — mint and publish a did:prism
// Built for the Hyperledger Identus Catalyst — StreetKode Fam · Indian Krump Festival 14
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
const agent = () => ({
base: process.env.AGENT_BASE_URL!.replace(/\/$/, ""),
headers: { "content-type": "application/json", apikey: process.env.AGENT_API_KEY! },
});
export const mintDid = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({ label: z.string().min(1) }).parse(d))
.handler(async () => {
const { base, headers } = agent();
const created = await fetch(`${base}/did-registrar/dids`, {
method: "POST", headers,
body: JSON.stringify({
documentTemplate: {
publicKeys: [
{ id: "auth-1", purpose: "authentication", curve: "secp256k1" },
{ id: "assert-1", purpose: "assertionMethod", curve: "secp256k1" },
],
services: [],
},
}),
}).then((r) => r.json());
await fetch(`${base}/did-registrar/dids/${created.longFormDid}/publications`, {
method: "POST", headers,
});
return { did: created.longFormDid };
});// src/lib/identus.functions.ts — DIDComm invitation
export const createInvitation = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({ label: z.string().min(1) }).parse(d))
.handler(async ({ data }) => {
const { base, headers } = agent();
const conn = await fetch(`${base}/connections`, {
method: "POST", headers,
body: JSON.stringify({ label: data.label, goalCode: "connect", goal: data.label }),
}).then((r) => r.json());
// Render conn.invitation.invitationUrl as a QR code; poll GET /connections/{id}
// until state === "ConnectionResponseSent".
return { connectionId: conn.connectionId, invitationUrl: conn.invitation.invitationUrl };
});// src/lib/identus.functions.ts — issue a JWT verifiable credential
export const offerCredential = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({
issuingDID: z.string(),
claims: z.record(z.string(), z.string()),
connectionId: z.string().optional(),
}).parse(d))
.handler(async ({ data }) => {
const { base, headers } = agent();
const rec = await fetch(`${base}/issue-credentials/credential-offers`, {
method: "POST", headers,
body: JSON.stringify({
claims: data.claims,
issuingDID: data.issuingDID, // must be PUBLISHED with assertionMethod
credentialFormat: "JWT",
automaticIssuance: true,
...(data.connectionId
? { connectionId: data.connectionId }
: { goalCode: "issue-vc", goal: "Claim your credential" }),
}),
}).then((r) => r.json());
// Poll GET /issue-credentials/records/{recordId} until "CredentialSent".
return { recordId: rec.recordId, state: rec.protocolState };
});// src/lib/identus.functions.ts — request and verify a proof
export const requestProof = createServerFn({ method: "POST" })
.inputValidator((d) => z.object({
connectionId: z.string(),
attributes: z.array(z.string()),
}).parse(d))
.handler(async ({ data }) => {
const { base, headers } = agent();
const rec = await fetch(`${base}/present-proof/presentations`, {
method: "POST", headers,
body: JSON.stringify({
connectionId: data.connectionId,
proofs: [],
options: { challenge: crypto.randomUUID(), domain: "https://example.app" },
claims: Object.fromEntries(data.attributes.map((a) => [a, {}])),
}),
}).then((r) => r.json());
// Poll GET /present-proof/presentations/{id} until "PresentationVerified".
return { presentationId: rec.presentationId, state: rec.status };
});# Simulated mode — no secrets at all. Start here. # Docker mode (local compose stack, APISIX gateway present): AGENT_BASE_URL=http://localhost:8085/cloud-agent AGENT_API_KEY=<DEFAULT_WALLET_AUTH_API_KEY> # Fly.io mode (direct deploy — NO /cloud-agent suffix): AGENT_BASE_URL=https://<app>.fly.dev AGENT_API_KEY=<DEFAULT_WALLET_AUTH_API_KEY> # The mega-prompt then: # - writes createServerFn wrappers around the Cloud Agent REST API # - polls protocolState instead of assuming the POST finished the job # - keeps the key on the server via process.env.AGENT_API_KEY
createServerFn handler.process.env inside the handler, never at module scope.PUBLISHED before issuing anything.protocolState; issuance and presentation are asynchronous./cloud-agent from the base URL. On Docker, keep it.