Skip to content

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 .crown archive 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/cli CLI 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.js

The 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 pack

Produces a .crown file at dist/{slug}-{version}.crown. The package contains:

EntryRequired forPurpose
META.jsonalwaysformat version, SDK version, build environment
manifest.jsonalwaysthe schema-validated app manifest (named capp.json in your source tree)
bundle.jswidget, fullyour compiled widget
runner.config.jsonheadless, fullrunner registration payload
assets/**optionalicons, screenshots, cover image
HASHES.jsonalwaysSHA-256 of every other entry
signature.jsonafter signHMAC 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-place

Signing 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:

  1. Authenticity: Chain Daddy records the signature so we can prove which of your keys uploaded the artifact.
  2. Replay protection: signedAt is checked against the server clock (24 h window, 5 min skew). Reusing the same signedPayloadSha256 within the window is also rejected.
  3. Required at ingest: unsigned packages are rejected. Run sign between pack and submit.

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 --watch

Chain Daddy runs (in order):

  1. Pre-checks: size ≤ 8 MB, MIME is zip, safe extract (no path traversal, no symlinks), well-formed entries.
  2. Cross-checks: META.appId == manifest.id, HASHES.json matches every file, appSha256 matches the Merkle summary, bundleHash matches bundle.js.
  3. 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 returns PACKAGE_UNKNOWN_KEY (rotate the key to fix).
  4. Manifest schema: the same JSON Schema the CLI uses.
  5. Auto-checks: CSP scan, accessibility scan, security risk scan (sandbox escapes, prototype pollution, XSS sinks, data exfiltration, wallet bypass), standards conformance, tier-cap enforcement.
  6. Bundle upload: bundle.js and assets/** are uploaded to apps.chaindaddy.io/{appId}/{version}/... with immutable cache headers.
  7. Manifest rewrite: bundleUrl, bundleHash, bundleSize are overwritten with the platform-assigned values.
  8. Submission record: created in pending status. 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:

State diagram of the submission lifecycle: a submitted app starts in pending; a failed scanner check moves it to auto_check_failed, where you fix, bump the version, and submit a new package; request-publish moves it to in_review, where the reviewer signs off, moving it to approved and then published, or rejects it, moving it to review_failed; a withdrawn submission ends as rejectedState diagram of the submission lifecycle: a submitted app starts in pending; a failed scanner check moves it to auto_check_failed, where you fix, bump the version, and submit a new package; request-publish moves it to in_review, where the reviewer signs off, moving it to approved and then published, or rejects it, moving it to review_failed; a withdrawn submission ends as rejected

--watch streams the scan log to your terminal and exits when the submission reaches a terminal state:

StatusMeaning
pendingSubmitted; auto-checks running OR dev iterating in preview
in_reviewDeveloper requested publish; in the human-review queue
approvedAuto-checks passed; reviewer signed off
publishedLive in the registry; users can install on their tokens
auto_check_failedThe bundle failed a scanner check; see app logs <id>
review_failedA human reviewer rejected the submission
rejectedWithdrawn

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: failed

Read 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:

MethodPathAuthPurpose
POST/api/v2/developer/auth/cli-challengenoneget a challenge to sign with your wallet
POST/api/v2/developer/auth/cli-tokenwallet signatureexchange a signed challenge for an API key
POST/api/v2/developer/apps/packagesbearer + multipartupload a signed .crown
GET/api/v2/developer/apps/submissions/{id}bearerfetch submission state
GET/api/v2/developer/apps/submissions/{id}/scan-logbearerfetch (or stream with ?stream=1) scan events
POST/api/v2/developer/apps/submissions/{id}/request-publishbearermove pending → in_review
POST/api/v2/developer/keysbearermint a new API key
GET/api/v2/developer/keysbearerlist keys
POST/api/v2/developer/keys/{id}/rotatebearerrotate a key
DELETE/api/v2/developer/keys/{id}bearerrevoke 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