Skip to content

What your app must do ​

An app runs on the coin page in a sandbox: a sealed frame with no origin of its own, so it cannot reach the page, the visitor's cookies or their Chain Daddy session. Everything it asks of the page goes through the rules below. The automated checks and the reviewer hold every build to them, and the page enforces them for every visitor. How we check, in plain words, is on How apps are checked.

Sandbox rules ​

AreaRuleReported as
NetworkWith the fetch:self-api permission, fetch() reaches only the platform API: a path under /api/ on the page's origin or the API origin. With net:connect, also the origins your manifest declares (your own server). Nothing else.unauthorized_fetch
Images and sound<img>, CSS backgrounds, <audio> and <video> load from the page's own site, the platform's image and media proxies ({appData.apiBase}/api/v2/img?url=… and /api/v2/media?url=…, for IPFS, Arweave and the usual token-image CDNs), the first of appData.ipfsGateways, data: and blob:. With net:connect, also your declared https:// origins. Anything else is refused by the browser.
PaceYour frame may send 30 actions at once, then 5 a second. Within that, api-call 10 then 2 a second, track 5 then 1 a second, session-token 3 then 1 a minute, the two uploads 3 then 1 every 20 seconds, and clipboard 2 then 1 every 2 seconds. Past it, an action resolves { ok: false, error: 'This app is asking too often: try again in a moment' } at once.rate_limited
CredentialsCalls go without cookies; credentials: 'include' is refused. Each call carries X-App-ID: <your app id>.credentialed_request
HeadersAuthorization, Cookie, Proxy-Authorization, X-Forwarded-For, X-Forwarded-Host and X-Real-IP are stripped.blocked_header
Money and the walletYour app asks for what its manifest declares and nothing else: the operations and currencies in payments, and the contracts and methods in wallet. Value moves only through the checkout.undeclared_action, wallet_policy
StoragelocalStorage is your app's own. Declare storage:local or nothing is kept: the frame starts empty each visit. Your app may keep 512K characters across its keys and values (keys up to 256). There is no sessionStorage, no cookies, and no IndexedDB but for an engine app's saves.
CodeNo eval or new Function: the frame refuses them, and the automated checks fail a build that uses them, or document.cookie.
Your boxEverything you draw stays inside your app's box, menus and React portals included. To move the visitor, use the navigate action; links open in a new tab.
Your frameYour app stays on the page the platform loads in its frame. Sending the frame anywhere else (setting location, a link with no target) stops your app there, and the frame is taken off the page.location_set
Who frames itAn app the API serves (one with net:connect, an engine or a web build) runs only inside a Chain Daddy page: chaindaddy.io and its coin sites. Any other site that frames it gets an empty frame. The frame says it is ready only to the page around it, where the browser names that page.

Every report names the app whose frame made the call. Reports reach you on the Violations tab of your app in the developer portal, with anything we did about them. Reports never switch your app off by themselves. When many different visitors report an app, a person on our team looks at the reports and decides what to do. If we switch it off or warn you, we tell you why and how to contest it.

Test against these rules before you submit: Test locally lists every violation your app triggers.

Your own server (net:connect) ​

A chat, a live dashboard, a multiplayer game: anything with a server of its own. Declare the permission and the origins it talks to, and the app may open WebSockets and fetch() calls to exactly those.

json
{
  "permissions": ["net:connect"],
  "network": { "origins": ["wss://live.example.com", "https://live.example.com"] }
}
  • What is allowed. Up to 8 origins, each wss:// or https:// plus an exact hostname and an optional port. No wildcards, IP addresses, localhost, paths, or Chain Daddy hosts.
  • What review sees. The origins are part of the version you submit: the reviewer sees them, the owner sees "Can connect to live.example.com" before installing, and changing them is a new version. The automated check fails a build with a literal new WebSocket('wss://…') or fetch('https://…') to a host you did not declare.
  • Don't gate on Origin. An app's frame has no origin, so its requests and WebSocket handshakes carry Origin: null; a game at its own address sends that address. Answer fetch() calls with Access-Control-Allow-Origin: * (no cookies are ever sent), and know the visitor by a session token instead.

A server the coin's owner runs (network.ownerOrigin) ​

Some apps are meant to talk to a server each community runs for itself: a forum that keeps posts in the owner's own storage, a chat on the owner's own server. Fixed origins would mean every community forks your app and submits its own build. Declare ownerOrigin instead, and one approved build works on every coin page: each page's owner types their server's address into the app's settings.

json
{
  "permissions": ["net:connect"],
  "network": {
    "ownerOrigin": {
      "title": "Forum server",
      "description": "Where your forum server runs, like https://forum.yourcoin.com."
    }
  }
}
  • What the owner sees. At install: "Can send data to a server you choose". In the app's settings: a field with your title and description, and, once they type an address, "This app will send data to forum.yourcoin.com". Until they save one, your app connects to nothing.
  • What is allowed. One https:// origin per install: an exact public hostname and an optional port. No IP addresses, localhost, paths, or Chain Daddy hosts. A trailing slash is dropped as the owner saves it.
  • Reading it. settings._ownerOrigin is the saved origin, or absent. Your app cannot set it: the page refuses _ownerOrigin in an onUpdateSettings patch, even from the owner's own view. Only the owner (or a manager) changes it, from the app's settings.
  • Where it applies. That page's frame opens to that origin and nothing else of the owner's; another page's frame opens to its own owner's choice. Images and sound may load from it too, as from a declared origin. Widgets only: an app with engine or source runs under a policy fixed per version, so declare its servers in origins. You may declare both origins and ownerOrigin.
  • No session tokens. Nobody reviewed the owner's server, so an app that declares ownerOrigin cannot ask for a session token: the visitor's requests there carry nothing of Chain Daddy's. Have the visitor's wallet sign what your server needs to trust (sign-typed-data, with wallet:sign).
  • Try it locally. chaindaddy app dev --owner-origin https://forum.example.com opens the frame to that server and hands it to your app, as a page whose owner saved it would.

The same CORS rule applies: the frame has no origin, so answer with Access-Control-Allow-Origin: *.

Show a page of the owner's server (ownerOrigin.frame) ​

Add "frame": "/embed" to ownerOrigin, and once the owner saves a server, the coin page shows that page of it as your app's view, to every visitor. Until then visitors see your app's own view, and so does the owner, so use that view to walk the owner through setting up their server.

  • How it is framed. The coin page frames <owner's server><frame>?theme=light (or dark) itself. It is not inside your app's frame, because an app's frame has no origin, and a page that names who may frame it refuses an ancestor with none. The page runs on its own origin (sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"). It gets no wallet bridge, no session token and nothing of Chain Daddy's. Its own storage is third-party storage, so keep a sign-in in the frame's own memory or sessionStorage.
  • What the page must do. Allow the token pages in its frame-ancestors: https://chaindaddy.io, https://www.chaindaddy.io and https://*.crown.info (and https://staging.chaindaddy.io and https://*.staging.crown.info for staging). When it loads, and when its height changes, post {type: 'cd-app:resize', height} to its parent (parent.postMessage(msg, '*')). The owner's settings show "answers" once that first message arrives. The page keeps its tile's height and scrolls inside it.
  • What the page shows around it. A line under the frame: "Run by $SYMBOL's creator at <host>", with Report to the creator (<owner's server>/report, so serve a report page there) and Report illegal content (Chain Daddy's notice page).

Session tokens ​

A session token is a short signed note from Chain Daddy to your server. It says: this wallet is signed in to Chain Daddy and is using your app on this coin page, now. It is how your server knows who is playing, with no login of your own. It needs net:connect.

typescript
const res = await onAction({ type: 'session-token' });
if (res.ok) sendToYourServer(res.data.token); // { token, expiresAt }: good for 5 minutes
else askToSignIn();                           // a signed-out visitor: send the sign-in action from their tap

To sign a visitor in, send sign-in from a button they tap: the visitor's wallet signs one message, for Chain Daddy. Don't ask their wallet to sign a login of your own as well.

On your server, check the token's signature against Chain Daddy's public keys, then its claims. Verifying tokens has the check as one TypeScript file, with test vectors for any language.

ClaimWhat it isCheck
typWhat the token isequals app-session+v1
audYour app id (<developerId>/<name>)equals yours
exp, iatWhen it ends, and when it was issuedexp is in the future
issWho signed itequals https://chaindaddy.io
subThe wallet: lowercase 0x… on EVM, base58 on Solanakey the player's data by it
jtiA unique idrefuse one you have seen, to stop replays
idvtrue when the wallet has proven at least one account: a social account through OAuth sign-in, or a link verified on a coin page it runs. It names no accountrequire true to let only verified people in
chainId, chainKey, tokenAddress, crownIdThe coin page it was opened on
siteThe coin's own website the app was opened on, an https:// origin, when it runs there; absent on the coin's page on Chain Daddytreat its absence as the coin page
identityhandle, displayName, platform, verified, when the wallet's owner has published one. identity.verified describes only that handle, so check idv, not this

A session token grants nothing on the Chain Daddy API; it only proves presence to your server.

Games at their own address ​

A web build, and a game you publish from your own storage, runs at an address of its own: <app>.play.chaindaddy.live once approved. Three things to do there:

  • Never keep anything in a cookie. The game gets none, and we clear them on every load. Use the browser storage you are given (localStorage, IndexedDB).
  • Expect it to run only inside a coin page. Only Chain Daddy's own pages can embed it; opened in a tab of its own, it shows nothing.
  • Treat your server as part of the review. Every file of the build is checked against the fingerprint we reviewed before it runs. What that cannot cover is code a game with net:connect downloads from the servers it declares, so a game whose server starts serving something else can be delisted like any other.

A runner's signatures, scopes and rate limits are in Runners and events.

What the server checks ​

The coin page checks every transaction and signature your app asks for before the visitor sees it: the chain, the contract and function, who it gives access to your visitor's tokens, and how much of the chain's coin it moves. Chain Daddy's servers hold the same rules, with the same results, for requests that reach them rather than a coin page, such as from an app running on another site. A request one refuses, the other refuses too, with the same message. The same holds for the message a Solana wallet signs (sign-message): one rule writes the text, on the page and on the server.

Review checks ​

Every build you upload is scanned before a reviewer sees it. chaindaddy app scan runs the checks that fail a build on your machine first.

These fail the build, with the rule and the file named so you can fix it:

RuleWhat it findsFix
payments-third-party-checkoutAnother checkout or payment provider: Stripe, PayPal, Coinbase Commerce, Gumroad, Lemon Squeezy, Paddle, MoonPay, Ramp, Transak, NOWPayments, BTCPaySell with iap-purchase and pay with pay, through the checkout
payments-raw-token-calldata, payments-token-writeCode that builds and sends token transfers or approvals itselfUse pay; for any other call, the transaction action
payments-bulk-transferBulk NFT approval and sweeping (Seaport fulfillAvailableOrders with setApprovalForAll)Remove it
malware-minerCryptocurrency miningRemove it
malware-secret-harvestA field asking the visitor for a seed phrase, recovery phrase or private keyRemove it: an app never asks for a wallet's secret
malware-drainer-kitA known wallet-drainer kitRemove it
malware-exfil-hostSending to a chat webhook or bot, a paste site, or a tunnelSend to your own server, declared in network.origins

These go to the reviewer, and do not fail the build:

  • A link to a payment page outside Chain Daddy.
  • Contract addresses in your code that wallet.contracts does not declare.
  • eth_sign, which the page never offers.
  • What this version asks for that your live version did not: new permissions, origins, chains, contracts, functions, payment operations or currencies. Say why in your release notes.
  • A name that uses a wallet's, an exchange's or Chain Daddy's name, or a name or description that reads like an airdrop claim or a wallet "validation".

These also fail the build, wherever they are in it:

  • Asking for, or reading, a recovery phrase or private key.
  • Sending visitors' wallet, session or stored data off the page.
  • Asking the wallet to approve or move a visitor's tokens to an address they did not choose.
  • Telling visitors to pay some way other than the checkout, or offering a reward, airdrop or "wallet check" to get them to connect, sign or pay.
  • Selling chance-based rewards (loot boxes, raffles, wagers) for money or tokens, or presenting anything as an investment.
  • Sexual content involving minors or anyone presented as under 18, or without consent.
  • A category that does not say what the app is, or sexual or graphic content its content rating and contentThemes do not declare. Fix the manifest and upload again.
  • Photographs of sexually explicit content, which apps may not show yet. Use illustrated or stylised art.

When a build is rejected, or your app is disabled, suspended or removed, you are told why and the rule it rests on: in your notifications, by email to your developer account's contact email, and next to the build or in the app's Violations tab. If you think it is wrong, reply or write to support@chaindaddy.io within six months and a person will review it.

Three builds in a row turned down pause your submissions. Each rejection says how many builds you have left. A wrong category, an undeclared content theme or a photograph does not count, and nor does a build that only failed to start. An approved build starts the count again. If your submissions pause, write to support@chaindaddy.io.

A build that appears to contain illegal content pauses your submissions at once: sexual content involving minors or without consent, code that reads a recovery phrase or private key or moves a visitor's tokens to an address they did not choose, or known malware. They stay paused until we lift the pause; write to support@chaindaddy.io to ask.