Oasis memperkenalkan rangka kerja untuk logik luar rantai masa jalan (ROFL) bagi membantu membina dan menjalankan aplikasi luar rantai sambil memastikan privasi dan mengekalkan kepercayaan dengan dalam rantaiOasis memperkenalkan rangka kerja untuk logik luar rantai masa jalan (ROFL) bagi membantu membina dan menjalankan aplikasi luar rantai sambil memastikan privasi dan mengekalkan kepercayaan dengan dalam rantai

跨链密钥生成指南(EVM / Base)使用 Oasis ROFL

2026/02/20 21:16
阅读时长 10 分钟

Oasis memperkenalkan rangka kerja untuk logik luar rantaian runtime (ROFL) untuk membantu membina dan menjalankan aplikasi luar rantaian sambil memastikan privasi dan mengekalkan kepercayaan dengan kebolehsahkan dalam rantaian. Terdapat banyak bahagian bergerak untuk membina dengan ROFL.
Dalam tutorial ini, saya akan menunjukkan cara membina aplikasi TypeScript kecil, menjana kunci secp256k1 di dalam ROFL. Ia akan menggunakan @oasisprotocol/rofl-client TypeScript SDK, yang berkomunikasi dengan appd REST API di belakang tabir. Aplikasi TypeScript juga akan:

Akan ada ujian asap mudah yang mencetak ke log.

Prasyarat

Untuk melakukan langkah-langkah yang diterangkan dalam panduan ini, anda memerlukan:

  • Node.js 20+ dan Docker (atau Podman)
  • Oasis CLI dan sekurang-kurangnya 120 token TEST dalam dompet anda (faucet Oasis Testnet)
  • Beberapa ETH ujian Base Sepiola (faucet Base Sepiola)

Untuk butiran persediaan, sila rujuk dokumentasi mengenai Prasyarat Mulakan Pantas.

Mulakan Aplikasi

Langkah pertama adalah memulakan aplikasi baharu menggunakan Oasis CLI.

oasis rofl init rofl-keygen
cd rofl-keygen

Cipta Aplikasi

Pada masa mencipta aplikasi di Testnet, anda akan dikehendaki mendepositkan token. Peruntukkan 100 token TEST pada ketika ini.

oasis rofl create --network testnet

Sebagai output, CLI akan menghasilkan App ID, ditandakan dengan rofl1….

Mulakan projek Hardhat (TypeScript)

Sekarang, anda sudah bersedia untuk memulakan projek.

npx hardhat init

Memandangkan kami mempamerkan aplikasi TypeScript, pilih TypeScript apabila diminta, kemudian terima tetapan lalai.
Langkah seterusnya adalah menambah deps runtime kecil untuk kegunaan di luar Hardhat.

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

Templat TypeScript Hardhat secara automatik mencipta tsconfig.json. Kita perlu menambah skrip kecil supaya kod aplikasi boleh dikompilasi ke dist/.

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

Struktur Aplikasi

Dalam bahagian ini, kami akan menambah beberapa fail TS kecil dan satu kontrak Solidity.

src/
├── appd.ts # pembungkus nipis atas @oasisprotocol/rofl-client
├── evm.ts # pembantu ethers (pembekal, dompet, tx, deploy)
├── keys.ts # pembantu kecil (checksum)
└── scripts/
├── deploy-contract.ts # skrip deploy generik untuk artifak yang dikompilasi
└── smoke-test.ts # demo hujung-ke-hujung (log)
contracts/
└── Counter.sol # kontrak sampel

  1. src/appd.ts — pembungkus nipis atas SDK Di sini, anda perlu menggunakan klien rasmi untuk berkomunikasi dengan appd (soket UNIX). Kami juga perlu menyimpan sandaran dev-tempatan eksplisit apabila berjalan di luar 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();
}
/**
* Menjana (atau menerbitkan semula secara deterministik) kunci secp256k1 di dalam ROFL dan
* mengembalikannya sebagai rentetan hex berprefiks 0x (untuk Wallet ethers.js).
*
* Pembangunan tempatan SAHAJA (di luar ROFL): Jika soket hilang dan anda menetapkan
* ALLOW_LOCAL_DEV=true dan LOCAL_DEV_SK=0x<64-hex>, nilai tersebut digunakan.
*/
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(
'soket rofl-appd tidak ditemui dan tiada LOCAL_DEV_SK disediakan (dev sahaja).'
);
}

2. src/evm.ts — pembantu 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("Transaksi digugurkan atau digantikan sebelum pengesahan");
}
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("TX deployment tidak dilombong");
}
return { address: contract.target as string, receipt };
}

3. src/keys.ts — pembantu kecil

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 — aliran hujung-ke-hujung tunggal

Ini adalah langkah penting kerana skrip ini mempunyai berbilang fungsi:

  • cetak App ID (di dalam ROFL), alamat, dan mesej yang ditandatangani
  • tunggu pembiayaan
  • deploy kontrak 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(`Menunggu pembiayaan... baki semasa=${formatEther(bal)} ETH`);
await sleep(pollMs);
}
throw new Error("Tamat masa menunggu pembiayaan.");
}
async function main() {
const appId = await getAppId().catch(() => null);
console.log(`ROFL App ID: ${appId ?? "(tidak tersedia di luar ROFL)"}`);
const sk = await getEvmSecretKey(KEY_ID);
// NOTA: Demo ini mempercayai pembekal RPC yang dikonfigurasikan. Untuk pengeluaran, pilih
// klien ringan (contohnya, Helios) supaya anda boleh mengesahkan keadaan rantaian jauh.
const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));
const addr = checksumAddress(await wallet.getAddress());
console.log(`Alamat EVM (Base Sepolia): ${addr}`);
const msg = "hello from rofl";
const sig = await signPersonalMessage(wallet, msg);
console.log(`Mesej ditandatangani: "${msg}"`);
console.log(`Tandatangan: ${sig}`);
const provider = wallet.provider as JsonRpcProvider;
let bal = await provider.getBalance(addr);
if (bal === 0n) {
console.log("Sila biayai alamat di atas dengan Base Sepolia ETH untuk meneruskan.");
bal = await waitForFunding(provider, addr);
}
console.log(`Baki dikesan: ${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("Artifak counter kehilangan abi/bytecode");
}
const { address: contractAddress, receipt: deployRcpt } =
await deployContract(wallet, artifact.abi, artifact.bytecode, []);
console.log(`Deployed Counter di ${contractAddress} (tx=${deployRcpt.hash})`);
console.log("Ujian asap berjaya diselesaikan!");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

5. contracts/Counter.sol — sampel minimum

// 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 generik

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");
/**
* Penggunaan:
* npm run deploy-contract -- ./artifacts/MyContract.json '[arg0, arg1]'
* Artifak mesti mengandungi { abi, bytecode }.
*/
async function main() {
const [artifactPath, ctorJson = "[]"] = process.argv.slice(2);
if (!artifactPath) {
console.error("Penggunaan: 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("Artifak mesti mengandungi { abi, bytecode }");
}
let args: unknown[];
try {
args = JSON.parse(ctorJson);
if (!Array.isArray(args)) throw new Error("args constructor mesti array JSON");
} catch (e) {
throw new Error(`Gagal menghuraikan args constructor JSON: ${String(e)}`);
}
const sk = await getEvmSecretKey(KEY_ID);
// NOTA: Demo ini mempercayai pembekal RPC yang dikonfigurasikan. Untuk pengeluaran, pilih
// klien ringan (contohnya, Helios) supaya anda boleh mengesahkan keadaan rantaian jauh.
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 (kontrak sahaja)

Pada peringkat ini, kita memerlukan konfigurasi minimum untuk mengkompil 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;

Perkara yang perlu diingat ialah kompilasi tempatan adalah pilihan, jadi anda boleh melangkaunya jika mahu. Langkah seterusnya adalah pilihan — sama ada padam fail contracts/Lock.sol sedia ada atau anda boleh mengemas kininya kepada Solidity versi 0.8.24.

npx hardhat compile

Kontainerisasi

Ini adalah langkah penting. Di sini, anda memerlukan Dockerfile yang membina TS dan mengkompil kontrak. Fail tersebut juga akan menjalankan ujian asap sekali, kemudian berdiri diam semasa anda memeriksa log.

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

Seterusnya, anda mesti melekap soket appd yang disediakan oleh ROFL. Tenang kerana tiada port awam didedahkan dalam proses ini.

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

Bina imej

Adalah penting untuk diingat bahawa ROFL hanya berjalan pada perkakasan yang didayakan Intel TDX. Jadi, jika anda mengkompil imej pada hos yang berbeza, seperti macOS, maka menghantar parameter — platform linux/amd64 adalah langkah tambahan yang penting.

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

Perkara menarik yang perlu diperhatikan di sini ialah anda boleh memilih keselamatan tambahan dan kebolehsahkan. Anda hanya perlu menyematkan digest dan menggunakan image: …@sha256:… dalam compose.yaml.

Bina bundle ROFL

Terdapat langkah yang mesti anda ambil sebelum menjalankan arahan oasis rofl build. Memandangkan membina segmen imej datang selepas kontainerisasi, anda perlu mengemas kini services.demo.image dalam compose.yaml kepada imej yang anda bina.
Untuk projek TypeScript mudah, seperti yang ini, kadang-kadang terdapat kemungkinan bahawa saiz imej lebih besar daripada yang dijangkakan. Oleh itu, adalah dinasihatkan untuk mengemas kini bahagian rofl.yaml resources kepada sekurang-kurangnya: memory: 1024 dan storage.size: 4096.
Sekarang, anda sudah bersedia.

oasis rofl build

Anda boleh seterusnya menerbitkan identiti enclave dan konfigurasi.

oasis rofl update

Deploy

Ini adalah langkah yang cukup mudah di mana anda men-deploy ke pembekal Testnet.

oasis rofl deploy

Hujung-ke-hujung (Base Sepolia)

Ini adalah proses 2 langkah, walaupun langkah kedua adalah pilihan.
Pertama, anda melihat log ujian-asap.

oasis rofl machine logs

Jika anda telah menyelesaikan semua langkah sehingga sekarang dengan betul, anda akan melihat dalam output:

  • App ID
  • Alamat EVM dan mesej yang ditandatangani
  • Gesaan untuk membiayai alamat
  • Setelah pembiayaan selesai, deployment Counter.sol

Seterusnya, dev tempatan. Di sini, anda perlu menjalankan npm run build:all untuk mengkompil kod TypeScript dan kontrak Solidity. Langkau langkah ini jika tidak diperlukan.

export ALLOW_LOCAL_DEV=true
export LOCAL_DEV_SK=0x<64-hex-dev-secret-key> # JANGAN GUNAKAN DALAM PROD
npm run smoke-test

Keselamatan & nota untuk diingat

  • Log pembekal tidak disulitkan semasa rehat. Jadi, jangan sekali-kali log kunci rahsia.
  • Soket appd /run/rofl-appd.sock wujud hanya di dalam ROFL.
  • Mungkin terdapat had kadar dalam RPC awam. Jadi, adalah dinasihatkan untuk memilih URL RPC Base khusus.

Terdapat demo penjanaan kunci dalam GitHub Oasis, yang boleh anda rujuk sebagai contoh tutorial ini. https://github.com/oasisprotocol/demo-rofl-keygen

Sekarang setelah anda berjaya menjana kunci dalam ROFL dengan appd, menandatangani mesej, men-deploy kontrak, dan memindahkan ETH pada Base Sepolia, beritahu kami di bahagian komen maklum balas anda. Untuk berbual pantas dengan pasukan kejuruteraan Oasis untuk bantuan mengenai isu tertentu, anda boleh mengemukakan komen anda di saluran dev-central dalam Discord rasmi.

Asalnya diterbitkan di https://dev.to pada 20 Februari 2026.


Panduan Penjanaan Kunci Merentas Rantaian (EVM / Base) Dengan Oasis ROFL pada asalnya diterbitkan dalam Coinmonks di Medium, di mana orang ramai meneruskan perbualan dengan menyerlahkan dan membalas cerita ini.

市场机遇
CROSS 图标
CROSS实时价格 (CROSS)
$0.10849
$0.10849$0.10849
+0.29%
USD
CROSS (CROSS) 实时价格图表
免责声明: 本网站转载的文章均来源于公开平台,仅供参考。这些文章不代表 MEXC 的观点或意见。所有版权归原作者所有。如果您认为任何转载文章侵犯了第三方权利,请联系 [email protected] 以便将其删除。MEXC 不对转载文章的及时性、准确性或完整性作出任何陈述或保证,并且不对基于此类内容所采取的任何行动或决定承担责任。转载材料仅供参考,不构成任何商业、金融、法律和/或税务决策的建议、认可或依据。