Oasis چارچوب منطق آفچین زمان اجرا (ROFL) را معرفی کرد تا به ساخت و اجرای برنامههای آفچین کمک کند، در حالی که حریم خصوصی را تضمین و اعتماد را با قابلیت تأیید درون زنجیرهای حفظ میکند. بخشهای زیادی برای ساخت با ROFL وجود دارد.
در این آموزش، نحوه ساخت یک برنامه کوچک TypeScript را نشان خواهم داد که یک کلید secp256k1 در داخل ROFL تولید میکند. این برنامه از @oasisprotocol/rofl-client TypeScript SDK استفاده خواهد کرد که در پشت صحنه با appd REST API ارتباط برقرار میکند. برنامه TypeScript همچنین:
یک تست دود ساده وجود خواهد داشت که در لاگها چاپ میشود.
برای انجام مراحل توضیح داده شده در این راهنما، به موارد زیر نیاز خواهید داشت:
برای جزئیات راهاندازی، لطفاً به مستندات پیشنیازهای شروع سریع مراجعه کنید.
اولین قدم، راهاندازی یک برنامه جدید با استفاده از Oasis CLI است.
oasis rofl init rofl-keygen
cd rofl-keygen
در زمان ایجاد برنامه در Testnet، باید توکنها را واریز کنید. در این مرحله 100 توکن TEST اختصاص دهید.
oasis rofl create --network testnet
به عنوان خروجی، CLI شناسه برنامه (App ID) را تولید میکند که با rofl1… نشان داده میشود.
اکنون، آماده هستید تا پروژه را شروع کنید.
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 # قرارداد نمونه
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 — جریان سرتاسر واحد
این یک مرحله مهم است زیرا این اسکریپت چندین عملکرد دارد:
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);
});
در این مرحله، به پیکربندی حداقلی برای کامپایل 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 استفاده کنید.
مرحلهای وجود دارد که باید قبل از اجرای دستور 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
این یک فرآیند 2 مرحلهای است، اگرچه مرحله دوم اختیاری است.
ابتدا، لاگهای تست دود را مشاهده میکنید.
oasis rofl machine logs
اگر تمام مراحل را تا به حال به درستی تکمیل کرده باشید، در خروجی خواهید دید:
بعد، توسعه محلی. در اینجا، باید 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
یک دموی تولید کلید در 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 منتشر شد، جایی که مردم با برجسته کردن و پاسخ دادن به این داستان به گفتگو ادامه میدهند.


امور مالی
اشتراکگذاری
این مقاله را به اشتراک بگذارید
کپی لینکX (Twitter)LinkedInFacebookEmail
دبی گام بعدی را برای املاک و مستغلات fl برمیدارد
