Oasis a introdus cadrul pentru logica off-chain în timpul execuției (ROFL) pentru a ajuta la construirea și rularea aplicațiilor off-chain, asigurând în același timp confidențialitatea și menținând încrederea cu on-chainOasis a introdus cadrul pentru logica off-chain în timpul execuției (ROFL) pentru a ajuta la construirea și rularea aplicațiilor off-chain, asigurând în același timp confidențialitatea și menținând încrederea cu on-chain

Ghid pentru Generarea de Chei Cross-Chain (EVM / Base) cu Oasis ROFL

2026/02/20 21:16
10 min de lectură
Pentru opinii sau preocupări cu privire la acest conținut, contactează-ne la [email protected]

Oasis a introdus cadrul pentru logica runtime off-chain (ROFL) pentru a ajuta la construirea și rularea aplicațiilor off-chain, asigurând în același timp confidențialitatea și menținând încrederea cu verificabilitate on-chain. Există multe componente în mișcare la construirea cu ROFL.
În acest tutorial, voi demonstra cum să construiești o aplicație TypeScript mică, generând o cheie secp256k1 în interiorul ROFL. Va utiliza @oasisprotocol/rofl-client TypeScript SDK, care comunică cu appd REST API în fundal. Aplicația TypeScript va face:

Va exista un simplu smoke test care afișează în jurnale.

Cerințe preliminare

Pentru a face pașii descriși în acest ghid, vei avea nevoie de:

  • Node.js 20+ și Docker (sau Podman)
  • Oasis CLI și minimum 120 de token-uri TEST în portofelul tău (Oasis Testnet faucet)
  • Ceva ETH de test Base Sepiola (Base Sepiola faucet)

Pentru detaliile de configurare, te rugăm să consulți documentația despre Cerințe preliminare pentru Quickstart.

Inițializare aplicație

Primul pas este să inițializezi o nouă aplicație folosind Oasis CLI.

oasis rofl init rofl-keygen
cd rofl-keygen

Creare aplicație

În momentul creării aplicației pe Testnet, va trebui să depui token-uri. Alocă 100 de token-uri TEST în acest moment.

oasis rofl create --network testnet

Ca rezultat, CLI-ul va produce App ID, notat prin rofl1….

Inițializare proiect Hardhat (TypeScript)

Acum, ești pregătit să începi proiectul.

npx hardhat init

Deoarece prezentăm o aplicație TypeScript, alege TypeScript când ești întrebat, apoi acceptă setările implicite.
Următorul pas ar fi să adaugi dependențele mici de runtime pentru utilizare în afara Hardhat.

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

Șablonul TypeScript al Hardhat creează automat un tsconfig.json. Trebuie să adăugăm un script mic astfel încât codul aplicației să poată fi compilat în dist/.

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

Structura aplicației

În această secțiune, vom adăuga câteva fișiere TS mici și un contract Solidity.

src/
├── appd.ts # wrapper subțire peste @oasisprotocol/rofl-client
├── evm.ts # ajutoare ethers (provider, wallet, tx, deploy)
├── keys.ts # ajutoare mici (checksum)
└── scripts/
├── deploy-contract.ts # script generic de deploy pentru artefacte compilate
└── smoke-test.ts # demo end-to-end (jurnale)
contracts/
└── Counter.sol # contract exemplu

  1. src/appd.ts — wrapper subțire peste SDK Aici, va trebui să folosești clientul oficial pentru a comunica cu appd (socket UNIX). Va trebui să păstrăm și un fallback local-dev explicit când rulăm în afara ROFL.

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();
}
/**
* Generează (sau re-derivă în mod determinist) o cheie secp256k1 în interiorul ROFL și
* o returnează ca un șir hex cu prefix 0x (pentru ethers.js Wallet).
*
* Dezvoltare locală DOAR (în afara ROFL): Dacă socket-ul lipsește și setezi
* ALLOW_LOCAL_DEV=true și LOCAL_DEV_SK=0x<64-hex>, acea valoare este folosită.
*/
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 not found and no LOCAL_DEV_SK provided (dev only).'
);
}

2. src/evm.ts — ajutoare ethers

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("Transaction dropped or replaced before confirmation");
}
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 not mined");
}
return { address: contract.target as string, receipt };
}

3. src/keys.ts — ajutoare mici

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 — flux end-to-end singular

Acesta este un pas important deoarece acest script are multiple funcții:

  • afișează App ID (în interiorul ROFL), adresa și un mesaj semnat
  • așteaptă finanțarea
  • implementează contractul counter

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(`Waiting for funding... current balance=${formatEther(bal)} ETH`);
await sleep(pollMs);
}
throw new Error("Timed out waiting for funding.");
}
async function main() {
const appId = await getAppId().catch(() => null);
console.log(`ROFL App ID: ${appId ?? "(unavailable outside ROFL)"}`);
const sk = await getEvmSecretKey(KEY_ID);
// NOTĂ: Acest demo are încredere în furnizorul RPC configurat. Pentru producție, preferă un
// light client (de exemplu, Helios) astfel încât să poți verifica starea lanțului la distanță.
const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));
const addr = checksumAddress(await wallet.getAddress());
console.log(`EVM address (Base Sepolia): ${addr}`);
const msg = "hello from rofl";
const sig = await signPersonalMessage(wallet, msg);
console.log(`Signed message: "${msg}"`);
console.log(`Signature: ${sig}`);
const provider = wallet.provider as JsonRpcProvider;
let bal = await provider.getBalance(addr);
if (bal === 0n) {
console.log("Please fund the above address with Base Sepolia ETH to continue.");
bal = await waitForFunding(provider, addr);
}
console.log(`Balance detected: ${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 missing abi/bytecode");
}
const { address: contractAddress, receipt: deployRcpt } =
await deployContract(wallet, artifact.abi, artifact.bytecode, []);
console.log(`Deployed Counter at ${contractAddress} (tx=${deployRcpt.hash})`);
console.log("Smoke test completed successfully!");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

5. contracts/Counter.sol — exemplu minimal

// 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 — deployer generic

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");
/**
* Utilizare:
* npm run deploy-contract -- ./artifacts/MyContract.json '[arg0, arg1]'
* Artefactul trebuie să conțină { abi, bytecode }.
*/
async function main() {
const [artifactPath, ctorJson = "[]"] = process.argv.slice(2);
if (!artifactPath) {
console.error("Usage: 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("Artifact must contain { abi, bytecode }");
}
let args: unknown[];
try {
args = JSON.parse(ctorJson);
if (!Array.isArray(args)) throw new Error("constructor args must be a JSON array");
} catch (e) {
throw new Error(`Failed to parse constructor args JSON: ${String(e)}`);
}
const sk = await getEvmSecretKey(KEY_ID);
// NOTĂ: Acest demo are încredere în furnizorul RPC configurat. Pentru producție, preferă un
// light client (de exemplu, Helios) astfel încât să poți verifica starea lanțului la distanță.
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 (doar contracte)

În această etapă, vom avea nevoie de configurație minimă pentru a compila Counter.sol

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;

Punctul de reținut este că compilarea locală este opțională, deci poți să o sari dacă vrei. Următorul pas este o alegere — fie ștergi fișierul existent contracts/Lock.sol, fie îl poți actualiza la Solidity versiunea 0.8.24.

npx hardhat compile

Containerizare

Acesta este un pas esențial. Aici, trebuie să creezi un Dockerfile care construiește TS și compilează contractul. Fișierul va rula și smoke test o dată, apoi va rămâne inactiv în timp ce inspectezi jurnalele.

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"]

Apoi, trebuie să montezi appd socket furnizat de ROFL. Fii sigur că niciun port public nu este expus în 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

Construire imagine

Este important să reții că ROFL rulează doar pe hardware cu Intel TDX activat. Deci, dacă compilezi imagini pe un host diferit, cum ar fi macOS, atunci trecerea parametrului — platform linux/amd64 este un pas suplimentar esențial.

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

Un punct interesant de reținut aici este că poți opta pentru securitate și verificabilitate suplimentară. Trebuie doar să fixezi digest-ul și să folosești image: …@sha256:… în compose.yaml.

Construire pachet ROFL

Există un pas pe care trebuie să îl faci înainte de a rula comanda oasis rofl build. Deoarece construirea segmentului de imagine vine după containerizare, va trebui să actualizezi services.demo.image în compose.yaml la imaginea pe care ai construit-o.
Pentru proiecte TypeScript simple, precum acesta, există uneori posibilitatea ca dimensiunea imaginii să fie mai mare decât se anticipează. Este astfel recomandabil să actualizezi secțiunea rofl.yaml resources la cel puțin: memory: 1024 și storage.size: 4096.
Acum, ești pregătit.

oasis rofl build

Poți apoi publica identitățile enclave și config.

oasis rofl update

Implementare

Acesta este un pas suficient de ușor unde implementezi la un furnizor Testnet.

oasis rofl deploy

End-to-end (Base Sepolia)

Acesta este un proces în 2 pași, deși al doilea pas este opțional.
Mai întâi, vizualizezi jurnalele smoke-test.

oasis rofl machine logs

Dacă ai finalizat corect toți pașii până acum, vei vedea în rezultat:

  • App ID
  • Adresa EVM și un mesaj semnat
  • O solicitare de a finanța adresa
  • Odată ce finanțarea este finalizată, o implementare Counter.sol

Apoi, dev local. Aici, trebuie să rulezi npm run build:all pentru a compila codul TypeScript și contractul Solidity. Sari acest pas dacă nu este necesar.

export ALLOW_LOCAL_DEV=true
export LOCAL_DEV_SK=0x<64-hex-dev-secret-key> # NU FOLOSI ÎN PROD
npm run smoke-test

Securitate & note de reținut

  • Jurnalele furnizorului nu sunt criptate în repaus. Deci, niciodată nu înregistra cheile secrete.
  • Socket-ul appd /run/rofl-appd.sock există doar în interiorul ROFL.
  • Pot exista limite de rată în RPC-urile publice. Deci, este recomandabil să optezi pentru un URL RPC Base dedicat.

Există o demo de generare de chei în GitHub-ul Oasis, pe care o poți consulta ca exemplu al acestui tutorial. https://github.com/oasisprotocol/demo-rofl-keygen

Acum că ai generat cu succes o cheie în ROFL cu appd, ai semnat mesaje, ai implementat un contract și ai transferat ETH pe Base Sepolia, anunță-ne în secțiunea de comentarii feedback-ul tău. Pentru o discuție rapidă cu echipa de inginerie Oasis pentru ajutor cu probleme specifice, poți lăsa comentariile tale în canalul dev-central din Discord oficial.

Publicat inițial la https://dev.to pe 20 februarie 2026.


Guide To Cross-Chain Key Generation (EVM / Base) With Oasis ROFL a fost publicat inițial în Coinmonks pe Medium, unde oamenii continuă conversația evidențiind și răspunzând la această poveste.

Oportunitate de piață
Logo CROSS
Pret CROSS (CROSS)
$0.1024
$0.1024$0.1024
+0.51%
USD
CROSS (CROSS) graficul prețurilor în timp real
Declinarea responsabilității: Articolele publicate pe această platformă provin de pe platforme publice și sunt furnizate doar în scop informativ. Acestea nu reflectă în mod necesar punctele de vedere ale MEXC. Toate drepturile rămân la autorii originali. Dacă consideri că orice conținut încalcă drepturile terților, contactează [email protected] pentru eliminare. MEXC nu oferă nicio garanție cu privire la acuratețea, exhaustivitatea sau actualitatea conținutului și nu răspunde pentru nicio acțiune întreprinsă pe baza informațiilor furnizate. Conținutul nu constituie consiliere financiară, juridică sau profesională și nici nu trebuie considerat o recomandare sau o aprobare din partea MEXC.