Oasis introduceerde het framework voor runtime off-chain logic (ROFL) om te helpen bij het bouwen en uitvoeren van apps off-chain, terwijl privacy wordt gewaarborgd en vertrouwen met on-chain wordt behoudenOasis introduceerde het framework voor runtime off-chain logic (ROFL) om te helpen bij het bouwen en uitvoeren van apps off-chain, terwijl privacy wordt gewaarborgd en vertrouwen met on-chain wordt behouden

Gids voor Cross-Chain Sleutelgeneratie (EVM / Base) met Oasis ROFL

2026/02/20 21:16
10 min lezen

Oasis introduceerde het framework voor runtime off-chain logic (ROFL) om te helpen bij het bouwen en uitvoeren van apps off-chain, terwijl privacy wordt gewaarborgd en vertrouwen wordt behouden met on-chain verifieerbaarheid. Er zijn veel onderdelen betrokken bij het bouwen met ROFL.
In deze tutorial laat ik zien hoe je een kleine TypeScript-app kunt bouwen die een secp256k1-sleutel genereert binnen ROFL. Het zal gebruikmaken van de @oasisprotocol/rofl-client TypeScript SDK, die onder de motorkap communiceert met de appd REST API. De TypeScript-app zal ook:

Er zal een eenvoudige smoke test zijn die naar logs print.

Vereisten

Om de stappen in deze handleiding uit te voeren, heb je nodig:

  • Node.js 20+ en Docker (of Podman)
  • Oasis CLI en minimaal 120 TEST-tokens in je portemonnee (Oasis Testnet faucet)
  • Wat Base Sepiola test ETH (Base Sepiola faucet)

Voor de setup-details, raadpleeg de documentatie over Quickstart Prerequisites.

App initialiseren

De eerste stap is het initialiseren van een nieuwe app met behulp van de Oasis CLI.

oasis rofl init rofl-keygen
cd rofl-keygen

App aanmaken

Bij het aanmaken van de app op het Testnet moet je tokens storten. Wijs op dit moment 100 TEST-tokens toe.

oasis rofl create --network testnet

Als output produceert de CLI de App ID, aangeduid met rofl1….

Een Hardhat (TypeScript)-project initialiseren

Nu ben je klaar om het project te starten.

npx hardhat init

Aangezien we een TypeScript-app demonstreren, kies TypeScript wanneer daarom wordt gevraagd en accepteer vervolgens de standaardinstellingen.
De volgende stap is het toevoegen van de kleine runtime-afhankelijkheden voor gebruik buiten Hardhat.

npm i @oasisprotocol/rofl-client ethers dotenv @types/node
npm i -D tsx

Het TypeScript-sjabloon van Hardhat creëert automatisch een tsconfig.json. We moeten een klein script toevoegen zodat de app-code kan compileren naar dist/.

// tsconfig.json
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}

App-structuur

In dit gedeelte voegen we een paar kleine TS-bestanden toe en één Solidity-contract.

src/
├── appd.ts # dunne wrapper over @oasisprotocol/rofl-client
├── evm.ts # ethers helpers (provider, wallet, tx, deploy)
├── keys.ts # kleine helpers (checksum)
└── scripts/
├── deploy-contract.ts # generiek deploy-script voor gecompileerde artefacten
└── smoke-test.ts # end-to-end demo (logs)
contracts/
└── Counter.sol # voorbeeldcontract

  1. src/appd.ts — dunne wrapper over de SDK Hier moet je de officiële client gebruiken om te communiceren met appd (UNIX socket). We moeten ook een expliciete local-dev fallback behouden wanneer we buiten ROFL draaien.

src/appd.ts

import {existsSync} from 'node:fs';
import {
RoflClient,
KeyKind,
ROFL_SOCKET_PATH
} from '@oasisprotocol/rofl-client';
const client = new RoflClient(); // UDS: /run/rofl-appd.sock
export async function getAppId(): Promise<string> {
return client.getAppId();
}
/**
* Genereert (of determistisch herleidt) een secp256k1-sleutel binnen ROFL en
* retourneert deze als een 0x-prefix hex string (voor ethers.js Wallet).
*
* Lokale ontwikkeling ALLEEN (buiten ROFL): Als de socket ontbreekt en je stelt
* ALLOW_LOCAL_DEV=true en LOCAL_DEV_SK=0x<64-hex> in, wordt die waarde gebruikt.
*/
export async function getEvmSecretKey(keyId: string): Promise<string> {
if (existsSync(ROFL_SOCKET_PATH)) {
const hex = await client.generateKey(keyId, KeyKind.SECP256K1);
return hex.startsWith('0x') ? hex : `0x${hex}`;
}
const allow = process.env.ALLOW_LOCAL_DEV === 'true';
const pk = process.env.LOCAL_DEV_SK;
if (allow && pk && /^0x[0-9a-fA-F]{64}$/.test(pk)) return pk;
throw new Error(
'rofl-appd socket niet gevonden en geen LOCAL_DEV_SK opgegeven (alleen dev).'
);
}

2. src/evm.ts — ethers helpers

import {
JsonRpcProvider,
Wallet,
parseEther,
type TransactionReceipt,
ContractFactory
} from "ethers";
export function makeProvider(rpcUrl: string, chainId: number) {
return new JsonRpcProvider(rpcUrl, chainId);
}
export function connectWallet(
skHex: string,
rpcUrl: string,
chainId: number
): Wallet {
const w = new Wallet(skHex);
return w.connect(makeProvider(rpcUrl, chainId));
}
export async function signPersonalMessage(wallet: Wallet, msg: string) {
return wallet.signMessage(msg);
}
export async function sendEth(
wallet: Wallet,
to: string,
amountEth: string
): Promise<TransactionReceipt> {
const tx = await wallet.sendTransaction({
to,
value: parseEther(amountEth)
});
const receipt = await tx.wait();
if (receipt == null) {
throw new Error("Transactie gedropt of vervangen vóór bevestiging");
}
return receipt;
}
export async function deployContract(
wallet: Wallet,
abi: any[],
bytecode: string,
args: unknown[] = []
): Promise<{ address: string; receipt: TransactionReceipt }> {
const factory = new ContractFactory(abi, bytecode, wallet);
const contract = await factory.deploy(...args);
const deployTx = contract.deploymentTransaction();
const receipt = await deployTx?.wait();
await contract.waitForDeployment();
if (!receipt) {
throw new Error("Deployment TX niet gemined");
}
return { address: contract.target as string, receipt };
}

3. src/keys.ts — kleine helpers

import { Wallet, getAddress } from "ethers";
export function secretKeyToWallet(skHex: string): Wallet {
return new Wallet(skHex);
}
export function checksumAddress(addr: string): string {
return getAddress(addr);
}

4. src/scripts/smoke-test.ts — enkele end-to-end flow

Dit is een belangrijke stap omdat dit script meerdere functies heeft:

  • print de App ID (binnen ROFL), adres en een ondertekend bericht
  • wacht op financiering
  • deploy het counter contract

import "dotenv/config";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { getAppId, getEvmSecretKey } from "../appd.js";
import { secretKeyToWallet, checksumAddress } from "../keys.js";
import { makeProvider, signPersonalMessage, sendEth, deployContract } from "../evm.js";
import { formatEther, JsonRpcProvider } from "ethers";
const RPC_URL = process.env.BASE_RPC_URL ?? "https://sepolia.base.org";
const CHAIN_ID = Number(process.env.BASE_CHAIN_ID ?? "84532");
const KEY_ID = process.env.KEY_ID ?? "evm:base:sepolia";
function sleep(ms: number): Promise<void> {
return new Promise((r) => setTimeout(r, ms));
}
async function waitForFunding(
provider: JsonRpcProvider,
addr: string,
minWei: bigint = 1n,
timeoutMs = 15 * 60 * 1000,
pollMs = 5_000
): Promise<bigint> {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const bal = await provider.getBalance(addr);
if (bal >= minWei) return bal;
console.log(`Wachten op financiering... huidig saldo=${formatEther(bal)} ETH`);
await sleep(pollMs);
}
throw new Error("Time-out tijdens wachten op financiering.");
}
async function main() {
const appId = await getAppId().catch(() => null);
console.log(`ROFL App ID: ${appId ?? "(niet beschikbaar buiten ROFL)"}`);
const sk = await getEvmSecretKey(KEY_ID);
// OPMERKING: Deze demo vertrouwt de geconfigureerde RPC-provider. Voor productie, geef de voorkeur aan een
// light client (bijvoorbeeld Helios) zodat je externe chain-status kunt verifiëren.
const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));
const addr = checksumAddress(await wallet.getAddress());
console.log(`EVM-adres (Base Sepolia): ${addr}`);
const msg = "hello from rofl";
const sig = await signPersonalMessage(wallet, msg);
console.log(`Ondertekend bericht: "${msg}"`);
console.log(`Handtekening: ${sig}`);
const provider = wallet.provider as JsonRpcProvider;
let bal = await provider.getBalance(addr);
if (bal === 0n) {
console.log("Vul het bovenstaande adres aan met Base Sepolia ETH om door te gaan.");
bal = await waitForFunding(provider, addr);
}
console.log(`Saldo gedetecteerd: ${formatEther(bal)} ETH`);
const artifactPath = join(process.cwd(), "artifacts", "contracts", "Counter.sol", "Counter.json");
const artifact = JSON.parse(readFileSync(artifactPath, "utf8"));
if (!artifact?.abi || !artifact?.bytecode) {
throw new Error("Counter artifact mist abi/bytecode");
}
const { address: contractAddress, receipt: deployRcpt } =
await deployContract(wallet, artifact.abi, artifact.bytecode, []);
console.log(`Counter gedeployed op ${contractAddress} (tx=${deployRcpt.hash})`);
console.log("Smoke test succesvol voltooid!");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

5. contracts/Counter.sol — minimaal voorbeeld

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Counter {
uint256 private _value;
event Incremented(uint256 v);
event Set(uint256 v);
function current() external view returns (uint256) { return _value; }
function inc() external { unchecked { _value += 1; } emit Incremented(_value); }
function set(uint256 v) external { _value = v; emit Set(v); }
}

6. src/scripts/deploy-contract.ts — generieke deployer

import "dotenv/config";
import { readFileSync } from "node:fs";
import { getEvmSecretKey } from "../appd.js";
import { secretKeyToWallet } from "../keys.js";
import { makeProvider, deployContract } from "../evm.js";
const KEY_ID = process.env.KEY_ID ?? "evm:base:sepolia";
const RPC_URL = process.env.BASE_RPC_URL ?? "https://sepolia.base.org";
const CHAIN_ID = Number(process.env.BASE_CHAIN_ID ?? "84532");
/**
* Gebruik:
* npm run deploy-contract -- ./artifacts/MyContract.json '[arg0, arg1]'
* Het artefact moet { abi, bytecode } bevatten.
*/
async function main() {
const [artifactPath, ctorJson = "[]"] = process.argv.slice(2);
if (!artifactPath) {
console.error("Gebruik: npm run deploy-contract -- <artifact.json> '[constructorArgsJson]'");
process.exit(2);
}
const artifactRaw = readFileSync(artifactPath, "utf8");
const artifact = JSON.parse(artifactRaw);
const { abi, bytecode } = artifact ?? {};
if (!abi || !bytecode) {
throw new Error("Artefact moet { abi, bytecode } bevatten");
}
let args: unknown[];
try {
args = JSON.parse(ctorJson);
if (!Array.isArray(args)) throw new Error("constructor args moeten een JSON-array zijn");
} catch (e) {
throw new Error(`Mislukt om constructor args JSON te parsen: ${String(e)}`);
}
const sk = await getEvmSecretKey(KEY_ID);
// OPMERKING: Deze demo vertrouwt de geconfigureerde RPC-provider. Voor productie, geef de voorkeur aan een
// light client (bijvoorbeeld Helios) zodat je externe chain-status kunt verifiëren.
const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));
const { address, receipt } = await deployContract(wallet, abi, bytecode, args);
console.log(JSON.stringify({ contractAddress: address, txHash: receipt.hash, status: receipt.status }, null, 2));
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

Hardhat (alleen contracten)

In deze fase hebben we minimale configuratie nodig om Counter.sol te compileren

hardhat.config.ts

import type { HardhatUserConfig } from "hardhat/config";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.24",
settings: {
optimizer: { enabled: true, runs: 200 }
}
},
paths: {
sources: "./contracts",
artifacts: "./artifacts",
cache: "./cache"
}
};
export default config;

Een belangrijk punt om te noteren is dat lokale compilatie optioneel is, dus je kunt dit overslaan als je wilt. De volgende stap is een keuze — verwijder het bestaande contracts/Lock.sol-bestand of je kunt het bijwerken naar Solidity versie 0.8.24.

npx hardhat compile

Containeriseren

Dit is een essentiële stap. Hier heb je een Dockerfile nodig die TS bouwt en het contract compileert. Het bestand zal ook de smoke test eenmaal uitvoeren en vervolgens inactief blijven terwijl je logs inspecteert.

Dockerfile

FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
COPY contracts ./contracts
COPY hardhat.config.ts ./
RUN npm run build && npx hardhat compile && npm prune --omit=dev
ENV NODE_ENV=production
CMD ["sh", "-c", "node dist/scripts/smoke-test.js || true; tail -f /dev/null"]

Vervolgens moet je de appd socket mounten die door ROFL wordt verstrekt. Wees gerust dat er geen openbare poorten worden blootgesteld in het proces.

compose.yaml

services:
demo:
image: docker.io/YOURUSER/rofl-keygen:0.1.0
platform: linux/amd64
environment:
- KEY_ID=${KEY_ID:-evm:base:sepolia}
- BASE_RPC_URL=${BASE_RPC_URL:-https://sepolia.base.org}
- BASE_CHAIN_ID=${BASE_CHAIN_ID:-84532}
volumes:
- /run/rofl-appd.sock:/run/rofl-appd.sock

Bouw de image

Het is belangrijk om te onthouden dat ROFL alleen draait op Intel TDX-enabled hardware. Dus als je images compileert op een andere host, zoals macOS, dan is het doorgeven van de parameter --platform linux/amd64 een essentiële extra stap.

docker buildx build --platform linux/amd64 \
-t docker.io/YOURUSER/rofl-keygen:0.1.0 --push .

Een interessant punt om hier op te merken is dat je kunt kiezen voor extra beveiliging en verifieerbaarheid. Je hoeft alleen de digest vast te pinnen en image: …@sha256:… in compose.yaml te gebruiken.

Bouw ROFL bundle

Er is een stap die je moet nemen voordat je het commando oasis rofl build uitvoert. Omdat het bouwen van het image-segment na containerisatie komt, moet je de services.demo.image in compose.yaml bijwerken naar de image die je hebt gebouwd.
Voor eenvoudige TypeScript-projecten, zoals deze, is er soms een mogelijkheid dat de image-grootte groter is dan verwacht. Het is daarom raadzaam om de rofl.yaml resources sectie bij te werken naar ten minste: memory: 1024 en storage.size: 4096.
Nu ben je klaar.

oasis rofl build

Je kunt vervolgens de enclave-identiteiten en config publiceren.

oasis rofl update

Deploy

Dit is een eenvoudige stap waarbij je deployt naar een Testnet-provider.

oasis rofl deploy

End-to-end (Base Sepolia)

Dit is een proces van 2 stappen, hoewel de tweede stap optioneel is.
Eerst bekijk je smoke-test logs.

oasis rofl machine logs

Als je alle stappen tot nu toe correct hebt voltooid, zie je in de output:

  • App ID
  • EVM-adres en een ondertekend bericht
  • Een prompt om het adres te financieren
  • Zodra de financiering is gedaan, een Counter.sol deployment

Vervolgens, lokale dev. Hier moet je npm run build:all uitvoeren om de TypeScript-code en het Solidity-contract te compileren. Sla deze stap over indien niet nodig.

export ALLOW_LOCAL_DEV=true
export LOCAL_DEV_SK=0x<64-hex-dev-secret-key> # NIET GEBRUIKEN IN PROD
npm run smoke-test

Beveiliging & punten om te onthouden

  • Provider logs zijn niet versleuteld in rust. Dus log nooit geheime sleutels.
  • De appd socket /run/rofl-appd.sock bestaat alleen binnen ROFL.
  • Er kunnen rate limits zijn in openbare RPC's. Dus het is raadzaam om te kiezen voor een toegewijde Base RPC URL.

Er is een key generation demo in de Oasis GitHub, die je kunt raadplegen als voorbeeld van deze tutorial. https://github.com/oasisprotocol/demo-rofl-keygen

Nu je met succes een sleutel hebt gegenereerd in ROFL met appd, berichten hebt ondertekend, een contract hebt gedeployed en ETH hebt verplaatst op Base Sepolia, laat ons in de reactiesectie je feedback weten. Voor een snelle chat met het Oasis engineering team voor hulp bij specifieke problemen, kun je je opmerkingen plaatsen in het dev-central channel in de officiële Discord.

Oorspronkelijk gepubliceerd op https://dev.to op 20 februari 2026.


Guide To Cross-Chain Key Generation (EVM / Base) With Oasis ROFL was oorspronkelijk gepubliceerd in Coinmonks op Medium, waar mensen het gesprek voortzetten door dit verhaal te markeren en erop te reageren.

Marktkans
CROSS logo
CROSS koers(CROSS)
$0.10061
$0.10061$0.10061
-5.34%
USD
CROSS (CROSS) live prijsgrafiek
Disclaimer: De artikelen die op deze site worden geplaatst, zijn afkomstig van openbare platforms en worden uitsluitend ter informatie verstrekt. Ze weerspiegelen niet noodzakelijkerwijs de standpunten van MEXC. Alle rechten blijven bij de oorspronkelijke auteurs. Als je van mening bent dat bepaalde inhoud inbreuk maakt op de rechten van derden, neem dan contact op met service@support.mexc.com om de content te laten verwijderen. MEXC geeft geen garanties met betrekking tot de nauwkeurigheid, volledigheid of tijdigheid van de inhoud en is niet aansprakelijk voor eventuele acties die worden ondernomen op basis van de verstrekte informatie. De inhoud vormt geen financieel, juridisch of ander professioneel advies en mag niet worden beschouwd als een aanbeveling of goedkeuring door MEXC.