OutLayer Documentation

Building OutLayer App

What is WASI?#

WASI (WebAssembly System Interface) is a standardized API that allows WebAssembly modules to interact with the outside world - read files, access environment variables, make network requests, and generate random numbers.

Think of WASI as a "syscall interface for WebAssembly" - it provides the basic building blocks your WASM code needs to do real work, like reading input data or calling external APIs, while maintaining security through sandboxing.

Supported Languages#

Any language that compiles to WASM with WASI support: Rust, C/C++, Go, AssemblyScript, and more. Rust is recommended for best tooling and ecosystem support.

WASI Preview 1 vs Preview 2#

OutLayer supports both WASI P1 and P2 standards. Choose based on your requirements:

WASI Preview 1 (P1)
  • Target: wasm32-wasip1
  • Use for: Simple computations, random numbers, basic I/O
  • Binary size: Smaller (~100-200KB)
  • Compilation: Faster
  • Stability: Mature and stable
WASI Preview 2 (P2)
  • Target: wasm32-wasip2
  • Use for: HTTP requests, complex I/O, modern features
  • Binary size: Larger (~500KB-1MB)
  • Features: HTTP client, advanced filesystem, sockets
  • Requires: wasmtime 28+

Rule of thumb: Use P1 unless you need HTTP or advanced I/O.

WASI Interface#

OutLayer provides a minimal WASI environment with support for:

  • stdin/stdout: Read JSON input data, write JSON output results
  • Environment variables: Access encrypted secrets via std::env::var()
  • Random numbers: Cryptographically secure random generation (WASI P1 & P2)
  • HTTP requests: Make external API calls (WASI P2 only, via wasi-http-client)
  • File I/O (limited): Basic file operations in sandboxed environment
  • NEAR context: Access execution metadata via env vars (NEAR_SENDER_ID, NEAR_BLOCK_HEIGHT, etc.)

Host Functions (Advanced)#

OutLayer provides advanced host functions for direct NEAR RPC access from WASM. These functions enable your code to interact with the NEAR blockchain without relying on external HTTP APIs.

What are Host Functions?

Host functions are native functions provided by the worker runtime that WASM code can call directly. They bypass HTTP and give your code privileged access to private NEAR RPC endpoints (powered by Fastnear), enabling operations like sending transactions and querying blockchain state.

Available Functions

call() - Execute NEAR contract call

Send function calls to NEAR contracts with attached deposit and gas. Your WASM provides the signer credentials via secrets.

call(signer_id, signer_key, receiver_id, method_name, args_json, deposit_yocto, gas) → (tx_hash, status)

transfer() - Send NEAR tokens

Transfer NEAR tokens from one account to another.

transfer(signer_id, signer_key, receiver_id, amount_yocto) → (tx_hash, status)

view() - Query contract state

Read-only view calls to query contract state without sending transactions.

view(contract_id, method_name, args_json) → (result, status)

Key Security Features

  • WASM provides signer: Your code passes signer_key from secrets - worker never uses its own keys
  • Private RPC access: Fastnear-powered endpoints with higher rate limits and reliability
  • Transaction tracking: All transactions are logged and can be verified on-chain
  • TEE isolation: Signing keys remain inside TEE and never leave the secure enclave

Example: botfather-ark

The botfather-ark example demonstrates host functions in action:

  • Creates multiple NEAR accounts programmatically using call()
  • Distributes NEAR tokens across accounts using transfer()
  • Executes batch contract calls (e.g., token purchases, staking delegation)
  • Queries account balances via view()

WIT Interface Definition

Host functions are defined in worker/wit/world.wit:

package near:[email protected];

interface api {
    view: func(
        contract-id: string,
        method-name: string,
        args-json: string
    ) -> tuple<string, string>;

    call: func(
        signer-id: string,
        signer-key: string,
        receiver-id: string,
        method-name: string,
        args-json: string,
        deposit-yocto: string,
        gas: string
    ) -> tuple<string, string>;

    transfer: func(
        signer-id: string,
        signer-key: string,
        receiver-id: string,
        amount-yocto: string
    ) -> tuple<string, string>;
}

world rpc-host {
    import api;
}

Requirements

  • WASI Preview 2: Host functions require wasm32-wasip2 target
  • Signer credentials: Must provide NEAR_SENDER_PRIVATE_KEY via secrets
  • NEAR tokens: Signer account must have sufficient balance for gas and deposits

API Versioning

Host functions are versioned using semantic versioning (@0.1.0). This ensures backward compatibility when the API evolves.

  • Current version: near:[email protected]
  • Multiple versions: Workers can run WASM compiled with different API versions simultaneously
  • No breaking changes: Your WASM will continue working even when new versions are released

Critical Requirements#

  • Binary format: Must use [[bin]] in Cargo.toml, NOT [lib]
  • Entry point: Must have fn main() function
  • Input: Always read from stdin (not command-line arguments)
  • Output: Always write to stdout (not stderr)
  • Format: JSON only (UTF-8 encoded)
  • Size limit: Output must be ≤900 bytes (NEAR Protocol limit)
  • Flush: Call stdout().flush() after writing

Working Examples#

We provide 9 complete, open-source examples demonstrating different WASI patterns:

random-arkP1

Random number generation (starter example)

echo-arkP1

NEAR context & environment variables

ai-arkP2

OpenAI API integration (HTTPS requests)

weather-arkP2

OpenWeather API oracle (specialized price oracle)

oracle-arkP2

Multi-source price oracle with aggregation

ethereum-apiP2

Ethereum blockchain data access via RPC

botfather-arkP2Host Functions

Account factory with AI names & batch operations. Demonstrates call() and transfer() host functions for NEAR RPC access.

intents-arkP2

DEX swaps via NEAR Intents (paused FT transfer)

private-dao-arkP1Advanced

Anonymous DAO voting with cryptographic privacy (ECIES, HKDF, Merkle trees)

captcha-arkP2Full Stack

Token launchpad with CAPTCHA verification

Detailed Documentation Available

Each example includes complete source code, input/output examples, build instructions, and deployment guides.

View All Examples →

Resource Considerations#

Be mindful of resource limits: instruction counts, memory usage, and execution time. Optimize your code to stay within requested limits to avoid failures and minimize costs.

  • Max Instructions: 100 billion per execution (fuel metering enforced)
  • Max Execution Time: 60 seconds per execution
  • Max Memory: Configurable up to platform limits
  • Output Size: Must be ≤900 bytes (NEAR limit)

Testing Locally#

Option 1: Test Compiler Script (Quick Compilation Test)

Use test_compiler.sh to test compilation of your GitHub repository locally without running the full worker infrastructure. This script uses the same Docker image (zavodil/wasmedge-compiler:latest with Rust 1.85) and compilation logic as the production worker.

Perfect for: Testing if your repository compiles correctly before deploying to OutLayer.

# Test compilation for WASI Preview 1
./scripts/test_compiler.sh \
  https://github.com/zavodil/random-ark main wasm32-wasip1

# Test compilation for WASI Preview 2
./scripts/test_compiler.sh \
  https://github.com/zavodil/ai-ark main wasm32-wasip2

# Custom output file
./scripts/test_compiler.sh \
  https://github.com/user/myproject main wasm32-wasip1 myapp.wasm

# The script will:
# 1. Pull zavodil/wasmedge-compiler:latest Docker image (instant if already up to date)
# 2. Clone your repository and checkout the commit
# 3. Run cargo build with the exact same flags as worker
# 4. Optimize WASM (wasm-opt for P1, wasm-tools for P2)
# 5. Output compiled WASM with SHA256 checksum

Key features:

  • Uses official Docker image from Docker Hub (no local builds needed)
  • Exactly mirrors worker compiler behavior
  • Supports all three targets: wasm32-wasip1, wasm32-wasip2, wasm32-wasi
  • Shows compilation time, file size, and SHA256 checksum
  • Configurable memory/CPU limits via environment variables

Troubleshooting: If you get cabi_realloc error with wasm32-wasip2, it means the project is not configured as a WASI P2 component. WASI Preview 2 requires projects to be built as components (using cargo-component) and export special memory management functions. Most existing projects are written for WASI P1. Solution: Use wasm32-wasip1 instead.

Note on Rust version: The Docker image uses Rust 1.85 for maximum compatibility. While newer Rust versions (1.88+) exist, they may have breaking changes with certain dependencies. The production worker uses 1.85 to ensure broad compatibility.

Option 2: WASI Test Runner (Full Validation)

We provide wasi-test-runner - a universal test tool that validates your WASM modules for OutLayer compatibility. It tests binary format, fuel metering, I/O handling, resource limits, JSON validation, and output size.

# Install test runner
cd wasi-examples/wasi-test-runner
cargo build --release

# Test your WASM module
./target/release/wasi-test \
  --wasm path/to/your-app.wasm \
  --input '{"test":"data"}' \
  --verbose

# Example output:
# ✓ Detected: WASI Preview 1 Module
# ✅ Execution successful!
# 📊 Fuel consumed: 456789 instructions
# 📤 Output: {"result":"success"}
# ✅ All checks passed!

What it validates:

  • Binary format (WASI P1 or P2)
  • Fuel metering (instruction counting)
  • Input/output handling (stdin → stdout)
  • Resource limits enforcement
  • JSON validation
  • Output size limits (<900 bytes)

Option 3: Manual Testing with wasmtime

Test directly using wasmtime:

# Install wasmtime
curl https://wasmtime.dev/install.sh -sSf | bash

# Test WASI P1 binary
echo '{"value":21}' | wasmtime your-app.wasm

# Test WASI P2 component
echo '{"prompt":"test"}' | wasmtime your-app.wasm

# Test with environment variables
echo '{"message":"test"}' | wasmtime --env SECRET=my-key your-app.wasm

Common Pitfalls#

Error: "entry symbol not defined: _initialize"

Using [lib] instead of [[bin]] in Cargo.toml

Empty output

Forgot to call io::stdout().flush()? after writing

HTTP requests fail

Using WASI P1 instead of P2 - HTTP requires wasm32-wasip2 target

Output truncated in explorer

Output exceeds 900 bytes - truncate before returning

Next Steps#

  • Explore working examples with complete source code and deployment guides
  • Read the complete WASI tutorial
  • Clone examples: git clone https://github.com/fastnear/near-outlayer.git
  • Test your WASM locally with wasmtime before deploying
  • Start with random-ark or echo-ark for simple use cases
  • Use ai-ark or oracle-ark for HTTPS-based applications
  • Study private-dao-ark for advanced cryptography (ECIES, HKDF, Merkle trees) and privacy patterns
  • Deploy captcha-ark for full-stack async human verification