Oasis esitles raamistikku runtime off-chain logika (ROFL) jaoks, mis aitab rakendusi ehitada ja käitada off-chain’is, tagades samal ajal privaatsuse ning säilitades usaldusväärsuse on-chain verifitseeritavusega. ROFL-i kasutamine nõuab mitmeid erinevaid komponente ja protsesse.
Selles õpetuses näitan ma, kuidas luua väike TypeScript-rakendus, milles genereeritakse secp256k1 võti ROFL-i sees. Rakendus kasutab @oasisprotocol/rofl-client TypeScript SDK-d, mis suhtleb varjatud taustal appd REST API-ga. Lisaks sellele toimib TypeScript-rakendus järgmiselt:
Rakendus sisaldab lihtsat suitsukatset, mille tulemused salvestatakse logidesse.
Selleks, et saaksid seda juhendit järgida, vajad:
Lisainformatsiooni saad Quickstart Prerequisites dokumentatsioonist.
Esimene samm on uue rakenduse algustamine Oasis CLI abil.
oasis rofl init rofl-keygen
cd rofl-keygen
Testnet’il rakenduse loomise käigus tuleb depositeerida tokenid. Sel hetkel määrake 100 TEST-tokenit.
oasis rofl create --network testnet
Kui kõik läheb hästi, genereerib CLI väljundina Rakenduse ID, milleks on näiteks rofl1….
Nüüd on aeg projekti käivitada.
npx hardhat init
Kuna me demonstreerime TypeScript-rakendust, valige küsitluse käigus TypeScript ja vastake vaikimisi. Järgmiseks lisame väikesed runtime-sõltuvused, mida saab kasutada ka väljaspool Hardhat’i.
npm i @oasisprotocol/rofl-client ethers dotenv @types/node
npm i -D tsx
Hardhat’i TypeScript-mall loob automaatselt tsconfig.json. Lisame aga väikese skripti, et rakenduskood saaks kompileeritud dist/-kausta.
// tsconfig.json
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
Selles osas lisame paar väikest TS-faili ja ühe Solidity-lepingu.
src/
├── appd.ts # õhuke wrapper @oasisprotocol/rofl-client ümber
├── evm.ts # ethers abistajad (provider, wallet, tx, deploy)
├── keys.ts # pisikesed abistajad (checksum)
└── scripts/
├── deploy-contract.ts # generiline deploy-skript kompileeritud artefaktide jaoks
└── smoke-test.ts # lõpuni viidud demo (logid)
contracts/
└── Counter.sol # näidisleping
Siin tuleb kasutada ametlikku klienti, et suhelda appd-ga (UNIX socket). Samuti peame hoidma ekspliitselt local-dev fallback’i, kui rakendust käivitame väljaspool ROFL-i.
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();
}
/**
* Genereerib (või deterministlikult uuesti tuletatud) secp256k1 võtme ROFL-i sees ja
* tagastab selle 0x-prefiksiga heksakujulise stringina (ethers.js Wallet jaoks).
*
* Kohalik arendus AINULT (ROFL-i väljas): Kui soket puudub ja olete seadistanud
* ALLOW_LOCAL_DEV=true ning LOCAL_DEV_SK=0x<64-hex>, kasutatakse seda väärtust.
*/
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 soketit ei leitud ja LOCAL_DEV_SK ei ole antud (ainult arendus).'
);
}
import { JsonRpcProvider, Wallet, parseEther, TransactionReceipt, ContractFactory } from 'ethers';
export function makeProvider(rpcUrl: string, chainId: number): JsonRpcProvider {
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): Promise<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("Transaktsioon kukkus ära või asendati enne kinnitust.");
}
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("Deploying TX ei ole minetud.");
}
return { address: contract.target as string, receipt };
}
import { Wallet, getAddress } from 'ethers';
export function secretKeyToWallet(skHex: string): Wallet {
return new Wallet(skHex);
}
export function checksumAddress(addr: string): string {
return getAddress(addr);
}
See on oluline samm, kuna see skript täidab mitmeid funktsioone:
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(`Ootame rahastamist... praegune saldo=${formatEther(bal)} ETH`);
await sleep(pollMs);
}
throw new Error("Aegumine rahastamise ootamisel.");
}
async function main() {
const appId = await getAppId().catch(() => null);
console.log(`ROFL Rakenduse ID: ${appId ?? "(ROFL-i väljas ei saada)"}`);
const sk = await getEvmSecretKey(KEY_ID);
const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));
const addr = checksumAddress(await wallet.getAddress());
console.log(`EVM-aadress (Base Sepolia): ${addr}`);
const msg = "hello from rofl";
const sig = await signPersonalMessage(wallet, msg);
console.log(`Allkirjastatud sõnum: "${msg}"`);
console.log(`Allkiri: ${sig}`);
const provider = wallet.provider as JsonRpcProvider;
let bal = await provider.getBalance(addr);
if (bal === 0n) {
console.log("Palun rahastage ülaltoodud aadress Base Sepolia ETH-ga, et jätkata.");
bal = await waitForFunding(provider, addr);
}
console.log(`Saldo avastatud: ${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 artefaktis puudub ABI/võtke.");
}
const { address: contractAddress, receipt: deployRcpt } = await deployContract(wallet, artifact.abi, artifact.bytecode, []);
console.log(`Counter on jaotatud aadressile ${contractAddress} (tx=${deployRcpt.hash})`);
console.log("Suitsukatse edukalt lõppenud!");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
// 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);
}
}
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");
/**
* Kasutus:
* npm run deploy-contract -- ./artifacts/MyContract.json '[arg0, arg1]'
* Artefakt peab sisaldama { abi, bytecode }.
*/
async function main() {
const [artifactPath, ctorJson = "[]"] = process.argv.slice(2);
if (!artifactPath) {
console.error("Kasutus: 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("Artefakt peab sisaldama { abi, bytecode }.");
}
let args: unknown[];
try {
args = JSON.parse(ctorJson);
if (!Array.isArray(args)) throw new Error("Konstruktori argumendid peavad olema JSON-array");
} catch (e) {
throw new Error(`Konstruktori argumendid ei õnnestunud JSON-iks analüüsida: ${String(e)}`);
}
const sk = await getEvmSecretKey(KEY_ID);
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);
});
Selles faasis vajame minimaalset konfiguratsiooni, et kompileerida 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;
Märkusena: kohalik kompileerimine on valikuline, nii et soovi korral võite selle vahele jätta. Järgmine samm on teie valik – kas kustutate olemasoleva contracts/Lock.sol faili või uuendate seda Solidity versiooniga 0.8.24.
npx hardhat compile
See on ülioluline samm. Siin tuleb luua Dockerfile, mis kompileerib TS-koodi ja kompileerib lepingu. Fail käivitab ka suitsukatse üks kord ja püsib siis tuimalt, kuni kontrollite logisid.
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"]
Järgmisena tuleb monteerida ROFL-i poolt pakutud appd soket. Olge kindlad, et protsessi käigus ei avaldata ühtegi avalikku porti.
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
Oluline on meeles pidada, et ROFL töötab ainult Intel TDX-toega riistvaral. Seega, kui kompileerite pilte teises hostis, näiteks macOS-is, siis on parameetri —platform linux/amd64 lisamine ülioluline samm.
docker buildx build --platform linux/amd64 \
-t docker.io/YOURUSER/rofl-keygen:0.1.0 --push .
Huvitav on see, et saate valida täiendava turvalisuse ja verifitseeritavuse. Lihtsalt kinnitage diges ja kasutage image: … @sha256: … koos compose.yaml-ga.
Enne oasis rofl build käskluse käivitamist tuleb teha üks samm. Kuna pildi ehitamine toimub konteineriseerimise järel, tuleb compose.yaml-s services.demo.image värskendada teie poolt loodud pildiga.
Lihtsate TypeScript-projektide puhul, nagu see, võib pildi suurus olla oodatust suurem. Seega on soovitatav värskendada rofl.yaml resources sektsiooni vähemalt: memory: 1024 ja storage.size: 4096.
Nüüd olete valmis!
oasis rofl build
Järgmisena saate publitseerida enklave identiteete ja konfiguratsioone.
oasis rofl update
See on üsna lihtne samm, kus juurdepanek tehakse Testnet-provider’ile.
oasis rofl deploy
See on 2‑sammuline protsess, kuigi teine samm on valikuline.
Esmalt vaadake suitsukatse logisid.
oasis rofl machine logs
Kui olete kõik sammud õigesti läbi viinud, näete väljundis:
Järgmisena kohalik arendus. Siin tuleb käivitada npm run build:all, et kompileerida TypeScript-kood ja Solidity-leping. Vajadusel võite selle sammu vahele jätta.
export ALLOW_LOCAL_DEV=true
export LOCAL_DEV_SK=0x<64-hex-dev-secret-key> # ÄRA KASUTA TOOTMISEKS
npm run smoke-test
Oasis GitHub-is on võtme genereerimise demo, mida saate kasutada selle õpetuse näiteks. https://github.com/oasisprotocol/demo-rofl-keygen
Nüüd, kui olete edukalt genereerinud võtme ROFL-i sees appd abil, allkirjastanud sõnumeid, jaotanud lepingu ja liigutanud ETH Base Sepoliast, andke meile kommentaarides oma tagasiside. Kiireks vestluseks Oasis’i insenerimeeskonnaga konkreetsete probleemide lahendamiseks võite oma kommentaarid postitada ametlikus Discordi dev-central kanalis.
Algselt avaldatud 20. veebruaril 2026. aastal



Kopi link X (Twitter) LinkedIn Facebook E-post
Pantera juhib 11,5 miljoni dollari suurust ringi Basedis, mis on Hyper…