{
  "schemaVersion": "1.0",
  "item": {
    "slug": "nova-app-builder",
    "name": "Nova App Builder",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/zfdang/nova-app-builder",
    "canonicalUrl": "https://clawhub.ai/zfdang/nova-app-builder",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/nova-app-builder",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=nova-app-builder",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md",
      "_meta.json",
      "assets/app-template/Dockerfile.txt",
      "assets/app-template/enclave/main.py",
      "assets/app-template/enclave/odyn.py",
      "assets/app-template/enclave/requirements.txt"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/nova-app-builder"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/nova-app-builder",
    "agentPageUrl": "https://openagent3.xyz/skills/nova-app-builder/agent",
    "manifestUrl": "https://openagent3.xyz/skills/nova-app-builder/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/nova-app-builder/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Nova App Builder",
        "body": "End-to-end workflow: scaffold → code → push to Git → create app → build → deploy → (on-chain: create app on-chain → enroll version → generate ZK proof → register instance)."
      },
      {
        "title": "Architecture Overview",
        "body": "Nova apps run inside AWS Nitro Enclaves, managed by Enclaver (Sparsity edition) and supervised by Odyn (PID 1 inside the enclave). Key concepts:\n\nEnclaver: packages your Docker image into an EIF (Enclave Image File) and manages the enclave lifecycle.\nOdyn: supervisor inside the enclave; provides Internal API for signing, attestation, encryption, KMS, S3, and manages networking.\nNova Platform: cloud platform at sparsity.cloud — builds EIFs from Git, runs enclaves, exposes app URLs.\nNova KMS: distributed key management; enclave apps derive keys via /v1/kms/derive.\nNova Python SDK: canonical SDK in enclave/nova_python_sdk/ — import as from nova_python_sdk.odyn import Odyn. Ships in nova-app-template and all examples."
      },
      {
        "title": "Two Development Paths",
        "body": "Minimal ScaffoldFull Template ForkStarting pointscripts/scaffold.pyFork nova-app-templateConfigadvanced field in Platform API handles all configSame — advanced field at app creation; platform generates enclaver.yamlenclaver.yaml needed?No — platform generates itNo — platform generates it from advancedenclave/config.pyN/AApp-level business logic config (contract address, chain IDs, etc.)FeaturesMinimal: signing, attestation, HTTPFull: KMS, App Wallet, E2E encryption, S3, Helios, React UI, oracleBest forSimple or custom appsApps needing KMS/wallet/storage/frontend\n\n⚠️ enclaver.yaml and nova-build.yaml are generated by Nova Platform — developers never need to write or provide these files. The control plane generates both from app settings before triggering the build workflow. S3 storage and AWS credentials are fully managed by the platform; developers never touch them."
      },
      {
        "title": "Prerequisites (collect from user before starting)",
        "body": "Ensure skill is up to date before starting:\nclawhub update nova-app-builder\n\nOlder versions are missing Dockerfile.txt in the template, causing scaffold to fail.\n\nApp idea: What does the app do?\n\n\nNova account + API key: Sign up at sparsity.cloud → Account → API Keys.\n\n\nGitHub repo + GitHub PAT: Used only to push your app code to GitHub. Nova Platform then builds from the repo URL. The PAT is not passed to Nova Platform.\nGitHub PAT setup:\n\nGitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens\nRequired permissions: Contents (Read & Write), Metadata (Read)\nPush with token:\ngit remote set-url origin https://oauth2:${GH_TOKEN}@github.com/<user>/<repo>.git\ngit push origin main\n\n⚠️ Do NOT ask for Docker registry credentials, AWS S3 credentials, or enclaver.yaml.\nNova Platform handles the Docker build, image registry, and S3/storage provisioning internally.\nDevelopers never touch AWS credentials or write enclaver.yaml — the platform generates it from the advanced field.\nThe app only calls Internal API endpoints (/v1/s3/*) for storage; Odyn handles the rest."
      },
      {
        "title": "Step 1 — Scaffold the project",
        "body": "python3 scripts/scaffold.py \\\n  --name <app-name> \\\n  --desc \"<one-line description>\" \\\n  --port <port> \\\n  --out <output-dir>\n\nPort choice: Any port works. Set it via advanced.app_listening_port when creating the app. Must also match EXPOSE in your Dockerfile. No requirement to use 8080.\n\nThis generates <output-dir>/<app-name>/ with the following structure:\n\n<app-name>/\n├── Dockerfile\n└── enclave/\n    ├── main.py\n    ├── odyn.py\n    └── requirements.txt\n\nNote: The template ships as Dockerfile.txt (clawhub cannot distribute extensionless files). scaffold.py automatically renames it to Dockerfile and substitutes the port. No manual action needed.\n\nAlternatively, fork nova-app-template for the full production-ready structure:\n\nnova-app-template/\n├── Makefile\n├── Dockerfile\n├── enclaver.yaml          ← reference template only; portal parses listening port from it;\n│                            platform generates the real enclaver.yaml from app settings\n├── enclave/\n│   ├── app.py             ← entry point (not main.py)\n│   ├── routes.py          ← FastAPI route handlers\n│   ├── config.py          ← app business logic config (chain RPCs, contract address, etc.)\n│   ├── chain.py           ← chain interaction helpers (app-specific ABI, contract reads)\n│   ├── tasks.py           ← background scheduler (oracle, periodic jobs)\n│   ├── nova_python_sdk/   ← canonical Nova SDK (do not modify)\n│   │   ├── odyn.py        ← identity, attestation, encryption, S3, KMS/app-wallet wrappers\n│   │   ├── kms_client.py  ← thin client for KMS and app-wallet request handlers\n│   │   ├── rpc.py         ← shared RPC transport + environment switching\n│   │   └── env.py         ← IN_ENCLAVE + endpoint resolution helpers\n│   └── requirements.txt\n├── frontend/              ← React/Next.js UI (served at /frontend)\n└── contracts/             ← example on-chain contracts\n\nFeatures: KMS, App Wallet, E2E encryption, S3 (KMS-encrypted), Helios dual-chain RPC, React dashboard, example contracts, oracle."
      },
      {
        "title": "Step 2 — Write the app logic",
        "body": "Edit enclave/main.py (scaffold) or enclave/routes.py (full template). Key patterns:\n\nMinimal FastAPI app:\n\nimport os, httpx\nfrom fastapi import FastAPI\n\napp = FastAPI()\nIN_ENCLAVE = os.getenv(\"IN_ENCLAVE\", \"false\").lower() == \"true\"\nODYN_BASE = \"http://localhost:18000\" if IN_ENCLAVE else \"http://odyn.sparsity.cloud:18000\"\n\n@app.get(\"/api/hello\")\ndef hello():\n    r = httpx.get(f\"{ODYN_BASE}/v1/eth/address\", timeout=10)\n    return {\"message\": \"Hello from TEE!\", \"enclave\": r.json()[\"address\"]}\n\n⚠️ IN_ENCLAVE is NOT injected automatically by Enclaver — it's an app-level convention. Set it in your Dockerfile as ENV IN_ENCLAVE=false for local dev; the platform sets it true in production.\n\nUsing the Nova Python SDK (recommended for full template; copy from nova-app-template):\n\nfrom nova_python_sdk.odyn import Odyn\nfrom nova_python_sdk.kms_client import NovaKmsClient\n\nodyn = Odyn()  # auto-detects IN_ENCLAVE env var\nkms = NovaKmsClient(endpoint=odyn.endpoint)\n\n@app.get(\"/api/hello\")\ndef hello():\n    return {\"address\": odyn.eth_address(), \"random\": odyn.get_random_bytes().hex()}\n\n@app.post(\"/api/sign\")\ndef sign(body: dict):\n    return odyn.sign_message(body[\"message\"])\n\n@app.post(\"/api/store\")\ndef store(body: dict):\n    odyn.s3_put(body[\"key\"], body[\"value\"].encode())\n    return {\"stored\": True}\n\nWith App Wallet + KMS:\n\nfrom nova_python_sdk.kms_client import NovaKmsClient\n\nkms = NovaKmsClient(endpoint=odyn.endpoint)\n\n@app.get(\"/api/wallet\")\ndef wallet():\n    return kms.app_wallet_address()     # {\"address\": \"0x...\", \"app_id\": 0}\n\n@app.post(\"/api/sign\")\ndef sign(body: dict):\n    return kms.app_wallet_sign(body[\"message\"])   # {\"signature\": \"0x...\"}\n\n@app.post(\"/api/sign-tx\")\ndef sign_tx(body: dict):\n    return kms.app_wallet_sign_tx(body)  # EIP-1559 transaction signing\n\nSDK module responsibilities:\n\nnova_python_sdk/odyn.py — identity, attestation, encryption, S3, convenience wrappers around /v1/kms/* and /v1/app-wallet/*\nnova_python_sdk/kms_client.py — preferred thin client for KMS and app-wallet flows in request/response handlers\nnova_python_sdk/rpc.py — shared RPC transport + environment switching; keep app-specific contract logic in enclave/chain.py\nnova_python_sdk/env.py — shared IN_ENCLAVE and endpoint resolution helpers\n\nRuntime endpoint precedence (from env.py):\n\nOdyn API: ODYN_API_BASE_URL → ODYN_ENDPOINT → http://127.0.0.1:18000 (when IN_ENCLAVE=true) → http://odyn.sparsity.cloud:18000 (otherwise)\nBusiness chain RPC: ETHEREUM_MAINNET_RPC_URL → BUSINESS_CHAIN_RPC_URL → http://127.0.0.1:18546 (enclave) → http://odyn.sparsity.cloud:18546 (otherwise)\nAuth chain RPC: NOVA_AUTH_CHAIN_RPC_URL → AUTH_CHAIN_RPC_URL → http://127.0.0.1:18545 (enclave) → http://odyn.sparsity.cloud:18545 (otherwise)\n\nDual-chain topology (as in the template):\n\nAuth/Registry chain: Base Sepolia (84532) — KMS authorization & app registry\nBusiness chain: Ethereum Mainnet (1) — your business logic\n\nHelios light-client RPC runs locally at http://127.0.0.1:18545 (Base Sepolia) and http://127.0.0.1:18546 (Mainnet).\n\n⚠️ Helios RPC ports must be decided before creating the app — they are set in advanced.helios_chains[].local_rpc_port and locked at creation time. Choose values carefully up front.\n\nRules for enclave code:\n\nOdyn calls (localhost): requests or httpx both work — Odyn is local.\nExternal outbound HTTP: must use httpx (proxy-aware). Never use requests or urllib for external calls — they may bypass the egress proxy.\nPersistent state → use /v1/s3/* endpoints (or odyn.s3_put()); the enclave filesystem is ephemeral.\nSecrets → derive via KMS (/v1/kms/derive); never from env vars or hardcoded.\nTest locally: IN_ENCLAVE=false python3 app.py (scaffold: uvicorn main:app --port <port>). Odyn calls hit the public mock.\n\n/.well-known/attestation routing note: In production Nova deployments, this endpoint is handled by Caddy routing to the Aux API (port 18001) — not your app code. Some examples implement it in app code as a local dev shim. Don't ship that in production."
      },
      {
        "title": "Step 3 — Commit & push to Git",
        "body": "Your repo only needs Dockerfile + app code. No local Docker build needed — Nova Platform builds from your Git repo directly.\n\nKMS integration is fully handled by the platform — just set enable_decentralized_kms: true (and optionally enable_app_wallet: true) in advanced when creating the app. No contract addresses, app IDs, or manual KMS config needed in your code.\n\ngit add .\ngit commit -m \"Initial Nova app\"\ngit push origin main"
      },
      {
        "title": "Step 4 — Deploy to Nova Platform",
        "body": "The deployment is a 3-step process: Create App → Trigger Build → Create Deployment.\n\nVia Portal (recommended for first-time)\n\nPortal flow (works for both scaffold and full template):\n\nGo to sparsity.cloud → Apps → Create App\n\n\nFill in:\n\nName, Repo URL, optional Description, metadata_uri, app_contract_addr\nConfigure Advanced settings (port, egress, KMS, App Wallet, S3, Helios toggles, chain selection)\nSubmit → copy the App sqid\n\n\nThe repo URL is stored on the app record and used for all subsequent builds — you don't provide it again at build time.\n\n\n\nIn the App page → Versions → + New Version:\n\nGit Ref: main (or tag / commit SHA)\nVersion: e.g. 1.0.0\nSubmit\n\n\n\nWait for build status → success (platform builds Docker image → EIF → generates PCRs and build-attestation.json signed with Sigstore/cosign)\n\n\nIn Versions, find the successful version → Deploy this version:\n\nSelect Region\nSelect Tier: standard or performance\nSubmit\n\n\n\nPoll until deployment state → running → copy the App URL (hostname from app detail)\n\nThe deploy modal has no environment-variable input — no env vars or credentials needed.\n\nVia API (scripted)\n\nThe script will ask interactively whether to run on-chain registration after the app is live. Use --onchain to always run it, --no-onchain to always skip.\n\n# Preview config without deploying\npython3 scripts/nova_deploy.py \\\n  --repo https://github.com/you/my-app \\\n  --name \"my-app\" \\\n  --port 8080 \\\n  --api-key <your-nova-api-key> \\\n  --dry-run\n\n# Deploy (will prompt for on-chain at the end)\npython3 scripts/nova_deploy.py \\\n  --repo https://github.com/you/my-app \\\n  --name \"my-app\" \\\n  --port 8080 \\\n  --api-key <your-nova-api-key>\n\n# Deploy + on-chain registration in one shot\npython3 scripts/nova_deploy.py \\\n  --repo https://github.com/you/my-app \\\n  --name \"my-app\" \\\n  --port 8080 \\\n  --api-key <your-nova-api-key> \\\n  --onchain\n\nOr via raw API:\n\nBASE=\"https://sparsity.cloud/api\"\nTOKEN=\"<your-api-key>\"\nREPO=\"https://github.com/you/my-app\"\n\n# 1. Create app — 'advanced' is the only config field needed\nSQID=$(curl -sX POST \"$BASE/apps\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\":\"my-app\",\n    \"repo_url\":\"'\"$REPO\"'\",\n    \"description\":\"optional description\",\n    \"metadata_uri\":\"\",\n    \"app_contract_addr\":\"\",\n    \"advanced\":{\n      \"directory\":\"/\",\n      \"app_listening_port\":8080,\n      \"egress_allow\":[\"**\"],\n      \"enable_decentralized_kms\":false,\n      \"enable_persistent_storage\":false,\n      \"enable_s3_storage\":false,\n      \"enable_s3_kms_encryption\":false,\n      \"enable_ipfs_storage\":false,\n      \"enable_walrus_storage\":false,\n      \"enable_app_wallet\":false,\n      \"enable_helios_rpc\":false\n    }\n  }' \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['sqid'])\")\necho \"App sqid: $SQID\"\n\n# 2. Create new version (build) — repo URL comes from app record, not repeated here\nBUILD_ID=$(curl -sX POST \"$BASE/apps/$SQID/builds\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"git_ref\":\"main\",\"version\":\"1.0.0\"}' \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['id'])\")\necho \"Build ID: $BUILD_ID\"\n\n# 3. Poll build\nwhile true; do\n  STATUS=$(curl -s \"$BASE/builds/$BUILD_ID/status\" \\\n    -H \"Authorization: Bearer $TOKEN\" \\\n    | python3 -c \"import sys,json; print(json.load(sys.stdin).get('status',''))\")\n  echo \"Build: $STATUS\"\n  [ \"$STATUS\" = \"success\" ] && break\n  [ \"$STATUS\" = \"failed\" ] && echo \"Build failed!\" && exit 1\n  sleep 15\ndone\n\n# 4. Deploy this version — specify region and tier\n# region options: ap-south-1 (default), us-east-1, us-west-1, eu-west-1\n# tier options: standard (2vCPU/5GiB), performance (6vCPU/13GiB)\nDEPLOY_ID=$(curl -sX POST \"$BASE/apps/$SQID/deployments\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"build_id\\\":$BUILD_ID,\\\"region\\\":\\\"ap-south-1\\\",\\\"tier\\\":\\\"standard\\\"}\" \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['id'])\")\necho \"Deployment ID: $DEPLOY_ID\"\n\n# 5. Poll deployment\nwhile true; do\n  RESP=$(curl -s \"$BASE/deployments/$DEPLOY_ID/status\" -H \"Authorization: Bearer $TOKEN\")\n  STATE=$(echo $RESP | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('deployment_state',''))\")\n  echo \"Deployment state: $STATE\"\n  [ \"$STATE\" = \"running\" ] && break\n  [ \"$STATE\" = \"failed\" ] && echo \"Deploy failed!\" && exit 1\n  sleep 15\ndone\n\n# 6. Get app URL (hostname from detail, or use unified status)\ncurl -s \"$BASE/apps/$SQID/detail\" -H \"Authorization: Bearer $TOKEN\" \\\n  | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d['app'].get('hostname',''))\"\n\n# Tip: GET /api/apps/{sqid}/status gives the full lifecycle in one call:\n# build_status, deployment_state, proof_status, onchain_instance_id, etc."
      },
      {
        "title": "Step 5 — Verify the live app",
        "body": "# Health check\ncurl https://<hostname>/\n\n# Hello endpoint (returns enclave address + signature)\ncurl https://<hostname>/api/hello\n\n# Attestation (proves it's a real Nitro Enclave) — POST, returns binary CBOR\n# In production, /.well-known/attestation is Caddy → Aux API; not app code\ncurl -sX POST https://<hostname>/.well-known/attestation --output attestation.bin\n\nNote: /api/app-wallet is only available if you forked nova-app-template. The scaffold template includes /api/hello instead."
      },
      {
        "title": "Step 6 — On-Chain Registration (optional but important for trust)",
        "body": "After the app is running, register it on-chain to establish verifiable trust.\n\nUsing the script (recommended):\n\npython3 scripts/nova_deploy.py ... --onchain\n\nThe script will automatically execute steps 6a–6d and poll until complete.\n\nManually via API — 4-step sequence:\n\n# 6a. Create app on-chain\ncurl -sX POST \"$BASE/apps/$SQID/create-onchain\" \\\n  -H \"Authorization: Bearer $TOKEN\"\n# Poll: GET /api/apps/{sqid}/status → onchain_app_id is set when done\n\n# 6b. Enroll build version on-chain (PCR measurements → trusted code fingerprint)\ncurl -sX POST \"$BASE/apps/$SQID/builds/$BUILD_ID/enroll\" \\\n  -H \"Authorization: Bearer $TOKEN\"\n\n# Poll enrollment via build status endpoint\nwhile true; do\n  ENROLLED=$(curl -s \"$BASE/builds/$BUILD_ID/status\" \\\n    -H \"Authorization: Bearer $TOKEN\" \\\n    | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('is_enrolled',''))\")\n  echo \"Enrolled: $ENROLLED\"\n  [ \"$ENROLLED\" = \"True\" ] && break\n  sleep 10\ndone\n\n# 6c. Generate ZK proof (SP1-based)\ncurl -sX POST \"$BASE/zkproof/generate\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"deployment_id\\\": $DEPLOY_ID}\"\n\n# Poll proof via deployment status endpoint (or /zkproof/status/{deployment_id})\nwhile true; do\n  PROOF=$(curl -s \"$BASE/deployments/$DEPLOY_ID/status\" \\\n    -H \"Authorization: Bearer $TOKEN\" \\\n    | python3 -c \"import sys,json; print(json.load(sys.stdin).get('proof_status',''))\")\n  echo \"Proof status: $PROOF\"\n  [ \"$PROOF\" = \"proved\" ] && break\n  [ \"$PROOF\" = \"failed\" ] && echo \"Proof failed!\" && exit 1\n  sleep 30\ndone\n\n# 6d. Register instance on-chain (manual — auto-registration has been removed)\ncurl -sX POST \"$BASE/apps/$SQID/instance/register\" \\\n  -H \"Authorization: Bearer $TOKEN\"\n\n# Poll: GET /api/deployments/{id}/status → onchain_instance_id is set when done\n# Or use unified: GET /api/apps/{sqid}/status → latest_onchain_instance_id\n\nWhat each step does:\n\nCreate app on-chain: Registers your app in the Nova App Registry contract (Base Sepolia)\nEnroll version: Records the EIF's PCR measurements on-chain — the trusted code fingerprint\nGenerate ZK proof: Platform generates an SP1 zero-knowledge proof from the enclave's Nitro attestation\nRegister instance: Verifies the ZK proof on-chain and links the live instance to its enrolled version\n\nAfter registration, anyone can verify on-chain that this running instance matches the audited build."
      },
      {
        "title": "Odyn Internal API Reference",
        "body": "All endpoints are on http://127.0.0.1:18000 (Primary API — inside enclave only, NOT publicly exposed in production).\n\nAlways-available endpoints:\n\nEndpointMethodDescription/v1/eth/addressGETEnclave Ethereum address + public key/v1/eth/signPOSTEIP-191 personal-sign (optionally with attestation)/v1/eth/sign-txPOSTSign EIP-1559 transactions/v1/randomGET32 bytes from NSM-backed randomness/v1/attestationPOSTRaw CBOR attestation bytes (not JSON)/v1/encryption/public_keyGETEnclave P-384 public key/v1/encryption/encryptPOSTEncrypt response to a client/v1/encryption/decryptPOSTDecrypt client payloads\n\nFeature-gated endpoints (set in advanced at app creation):\n\nEndpoint GroupGateDescription/v1/s3/*enable_s3_storage: trueBase64 object storage backed by S3/v1/kms/deriveenable_decentralized_kms: trueDeterministic key derivation/v1/kms/kv/put|get|deleteenable_decentralized_kms: trueKMS-backed KV store with optional TTL/v1/app-wallet/addressenable_app_wallet: trueApp-specific wallet address/v1/app-wallet/signenable_app_wallet: trueEIP-191 message signing via app wallet/v1/app-wallet/sign-txenable_app_wallet: trueEIP-1559 transaction signing via app wallet\n\nPort exposure in production:\n\nRuntime publishes exactly two ports: host app port → enclave app port and host attestation port → enclave Aux API port 18001\n/.well-known/attestation* is routed by Caddy to the host attestation port → Aux API (18001)\nPrimary API (18000) is NOT exposed publicly for normal production apps\n\nOdyn mock service (local development):\n\nPrimary API: http://odyn.sparsity.cloud:18000\nAux API: http://odyn.sparsity.cloud:18001\nHelios RPC presets: http://odyn.sparsity.cloud:18545 through :18553"
      },
      {
        "title": "Key Notes",
        "body": "advanced is REQUIRED at app creation. Omitting it will cause the build to fail. Only advanced exists — the old enclaver field has been removed from the API.\nYour repo only needs Dockerfile + app code. The platform handles everything else at build time.\nApp ID format: sqid (string like abc123) — use in all URL paths, not the integer id.\nPort: Set via advanced.app_listening_port. Must also match EXPOSE in Dockerfile. The enclaver.yaml in the template repo is a reference; portal reads the listening port from it.\nRepo URL is set once at app creation — not repeated at build time. Build only needs git_ref + version.\nDeploy requires region + tier: regions are ap-south-1 (default), us-east-1, us-west-1, eu-west-1. tier is \"standard\" (2 vCPU/5 GiB) or \"performance\" (6 vCPU/13 GiB). No env var injection in deploy.\nHelios RPC: Use the canonical port mapping in references/nova-api.md — ports are fixed and locked at app creation.\nKMS dependency chain: enable_app_wallet or enable_s3_kms_encryption implies KMS → implies Helios (with base-sepolia chain) → implies kms_app_id + nova_app_registry.\nKMS/S3 fully managed by platform — no AWS credentials, no enclaver.yaml authoring needed.\nNo Docker push: Platform builds from Git.\nOn-chain steps (create-onchain → enroll → ZK proof → register) are required for public verifiability, but optional for a functional running app.\nInstance registration is now manual — auto-registration has been removed. Use POST /api/apps/{app_sqid}/instance/register after proof is proved.\nPortal helper APIs (clone-repo, get-enclaver-config) are used by the portal UI to auto-fill the listening port from enclaver.yaml when you enter a repo URL. Not needed for direct API usage.\nBuild provenance: Builds produce a build-attestation.json with PCR0/PCR1/PCR2, source repo, commit, GitHub run metadata — signed with Sigstore/cosign and stored off-chain.\nIN_ENCLAVE is not injected by Enclaver — set ENV IN_ENCLAVE=false in Dockerfile for local dev; production sets it true."
      },
      {
        "title": "Common Issues",
        "body": "SymptomFixDockerfile missing after scaffoldRun clawhub update nova-app-builder then re-scaffold. The template ships as Dockerfile.txt and scaffold renames it to Dockerfile automatically.App expects enclaver.yaml or old configenclaver.yaml is always generated by Nova Platform — developers never provide it. The advanced field at app creation drives all platform-level config. Only enclave/config.py (app business logic) needs developer attention when using the full template.API endpoint console.sparsity.cloud not resolvingOld version of nova_deploy.py. Run clawhub update nova-app-builder. Correct endpoint is https://sparsity.cloud/api.from odyn import Odyn failsOld import path. New SDK is at nova_python_sdk/. Use from nova_python_sdk.odyn import Odyn.Build stuck in pendingCheck GitHub Actions in nova build repo; may be queuedBuild failedCheck error_message in build response; usually Dockerfile issueDeploy API returns 401Regenerate API key at sparsity.cloudApp stuck in provisioning >10 minCheck app logs via GET /api/apps/{sqid}/detailhttpx request fails inside enclaveAdd domain to advanced.egress_allow. Note: \"**\" matches domains only — add \"0.0.0.0/0\" for direct IP connectionsDirect IP connection blocked\"**\" does NOT cover IPs. Add \"0.0.0.0/0\" (IPv4) and/or \"::/0\" (IPv6) to egress_allowS3 failsEnsure 169.254.169.254 and S3 endpoint are in egress allow list/v1/kms/* returns 400Ensure enable_decentralized_kms: true and enable_helios_rpc: true in advanced at app creationApp Wallet unavailableEnsure enable_app_wallet: true in advanced at app creationProxy not respected for external callsUse httpx for external HTTP calls (proxy-aware). requests/urllib may bypass the egress proxy. Note: requests is fine for internal Odyn calls (localhost).Health check returns 502App is starting; wait for enclave to fully bootZK proof stuckCheck GET /api/zkproof/status/{deployment_id} for details/.well-known/attestation returning wrong format in prodIn production, this is Caddy → Aux API routing — not app code. Remove app-side shim for production builds."
      },
      {
        "title": "Reference Files",
        "body": "references/odyn-api.md — Full Odyn Internal API (signing, encryption, S3, KMS, App Wallet, attestation)\nreferences/nova-api.md — Nova Platform REST API (full endpoint reference)"
      },
      {
        "title": "Nova Examples",
        "body": "ExampleDescriptionhello-world-teeSimplest start — identity + attestation only; uses nova_python_sdk/echo-vaultBest backend reference — S3 persistence + Helios RPC; source of canonical SDKsecured-chat-botBest E2E encryption reference — browser-to-enclave P-384 ECDHrng-oracleOn-chain randomness flow with enclave signingprice-oracleExternal API verification and signing patterns\n\nNova Python SDK: The canonical SDK lives in enclave/nova_python_sdk/ in both nova-app-template and all examples. Do not modify SDK files — copy the whole directory into your project."
      },
      {
        "title": "Key URLs",
        "body": "Nova Platform: https://sparsity.cloud\nNova Platform API docs: https://sparsity.cloud/api/docs\nNova Sales Deck: https://sparsity.cloud/decks/nova-sales-deck.html\nNova Resources: https://sparsity.cloud/resources/\nNova Examples: https://github.com/sparsity-xyz/sparsity-nova-examples/\nNova Python SDK (canonical): enclave/nova_python_sdk/ in nova-app-template or echo-vault\nEnclaver (Sparsity): https://github.com/sparsity-xyz/enclaver\nNova App Template: https://github.com/sparsity-xyz/nova-app-template\nEnclaver Docs: odyn.md, internal_api.md\nNova Platform Architecture: build-attestation.md, runtime-port-exposure-flow.md"
      }
    ],
    "body": "Nova App Builder\n\nEnd-to-end workflow: scaffold → code → push to Git → create app → build → deploy → (on-chain: create app on-chain → enroll version → generate ZK proof → register instance).\n\nArchitecture Overview\n\nNova apps run inside AWS Nitro Enclaves, managed by Enclaver (Sparsity edition) and supervised by Odyn (PID 1 inside the enclave). Key concepts:\n\nEnclaver: packages your Docker image into an EIF (Enclave Image File) and manages the enclave lifecycle.\nOdyn: supervisor inside the enclave; provides Internal API for signing, attestation, encryption, KMS, S3, and manages networking.\nNova Platform: cloud platform at sparsity.cloud — builds EIFs from Git, runs enclaves, exposes app URLs.\nNova KMS: distributed key management; enclave apps derive keys via /v1/kms/derive.\nNova Python SDK: canonical SDK in enclave/nova_python_sdk/ — import as from nova_python_sdk.odyn import Odyn. Ships in nova-app-template and all examples.\nTwo Development Paths\n\tMinimal Scaffold\tFull Template Fork\nStarting point\tscripts/scaffold.py\tFork nova-app-template\nConfig\tadvanced field in Platform API handles all config\tSame — advanced field at app creation; platform generates enclaver.yaml\nenclaver.yaml needed?\tNo — platform generates it\tNo — platform generates it from advanced\nenclave/config.py\tN/A\tApp-level business logic config (contract address, chain IDs, etc.)\nFeatures\tMinimal: signing, attestation, HTTP\tFull: KMS, App Wallet, E2E encryption, S3, Helios, React UI, oracle\nBest for\tSimple or custom apps\tApps needing KMS/wallet/storage/frontend\n\n⚠️ enclaver.yaml and nova-build.yaml are generated by Nova Platform — developers never need to write or provide these files. The control plane generates both from app settings before triggering the build workflow. S3 storage and AWS credentials are fully managed by the platform; developers never touch them.\n\nPrerequisites (collect from user before starting)\n\nEnsure skill is up to date before starting:\n\nclawhub update nova-app-builder\n\n\nOlder versions are missing Dockerfile.txt in the template, causing scaffold to fail.\n\nApp idea: What does the app do?\n\nNova account + API key: Sign up at sparsity.cloud → Account → API Keys.\n\nGitHub repo + GitHub PAT: Used only to push your app code to GitHub. Nova Platform then builds from the repo URL. The PAT is not passed to Nova Platform.\n\nGitHub PAT setup:\n\nGitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens\nRequired permissions: Contents (Read & Write), Metadata (Read)\nPush with token:\ngit remote set-url origin https://oauth2:${GH_TOKEN}@github.com/<user>/<repo>.git\ngit push origin main\n\n\n⚠️ Do NOT ask for Docker registry credentials, AWS S3 credentials, or enclaver.yaml. Nova Platform handles the Docker build, image registry, and S3/storage provisioning internally. Developers never touch AWS credentials or write enclaver.yaml — the platform generates it from the advanced field. The app only calls Internal API endpoints (/v1/s3/*) for storage; Odyn handles the rest.\n\nFull Workflow\nStep 1 — Scaffold the project\npython3 scripts/scaffold.py \\\n  --name <app-name> \\\n  --desc \"<one-line description>\" \\\n  --port <port> \\\n  --out <output-dir>\n\n\nPort choice: Any port works. Set it via advanced.app_listening_port when creating the app. Must also match EXPOSE in your Dockerfile. No requirement to use 8080.\n\nThis generates <output-dir>/<app-name>/ with the following structure:\n\n<app-name>/\n├── Dockerfile\n└── enclave/\n    ├── main.py\n    ├── odyn.py\n    └── requirements.txt\n\n\nNote: The template ships as Dockerfile.txt (clawhub cannot distribute extensionless files). scaffold.py automatically renames it to Dockerfile and substitutes the port. No manual action needed.\n\nAlternatively, fork nova-app-template for the full production-ready structure:\n\nnova-app-template/\n├── Makefile\n├── Dockerfile\n├── enclaver.yaml          ← reference template only; portal parses listening port from it;\n│                            platform generates the real enclaver.yaml from app settings\n├── enclave/\n│   ├── app.py             ← entry point (not main.py)\n│   ├── routes.py          ← FastAPI route handlers\n│   ├── config.py          ← app business logic config (chain RPCs, contract address, etc.)\n│   ├── chain.py           ← chain interaction helpers (app-specific ABI, contract reads)\n│   ├── tasks.py           ← background scheduler (oracle, periodic jobs)\n│   ├── nova_python_sdk/   ← canonical Nova SDK (do not modify)\n│   │   ├── odyn.py        ← identity, attestation, encryption, S3, KMS/app-wallet wrappers\n│   │   ├── kms_client.py  ← thin client for KMS and app-wallet request handlers\n│   │   ├── rpc.py         ← shared RPC transport + environment switching\n│   │   └── env.py         ← IN_ENCLAVE + endpoint resolution helpers\n│   └── requirements.txt\n├── frontend/              ← React/Next.js UI (served at /frontend)\n└── contracts/             ← example on-chain contracts\n\n\nFeatures: KMS, App Wallet, E2E encryption, S3 (KMS-encrypted), Helios dual-chain RPC, React dashboard, example contracts, oracle.\n\nStep 2 — Write the app logic\n\nEdit enclave/main.py (scaffold) or enclave/routes.py (full template). Key patterns:\n\nMinimal FastAPI app:\n\nimport os, httpx\nfrom fastapi import FastAPI\n\napp = FastAPI()\nIN_ENCLAVE = os.getenv(\"IN_ENCLAVE\", \"false\").lower() == \"true\"\nODYN_BASE = \"http://localhost:18000\" if IN_ENCLAVE else \"http://odyn.sparsity.cloud:18000\"\n\n@app.get(\"/api/hello\")\ndef hello():\n    r = httpx.get(f\"{ODYN_BASE}/v1/eth/address\", timeout=10)\n    return {\"message\": \"Hello from TEE!\", \"enclave\": r.json()[\"address\"]}\n\n\n⚠️ IN_ENCLAVE is NOT injected automatically by Enclaver — it's an app-level convention. Set it in your Dockerfile as ENV IN_ENCLAVE=false for local dev; the platform sets it true in production.\n\nUsing the Nova Python SDK (recommended for full template; copy from nova-app-template):\n\nfrom nova_python_sdk.odyn import Odyn\nfrom nova_python_sdk.kms_client import NovaKmsClient\n\nodyn = Odyn()  # auto-detects IN_ENCLAVE env var\nkms = NovaKmsClient(endpoint=odyn.endpoint)\n\n@app.get(\"/api/hello\")\ndef hello():\n    return {\"address\": odyn.eth_address(), \"random\": odyn.get_random_bytes().hex()}\n\n@app.post(\"/api/sign\")\ndef sign(body: dict):\n    return odyn.sign_message(body[\"message\"])\n\n@app.post(\"/api/store\")\ndef store(body: dict):\n    odyn.s3_put(body[\"key\"], body[\"value\"].encode())\n    return {\"stored\": True}\n\n\nWith App Wallet + KMS:\n\nfrom nova_python_sdk.kms_client import NovaKmsClient\n\nkms = NovaKmsClient(endpoint=odyn.endpoint)\n\n@app.get(\"/api/wallet\")\ndef wallet():\n    return kms.app_wallet_address()     # {\"address\": \"0x...\", \"app_id\": 0}\n\n@app.post(\"/api/sign\")\ndef sign(body: dict):\n    return kms.app_wallet_sign(body[\"message\"])   # {\"signature\": \"0x...\"}\n\n@app.post(\"/api/sign-tx\")\ndef sign_tx(body: dict):\n    return kms.app_wallet_sign_tx(body)  # EIP-1559 transaction signing\n\n\nSDK module responsibilities:\n\nnova_python_sdk/odyn.py — identity, attestation, encryption, S3, convenience wrappers around /v1/kms/* and /v1/app-wallet/*\nnova_python_sdk/kms_client.py — preferred thin client for KMS and app-wallet flows in request/response handlers\nnova_python_sdk/rpc.py — shared RPC transport + environment switching; keep app-specific contract logic in enclave/chain.py\nnova_python_sdk/env.py — shared IN_ENCLAVE and endpoint resolution helpers\n\nRuntime endpoint precedence (from env.py):\n\nOdyn API: ODYN_API_BASE_URL → ODYN_ENDPOINT → http://127.0.0.1:18000 (when IN_ENCLAVE=true) → http://odyn.sparsity.cloud:18000 (otherwise)\nBusiness chain RPC: ETHEREUM_MAINNET_RPC_URL → BUSINESS_CHAIN_RPC_URL → http://127.0.0.1:18546 (enclave) → http://odyn.sparsity.cloud:18546 (otherwise)\nAuth chain RPC: NOVA_AUTH_CHAIN_RPC_URL → AUTH_CHAIN_RPC_URL → http://127.0.0.1:18545 (enclave) → http://odyn.sparsity.cloud:18545 (otherwise)\n\nDual-chain topology (as in the template):\n\nAuth/Registry chain: Base Sepolia (84532) — KMS authorization & app registry\nBusiness chain: Ethereum Mainnet (1) — your business logic\n\nHelios light-client RPC runs locally at http://127.0.0.1:18545 (Base Sepolia) and http://127.0.0.1:18546 (Mainnet).\n\n⚠️ Helios RPC ports must be decided before creating the app — they are set in advanced.helios_chains[].local_rpc_port and locked at creation time. Choose values carefully up front.\n\nRules for enclave code:\n\nOdyn calls (localhost): requests or httpx both work — Odyn is local.\nExternal outbound HTTP: must use httpx (proxy-aware). Never use requests or urllib for external calls — they may bypass the egress proxy.\nPersistent state → use /v1/s3/* endpoints (or odyn.s3_put()); the enclave filesystem is ephemeral.\nSecrets → derive via KMS (/v1/kms/derive); never from env vars or hardcoded.\nTest locally: IN_ENCLAVE=false python3 app.py (scaffold: uvicorn main:app --port <port>). Odyn calls hit the public mock.\n\n/.well-known/attestation routing note: In production Nova deployments, this endpoint is handled by Caddy routing to the Aux API (port 18001) — not your app code. Some examples implement it in app code as a local dev shim. Don't ship that in production.\n\nStep 3 — Commit & push to Git\n\nYour repo only needs Dockerfile + app code. No local Docker build needed — Nova Platform builds from your Git repo directly.\n\nKMS integration is fully handled by the platform — just set enable_decentralized_kms: true (and optionally enable_app_wallet: true) in advanced when creating the app. No contract addresses, app IDs, or manual KMS config needed in your code.\n\ngit add .\ngit commit -m \"Initial Nova app\"\ngit push origin main\n\nStep 4 — Deploy to Nova Platform\n\nThe deployment is a 3-step process: Create App → Trigger Build → Create Deployment.\n\nVia Portal (recommended for first-time)\n\nPortal flow (works for both scaffold and full template):\n\nGo to sparsity.cloud → Apps → Create App\n\nFill in:\n\nName, Repo URL, optional Description, metadata_uri, app_contract_addr\nConfigure Advanced settings (port, egress, KMS, App Wallet, S3, Helios toggles, chain selection)\nSubmit → copy the App sqid\n\nThe repo URL is stored on the app record and used for all subsequent builds — you don't provide it again at build time.\n\nIn the App page → Versions → + New Version:\n\nGit Ref: main (or tag / commit SHA)\nVersion: e.g. 1.0.0\nSubmit\n\nWait for build status → success (platform builds Docker image → EIF → generates PCRs and build-attestation.json signed with Sigstore/cosign)\n\nIn Versions, find the successful version → Deploy this version:\n\nSelect Region\nSelect Tier: standard or performance\nSubmit\n\nPoll until deployment state → running → copy the App URL (hostname from app detail)\n\nThe deploy modal has no environment-variable input — no env vars or credentials needed.\n\nVia API (scripted)\n\nThe script will ask interactively whether to run on-chain registration after the app is live. Use --onchain to always run it, --no-onchain to always skip.\n\n# Preview config without deploying\npython3 scripts/nova_deploy.py \\\n  --repo https://github.com/you/my-app \\\n  --name \"my-app\" \\\n  --port 8080 \\\n  --api-key <your-nova-api-key> \\\n  --dry-run\n\n# Deploy (will prompt for on-chain at the end)\npython3 scripts/nova_deploy.py \\\n  --repo https://github.com/you/my-app \\\n  --name \"my-app\" \\\n  --port 8080 \\\n  --api-key <your-nova-api-key>\n\n# Deploy + on-chain registration in one shot\npython3 scripts/nova_deploy.py \\\n  --repo https://github.com/you/my-app \\\n  --name \"my-app\" \\\n  --port 8080 \\\n  --api-key <your-nova-api-key> \\\n  --onchain\n\n\nOr via raw API:\n\nBASE=\"https://sparsity.cloud/api\"\nTOKEN=\"<your-api-key>\"\nREPO=\"https://github.com/you/my-app\"\n\n# 1. Create app — 'advanced' is the only config field needed\nSQID=$(curl -sX POST \"$BASE/apps\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\":\"my-app\",\n    \"repo_url\":\"'\"$REPO\"'\",\n    \"description\":\"optional description\",\n    \"metadata_uri\":\"\",\n    \"app_contract_addr\":\"\",\n    \"advanced\":{\n      \"directory\":\"/\",\n      \"app_listening_port\":8080,\n      \"egress_allow\":[\"**\"],\n      \"enable_decentralized_kms\":false,\n      \"enable_persistent_storage\":false,\n      \"enable_s3_storage\":false,\n      \"enable_s3_kms_encryption\":false,\n      \"enable_ipfs_storage\":false,\n      \"enable_walrus_storage\":false,\n      \"enable_app_wallet\":false,\n      \"enable_helios_rpc\":false\n    }\n  }' \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['sqid'])\")\necho \"App sqid: $SQID\"\n\n# 2. Create new version (build) — repo URL comes from app record, not repeated here\nBUILD_ID=$(curl -sX POST \"$BASE/apps/$SQID/builds\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"git_ref\":\"main\",\"version\":\"1.0.0\"}' \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['id'])\")\necho \"Build ID: $BUILD_ID\"\n\n# 3. Poll build\nwhile true; do\n  STATUS=$(curl -s \"$BASE/builds/$BUILD_ID/status\" \\\n    -H \"Authorization: Bearer $TOKEN\" \\\n    | python3 -c \"import sys,json; print(json.load(sys.stdin).get('status',''))\")\n  echo \"Build: $STATUS\"\n  [ \"$STATUS\" = \"success\" ] && break\n  [ \"$STATUS\" = \"failed\" ] && echo \"Build failed!\" && exit 1\n  sleep 15\ndone\n\n# 4. Deploy this version — specify region and tier\n# region options: ap-south-1 (default), us-east-1, us-west-1, eu-west-1\n# tier options: standard (2vCPU/5GiB), performance (6vCPU/13GiB)\nDEPLOY_ID=$(curl -sX POST \"$BASE/apps/$SQID/deployments\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"build_id\\\":$BUILD_ID,\\\"region\\\":\\\"ap-south-1\\\",\\\"tier\\\":\\\"standard\\\"}\" \\\n  | python3 -c \"import sys,json; print(json.load(sys.stdin)['id'])\")\necho \"Deployment ID: $DEPLOY_ID\"\n\n# 5. Poll deployment\nwhile true; do\n  RESP=$(curl -s \"$BASE/deployments/$DEPLOY_ID/status\" -H \"Authorization: Bearer $TOKEN\")\n  STATE=$(echo $RESP | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('deployment_state',''))\")\n  echo \"Deployment state: $STATE\"\n  [ \"$STATE\" = \"running\" ] && break\n  [ \"$STATE\" = \"failed\" ] && echo \"Deploy failed!\" && exit 1\n  sleep 15\ndone\n\n# 6. Get app URL (hostname from detail, or use unified status)\ncurl -s \"$BASE/apps/$SQID/detail\" -H \"Authorization: Bearer $TOKEN\" \\\n  | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d['app'].get('hostname',''))\"\n\n# Tip: GET /api/apps/{sqid}/status gives the full lifecycle in one call:\n# build_status, deployment_state, proof_status, onchain_instance_id, etc.\n\nStep 5 — Verify the live app\n# Health check\ncurl https://<hostname>/\n\n# Hello endpoint (returns enclave address + signature)\ncurl https://<hostname>/api/hello\n\n# Attestation (proves it's a real Nitro Enclave) — POST, returns binary CBOR\n# In production, /.well-known/attestation is Caddy → Aux API; not app code\ncurl -sX POST https://<hostname>/.well-known/attestation --output attestation.bin\n\n\nNote: /api/app-wallet is only available if you forked nova-app-template. The scaffold template includes /api/hello instead.\n\nStep 6 — On-Chain Registration (optional but important for trust)\n\nAfter the app is running, register it on-chain to establish verifiable trust.\n\nUsing the script (recommended):\n\npython3 scripts/nova_deploy.py ... --onchain\n\n\nThe script will automatically execute steps 6a–6d and poll until complete.\n\nManually via API — 4-step sequence:\n\n# 6a. Create app on-chain\ncurl -sX POST \"$BASE/apps/$SQID/create-onchain\" \\\n  -H \"Authorization: Bearer $TOKEN\"\n# Poll: GET /api/apps/{sqid}/status → onchain_app_id is set when done\n\n# 6b. Enroll build version on-chain (PCR measurements → trusted code fingerprint)\ncurl -sX POST \"$BASE/apps/$SQID/builds/$BUILD_ID/enroll\" \\\n  -H \"Authorization: Bearer $TOKEN\"\n\n# Poll enrollment via build status endpoint\nwhile true; do\n  ENROLLED=$(curl -s \"$BASE/builds/$BUILD_ID/status\" \\\n    -H \"Authorization: Bearer $TOKEN\" \\\n    | python3 -c \"import sys,json; d=json.load(sys.stdin); print(d.get('is_enrolled',''))\")\n  echo \"Enrolled: $ENROLLED\"\n  [ \"$ENROLLED\" = \"True\" ] && break\n  sleep 10\ndone\n\n# 6c. Generate ZK proof (SP1-based)\ncurl -sX POST \"$BASE/zkproof/generate\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"{\\\"deployment_id\\\": $DEPLOY_ID}\"\n\n# Poll proof via deployment status endpoint (or /zkproof/status/{deployment_id})\nwhile true; do\n  PROOF=$(curl -s \"$BASE/deployments/$DEPLOY_ID/status\" \\\n    -H \"Authorization: Bearer $TOKEN\" \\\n    | python3 -c \"import sys,json; print(json.load(sys.stdin).get('proof_status',''))\")\n  echo \"Proof status: $PROOF\"\n  [ \"$PROOF\" = \"proved\" ] && break\n  [ \"$PROOF\" = \"failed\" ] && echo \"Proof failed!\" && exit 1\n  sleep 30\ndone\n\n# 6d. Register instance on-chain (manual — auto-registration has been removed)\ncurl -sX POST \"$BASE/apps/$SQID/instance/register\" \\\n  -H \"Authorization: Bearer $TOKEN\"\n\n# Poll: GET /api/deployments/{id}/status → onchain_instance_id is set when done\n# Or use unified: GET /api/apps/{sqid}/status → latest_onchain_instance_id\n\n\nWhat each step does:\n\nCreate app on-chain: Registers your app in the Nova App Registry contract (Base Sepolia)\nEnroll version: Records the EIF's PCR measurements on-chain — the trusted code fingerprint\nGenerate ZK proof: Platform generates an SP1 zero-knowledge proof from the enclave's Nitro attestation\nRegister instance: Verifies the ZK proof on-chain and links the live instance to its enrolled version\n\nAfter registration, anyone can verify on-chain that this running instance matches the audited build.\n\nOdyn Internal API Reference\n\nAll endpoints are on http://127.0.0.1:18000 (Primary API — inside enclave only, NOT publicly exposed in production).\n\nAlways-available endpoints:\n\nEndpoint\tMethod\tDescription\n/v1/eth/address\tGET\tEnclave Ethereum address + public key\n/v1/eth/sign\tPOST\tEIP-191 personal-sign (optionally with attestation)\n/v1/eth/sign-tx\tPOST\tSign EIP-1559 transactions\n/v1/random\tGET\t32 bytes from NSM-backed randomness\n/v1/attestation\tPOST\tRaw CBOR attestation bytes (not JSON)\n/v1/encryption/public_key\tGET\tEnclave P-384 public key\n/v1/encryption/encrypt\tPOST\tEncrypt response to a client\n/v1/encryption/decrypt\tPOST\tDecrypt client payloads\n\nFeature-gated endpoints (set in advanced at app creation):\n\nEndpoint Group\tGate\tDescription\n/v1/s3/*\tenable_s3_storage: true\tBase64 object storage backed by S3\n/v1/kms/derive\tenable_decentralized_kms: true\tDeterministic key derivation\n/v1/kms/kv/put|get|delete\tenable_decentralized_kms: true\tKMS-backed KV store with optional TTL\n/v1/app-wallet/address\tenable_app_wallet: true\tApp-specific wallet address\n/v1/app-wallet/sign\tenable_app_wallet: true\tEIP-191 message signing via app wallet\n/v1/app-wallet/sign-tx\tenable_app_wallet: true\tEIP-1559 transaction signing via app wallet\n\nPort exposure in production:\n\nRuntime publishes exactly two ports: host app port → enclave app port and host attestation port → enclave Aux API port 18001\n/.well-known/attestation* is routed by Caddy to the host attestation port → Aux API (18001)\nPrimary API (18000) is NOT exposed publicly for normal production apps\n\nOdyn mock service (local development):\n\nPrimary API: http://odyn.sparsity.cloud:18000\nAux API: http://odyn.sparsity.cloud:18001\nHelios RPC presets: http://odyn.sparsity.cloud:18545 through :18553\nKey Notes\nadvanced is REQUIRED at app creation. Omitting it will cause the build to fail. Only advanced exists — the old enclaver field has been removed from the API.\nYour repo only needs Dockerfile + app code. The platform handles everything else at build time.\nApp ID format: sqid (string like abc123) — use in all URL paths, not the integer id.\nPort: Set via advanced.app_listening_port. Must also match EXPOSE in Dockerfile. The enclaver.yaml in the template repo is a reference; portal reads the listening port from it.\nRepo URL is set once at app creation — not repeated at build time. Build only needs git_ref + version.\nDeploy requires region + tier: regions are ap-south-1 (default), us-east-1, us-west-1, eu-west-1. tier is \"standard\" (2 vCPU/5 GiB) or \"performance\" (6 vCPU/13 GiB). No env var injection in deploy.\nHelios RPC: Use the canonical port mapping in references/nova-api.md — ports are fixed and locked at app creation.\nKMS dependency chain: enable_app_wallet or enable_s3_kms_encryption implies KMS → implies Helios (with base-sepolia chain) → implies kms_app_id + nova_app_registry.\nKMS/S3 fully managed by platform — no AWS credentials, no enclaver.yaml authoring needed.\nNo Docker push: Platform builds from Git.\nOn-chain steps (create-onchain → enroll → ZK proof → register) are required for public verifiability, but optional for a functional running app.\nInstance registration is now manual — auto-registration has been removed. Use POST /api/apps/{app_sqid}/instance/register after proof is proved.\nPortal helper APIs (clone-repo, get-enclaver-config) are used by the portal UI to auto-fill the listening port from enclaver.yaml when you enter a repo URL. Not needed for direct API usage.\nBuild provenance: Builds produce a build-attestation.json with PCR0/PCR1/PCR2, source repo, commit, GitHub run metadata — signed with Sigstore/cosign and stored off-chain.\nIN_ENCLAVE is not injected by Enclaver — set ENV IN_ENCLAVE=false in Dockerfile for local dev; production sets it true.\nCommon Issues\nSymptom\tFix\nDockerfile missing after scaffold\tRun clawhub update nova-app-builder then re-scaffold. The template ships as Dockerfile.txt and scaffold renames it to Dockerfile automatically.\nApp expects enclaver.yaml or old config\tenclaver.yaml is always generated by Nova Platform — developers never provide it. The advanced field at app creation drives all platform-level config. Only enclave/config.py (app business logic) needs developer attention when using the full template.\nAPI endpoint console.sparsity.cloud not resolving\tOld version of nova_deploy.py. Run clawhub update nova-app-builder. Correct endpoint is https://sparsity.cloud/api.\nfrom odyn import Odyn fails\tOld import path. New SDK is at nova_python_sdk/. Use from nova_python_sdk.odyn import Odyn.\nBuild stuck in pending\tCheck GitHub Actions in nova build repo; may be queued\nBuild failed\tCheck error_message in build response; usually Dockerfile issue\nDeploy API returns 401\tRegenerate API key at sparsity.cloud\nApp stuck in provisioning >10 min\tCheck app logs via GET /api/apps/{sqid}/detail\nhttpx request fails inside enclave\tAdd domain to advanced.egress_allow. Note: \"**\" matches domains only — add \"0.0.0.0/0\" for direct IP connections\nDirect IP connection blocked\t\"**\" does NOT cover IPs. Add \"0.0.0.0/0\" (IPv4) and/or \"::/0\" (IPv6) to egress_allow\nS3 fails\tEnsure 169.254.169.254 and S3 endpoint are in egress allow list\n/v1/kms/* returns 400\tEnsure enable_decentralized_kms: true and enable_helios_rpc: true in advanced at app creation\nApp Wallet unavailable\tEnsure enable_app_wallet: true in advanced at app creation\nProxy not respected for external calls\tUse httpx for external HTTP calls (proxy-aware). requests/urllib may bypass the egress proxy. Note: requests is fine for internal Odyn calls (localhost).\nHealth check returns 502\tApp is starting; wait for enclave to fully boot\nZK proof stuck\tCheck GET /api/zkproof/status/{deployment_id} for details\n/.well-known/attestation returning wrong format in prod\tIn production, this is Caddy → Aux API routing — not app code. Remove app-side shim for production builds.\nReference Files\nreferences/odyn-api.md — Full Odyn Internal API (signing, encryption, S3, KMS, App Wallet, attestation)\nreferences/nova-api.md — Nova Platform REST API (full endpoint reference)\nNova Examples\nExample\tDescription\nhello-world-tee\tSimplest start — identity + attestation only; uses nova_python_sdk/\necho-vault\tBest backend reference — S3 persistence + Helios RPC; source of canonical SDK\nsecured-chat-bot\tBest E2E encryption reference — browser-to-enclave P-384 ECDH\nrng-oracle\tOn-chain randomness flow with enclave signing\nprice-oracle\tExternal API verification and signing patterns\n\nNova Python SDK: The canonical SDK lives in enclave/nova_python_sdk/ in both nova-app-template and all examples. Do not modify SDK files — copy the whole directory into your project.\n\nKey URLs\nNova Platform: https://sparsity.cloud\nNova Platform API docs: https://sparsity.cloud/api/docs\nNova Sales Deck: https://sparsity.cloud/decks/nova-sales-deck.html\nNova Resources: https://sparsity.cloud/resources/\nNova Examples: https://github.com/sparsity-xyz/sparsity-nova-examples/\nNova Python SDK (canonical): enclave/nova_python_sdk/ in nova-app-template or echo-vault\nEnclaver (Sparsity): https://github.com/sparsity-xyz/enclaver\nNova App Template: https://github.com/sparsity-xyz/nova-app-template\nEnclaver Docs: odyn.md, internal_api.md\nNova Platform Architecture: build-attestation.md, runtime-port-exposure-flow.md"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/zfdang/nova-app-builder",
    "publisherUrl": "https://clawhub.ai/zfdang/nova-app-builder",
    "owner": "zfdang",
    "version": "2.3.1",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/nova-app-builder",
    "downloadUrl": "https://openagent3.xyz/downloads/nova-app-builder",
    "agentUrl": "https://openagent3.xyz/skills/nova-app-builder/agent",
    "manifestUrl": "https://openagent3.xyz/skills/nova-app-builder/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/nova-app-builder/agent.md"
  }
}