build-nova-app-claw-skill

star 0

Build and deploy Nova Platform apps (TEE apps on Sparsity Nova / sparsity.cloud). Use when a user wants to create a Nova app, write enclave application code, build it into a Docker image, and deploy it to the Nova Platform to get a live running URL. Handles the full lifecycle: scaffold, code, build, push, deploy, verify running, and register on-chain. Triggers on requests like 'build me a Nova app', 'deploy to Nova Platform', 'create a TEE app on sparsity.cloud', 'I want to run an enclave app on Nova'.

sparsity-xyz By sparsity-xyz schedule Updated 3/1/2026

name: build-nova-app-claw-skill description: "Build and deploy Nova Platform apps (TEE apps on Sparsity Nova / sparsity.cloud). Use when a user wants to create a Nova app, write enclave application code, build it into a Docker image, and deploy it to the Nova Platform to get a live running URL. Handles the full lifecycle: scaffold, code, build, push, deploy, verify running, and register on-chain. Triggers on requests like 'build me a Nova app', 'deploy to Nova Platform', 'create a TEE app on sparsity.cloud', 'I want to run an enclave app on Nova'."

Nova App Builder

End-to-end workflow: scaffold → code → push to Git → create app → build → deploy → register on-chain (create app on-chain → enroll version → generate ZK proof → register instance).

Architecture Overview

Nova apps run inside AWS Nitro Enclaves, managed by Enclaver (Sparsity edition) and supervised by Odyn (PID 1 inside the enclave). Key concepts:

  • Enclaver: packages your Docker image into an EIF (Enclave Image File) and manages the enclave lifecycle.
  • Odyn: supervisor inside the enclave; provides Internal API for signing, attestation, encryption, KMS, S3, and manages networking.
  • Nova Platform: cloud platform at sparsity.cloud — builds EIFs from Git, runs enclaves, exposes app URLs.
  • Nova KMS: distributed key management; enclave apps derive keys via /v1/kms/derive.

Enclave Identity — Two Keys

  • Encumbered Key / Enclave Address (/v1/eth/address, /v1/eth/sign) — An ephemeral key generated by Odyn inside the enclave at boot. Unique per enclave instance. The endpoint returns both the enclave address (20-byte Ethereum address) and the public key (65-byte uncompressed secp256k1 key, starting with 0x04). The address is simply derived from the public key (last 20 bytes of its keccak256 hash). Use it to prove a response originated from inside this specific TEE instance. This is an encumbered key as defined in ERC-733 — a private key that exists only within the enclave and can never be extracted.
  • App Wallet (/v1/app-wallet/address, /v1/app-wallet/sign) — A persistent wallet derived via Nova KMS. Shared across all instances of the same app — the same app always gets the same wallet address regardless of which enclave is running. Use for stable on-chain identity (holding funds, signing transactions). Requires enable_app_wallet: true and enable_decentralized_kms: true at app creation.

Runtime On-Chain Self-Discovery

The on-chain app ID is known before build (create app → create-onchain → get app ID), so it should be hardcoded into the enclave code. It's stable and never changes.

However, instance-level data (instance ID, version enrollment status) only exists after deployment and changes per deployment. With Helios always enabled, the enclave can query this live:

  1. Use the hardcoded app ID to query the Nova App Registry contract via Helios RPC (http://127.0.0.1:18545, Base Sepolia)
  2. The contract returns the app's registered instances, version data, etc.

For data not stored on-chain (e.g. registration TX hashes, proof URIs), the app can call the Nova Platform API (GET /api/apps/{sqid}/status) at runtime.

Prerequisites (collect from user before starting)

Ensure skill is up to date before starting:

git -C /path/to/build-nova-app-claw-skill pull origin main

Older versions are missing Dockerfile.txt in the template, causing scaffold to fail.

  • App idea: What does the app do?

  • Nova account + API key: Sign up at sparsity.cloud → Account → API Keys.

  • GitHub 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.

    GitHub PAT setup:

    1. GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
    2. Required permissions: Contents (Read & Write), Metadata (Read)
    3. Push with token:
      git remote set-url origin https://oauth2:${GH_TOKEN}@github.com/<user>/<repo>.git
      git push origin main
      

⚠️ Do NOT ask for Docker registry credentials or AWS S3 credentials. Nova Platform handles the Docker build and image registry internally (Git-based build pipeline). S3 storage credentials are managed by the Enclaver/Odyn layer — the app only calls Internal API endpoints (/v1/s3/*). Users never touch AWS credentials.

Full Workflow

Step 1 — Scaffold the project

python3 scripts/scaffold.py \
  --name <app-name> \
  --desc "<one-line description>" \
  --port <port> \
  --out <output-dir>

Port 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.

This generates <output-dir>/<app-name>/ with the following structure:

<app-name>/
├── Dockerfile
└── enclave/
    ├── main.py
    ├── odyn.py
    └── requirements.txt

Note: The template ships as Dockerfile.txt (GitHub cannot distribute extensionless files in skill templates). scaffold.py automatically renames it to Dockerfile and substitutes the port. No manual action needed.

Alternatively, fork nova-app-template — a production-ready starter with KMS, S3, App Wallet, E2E encryption, and a built-in React frontend.

Step 2 — Write the app logic

Edit enclave/main.py. Key patterns:

Minimal FastAPI app:

import os, httpx
from fastapi import FastAPI

app = FastAPI()
IN_ENCLAVE = os.getenv("IN_ENCLAVE", "false").lower() == "true"
ODYN_BASE = "http://127.0.0.1:18000" if IN_ENCLAVE else "http://odyn.sparsity.cloud:18000"

@app.get("/api/hello")
def hello():
    r = httpx.get(f"{ODYN_BASE}/v1/eth/address", timeout=10)
    return {"message": "Hello from TEE!", "enclave": r.json()["address"]}

With App Wallet + KMS (from nova-app-template):

from kms_client import NovaKmsClient

kms = NovaKmsClient(endpoint=ODYN_BASE)

@app.get("/api/wallet")
def wallet():
    return kms.app_wallet_address()     # {"address": "0x...", "app_id": 0}

@app.post("/api/sign")
def sign(body: dict):
    return kms.app_wallet_sign(body["message"])   # {"signature": "0x..."}

Dual-chain topology (as in the template):

  • Auth/Registry chain: Base Sepolia (84532) — KMS authorization & app registry
  • Business chain: Ethereum Mainnet (1) — your business logic

Helios light-client RPC runs locally at http://127.0.0.1:18545 (Base Sepolia) and http://127.0.0.1:18546 (Mainnet).

⚠️ 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.

Rules for enclave code:

  • All outbound HTTP must use httpx (proxy-aware). Never use requests or urllib — they may bypass the egress proxy.
  • Persistent state → use /v1/s3/* endpoints; the enclave filesystem is ephemeral.
  • Secrets → derive via KMS (/v1/kms/derive); never from env vars or hardcoded.
  • Test locally: IN_ENCLAVE=false uvicorn main:app --port <port> (Odyn calls hit the public mock).

Step 3 — Commit & push to Git

Your repo only needs Dockerfile + app code. No local Docker build needed — Nova Platform builds from your Git repo directly.

KMS 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.

git add .
git commit -m "Initial Nova app"
git push origin main

Step 4 — Deploy to Nova Platform

The deployment is a 3-step process: Create App → Trigger Build → Create Deployment.

Recommended flow: After creating the app, immediately run create-onchain (Step 6a) to get the on-chain app ID before writing your app code. This lets you hardcode the app ID into the enclave so it's aware of its own on-chain identity from the start. The enclave can then query the Nova App Registry contract at runtime via Helios light-client RPC to pull live on-chain state (version, instance, etc.).

Via Portal (recommended for first-time)

  1. Go to sparsity.cloudAppsCreate App
  2. Fill in Name, Description, Git Repo URL, configure Advanced settings → Submit → copy the App sqid
  3. In the App page → BuildsTrigger Build:
    • Git Ref: main (or tag / commit SHA)
    • Version: e.g. v1.0.0
  4. Wait for build status → success (Nova builds Docker image → EIF → generates PCRs)
  5. DeploymentsCreate Deployment → select the successful build → Deploy
  6. Poll until state → running → copy the App URL (hostname from app detail)

Via API (scripted)

The script handles the full pipeline including on-chain registration automatically.

# Preview config without deploying
python3 scripts/nova_deploy.py \
  --repo https://github.com/you/my-app \
  --name "my-app" \
  --port 8080 \
  --api-key <your-nova-api-key> \
  --dry-run

# Deploy (includes on-chain registration automatically)
python3 scripts/nova_deploy.py \
  --repo https://github.com/you/my-app \
  --name "my-app" \
  --port 8080 \
  --api-key <your-nova-api-key>

Or via raw API:

BASE="https://sparsity.cloud/api"
TOKEN="<your-api-key>"
REPO="https://github.com/you/my-app"

# 1. Create app — 'advanced' is the only config field needed
SQID=$(curl -sX POST "$BASE/apps" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name":"my-app",
    "repo_url":"'"$REPO"'",
    "advanced":{
      "directory":"/",
      "app_listening_port":8080,
      "egress_allow":["**"],
      "enable_decentralized_kms":false,
      "enable_persistent_storage":false,
      "enable_s3_storage":false,
      "enable_s3_kms_encryption":false,
      "enable_ipfs_storage":false,
      "enable_walrus_storage":false,
      "enable_app_wallet":false,
      "enable_helios_rpc":true,
      "helios_chains":[
        {"chain_id":"84532","kind":"opstack","network":"base-sepolia","execution_rpc":"https://sepolia.base.org","local_rpc_port":18545}
      ]
    }
  }' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['sqid'])")
echo "App sqid: $SQID"

# 2. Trigger build
BUILD_ID=$(curl -sX POST "$BASE/apps/$SQID/builds" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"git_ref":"main","version":"1.0.0"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Build ID: $BUILD_ID"

# 3. Poll build
while true; do
  STATUS=$(curl -s "$BASE/builds/$BUILD_ID/status" \
    -H "Authorization: Bearer $TOKEN" \
    | python3 -c "import sys,json; print(json.load(sys.stdin).get('status',''))")
  echo "Build: $STATUS"
  [ "$STATUS" = "success" ] && break
  [ "$STATUS" = "failed" ] && echo "Build failed!" && exit 1
  sleep 15
done

# 4. Create deployment
DEPLOY_ID=$(curl -sX POST "$BASE/apps/$SQID/deployments" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"build_id\":$BUILD_ID}" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Deployment ID: $DEPLOY_ID"

# 5. Poll deployment
while true; do
  RESP=$(curl -s "$BASE/deployments/$DEPLOY_ID/status" -H "Authorization: Bearer $TOKEN")
  STATE=$(echo $RESP | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('deployment_state',''))")
  echo "Deployment state: $STATE"
  [ "$STATE" = "running" ] && break
  [ "$STATE" = "failed" ] && echo "Deploy failed!" && exit 1
  sleep 15
done

# 6. Get app URL (hostname from detail, or use unified status)
curl -s "$BASE/apps/$SQID/detail" -H "Authorization: Bearer $TOKEN" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['app'].get('hostname',''))"

# Tip: GET /api/apps/{sqid}/status gives the full lifecycle in one call:
# build_status, deployment_state, proof_status, onchain_instance_id, etc.

Step 5 — Verify the live app

# Health check
curl https://<hostname>/

# Hello endpoint (returns enclave address + signature)
curl https://<hostname>/api/hello

# Attestation (proves it's a real Nitro Enclave) — POST, returns binary CBOR
curl -sX POST https://<hostname>/.well-known/attestation --output attestation.bin

Note: /api/app-wallet is only available if you forked nova-app-template. The scaffold template includes /api/hello instead.

Step 6 — On-Chain Registration

After the app is running, register it on-chain to establish verifiable trust. This step is required — it's what makes Nova apps provably secure.

Using the script (recommended):

python3 scripts/nova_deploy.py ...

The script automatically executes steps 6a–6d after deployment.

Manually via API — 4-step sequence:

# 6a. Create app on-chain
curl -sX POST "$BASE/apps/$SQID/create-onchain" \
  -H "Authorization: Bearer $TOKEN"
# Poll: GET /api/apps/{sqid}/status → onchain_app_id is set when done

# 6b. Enroll build version on-chain (PCR measurements → trusted code fingerprint)
curl -sX POST "$BASE/apps/$SQID/builds/$BUILD_ID/enroll" \
  -H "Authorization: Bearer $TOKEN"

# Poll enrollment via build status endpoint
while true; do
  ENROLLED=$(curl -s "$BASE/builds/$BUILD_ID/status" \
    -H "Authorization: Bearer $TOKEN" \
    | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('is_enrolled',''))")
  echo "Enrolled: $ENROLLED"
  [ "$ENROLLED" = "True" ] && break
  sleep 10
done

# 6c. Generate ZK proof
curl -sX POST "$BASE/zkproof/generate" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"deployment_id\": $DEPLOY_ID}"

# Poll proof via deployment status endpoint (or /zkproof/status/{deployment_id})
while true; do
  PROOF=$(curl -s "$BASE/deployments/$DEPLOY_ID/status" \
    -H "Authorization: Bearer $TOKEN" \
    | python3 -c "import sys,json; print(json.load(sys.stdin).get('proof_status',''))")
  echo "Proof status: $PROOF"
  [ "$PROOF" = "proved" ] && break
  [ "$PROOF" = "failed" ] && echo "Proof failed!" && exit 1
  sleep 30
done

# 6d. Register instance on-chain
curl -sX POST "$BASE/zkproof/onchain/register" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"deployment_id\": $DEPLOY_ID}"

# Poll: GET /api/deployments/{id}/status → onchain_instance_id is set when done
# Or use unified: GET /api/apps/{sqid}/status → latest_onchain_instance_id

What each step does:

  • Create app on-chain: Registers your app in the Nova App Registry contract (Base Sepolia)
  • Enroll version: Records the EIF's PCR measurements on-chain — the trusted code fingerprint
  • Generate ZK proof: Platform generates a zero-knowledge proof from the enclave's attestation
  • Register instance: Verifies the ZK proof on-chain and links the live instance to its enrolled version

After registration, anyone can verify on-chain that this running instance matches the audited build.

Always provide clickable explorer URLs for all transaction hashes. The registry contract is on Base Sepolia (chain 84532). Format:

  • TX: https://sepolia.basescan.org/tx/0x{hash}
  • Contract: https://sepolia.basescan.org/address/0x{address}
  • Registry contract: 0x0f68E6e699f2E972998a1EcC000c7ce103E64cc8

Key Notes

  1. 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.
  2. Your repo only needs Dockerfile + app code. The platform handles everything else at build time.
  3. App ID format: sqid (string like abc123) — use in all URL paths, not the integer id.
  4. Port: Set via advanced.app_listening_port. Must also match EXPOSE in Dockerfile.
  5. Helios RPC: Use the canonical port mapping in references/nova-api.md — ports are fixed and locked at app creation.
  6. KMS 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.
  7. KMS is handled by Enclaver — no contract addresses or config.py changes needed in your app code.
  8. No Docker push: Platform builds from Git.
  9. On-chain registration (create-onchain → enroll → ZK proof → register) is required for public verifiability and completes the full deployment lifecycle.
  10. Deployments expire after ~1 hour on the free tier. Check remaining_seconds in deployment status. Redeploy if expired.
  11. Helios RPC is always enabled. Every Nova app gets a local Helios light-client RPC on Base Sepolia (127.0.0.1:18545) so the enclave can query the Nova App Registry contract and discover its own on-chain identity at runtime.

Common Issues

Symptom Fix
Dockerfile missing after scaffold Run git pull in the skill directory then re-scaffold. The template ships as Dockerfile.txt and scaffold renames it to Dockerfile automatically.
scaffold fails / enclaver.yaml or config.py expected You have an old version. Run git pull in the skill directory. These files are no longer needed — advanced field at app creation replaces all manual config.
API endpoint console.sparsity.cloud not resolving Old version of nova_deploy.py. Run git pull in the skill directory. Correct endpoint is https://sparsity.cloud/api.
Build stuck in pending Check GitHub Actions in nova build repo; may be queued
Build failed Check error_message in build response; usually Dockerfile issue
Deploy API returns 401 Regenerate API key at sparsity.cloud
App stuck in provisioning >10 min Check app logs via GET /api/apps/{sqid}/detail
httpx request fails inside enclave Add domain to advanced.egress_allow. Note: "**" matches domains only — add "0.0.0.0/0" for direct IP connections
Direct IP connection blocked "**" does NOT cover IPs. Add "0.0.0.0/0" (IPv4) and/or "::/0" (IPv6) to egress_allow
S3 fails Ensure 169.254.169.254 and S3 endpoint are in egress allow list
/v1/kms/* returns 400 Ensure enable_decentralized_kms: true and enable_helios_rpc: true in advanced at app creation
App Wallet unavailable Ensure enable_app_wallet: true in advanced at app creation
Proxy not respected Switch from requests/urllib to httpx
Health check returns 502 App is starting; wait for enclave to fully boot
ZK proof stuck Check GET /api/zkproof/status/{deployment_id} for details
Deployment expired / stopped unexpectedly Free-tier deployments expire after ~1 hour. Create a new deployment from the same build with POST /api/apps/{sqid}/deployments

Reference Files

  • references/odyn-api.md — Full Odyn Internal API (signing, encryption, S3, KMS, App Wallet, attestation)
  • references/nova-api.md — Nova Platform REST API (full endpoint reference)

Key URLs

Install via CLI
npx skills add https://github.com/sparsity-xyz/build-nova-app-claw-skill --skill build-nova-app-claw-skill
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
sparsity-xyz
sparsity-xyz Explore all skills →