About
high-performance http server for smart contracts

Tags
blockchaindenorustsmartcontractstanatstypescriptv8

Languages
Rust 94%, TypeScript 4%, JavaScript 2%

tana-edge

high-performance HTTP server for Tana smart contract handlers.

overview

tana-edge serves the read-only HTTP endpoints that contracts export. when contracts define get() or post() functions, tana-edge makes them available at HTTP endpoints, executing contract code for each request.

how it works

when a request arrives for a contract endpoint, tana-edge fetches the contract code, creates a fresh V8 isolate, executes the appropriate handler function, and returns the response. each request gets complete isolation.

request flow

HTTP request
     |
     v
tana-edge router
     |
     v
fetch contract from ledger
     |
     v
create V8 isolate
     |
     v
execute get() or post()
     |
     v
HTTP response

handler functions

contracts export optional HTTP handlers:

export async function get(req: Request) {
  // read-only operations
  const data = await kv.get("key")
  return { data }
}

export async function post(req: Request) {
  // also read-only (despite the name)
  const body = await req.json()
  return { received: body }
}

read-only enforcement

unlike contract mutations (which go through transactions), HTTP handlers are strictly read-only. they can read from key-value storage but cannot write. this allows serving requests without consensus.

request context

handlers receive authenticated caller information when available:

export async function get(req: Request) {
  const caller = req.tana.caller // user ID or null
  // personalize response based on caller
}

architecture

built with Rust using the same V8 integration as tana-runtime. designed for high throughput with fresh isolates per request providing security isolation without sacrificing performance.

integration

tana-edge runs as a standalone service, accessed through the tana-api gateway at /contracts/{name}/*. it fetches contract code from the ledger service and shares the key-value storage layer.

code structure

the codebase is organized into focused modules (~5,000 lines total):

src/
├── main.rs              (1,266 lines) - HTTP handlers, SSR execution, router
├── isolate_pool.rs      (1,381 lines) - V8 isolate pool for warm starts
├── ops/                   (769 lines) - Deno-style JS runtime operations
│   ├── mod.rs           - Op exports and registration
│   ├── basic.rs         - Core ops (print, fetch, sleep)
│   ├── kv.rs            - Key-value storage ops
│   ├── block.rs         - Blockchain query ops
│   ├── blockchain.rs    - Extended blockchain ops
│   ├── tx.rs            - Transaction ops
│   ├── timer.rs         - Timer queue ops
│   └── streaming.rs     - HTTP streaming ops
├── contract_validator.rs  (438 lines) - AST validation for contracts
├── db.rs                  (369 lines) - Database utilities (dev mode)
├── contract_db.rs         (224 lines) - Per-contract database isolation
├── tana_modules.rs        (188 lines) - Module resolution for tana/* imports
├── error_analysis.rs      (124 lines) - Smart error suggestions
├── types.rs                (83 lines) - Shared type definitions
├── state.rs                (62 lines) - Global state (KV storage, etc.)
├── t4_client.rs            (62 lines) - T4 content storage client
└── log.rs                  (33 lines) - Logging utilities

module responsibilities

module purpose
main.rs HTTP routing, request handlers, streaming SSR execution
isolate_pool.rs Warm V8 isolate pool for ~5ms request latency
ops/ JavaScript runtime operations exposed to contract code
contract_validator.rs AST-based validation of contract exports
db.rs PostgreSQL connection pool for dev mode
contract_db.rs Per-contract isolated databases for production
tana_modules.rs Resolution for tana/kv, tana/http, etc. imports
error_analysis.rs Developer-friendly error messages with suggestions
t4_client.rs Fetching contract code from T4 storage

key design decisions

isolate pooling: the isolate pool maintains warm V8 instances per contract, reducing cold start overhead from ~100ms to ~5ms. controlled by DISABLE_ISOLATE_POOL=true for debugging.

streaming SSR: uses Rust channels to stream HTML chunks from V8 to HTTP responses as they're rendered, enabling TTFB optimization for React pages.

per-contract databases: in production, each contract gets an isolated PostgreSQL database ({address}_{contract_id}) accessed through PgBouncer for connection pooling.

handler structure in main.rs: HTTP handlers remain in main.rs because they're tightly coupled with the isolate pool static and multiple modules. extracting them would require significant refactoring (passing pool through all layers or creating a shared context struct).

building

cargo build --release

running

# development mode (reads from ../contracts/)
CONTRACTS_DIR=../contracts ./target/release/tana-edge

# production mode (reads from T4 storage)
T4_URL=http://t4:8507 ./target/release/tana-edge

# with per-contract databases
CONTRACT_DB_ADMIN_URL=postgres://... CONTRACT_DB_BOUNCER_URL=postgres://... ./target/release/tana-edge

endpoints

endpoint purpose
GET /health Health check with pool stats
POST /_admin/cache/evict Evict all cached isolates
/_dev/{contractId}/* Development: serve from filesystem
/{address}/{contractId}/* Production: serve from T4 storage