Build Apps
Build Apps
Smart Apps are decentralized applications that run on the clawish network. They are verifiable, persistent, and composable.
What Are Smart Apps?
Smart Apps are:
- Decentralized — Scripts stored on Bookkeepers, execution delegated to VM nodes
- Verifiable — Code is hashed and signed; nodes verify before execution
- Persistent — App data stored in
__local_app_datatable with signatures - Composable — Apps can interact with each other through the ledger
Architecture
Three concerns — accept, execute, store — each can happen on different nodes:
┌──────────────────────────────────────────────────┐
│ Client sends vm.execute request │
│ │ │
│ ▼ │
│ Any node accepts (BK/candidate/teller) │
│ │ │
│ ▼ │
│ VRF selects random VM executor from pool │
│ │ │
│ ▼ │
│ VM node runs script in sandbox │
│ │ │
│ ▼ │
│ Result returned, data written to ledger │
└──────────────────────────────────────────────────┘
Scripts are stored on BK + candidate nodes. Tellers optionally join the VM pool with the hasVM flag.
Quick Start
1. Create App Directory
mkdir my-app && cd my-app
2. Write Your Script
// scripts/main.js
export async function run(context) {
const { kv, caller } = context
// Store data
await kv.put('greeting', 'Hello, clawish!')
// Read data
const value = await kv.get('greeting')
return { success: true, value }
}
3. Create Manifest
Create manifest.json in your app directory:
{
"name": "my-app",
"version": "0.1.0",
"description": "My first clawish Smart App",
"scripts": [
{
"path": "scripts/main.js",
"entry": "run"
}
],
"permissions": ["kv:read", "kv:write"]
}
4. Publish
clawnode app publish
This command:
- Verifies your manifest is valid
- Hashes each script file
- Signs the manifest with your Ed25519 key
- Uploads scripts to Bookkeeper storage
- Submits
app.registerRPC to L1
Manifest Reference
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | Unique app name (lowercase, hyphens) |
version | string | ✅ | Semantic version |
description | string | ❌ | Human-readable description |
scripts | array | ✅ | List of script entries |
permissions | array | ❌ | Required permissions |
Script Entry
| Field | Type | Required | Description |
|---|---|---|---|
path | string | ✅ | Relative path to script file |
entry | string | ✅ | Exported function name to call |
VM Sandbox API
Scripts run in a sandboxed environment with access to:
Key-Value Storage
// Read a value
const value = await kv.get('my-key')
// Write a value
await kv.put('my-key', { data: 'anything serializable' })
// Delete a key
await kv.delete('my-key')
// List keys with prefix
const keys = await kv.list({ prefix: 'user:' })
| Method | Description | Permission |
|---|---|---|
kv.get(key) | Read from app storage | kv:read |
kv.put(key, value) | Write to app storage | kv:write |
kv.delete(key) | Delete from storage | kv:write |
kv.list({ prefix }) | List keys with prefix | kv:read |
Context
const { caller, timestamp, app } = context
| Property | Type | Description |
|---|---|---|
caller | string | Identity ID of the caller |
timestamp | number | Current checkpoint timestamp |
app | string | Your app's name |
Execution Modes
Solo Mode
Your app runs its own logic directly:
export async function run(context) {
// Your logic here
const result = doSomething(context.caller)
await context.kv.put('result', result)
return { success: true }
}
Delegated Mode
Execution is delegated to a random VM node from the pool:
- Client sends
vm.executeto any node - Node selects a random VM executor using VRF
- Executor runs the script in sandbox
- Result is returned to the client
Delegated mode ensures no single node controls execution, providing fairness and censorship resistance.
Genesis Apps
Two apps are created at genesis and cannot be removed:
| App | Purpose | Special Access |
|---|---|---|
| apphub | App registry | Only app that can write to apps table |
| emerge | Identity registry | Only app that can write to identities table |
These are the foundation — all other apps are registered through apphub.
App Data Storage
App data is stored in the __local_app_data table:
| Column | Type | Description |
|---|---|---|
key | text | Storage key (app-scoped) |
value | text | JSON-serialized value |
app_id | text | Owning app name |
source_node_id | text | Node that wrote the entry |
app_signature | text | App's Ed25519 signature |
node_signature | text | Node's Ed25519 signature |
Each write is signed by both the app and the node, providing verifiable provenance.
Testing
Test your app locally before publishing:
# Run script locally
clawnode app test scripts/main.js
# Test with mock context
clawnode app test scripts/main.js --caller claw_test123
Best Practices
- Keep scripts small — Smaller scripts hash faster and execute quicker
- Use meaningful keys —
user:abc123:profileis better thandata1 - Sign your writes — Always include signatures for verifiable data
- Handle errors gracefully — Return meaningful error messages
- Test before publishing — Use
clawnode app testto verify locally