قدمت Oasis إطار عمل منطق خارج السلسلة في وقت التشغيل (ROFL) للمساعدة في بناء وتشغيل التطبيقات خارج السلسلة مع ضمان الخصوصية والحفاظ على الثقة مع السلسلةقدمت Oasis إطار عمل منطق خارج السلسلة في وقت التشغيل (ROFL) للمساعدة في بناء وتشغيل التطبيقات خارج السلسلة مع ضمان الخصوصية والحفاظ على الثقة مع السلسلة

دليل إنشاء المفاتيح عبر السلاسل (EVM / Base) باستخدام Oasis ROFL

2026/02/20 21:16
10 دقيقة قراءة
للحصول على ملاحظات أو استفسارات بشأن هذا المحتوى، يرجى التواصل معنا على [email protected]

قدمت Oasis إطار عمل لمنطق وقت التشغيل خارج السلسلة (ROFL) للمساعدة في بناء وتشغيل التطبيقات خارج السلسلة مع ضمان الخصوصية والحفاظ على الثقة من خلال إمكانية التحقق على السلسلة. هناك العديد من الأجزاء المتحركة للبناء باستخدام ROFL.
في هذا البرنامج التعليمي، سأوضح كيفية بناء تطبيق TypeScript صغير، يولد مفتاح secp256k1 داخل ROFL. سيستخدم @oasisprotocol/rofl-client TypeScript SDK، والذي يتواصل مع appd REST API تحت الغطاء. سيقوم تطبيق TypeScript أيضًا بـ:

سيكون هناك اختبار دخان بسيط يطبع في السجلات.

المتطلبات الأساسية

لإجراء الخطوات الموضحة في هذا الدليل، ستحتاج إلى:

  • Node.js 20+ و Docker (أو Podman)
  • Oasis CLI وحد أدنى 120 TEST tokens في محفظتك (صنبور Oasis Testnet)
  • بعض Base Sepiola test ETH (صنبور Base Sepiola)

للحصول على تفاصيل الإعداد، يرجى الرجوع إلى الوثائق الخاصة بالمتطلبات الأساسية للبدء السريع.

تهيئة التطبيق

الخطوة الأولى هي تهيئة تطبيق جديد باستخدام Oasis CLI.

oasis rofl init rofl-keygen
cd rofl-keygen

إنشاء التطبيق

في وقت إنشاء التطبيق على Testnet، سيُطلب منك إيداع الرموز. قم بتخصيص 100 TEST tokens في هذه المرحلة.

oasis rofl create --network testnet

كمخرج، ستنتج CLI معرف التطبيق، المشار إليه بـ 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).
*
* التطوير المحلي فقط (خارج 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 socket not found and no LOCAL_DEV_SK provided (dev only).'
);
}

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("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 — مساعدون صغار

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(`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);
// ملاحظة: هذا العرض التوضيحي يثق بمزود RPC المكوّن. للإنتاج، يفضل استخدام
// عميل خفيف (على سبيل المثال، Helios) حتى تتمكن من التحقق من حالة السلسلة البعيدة.
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 — عينة بسيطة

// 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("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);
// ملاحظة: هذا العرض التوضيحي يثق بمزود 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. كن مطمئنًا أنه لا يتم عرض أي منافذ عامة في العملية.

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 .

نقطة مثيرة للاهتمام يجب ملاحظتها هنا هي أنه يمكنك اختيار الأمان الإضافي وإمكانية التحقق. تحتاج فقط إلى تثبيت الملخص واستخدام image: …@sha256:… في compose.yaml.

بناء حزمة ROFL

هناك خطوة يجب عليك اتخاذها قبل تشغيل أمر oasis rofl build. نظرًا لأن بناء جزء الصورة يأتي بعد الحاوية، ستحتاج إلى تحديث services.demo.image في compose.yaml إلى الصورة التي قمت ببنائها.
بالنسبة لمشاريع TypeScript البسيطة، مثل هذا، هناك أحيانًا احتمال أن يكون حجم الصورة أكبر من المتوقع. لذلك يُنصح بتحديث قسم موارد rofl.yaml إلى الأقل: memory: 1024 و storage.size: 4096.
الآن، أنت جاهز.

oasis rofl build

يمكنك بعد ذلك نشر هويات الجيب المحمي والتكوين.

oasis rofl update

النشر

هذه خطوة سهلة بما يكفي حيث تنشر على مزود Testnet.

oasis rofl deploy

شامل (Base Sepolia)

هذه عملية من خطوتين، على الرغم من أن الخطوة الثانية اختيارية.
أولاً، تعرض سجلات اختبار الدخان.

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.
  • قد تكون هناك حدود معدل في RPCs العامة. لذا، يُنصح باختيار عنوان URL مخصص لـ Base RPC.

هناك عرض توضيحي لتوليد المفتاح في Oasis GitHub، والذي يمكنك الرجوع إليه كمثال على هذا البرنامج التعليمي. https://github.com/oasisprotocol/demo-rofl-keygen

الآن بعد أن قمت بنجاح بتوليد مفتاح في ROFL مع appd، ووقعت الرسائل، ونشرت عقدًا، ونقلت ETH على Base Sepolia، أخبرنا في قسم التعليقات بملاحظاتك. للدردشة السريعة مع فريق هندسة Oasis للحصول على مساعدة بشأن قضايا محددة، يمكنك ترك تعليقاتك في قناة dev-central في Discord الرسمي.

نُشر في الأصل على https://dev.to في 20 فبراير 2026.


تم نشر دليل توليد المفاتيح عبر السلاسل (EVM / Base) مع Oasis ROFL في الأصل في Coinmonks على Medium، حيث يواصل الناس المحادثة من خلال تسليط الضوء والرد على هذه القصة.

فرصة السوق
شعار CROSS
CROSS السعر(CROSS)
$0.10023
$0.10023$0.10023
-1.61%
USD
مخطط أسعار CROSS (CROSS) المباشر
إخلاء مسؤولية: المقالات المُعاد نشرها على هذا الموقع مستقاة من منصات عامة، وهي مُقدمة لأغراض إعلامية فقط. لا تُظهِر بالضرورة آراء MEXC. جميع الحقوق محفوظة لمؤلفيها الأصليين. إذا كنت تعتقد أن أي محتوى ينتهك حقوق جهات خارجية، يُرجى التواصل عبر البريد الإلكتروني [email protected] لإزالته. لا تقدم MEXC أي ضمانات بشأن دقة المحتوى أو اكتماله أو حداثته، وليست مسؤولة عن أي إجراءات تُتخذ بناءً على المعلومات المُقدمة. لا يُمثل المحتوى نصيحة مالية أو قانونية أو مهنية أخرى، ولا يُعتبر توصية أو تأييدًا من MEXC.