Launch Trustless AI Agents on Oasis
Learn how to deploy a trustless Eliza agent on Oasis using ROFL enclaves.
What You’ll Build
By the end you will have:
- a ROFL-deployed Eliza agent running inside a TEE
- enclave-managed keys for on-chain actions
Prerequisites
You will need:
- Docker (or Podman)
- Oasis CLI
- Node.js 18+ (for Eliza and helper scripts)
- a Sapphire Testnet wallet funded with TEST
Optional depending on your LLM choice:
- OpenAI API key or a reachable Ollama instance
Architecture Overview
- ROFL (Runtime Off-chain Logic)
- Enclave execution, sealed secrets, app identity, attestation
- In-enclave key manager via
appdUNIX socket
- Sapphire (Verification Layer)
- Confidential EVM chain verifying ROFL identities and policies
- Runs confidential contracts that can verify ROFL origin
Init and Create App
We package the agent as a container and deploy it to a TEE via ROFL. ROFL handles attestation, sealed secrets, and app identity bound to your on-chain registration.
oasis rofl init rofl-eliza
cd rofl-eliza
oasis rofl create --network testnet
After creation, note your ROFL app ID. You can monitor deployments with:
oasis rofl machine show and oasis rofl machine logs, or use the Oasis
Explorer on Sapphire Testnet.
Eliza example (local run)
First we initialize our Eliza agent:
mkdir eliza-app && cd eliza-app
npm init -y
# install your Eliza framework deps here
# npm install @your-eliza/pkg ...
Initialize a project using the ElizaOS CLI and prepare it for ROFL.
# 1) Install bun and ElizaOS CLI
bun --version || curl -fsSL https://bun.sh/install | bash
bun install -g @elizaos/cli
# Configure model credentials locally
elizaos create eliza-agent
# OPENAI key when prompted
# Start the agent locally
elizaos start
Containerize and deploy to ROFL
Proceed to “Eliza agent container (inside ROFL)” below to package the agent for enclave execution and deploy it using the Oasis CLI.
Create a docker folder with a minimal runner that starts an ElizaOS agent via the CLI and reads model configuration from environment variables.
docker/entry.sh
#!/bin/sh
set -e
# Expected env in ROFL:
# OPENAI_API_KEY for the model provider
# Provide any additional vars your agent needs here.
exec elizaos start
docker/Dockerfile
FROM node:24-alpine
WORKDIR /app
# Copy your ElizaOS project (created with `elizaos create ...`).
ADD . /app
# Install ElizaOS CLI and project dependencies (npm-based).
RUN npm i -g @elizaos/cli && npm ci
# Minimal entrypoint that launches the agent.
ENTRYPOINT ["/bin/sh", "/app/docker/entry.sh"]
Add compose.yaml at project root:
compose.yaml
services:
eliza-agent:
build: ./docker
image: docker.io/YOUR_USERNAME/rofl-eliza:latest
platform: linux/amd64
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
Build and push:
docker compose build
docker compose push
For extra security, pin the image digest in compose.yaml.
Build ROFL bundle
- Native Linux
- Docker (Windows, Mac, Linux)
oasis rofl build
docker run --platform linux/amd64 --volume .:/src -it \
ghcr.io/oasisprotocol/rofl-dev:main oasis rofl build
This produces the .orc bundle for deployment, sealing in enclave identity.
Then publish the enclave identities and config:
oasis rofl update
Deploy
oasis rofl deploy
By default, the Oasis-maintained provider is selected on Testnet.
How the agent interacts on Sapphire
- Encode calldata for your contract method and call
signSubmit()viaappd. The tx originates from the enclave-bound key of your ROFL app. - Contracts can require verified ROFL origin to bind calls to your app.
Eliza + ROFL TypeScript client (inline)
Use the official ROFL TypeScript client directly inside your Eliza agent. Add a small helper you can import from any Eliza action/tool to derive the enclave key, expose the wallet address, and sign+submit transactions via appd.
src/rofl.ts
import { RoflClient, KeyKind } from "@oasisprotocol/rofl-client";
import { Interface, Wallet } from "ethers";
import http from "node:http";
export async function ensureEnclaveWallet(keyId = "agent-identity-key") {
const client = new RoflClient(); // connects to /run/rofl-appd.sock inside ROFL
const key = await client.generateKey(keyId, KeyKind.SECP256K1);
const wallet = new Wallet("0x" + key);
await client.setMetadata({ key_fingerprint: key.slice(0, 16) });
return { wallet, key, client };
}
export async function signSubmitEthTx(params: {
to: string;
abi: any[];
method: string;
args?: any[];
gasLimit?: number;
}) {
const { to, abi, method, args, gasLimit } = params;
const iface = new Interface(abi);
const data = iface.encodeFunctionData(method, args ?? []);
const body = JSON.stringify({
tx: { kind: "eth", data: { gas_limit: gasLimit ?? 200000, to, value: 0, data } },
});
return await new Promise<any>((resolve, reject) => {
const req = http.request(
{
socketPath: "/run/rofl-appd.sock",
path: "/rofl/v1/tx/sign-submit",
method: "POST",
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
},
(res) => {
let buf = "";
res.on("data", (c) => (buf += c));
res.on("end", () => {
try {
resolve(JSON.parse(buf || "{}"));
} catch {
resolve({ raw: buf });
}
});
}
);
req.on("error", reject);
req.write(body);
req.end();
});
}
Use the helper from inside your Eliza agent (e.g., in a tool/action handler):
Example usage in an Eliza handler
import { ensureEnclaveWallet, signSubmitEthTx } from "./rofl";
// During agent startup (once):
const { wallet } = await ensureEnclaveWallet("agent-identity-key");
console.log("ROFL enclave EVM address:", wallet.address);
// Later, on user intent (e.g., tool call):
await signSubmitEthTx({
to: "0xYourContract",
abi: [ /* contract ABI */ ],
method: "yourMethod",
args: [ /* arguments */ ],
gasLimit: 200000,
});
Wire into ElizaOS (enable tool and prompting)
Add a small tool wrapper Eliza can call. Register it in your agent config and nudge the model to invoke it via system instructions.
tools/rofl.ts
import { ensureEnclaveWallet, signSubmitEthTx } from "../src/rofl";
export async function generate_rofl_wallet(params?: { keyId?: string }) {
const { wallet } = await ensureEnclaveWallet(params?.keyId ?? "agent-identity-key");
return { address: wallet.address };
}
export async function submit_rofl_tx(input: {
to: string;
abi: any[];
method: string;
args?: any[];
gasLimit?: number;
}) {
return await signSubmitEthTx(input);
}
Register the tool in your Eliza agent configuration (adjust pathing to your build output):
agent.config.json
{
"name": "rofl-eliza",
"model": "openai:gpt-4o-mini",
"tools": ["./dist/tools/rofl.js"]
}
Guide the model to use the tool via system prompt:
system-prompt.txt
You are a ROFL-enabled Eliza agent. When a user asks to create a wallet or perform an on-chain action:
- First call generate_rofl_wallet to ensure an enclave-bound EVM address exists.
- Then call submit_rofl_tx with the target contract (to), ABI, method and args.
Never expose private keys; only share the EVM address.
Ensure the appd UNIX socket is available to your container:
compose.yaml (socket mount)
services:
eliza-agent:
# ...
volumes:
- /run/rofl-appd.sock:/run/rofl-appd.sock
Install dependencies and build with your app:
npm i ethers @oasisprotocol/rofl-client typescript
npx tsc
Your docker/entry.sh can continue to start just the Eliza agent:
#!/bin/sh
set -e
exec elizaos start
Testing it out
After oasis rofl deploy, use the CLI to check machine status and view logs.
# Show machine details (IDs, state, proxy URLs, expiration).
oasis rofl machine show
# Fetch logs from your running ROFL app.
oasis rofl machine logs
- Expect standard output from your app container (anything your entrypoint prints).
- If your app initializes services on startup, those startup logs will appear here.
- Use this to check enclave startup issues and app readiness.
Note: Logs are accessible to the app admin and are stored unencrypted on the ROFL node. Avoid printing secrets. See the official docs:
Inspect on-chain activity and app details in the Oasis Explorer:
- Oasis Explorer — inspect ROFL apps and Sapphire activity