Oasis چارچوب منطق خارج از زنجیره زمان اجرا (ROFL) را معرفی کرد تا به ساخت و اجرای برنامه‌های خارج از زنجیره کمک کند و در عین حال حریم خصوصی را تضمین و اعتماد را با درون زنجیره حفظ کندOasis چارچوب منطق خارج از زنجیره زمان اجرا (ROFL) را معرفی کرد تا به ساخت و اجرای برنامه‌های خارج از زنجیره کمک کند و در عین حال حریم خصوصی را تضمین و اعتماد را با درون زنجیره حفظ کند

راهنمای تولید کلید میان زنجیره ای (EVM / Base) با Oasis ROFL

2026/02/20 21:16
مدت مطالعه: 11 دقیقه

Oasis چارچوب منطق آف‌چین زمان اجرا (ROFL) را معرفی کرد تا به ساخت و اجرای برنامه‌های آف‌چین کمک کند، در حالی که حریم خصوصی را تضمین و اعتماد را با قابلیت تأیید درون زنجیره‌ای حفظ می‌کند. بخش‌های زیادی برای ساخت با ROFL وجود دارد.
در این آموزش، نحوه ساخت یک برنامه کوچک TypeScript را نشان خواهم داد که یک کلید secp256k1 در داخل ROFL تولید می‌کند. این برنامه از @oasisprotocol/rofl-client TypeScript SDK استفاده خواهد کرد که در پشت صحنه با appd REST API ارتباط برقرار می‌کند. برنامه TypeScript همچنین:

یک تست دود ساده وجود خواهد داشت که در لاگ‌ها چاپ می‌شود.

پیش‌نیازها

برای انجام مراحل توضیح داده شده در این راهنما، به موارد زیر نیاز خواهید داشت:

  • Node.js 20+ و Docker (یا Podman)
  • Oasis CLI و حداقل 120 توکن TEST در کیف پول شما (فاست Oasis Testnet)
  • مقداری Base Sepiola test ETH (فاست Base Sepiola)

برای جزئیات راه‌اندازی، لطفاً به مستندات پیش‌نیازهای شروع سریع مراجعه کنید.

راه‌اندازی اولیه برنامه

اولین قدم، راه‌اندازی یک برنامه جدید با استفاده از Oasis CLI است.

oasis rofl init rofl-keygen
cd rofl-keygen

ایجاد برنامه

در زمان ایجاد برنامه در Testnet، باید توکن‌ها را واریز کنید. در این مرحله 100 توکن TEST اختصاص دهید.

oasis rofl create --network testnet

به عنوان خروجی، CLI شناسه برنامه (App ID) را تولید می‌کند که با rofl1… نشان داده می‌شود.

راه‌اندازی اولیه یک پروژه Hardhat (TypeScript)

اکنون، آماده هستید تا پروژه را شروع کنید.

npx hardhat init

از آنجایی که ما یک برنامه TypeScript را به نمایش می‌گذاریم، هنگام درخواست TypeScript را انتخاب کنید و سپس پیش‌فرض‌ها را بپذیرید.
مرحله بعدی افزودن وابستگی‌های کوچک زمان اجرا برای استفاده خارج از Hardhat خواهد بود.

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

الگوی TypeScript Hardhat به طور خودکار یک tsconfig.json ایجاد می‌کند. باید یک اسکریپت کوچک اضافه کنیم تا کد برنامه بتواند در dist/ کامپایل شود.

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

ساختار برنامه

در این بخش، چند فایل کوچک TS و یک قرارداد Solidity اضافه خواهیم کرد.

src/
├── appd.ts # پوشش نازک روی @oasisprotocol/rofl-client
├── evm.ts # کمک‌کننده‌های ethers (ارائه‌دهنده، کیف پول، تراکنش، استقرار)
├── keys.ts # کمک‌کننده‌های کوچک (چک‌سام)
└── scripts/
├── deploy-contract.ts # اسکریپت استقرار عمومی برای آرتیفکت‌های کامپایل شده
└── smoke-test.ts # دموی سرتاسر (لاگ‌ها)
contracts/
└── Counter.sol # قرارداد نمونه

  1. src/appd.ts — پوشش نازک روی SDK در اینجا، باید از کلاینت رسمی برای ارتباط با appd (سوکت UNIX) استفاده کنید. همچنین باید یک بازگشت محلی‑توسعه صریح را هنگام اجرا خارج از 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();
}
/**
* یک کلید secp256k1 را در داخل ROFL تولید (یا به صورت قطعی مجدداً مشتق) می‌کند و
* آن را به عنوان یک رشته هگز با پیشوند 0x (برای ethers.js Wallet) برمی‌گرداند.
*
* فقط توسعه محلی (خارج از ROFL): اگر سوکت وجود نداشته باشد و شما
* ALLOW_LOCAL_DEV=true و LOCAL_DEV_SK=0x<64-hex> را تنظیم کنید، از آن مقدار استفاده می‌شود.
*/
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 یافت نشد و LOCAL_DEV_SK ارائه نشده است (فقط توسعه).'
);
}

2. src/evm.ts — کمک‌کننده‌های 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("تراکنش قبل از تأیید رها یا جایگزین شد");
}
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("تراکنش استقرار استخراج نشد");
}
return { address: contract.target as string, receipt };
}

3. src/keys.ts — کمک‌کننده‌های کوچک

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 — جریان سرتاسر واحد

این یک مرحله مهم است زیرا این اسکریپت چندین عملکرد دارد:

  • چاپ شناسه برنامه (در داخل ROFL)، آدرس و یک پیام امضا شده
  • انتظار برای تأمین مالی
  • استقرار قرارداد شمارنده

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(`در انتظار تأمین مالی... موجودی فعلی=${formatEther(bal)} ETH`);
await sleep(pollMs);
}
throw new Error("زمان انتظار برای تأمین مالی به پایان رسید.");
}
async function main() {
const appId = await getAppId().catch(() => null);
console.log(`شناسه برنامه ROFL: ${appId ?? "(در دسترس نیست خارج از ROFL)"}`);
const sk = await getEvmSecretKey(KEY_ID);
// توجه: این دمو به ارائه‌دهنده RPC پیکربندی شده اعتماد می‌کند. برای تولید، ترجیحاً از یک
// کلاینت سبک (به عنوان مثال، Helios) استفاده کنید تا بتوانید وضعیت زنجیره از راه دور را تأیید کنید.
const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));
const addr = checksumAddress(await wallet.getAddress());
console.log(`آدرس EVM (Base Sepolia): ${addr}`);
const msg = "سلام از rofl";
const sig = await signPersonalMessage(wallet, msg);
console.log(`پیام امضا شده: "${msg}"`);
console.log(`امضا: ${sig}`);
const provider = wallet.provider as JsonRpcProvider;
let bal = await provider.getBalance(addr);
if (bal === 0n) {
console.log("لطفاً آدرس فوق را با Base Sepolia ETH برای ادامه تأمین مالی کنید.");
bal = await waitForFunding(provider, addr);
}
console.log(`موجودی شناسایی شد: ${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 فاقد abi/bytecode است");
}
const { address: contractAddress, receipt: deployRcpt } =
await deployContract(wallet, artifact.abi, artifact.bytecode, []);
console.log(`Counter در ${contractAddress} مستقر شد (tx=${deployRcpt.hash})`);
console.log("تست دود با موفقیت تکمیل شد!");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

5. contracts/Counter.sol — نمونه حداقلی

// 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 — مستقرکننده عمومی

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");
/**
* استفاده:
* npm run deploy-contract -- ./artifacts/MyContract.json '[arg0, arg1]'
* آرتیفکت باید شامل { abi, bytecode } باشد.
*/
async function main() {
const [artifactPath, ctorJson = "[]"] = process.argv.slice(2);
if (!artifactPath) {
console.error("استفاده: 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("آرتیفکت باید شامل { abi, bytecode } باشد");
}
let args: unknown[];
try {
args = JSON.parse(ctorJson);
if (!Array.isArray(args)) throw new Error("آرگومان‌های سازنده باید یک آرایه JSON باشند");
} catch (e) {
throw new Error(`تجزیه JSON آرگومان‌های سازنده ناموفق بود: ${String(e)}`);
}
const sk = await getEvmSecretKey(KEY_ID);
// توجه: این دمو به ارائه‌دهنده RPC پیکربندی شده اعتماد می‌کند. برای تولید، ترجیحاً از یک
// کلاینت سبک (به عنوان مثال، Helios) استفاده کنید تا بتوانید وضعیت زنجیره از راه دور را تأیید کنید.
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 (فقط قراردادها)

در این مرحله، به پیکربندی حداقلی برای کامپایل 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;

نکته قابل توجه این است که کامپایل محلی اختیاری است، بنابراین اگر می‌خواهید می‌توانید آن را رد کنید. مرحله بعدی یک انتخاب است — یا فایل موجود contracts/Lock.sol را حذف کنید یا می‌توانید آن را به Solidity نسخه 0.8.24 به‌روزرسانی کنید.

npx hardhat compile

کانتینری‌سازی

این یک مرحله ضروری است. در اینجا، به یک Dockerfile نیاز دارید که TS را بسازد و قرارداد را کامپایل کند. این فایل همچنین تست دود را یک بار اجرا می‌کند و سپس در حالت آماده‌به‌کار می‌ماند تا شما لاگ‌ها را بررسی کنید.

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

در مرحله بعد، باید سوکت appd ارائه شده توسط ROFL را mount کنید. مطمئن باشید که هیچ پورت عمومی در این فرآیند فاش نمی‌شود.

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

ساخت تصویر

مهم است که به خاطر بسپارید ROFL فقط بر روی سخت‌افزار فعال Intel TDX اجرا می‌شود. بنابراین، اگر در حال کامپایل تصاویر در یک هاست متفاوت، مانند macOS هستید، پس عبور پارامتر --platform linux/amd64 یک مرحله اضافی ضروری است.

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

یک نکته جالب برای توجه در اینجا این است که می‌توانید برای امنیت و قابلیت تأیید اضافی انتخاب کنید. فقط باید digest را پین کنید و از image: …@sha256:… در compose.yaml استفاده کنید.

ساخت بسته ROFL

مرحله‌ای وجود دارد که باید قبل از اجرای دستور oasis rofl build انجام دهید. از آنجایی که ساخت بخش تصویر پس از کانتینری‌سازی انجام می‌شود، باید services.demo.image در compose.yaml را به تصویری که ساختید به‌روزرسانی کنید.
برای پروژه‌های ساده TypeScript، مانند این پروژه، گاهی اوقات احتمال دارد که اندازه تصویر بزرگ‌تر از حد انتظار باشد. بنابراین توصیه می‌شود بخش منابع rofl.yaml را حداقل به: memory: 1024 و storage.size: 4096 به‌روزرسانی کنید.
اکنون، آماده هستید.

oasis rofl build

سپس می‌توانید هویت‌های enclave و پیکربندی را منتشر کنید.

oasis rofl update

استقرار

این یک مرحله آسان است که در آن شما را به یک ارائه‌دهنده Testnet مستقر می‌کنید.

oasis rofl deploy

سرتاسر (Base Sepolia)

این یک فرآیند 2 مرحله‌ای است، اگرچه مرحله دوم اختیاری است.
ابتدا، لاگ‌های تست دود را مشاهده می‌کنید.

oasis rofl machine logs

اگر تمام مراحل را تا به حال به درستی تکمیل کرده باشید، در خروجی خواهید دید:

  • شناسه برنامه
  • آدرس EVM و یک پیام امضا شده
  • یک درخواست برای تأمین مالی آدرس
  • پس از انجام تأمین مالی، یک استقرار Counter.sol

بعد، توسعه محلی. در اینجا، باید npm run build:all را اجرا کنید تا کد TypeScript و قرارداد Solidity را کامپایل کنید. اگر نیازی نیست این مرحله را رد کنید.

export ALLOW_LOCAL_DEV=true
export LOCAL_DEV_SK=0x<64-hex-dev-secret-key> # در تولید استفاده نکنید
npm run smoke-test

امنیت و نکات قابل یادآوری

  • لاگ‌های ارائه‌دهنده در حالت استراحت رمزگذاری نمی‌شوند. بنابراین، هرگز کلیدهای مخفی را لاگ نکنید.
  • سوکت appd /run/rofl-appd.sock فقط در داخل ROFL وجود دارد.
  • ممکن است محدودیت‌های نرخ در RPCهای عمومی وجود داشته باشد. بنابراین، توصیه می‌شود برای یک آدرس RPC اختصاصی Base انتخاب کنید.

یک دموی تولید کلید در GitHub Oasis وجود دارد که می‌توانید به عنوان نمونه‌ای از این آموزش به آن مراجعه کنید. https://github.com/oasisprotocol/demo-rofl-keygen

اکنون که با موفقیت یک کلید در ROFL با appd تولید کرده‌اید، پیام‌ها را امضا کرده‌اید، یک قرارداد را مستقر کرده‌اید و ETH را در Base Sepolia منتقل کرده‌اید، بازخورد خود را در بخش نظرات به ما اطلاع دهید. برای گفتگوی سریع با تیم مهندسی Oasis برای کمک در مورد مسائل خاص، می‌توانید نظرات خود را در کانال dev-central در Discord رسمی بگذارید.

در اصل در https://dev.to در 1404/12/01 منتشر شده است.


راهنمای تولید کلید میان زنجیره‌ای (EVM / Base) با Oasis ROFL در ابتدا در Coinmonks در Medium منتشر شد، جایی که مردم با برجسته کردن و پاسخ دادن به این داستان به گفتگو ادامه می‌دهند.

فرصت‌ های بازار
لوگو CROSS
CROSS قیمت لحظه ای(CROSS)
$0.10843
$0.10843$0.10843
+0.24%
USD
نمودار قیمت لحظه ای CROSS (CROSS)
سلب مسئولیت: مطالب بازنشرشده در این وب‌ سایت از منابع عمومی گردآوری شده‌ اند و صرفاً به‌ منظور اطلاع‌ رسانی ارائه می‌ شوند. این مطالب لزوماً بازتاب‌ دهنده دیدگاه‌ ها یا مواضع MEXC نیستند. کلیه حقوق مادی و معنوی آثار متعلق به نویسندگان اصلی است. در صورت مشاهده هرگونه محتوای ناقض حقوق اشخاص ثالث، لطفاً از طریق آدرس ایمیل service@support.mexc.com با ما تماس بگیرید تا مورد بررسی و حذف قرار گیرد.MEXC هیچ‌ گونه تضمینی نسبت به دقت، جامعیت یا به‌ روزبودن اطلاعات ارائه‌ شده ندارد و مسئولیتی در قبال هرگونه اقدام یا تصمیم‌ گیری مبتنی بر این اطلاعات نمی‌ پذیرد. همچنین، محتوای منتشرشده نباید به‌عنوان توصیه مالی، حقوقی یا حرفه‌ ای تلقی شود و به منزله پیشنهاد یا تأیید رسمی از سوی MEXC نیست.