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 yetwindow.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.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.
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.signIn({ 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.aud).requestAddresses({ nonce, chains, reason }) when you need them: a drop, an inventory. A new token carries them.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" }
]
}
}
}
| Field | Type | Notes |
|---|---|---|
identity.reason | string | Max 120 characters. Shown on the sign-in sheet. |
identity.addresses | array | The most you may ever ask for, per chain. A request beyond it is refused. Omit it if you only need the account. |
…chain | CAIP-2 | Counterparty assets live on Bitcoin: use the Bitcoin id (the legacy address is shared). |
…max | 1–5 | Addresses the player may tick for that chain. |
…reason | string | Max 80 characters, shown above the chain’s checkboxes. |
Available inside the Casa Tookan browser when the manifest declares identity.ceid. Calls reject with an error whose code says why:
| code | When |
|---|---|
rejected | The player closed Casa's sheet or cancelled a signature. |
not_declared | Your manifest doesn't declare identity.ceid. |
over_limit | More addresses, or another chain, than identity.addresses allows. Checked before any sheet. |
invalid | Missing nonce, malformed chains. |
failed | Casa Tookan's server refused (for example another CEID is active on the device); the message says why. |
unavailable | Not 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?',
});
| Call | Returns | Notes |
|---|---|---|
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.available | boolean | False outside Casa Tookan or in an older version: offer another sign-in. |
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
}
| Claim | Meaning |
|---|---|
ceid / sub | The player. Stable for life — usernames change, this doesn’t. |
env | production, 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, usernameDefault | Current global username; true while it is still CasaTookanUser#…. |
addresses | Only those the player shared with your origin. since: when the address joined this CEID. Empty array when none. |
facts.deviceAttested | The request came from the genuine Casa Tookan app on a real device (App Attest / Play Integrity). |
facts.deviceUnique | No other CEID is active on that device. |
facts.transfers | How many times the CEID moved to a new recovery phrase. |
facts.ceidAge | Days since the CEID was created. |
facts.farcaster | Present when the player linked a Farcaster account. |
Never in the page alone: a token checked only in the browser can be forged by the player.
https://ctserver.everdreamsoft.com/.well-known/jwks.json (cache them, follow kid). They sign production tokens only: a development token fails there by itself.aud is exactly your origin, iss is Casa Tookan’s server.exp 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
}
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.
Sharing addresses per app doesn’t stop one person from playing as many — that isn’t its job. Each attack has its own answer.
| Attack | Who handles it | How |
|---|---|---|
| One CEID plays twice | Casa Tookan | You always get the same ceid, whatever addresses are shared. |
| Many CEIDs on one phone | Casa Tookan | One device, one CEID: a new phrase moves the CEID instead of creating one. |
| One address feeds two players | Casa Tookan | An address belongs to one CEID; an unlinked one waits 90 days before joining another. |
| An asset moves between wallets | You | Casa Tookan can’t see it. Snapshot holdings, lock them, or require a holding period. |
| Many phones | Both | Casa Tookan gives the facts; you set your threshold (e.g. rewards only for deviceAttested and ceidAge ≥ 30). |
| Rule | What it means for you |
|---|---|
| Username: once every 30 days | Refresh it from each token; never use it as a key. The old name stays reserved 30 days. |
| Default pattern reserved | Nobody can choose CasaTookanUser#…: it always means “has not picked a name”. |
| Unlinking an address | It leaves the next token. Stop using it then. |
| Token: 24 hours | Re-ask daily, or on each session; keep your own session meanwhile. |
| Lost phrase, no transfer | The player can’t get a new CEID on that device. Your support can’t merge accounts: there is nothing to merge into. |
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.
Once shared, you know they belong to this player. If you publish them (leaderboards, profiles), say so in your reason.
Addresses shared with another app never reach you, and the list of a CEID’s addresses is not public.
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.