install
One identity. One client.
@wrldbld/dots 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.
Every dots login strengthens one portable identity. Overlapping verified signals appear on /connect so people can connect across the apps you ship — feeding a shared context engine without merging each app's private data.
quick start
- 1. Register an app at /dev/clients.
- 2. Copy the public
DOTS_API_KEY. - 3. Use OIDC/REST now, or install a local SDK tarball.
- 4. Add a PKCE
/dots/callbackroute.
install
Add the SDK
The npm package is not published yet. Agents and CI must verify the package exists before installing; use the REST/OIDC API or the local tarball in the meantime.
# npm publishing is pending — verify before installing
npm view @wrldbld/dots
# after publication:
npm i @wrldbld/dots@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.1.0-alpha.0.tgzAgent-readable integration rules: /llms.txt.
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"better auth
Use dots with Better Auth
dots is a standard OIDC provider. Better Auth owns your app session; the dots sub remains the portable identity. Register /api/auth/callback/dots as the exact callback URL.
import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
genericOAuth({
config: [{
providerId: "dots",
discoveryUrl:
"https://www.dots.id/.well-known/openid-configuration",
clientId: process.env.DOTS_CLIENT_ID!,
clientSecret: process.env.DOTS_CLIENT_SECRET!,
scopes: ["openid", "profile", "email", "offline_access"],
pkce: true,
}],
}),
],
});init
Create the dots client
import { createDots } from "@wrldbld/dots";
export const dots = createDots({
apiKey: process.env.NEXT_PUBLIC_DOTS_API_KEY!,
scopes: ["openid", "profile", "email", "offline_access"],
});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
Two sign-in levels
App sign-in uses Privy plus a one-time wallet confirmation; it does not require Vercel. Choose full dots.id when your app needs the user's public dots.id or colors.bio profile. 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({ usd: 3 }); // or { credits: 300 }
// → 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,
}),
});