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. 1. Register an app at /dev/clients.
  2. 2. Copy the public DOTS_API_KEY.
  3. 3. npx wrldbld add ui (or npm i worldbuild@alpha).
  4. 4. Add a /dots/callback route.

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@alpha

local 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.tgz

cli

…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 attestations

ui

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 Checkout

verified 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

openid

Required OIDC identity scope.

profile

Username and basic dots.id profile.

email

Verified email claims.

wallet

Connected wallet address.

social:twitter

Connected Twitter/X username.

social:instagram

Connected Instagram username.

social:google

Connected Google account.

data:read

Read app data from the user's dots data layer.

data:write

Write app data into the user's dots data layer.

pute:read

Read the user's pute balance.

pute:spend

Debit pute on the user's behalf.

pute:topup

Start a checkout to add pute credits.

offline_access

Issue 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,
  }),
});