Appearance
Submitting an App
Package your app locally, sign it, upload a single .crown artifact, and Chain Daddy scans, hosts, and reviews it. The CLI handles every step, pack, sign, submit.
┌─────────────────────┐ pack ┌─────────────────────┐ sign ┌─────────────────────┐ submit
│ manifest + ├────────►│ signed .crown ├────────►│ signed + replay- ├────────► Chain Daddy
│ bundle + assets │ │ archive (8 MB max) │ │ protected artifact │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘You don't host the bundle. Chain Daddy extracts it from the package and serves it from apps.chaindaddy.io/{appId}/{version}/bundle.js with immutable caching. Your source code stays on your laptop or in your private git repo.
The
.crownarchive contains the app manifest, the compiled bundle, a runner config (if any), and assets, all integrity-checked.
Prerequisites
- A registered developer account with a verified KYC status
- An active Developer or Partner subscription
- The
@chaindaddy/cliCLI installed (npm install -g @chaindaddy/cli) - An API key (run
chaindaddy app key create --save)
Use the .crown flow described below.
Step 1: Build
bash
chaindaddy app build # widgets only, bundles src/index.tsx to dist/bundle.jsThe CLI's default bundler is esbuild with react and react-dom declared as externals, minification on, source maps off. If you need a custom build, run your own bundler and skip to step 2 with chaindaddy app pack --bundle path/to/bundle.js.
Step 2: Package
bash
chaindaddy app packProduces a .crown file at dist/{slug}-{version}.crown. The package contains:
| Entry | Required for | Purpose |
|---|---|---|
META.json | always | format version, SDK version, build environment |
manifest.json | always | the schema-validated app manifest (named capp.json in your source tree) |
bundle.js | widget, full | your compiled widget |
runner.config.json | headless, full | runner registration payload |
assets/** | optional | icons, screenshots, cover image |
HASHES.json | always | SHA-256 of every other entry |
signature.json | after sign | HMAC over manifest + HASHES |
The pack step is reproducible by default, running it twice on the same source produces byte-identical archives. Disable with --no-reproducible if you want real timestamps for debugging.
The archive follows the .capp package format spec (the format's internal name); the full spec document is available with the developer beta — sign up at chaindaddy.io/_developer. Use chaindaddy app inspect <package> to see exactly what a built archive contains.
Step 3: Sign
bash
chaindaddy app sign dist/yield-tracker-0.1.0.crown --in-placeSigning computes an HMAC-SHA256 over a canonical payload (the manifest, the HASHES.json, plus identity fields) using your API key secret. The signature is added as signature.json inside the same archive.
Why sign:
- Authenticity: Chain Daddy records the signature so we can prove which of your keys uploaded the artifact.
- Replay protection:
signedAtis checked against the server clock (24 h window, 5 min skew). Reusing the samesignedPayloadSha256within the window is also rejected. - Required at ingest: unsigned packages are rejected. Run
signbetweenpackandsubmit.
You can sign in an environment that has the secret (your laptop, a CI secret store) and upload from one that doesn't (a sandbox runner): the secret never has to travel with the package.
Step 4: Submit
bash
chaindaddy app submit dist/yield-tracker-0.1.0.crown --watchChain Daddy runs (in order):
- Pre-checks: size ≤ 8 MB, MIME is zip, safe extract (no path traversal, no symlinks), well-formed entries.
- Cross-checks:
META.appId==manifest.id,HASHES.jsonmatches every file,appSha256matches the Merkle summary,bundleHashmatchesbundle.js. - Signature: replay window check + HMAC verification. The server recovers your API key secret from its encrypted-at-rest store (sealed at issue time with AES-256-GCM) and re-computes the HMAC over a canonical payload. A mismatch returns
PACKAGE_SIGNATURE_MISMATCH; an unknown key returnsPACKAGE_UNKNOWN_KEY(rotate the key to fix). - Manifest schema: the same JSON Schema the CLI uses.
- Auto-checks: CSP scan, accessibility scan, security risk scan (sandbox escapes, prototype pollution, XSS sinks, data exfiltration, wallet bypass), standards conformance, tier-cap enforcement.
- Bundle upload:
bundle.jsandassets/**are uploaded toapps.chaindaddy.io/{appId}/{version}/...with immutable cache headers. - Manifest rewrite:
bundleUrl,bundleHash,bundleSizeare overwritten with the platform-assigned values. - Submission record: created in
pendingstatus. The developer can install on their own tokens for preview testing. When ready,chaindaddy app request-publish <id>moves it into the human-review queue.
The submission then moves through this lifecycle:
--watch streams the scan log to your terminal and exits when the submission reaches a terminal state:
| Status | Meaning |
|---|---|
pending | Submitted; auto-checks running OR dev iterating in preview |
in_review | Developer requested publish; in the human-review queue |
approved | Auto-checks passed; reviewer signed off |
published | Live in the registry; users can install on their tokens |
auto_check_failed | The bundle failed a scanner check; see app logs <id> |
review_failed | A human reviewer rejected the submission |
rejected | Withdrawn |
Step 5: Iterate
If auto-checks fail, the scan log tells you exactly which rule fired:
[INFO] package_extract extracted 142 KB package (sha256 8f3c…)
[INFO] package_signature signature verified (keyId=cd_live_3f7c…)
[ERROR] csp_scan 1 CSP errors found
[INFO] accessibility_scan score=92, 1 issues
[INFO] security_scan 0 critical, 0 high, 2 medium, 0 low
[INFO] standards_conformance 0 findings
[INFO] bundle_size bundle is 142337 bytes (uncompressed)
[INFO] auto_check_complete auto-check status: failedRead the structured details:
bash
chaindaddy app status <submissionId> --json | jq '.auto_check_results'Then fix, bump the patch version, and re-run from step 1. The version bump is required, duplicate (appId, version) pairs are rejected.
Endpoint reference
The CLI exists so you don't have to call these directly, but for tools that integrate at a lower level:
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /api/v2/developer/auth/cli-challenge | none | get a challenge to sign with your wallet |
POST | /api/v2/developer/auth/cli-token | wallet signature | exchange a signed challenge for an API key |
POST | /api/v2/developer/apps/packages | bearer + multipart | upload a signed .crown |
GET | /api/v2/developer/apps/submissions/{id} | bearer | fetch submission state |
GET | /api/v2/developer/apps/submissions/{id}/scan-log | bearer | fetch (or stream with ?stream=1) scan events |
POST | /api/v2/developer/apps/submissions/{id}/request-publish | bearer | move pending → in_review |
POST | /api/v2/developer/keys | bearer | mint a new API key |
GET | /api/v2/developer/keys | bearer | list keys |
POST | /api/v2/developer/keys/{id}/rotate | bearer | rotate a key |
DELETE | /api/v2/developer/keys/{id} | bearer | revoke a key |
Bearer-protected endpoints require a wallet session token on Authorization: Bearer … — the web dashboard uses its sign-in session (JWT), and the CLI uses the credential minted by the challenge → token flow above (stored by chaindaddy app login).
Why use the .crown flow?
- No bundle hosting. Chain Daddy serves the bundle from its CDN.
- Integrity guaranteed. No more "your CDN cached a stale bundle and the manifest hash drifted" bugs.
- Reproducible builds. Two CI runs of the same commit produce the same package bytes.
- Replay protection via signed timestamps.
- Your source stays on your machine. Only the compiled bundle is uploaded.
- Agent-friendly. One artifact, one command, one exit code.
A legacy JSON endpoint (POST /api/v2/developer/apps/submit) remains online for tools built before the package format shipped.
See also
chaindaddy appCLI reference (or read the source in the CLI package)- Security model: sandbox, runner auth, permission scopes
- Getting Started: the happy path from zero