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 --releaseWASI 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 Case | SDK Required? |
|---|---|
| Persistent storage across executions | Yes |
| Caller identity (who signed the transaction) | Yes (or read env vars directly) |
| Atomic operations (counters, compare-and-swap) | Yes |
| Cross-project public data sharing | Yes |
| Pure computation (no state) | No |
| HTTP requests to external APIs | No |
| Reading secrets via env vars | No (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#
| Example | SDK Features Used |
|---|---|
| oracle-ark | storage::set_worker_with_options() — stores prices as public (unencrypted) worker data for cross-project reads |
| private-token-ark | storage::get_worker(), set_worker() — private FT balances in worker storage; env::signer_account_id() for authorization |
| test-storage-ark | All storage operations — comprehensive test suite for every API method |
| payment-keys-with-intents | env::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 = truesrc/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.wasmAPI Reference#
outlayer::env#
| Function | Returns | Description |
|---|---|---|
| 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) | bool | Check if env var exists |
outlayer::storage#
| Function | Description |
|---|---|
| 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_string | String convenience methods |
| set_json / get_json | JSON 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