install
One package. One key.
worldbuild is a drop-in SDK that adds Sign in with dots, cross-app credits (pute), verified attestations, and per-user data storage to any app. Configure it with the public API key from your dots app.
quick start
- 1. Register an app at /dev/clients.
- 2. Copy the public
DOTS_API_KEY. - 3.
npx wrldbld add ui(ornpm i worldbuild@alpha). - 4. Add a
/dots/callbackroute.
install
Add the SDK
One package for the whole ecosystem, alpha-tagged. Pin an exact version while the API is still evolving. Want just one module? Install the scoped package directly — @wrldbld/dots (SDK), @wrldbld/ui (React blocks). The CLI stays npx wrldbld.
npm i worldbuild@alphalocal install (until npm publish lands)
# from a local clone of the monorepo (while npm publish is pending)
git clone https://github.com/worldbuild-co/wrldbld
cd wrldbld && bun install && cd packages/dots && bun run build && npm pack
npm i /path/to/wrldbld/packages/dots/wrldbld-dots-0.0.1-alpha.0.tgzcli
…or let the CLI wire it for you
npx wrldbld add installs the SDK, the @wrldbld/ui render engine, and any UI blocks — package-manager aware, pinned to alpha. Agents can discover the whole funnel at /llms.txt.
# install the SDK + render engine, package-manager aware
npx wrldbld add ui
# or pull individual UI blocks into your project
npx wrldbld add provider connector pute-balance attestationsui
Drop-in UI blocks (the render engine)
@wrldbld/ui ships the built-in features as React components: <SignInWithDots/>, <Connector/>, <PuteBalance/>, <Attestations/>. Wrap once, render anywhere. Preview and configure them live at /dev/ui.
import { DotsProvider, Connector, PuteBalance } from "@wrldbld/ui";
export default function App({ children }) {
return (
<DotsProvider apiKey={process.env.NEXT_PUBLIC_DOTS_API_KEY!}>
<header><Connector /></header> {/* sign-in / identity chip + menu */}
<PuteBalance /> {/* cross-app credits + top-up */}
{children}
</DotsProvider>
);
}env
Configure the API key
The API key is your dots app's public OAuth client id. It's safe in client code (it's not a secret). Public/native clients use PKCE — no client secret needed.
NEXT_PUBLIC_DOTS_API_KEY="dots_client_..." # your dots app's public client id
# optional override (defaults to https://www.dots.id):
# NEXT_PUBLIC_DOTS_ISSUER="https://www.dots.id"init
Create the dots client
import { createDots } from "worldbuild";
export const dots = createDots({
apiKey: process.env.NEXT_PUBLIC_DOTS_API_KEY!,
});auth
Sign in with dots
OAuth 2.1 + PKCE against the dots-hosted Supabase OAuth Server, with branded consent at dots.id. Identity comes from the access-token claims — no extra round-trip.
1. Call the SDK
// kick off sign-in (button, link, anywhere)
await dots.signIn();
// in your /dots/callback route:
const user = await dots.handleCallback();
// anywhere:
const user = await dots.getUser();
// { sub, username, walletAddress, dotsIdStatus, email }
const token = await dots.getAccessToken();
await dots.signOut();2. Add the callback route
// app/dots/callback/page.tsx
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { dots } from "@/lib/dots";
export default function DotsCallback() {
const router = useRouter();
useEffect(() => {
dots.handleCallback().then(() => router.replace("/"));
}, [router]);
return <p>Signing you in…</p>;
}creation
dots.id onboarding progress
Read creation progress to drive onboarding UI, then confirm with a wallet signature. The activity feed gives you the user-visible event log.
const s = await dots.identity.status();
// { status: "incomplete"|"pending"|"complete",
// wallet, username, vercel, email, subscription, baseColor }
await dots.identity.confirm({
signature, message, walletAddress, timestamp,
});
const feed = await dots.identity.activity({ limit: 50 });credits
pute — one cross-app credit balance
Users carry a single pute balance everywhere. Your app debits it for compute, agent runs, or any metered action. Debits are idempotent via requestId; ok:false means the user is out of credits.
const balance = await dots.credits.balance();
const { ok, balance: left } = await dots.credits.debit(1, {
requestId: jobId, // idempotent; ok:false ⇒ out of credits
});
const { url } = await dots.credits.topUp({ credits: 1000 });
// → Stripe Checkoutverified claims
emails + attestations
Verified emails attach to the dots and resolve back to the same user across apps. Attestations carry any verified fact (zkTLS-proven or otherwise) bound to a dots.
await dots.emails.add("me@work.com"); // sends verification link
const emails = await dots.emails.list();
const list = await dots.attestations.list();
// [{ kind, identifier, issuer, verified, verifiedAt }]
await dots.attestations.submitProof(zkTlsProof);data
Per-user app data
Per-user storage in the user's own database, namespaced to your app. Requires data:read / data:write scopes.
await dots.data.set("prefs", "theme", { mode: "dark" });
const theme = await dots.data.get("prefs", "theme");
const all = await dots.data.list("prefs");surface
SDK at a glance
dots.signIn() / handleCallback() / signOut()OAuth 2.1 + PKCE against the dots Supabase OAuth Server.
dots.getUser() / getAccessToken()Identity from token claims — no extra round-trip.
dots.identity.status() / confirm() / activity()Drive your onboarding UI off the live creation progress.
dots.credits.balance() / debit() / topUp()One pute balance, cross-app, idempotent debits.
dots.emails.add() / list()Verified emails attached to the dots.
dots.attestations.list() / submitProof()Verified claims (zkTLS or otherwise) bound to the dots.
dots.data.get() / set() / list()Per-user storage namespaced to your app (data:read/write scopes).
scopes
Choose what your app can sync
openidRequired OIDC identity scope.
profileUsername and basic dots.id profile.
emailVerified email claims.
walletConnected wallet address.
social:twitterConnected Twitter/X username.
social:instagramConnected Instagram username.
social:googleConnected Google account.
data:readRead app data from the user's dots data layer.
data:writeWrite app data into the user's dots data layer.
pute:readRead the user's pute balance.
pute:spendDebit pute on the user's behalf.
pute:topupStart a checkout to add pute credits.
offline_accessIssue refresh tokens for long-lived sessions.
advanced
No SDK? Use the OAuth endpoints directly
The SDK is a thin wrapper over the dots OAuth 2.1 server. If you're in a language or framework where the SDK doesn't fit, talk to the endpoints directly. Issuer: https://www.dots.id.
Authorize
const params = new URLSearchParams({
client_id: process.env.DOTS_CLIENT_ID!,
redirect_uri: process.env.DOTS_REDIRECT_URI!,
response_type: "code",
scope: "openid profile email wallet",
state,
code_challenge: challenge,
code_challenge_method: "S256",
});
redirect(`${process.env.DOTS_ISSUER}/api/oauth/authorize?${params}`);Token exchange
const tokenRes = await fetch(`${process.env.DOTS_ISSUER}/api/oauth/token`, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
client_id: process.env.DOTS_CLIENT_ID!,
redirect_uri: process.env.DOTS_REDIRECT_URI!,
code_verifier: verifier,
}),
});