Casa Tookan · documentation

Sign in with a Casa Estates ID

How a game or app identifies a Casa Tookan player with a CEID: one account per player, the addresses they choose to show you, and facts to defend against multiple accounts.

Preview — in Casa Tookan development builds, not in the store app yet
In one sentenceCall window.casaTookan.ceid.signIn({ nonce }): the player approves once, you get a 24-hour token signed by Casa Tookan with their CEID and username — and, only when you ask and they agree, the addresses whose assets you then read yourself on-chain.

The CEID

A Casa Estates ID is a player account, public like a Farcaster id. A player may never show you an address: a game without a blockchain, or one that only needs an address months later for a reward drop, works the same.

Account

Number + username

  • ceid: 8 digits drawn at random, never changing, never reused — it tells nothing about how many players there are. Key your players on it.
  • username: global and unique, CasaTookanUser#48213907 by default, changed at most once every 30 days.
  • You may show your own in-game name instead.
Addresses

Optional, chosen per app

  • None at sign-in unless you ask.
  • The player picks which casas to show you, chain by chain.
  • An address belongs to one CEID at a time.
Device

One device, one CEID

  • A new recovery phrase on the same phone moves the CEID, it doesn’t create a second one.
  • The token says whether the device is attested and how many times the CEID moved.

Flow

  1. Your server makes a nonceRandom, single use, short-lived, bound to the session that will receive the token.
  2. The page askssignIn({ nonce }). Casa Tookan shows a sheet with your name, icon and domain. The first time, it creates the player’s CEID — nothing to fill in.
  3. The player approvesThe CEID key signs around your nonce in Casa Tookan’s native signer. Their recovery phrase never leaves it.
  4. Casa Tookan’s server issues the tokenIt checks the signature and the device, adds the facts and signs a JWT for your origin (aud).
  5. Your server verifies itAgainst the published keys (JWKS), without calling Casa Tookan. Then you read the shared addresses on-chain yourself — never trust a wallet about what a player owns.
  6. Later, ask for addressesrequestAddresses({ nonce, chains, reason }) when you need them: a drop, an inventory. A new token carries them.

Manifest

Declare it in /.well-known/casatookan.json. The identity field, reserved until now, takes this shape; the capability identity.ceid replaces the reserved identity.estate.

{
  "version": "1",
  "app": {
    "name": "Orb Quest",
    "capabilities": { "required": ["identity.ceid"] },
    "identity": {
      "reason": "Your CEID keeps your progress across devices.",
      "addresses": [
        { "chain": "bip122:000000000019d6689c085ae165831e93", "max": 2, "reason": "Activate the orbs you hold on Counterparty" },
        { "chain": "eip155:1", "max": 1, "reason": "Your Ethereum cards" }
      ]
    }
  }
}
FieldTypeNotes
identity.reasonstringMax 120 characters. Shown on the sign-in sheet.
identity.addressesarrayThe most you may ever ask for, per chain. A request beyond it is refused. Omit it if you only need the account.
…chainCAIP-2Counterparty assets live on Bitcoin: use the Bitcoin id (the legacy address is shared).
…max1–5Addresses the player may tick for that chain.
…reasonstringMax 80 characters, shown above the chain’s checkboxes.

In the page

Available inside the Casa Tookan browser when the manifest declares identity.ceid. Calls reject with an error whose code says why:

codeWhen
rejectedThe player closed Casa's sheet or cancelled a signature.
not_declaredYour manifest doesn't declare identity.ceid.
over_limitMore addresses, or another chain, than identity.addresses allows. Checked before any sheet.
invalidMissing nonce, malformed chains.
failedCasa Tookan's server refused (for example another CEID is active on the device); the message says why.
unavailableNot offered here (outside the browser, older version).

const { nonce } = await fetch('/auth/nonce').then((r) => r.json());

// account only: no address leaves the wallet
const { token } = await window.casaTookan.ceid.signIn({ nonce });
await fetch('/auth/casa', { method: 'POST', body: token });

// months later, for the drop
const { nonce: n2 } = await fetch('/auth/nonce').then((r) => r.json());
const { token: withAddresses } = await window.casaTookan.ceid.requestAddresses({
  nonce: n2,
  chains: [{ chain: 'bip122:000000000019d6689c085ae165831e93', max: 1 }],
  reason: 'Where should we send your reward?',
});
CallReturnsNotes
ceid.signIn({ nonce }){ token }Addresses already shared with your origin are included again; none are added.
ceid.requestAddresses({ nonce, chains, reason }){ token }Within the manifest’s limits. Already-shared addresses come pre-ticked; the player may untick them, and they leave the token.
ceid.availablebooleanFalse outside Casa Tookan or in an older version: offer another sign-in.

The token

A JWT (ES256) signed by Casa Tookan’s server, valid 24 hours. Ask for a new one after that — no sheet if nothing changed.

{
  "iss": "https://ctserver.everdreamsoft.com",
  "aud": "https://orbquest.game",
  "sub": "ceid:48213907",
  "env": "production",
  "ceid": 48213907,
  "username": "CasaTookanUser#48213907",
  "usernameDefault": true,
  "addresses": [
    { "chain": "bip122:000000000019d6689c085ae165831e93", "address": "1Abc…k9Z", "since": "2026-03-02" }
  ],
  "facts": {
    "deviceAttested": true,
    "deviceUnique": true,
    "transfers": 0,
    "ceidAge": 214,
    "farcaster": { "fid": 4242 }
  },
  "nonce": "…", "iat": 1791878400, "exp": 1791964800
}
ClaimMeaning
ceid / subThe player. Stable for life — usernames change, this doesn’t.
envproduction, or development for Casa Tookan Dev builds, simulators and unattested devices. Development CEIDs are a separate set: their numbers and usernames are not production ones.
username, usernameDefaultCurrent global username; true while it is still CasaTookanUser#….
addressesOnly those the player shared with your origin. since: when the address joined this CEID. Empty array when none.
facts.deviceAttestedThe request came from the genuine Casa Tookan app on a real device (App Attest / Play Integrity).
facts.deviceUniqueNo other CEID is active on that device.
facts.transfersHow many times the CEID moved to a new recovery phrase.
facts.ceidAgeDays since the CEID was created.
facts.farcasterPresent when the player linked a Farcaster account.

Verify it on your server

Never in the page alone: a token checked only in the browser can be forged by the player.

  1. SignatureAgainst the keys at https://ctserver.everdreamsoft.com/.well-known/jwks.json (cache them, follow kid). They sign production tokens only: a development token fails there by itself.
  2. Audience and issueraud is exactly your origin, iss is Casa Tookan’s server.
  3. NonceThe one you issued for this session, unused. Burn it.
  4. Timeexp not passed (allow a minute of clock skew).
import { createRemoteJWKSet, jwtVerify } from 'jose';

const JWKS = createRemoteJWKSet(new URL('https://ctserver.everdreamsoft.com/.well-known/jwks.json'));

export async function verifyCasaToken(token, expectedNonce) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: 'https://ctserver.everdreamsoft.com',
    audience: 'https://orbquest.game',
    clockTolerance: 60,
  });
  if (payload.nonce !== expectedNonce) throw new Error('nonce');
  return payload; // key the player on payload.ceid
}

Development builds

Test with Casa Tookan Dev. Its CEIDs live apart from production ones and are signed by a development key published at https://ctserver.everdreamsoft.com/.well-known/jwks-dev.json. Point your staging server there and check env === "development"; your production server keeps the production keys and never sees test accounts. TestFlight builds of the main app attest as production: don’t test with them.

Multiple accounts

Sharing addresses per app doesn’t stop one person from playing as many — that isn’t its job. Each attack has its own answer.

AttackWho handles itHow
One CEID plays twiceCasa TookanYou always get the same ceid, whatever addresses are shared.
Many CEIDs on one phoneCasa TookanOne device, one CEID: a new phrase moves the CEID instead of creating one.
One address feeds two playersCasa TookanAn address belongs to one CEID; an unlinked one waits 90 days before joining another.
An asset moves between walletsYouCasa Tookan can’t see it. Snapshot holdings, lock them, or require a holding period.
Many phonesBothCasa Tookan gives the facts; you set your threshold (e.g. rewards only for deviceAttested and ceidAge ≥ 30).

Rules players live by

RuleWhat it means for you
Username: once every 30 daysRefresh it from each token; never use it as a key. The old name stays reserved 30 days.
Default pattern reservedNobody can choose CasaTookanUser#…: it always means “has not picked a name”.
Unlinking an addressIt leaves the next token. Stop using it then.
Token: 24 hoursRe-ask daily, or on each session; keep your own session meanwhile.
Lost phrase, no transferThe player can’t get a new CEID on that device. Your support can’t merge accounts: there is nothing to merge into.

Privacy

Never shown

Private casas

A casa the player didn’t pick for you doesn’t exist for you. Don’t ask players to paste other addresses into your game.

Your choice

Shared addresses

Once shared, you know they belong to this player. If you publish them (leaderboards, profiles), say so in your reason.

Per origin

Nothing travels

Addresses shared with another app never reach you, and the list of a CEID’s addresses is not public.

Try it in the playground

The developer playground mini app has a CEID tab that plays every scenario above — first sign-in, play without addresses, the drop, refusals, expired, replayed and foreign tokens, a rename, a new phrase, the anti-Sybil threshold — next to tabs for the environment, the manifest, the providers and deep links. Open /playground/ in Casa Tookan: it signs in against this server (a development build gets env: "development" tokens). In a plain browser it plays against a mock wallet and a mock issuer, under their own issuer and key, so a mock token never passes for Casa Tookan's.