OutLayer Documentation

OutLayer SDK

The outlayer crate provides a Rust SDK for building WASI applications on OutLayer. It gives you access to persistent encrypted storage, execution context (caller identity, secrets), and structured I/O.

Installation#

Add the crate to your Cargo.toml:

[dependencies]
outlayer = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Published at crates.io/crates/outlayer. Build with the wasm32-wasip2 target:

rustup target add wasm32-wasip2
cargo build --target wasm32-wasip2 --release

WASI Preview 2 required. The SDK will fail to compile if you target wasm32-wasip1 or wasm32-wasi. You must use wasm32-wasip2.

When Do You Need the SDK?#

Use CaseSDK Required?
Persistent storage across executionsYes
Caller identity (who signed the transaction)Yes (or read env vars directly)
Atomic operations (counters, compare-and-swap)Yes
Cross-project public data sharingYes
Pure computation (no state)No
HTTP requests to external APIsNo
Reading secrets via env varsNo (use std::env::var)

In short: if your WASI app needs persistent storage or structured access to execution context, use the SDK. For stateless computation (like API calls, random numbers, weather data), you can use plain Rust with stdin/stdout.

Environment Module (outlayer::env)#

Access execution context: who called you, what input they sent, and environment variables (including secrets).

Caller Context#

use outlayer::env;

// Who initiated the execution
let signer = env::signer_account_id();     // e.g., Some("alice.near")
let predecessor = env::predecessor_account_id(); // e.g., Some("token.near")
let tx_hash = env::transaction_hash();
let request_id = env::request_id();

Input / Output#

use outlayer::env;
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Request { action: String }

#[derive(Serialize)]
struct Response { result: String }

fn main() {
    // Read JSON input from stdin
    let req: Request = match env::input_json() {
        Ok(Some(r)) => r,
        _ => return env::output_string("No valid input"),
    };

    // Write JSON output to stdout
    env::output_json(&Response {
        result: format!("Got action: {}", req.action),
    }).ok();
}

Environment Variables#

Secrets stored via the dashboard are injected as environment variables. You can read them with env::var() or std::env::var():

// SDK helper
let api_key = outlayer::env::var("OPENAI_API_KEY");

// Standard library (also works)
let api_key = std::env::var("OPENAI_API_KEY").ok();

Injected environment variables: NEAR_SENDER_ID, NEAR_PREDECESSOR_ID, NEAR_TRANSACTION_HASH, OUTLAYER_REQUEST_ID, OUTLAYER_PROJECT_UUID, OUTLAYER_PROJECT_OWNER, OUTLAYER_PROJECT_NAME, USD_PAYMENT, plus any custom secrets stored via the dashboard.

Storage Module (outlayer::storage)#

Persistent encrypted key-value storage that survives across executions. Storage is automatically isolated per caller — alice.near cannot read bob.near's data.

Basic Operations#

use outlayer::storage;

// String convenience methods
storage::set_string("greeting", "hello")?;
let val = storage::get_string("greeting")?; // Some("hello")

// JSON convenience methods
storage::set_json("config", &my_config)?;
let config: MyConfig = storage::get_json("config")?.unwrap();

// Binary data
storage::set("data", &bytes)?;
let data = storage::get("data")?;

// Check existence and delete
if storage::has("key") {
    storage::delete("key");
}

// List keys by prefix
let keys = storage::list_keys("user:")?;

// Clear all data for current caller
storage::clear_all()?;

Atomic Operations#

For concurrent-safe operations (e.g., counters, voting):

// Atomic counter
let new_count = storage::increment("visits", 1)?;  // Returns new value
let new_count = storage::decrement("credits", 10)?;

// Insert only if key doesn't exist (mutex-like)
let inserted = storage::set_if_absent("lock", b"held")?;

// Compare-and-swap
let (success, old) = storage::set_if_equals(
    "version", b"v1", b"v2"
)?;

Worker Storage (Shared Across Users)#

Worker storage is shared across all callers and accessible only from within the WASI module. Use it for global state like price feeds, configuration, or cached data.

// Encrypted worker storage (default)
storage::set_worker("cache:key", &data)?;
let cached = storage::get_worker("cache:key")?;

// Public (unencrypted) worker storage — readable by other projects
storage::set_worker_with_options("oracle:ETH", &price, Some(false))?;

// Read public data from another project
let price = storage::get_worker_from_project(
    "oracle:ETH",
    Some("p0000000000000042"),  // target project UUID
)?;

Version Migration#

When upgrading your WASM binary, data from the previous version is accessible via its hash:

// Read data from a previous version
let old_data = storage::get_by_version("key", "abc123...")?;

// Clean up old version data after migration
storage::clear_version("abc123...")?;

Examples Using the SDK#

ExampleSDK Features Used
oracle-arkstorage::set_worker_with_options() — stores prices as public (unencrypted) worker data for cross-project reads
private-token-arkstorage::get_worker(), set_worker() — private FT balances in worker storage; env::signer_account_id() for authorization
test-storage-arkAll storage operations — comprehensive test suite for every API method
payment-keys-with-intentsenv::input_json(), env::output_json() — structured I/O for token swap orchestration

Minimal Project Template#

Cargo.toml#

[package]
name = "my-outlayer-app"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "my-outlayer-app"
path = "src/main.rs"

[dependencies]
outlayer = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[profile.release]
opt-level = "s"
lto = true
strip = true

src/main.rs#

use outlayer::{env, storage};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Request {
    action: String,
}

#[derive(Serialize)]
struct Response {
    count: i64,
    message: String,
}

fn main() {
    let req: Request = match env::input_json() {
        Ok(Some(r)) => r,
        _ => return env::output_string("No valid input"),
    };

    match req.action.as_str() {
        "increment" => {
            let count = storage::increment("counter", 1)
                .unwrap_or(0);
            env::output_json(&Response {
                count,
                message: format!("Counter incremented by {}",
                    env::signer_account_id().unwrap_or_default()),
            }).ok();
        }
        "get" => {
            let val = storage::get_string("counter")
                .ok().flatten()
                .and_then(|s| s.parse::<i64>().ok())
                .unwrap_or(0);
            env::output_json(&Response {
                count: val,
                message: "Current count".into(),
            }).ok();
        }
        _ => env::output_string("Unknown action"),
    }
}
# Build
cargo build --target wasm32-wasip2 --release

# Output: target/wasm32-wasip2/release/my-outlayer-app.wasm

API Reference#

outlayer::env#

FunctionReturnsDescription
signer_account_id()Option<String>User who initiated execution
predecessor_account_id()Option<String>Contract that called OutLayer
transaction_hash()Option<String>Transaction hash (if applicable)
request_id()Option<String>OutLayer-assigned request ID
input()Vec<u8>Raw stdin bytes
input_string()Option<String>Stdin as UTF-8 string
input_json<T>()Result<Option<T>>Deserialize stdin as JSON
output(data)()Write bytes to stdout
output_string(s)()Write string to stdout
output_json(value)Result<()>Serialize and write JSON
var(key)Option<String>Read env var (incl. secrets)
has_var(key)boolCheck if env var exists

outlayer::storage#

FunctionDescription
set(key, value)Store bytes (user-isolated)
get(key)Read bytes
has(key)Check existence
delete(key)Delete key
list_keys(prefix)List keys by prefix
set_string / get_stringString convenience methods
set_json / get_jsonJSON convenience methods
increment(key, delta)Atomic counter increment
decrement(key, delta)Atomic counter decrement
set_if_absent(key, value)Insert only if key doesn't exist
set_if_equals(key, expected, new)Compare-and-swap
set_worker(key, value)Encrypted shared storage
get_worker(key)Read shared storage
set_worker_with_options(key, val, encrypted)Shared storage with encryption toggle
get_worker_from_project(key, uuid)Cross-project public data read
get_by_version(key, hash)Read data from previous WASM version
clear_all()Delete all data for current caller
clear_version(hash)Clean up old version data

Storage Types Overview#

User Storage

set() / get()

  • Isolated per caller account
  • Encrypted at rest
  • alice.near can't read bob.near's data

Worker Storage

set_worker() / get_worker()

  • Shared across all callers
  • Encrypted by default
  • Only accessible within WASI module

Public Storage

set_worker_with_options(..., false)

  • Unencrypted, readable by other projects
  • Cross-project data sharing
  • Useful for oracles, shared state