Oasis রানটাইম অফ-চেইন লজিক (ROFL) এর জন্য ফ্রেমওয়ার্ক চালু করেছে যা অফ-চেইন অ্যাপ তৈরি এবং চালাতে সাহায্য করে এবং একই সাথে গোপনীয়তা নিশ্চিত করে এবং অন-চেইন যাচাইযোগ্যতার সাথে বিশ্বাস বজায় রাখে। ROFL দিয়ে তৈরি করতে অনেকগুলি চলমান অংশ রয়েছে।
এই টিউটোরিয়ালে, আমি একটি ছোট TypeScript অ্যাপ তৈরি করার প্রদর্শন করব, ROFL এর ভিতরে একটি secp256k1 কী তৈরি করা। এটি @oasisprotocol/rofl-client TypeScript SDK ব্যবহার করবে, যা অভ্যন্তরীণভাবে appd REST API এর সাথে যোগাযোগ করে। TypeScript অ্যাপটি এছাড়াও:
একটি সাধারণ smoke test থাকবে যা লগে প্রিন্ট করবে।
এই গাইডে বর্ণিত পদক্ষেপগুলি করতে, আপনার প্রয়োজন হবে:
সেটআপ বিস্তারিত জানার জন্য, Quickstart Prerequisites এর ডকুমেন্টেশন দেখুন।
প্রথম ধাপ হল 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 এর বাইরে ব্যবহারের জন্য ছোট রানটাইম deps যোগ করা।
npm i @oasisprotocol/rofl-client ethers dotenv @types/node
npm i -D tsx
Hardhat এর TypeScript টেমপ্লেট স্বয়ংক্রিয়ভাবে একটি tsconfig.json তৈরি করে। আমাদের একটি ছোট স্ক্রিপ্ট যোগ করতে হবে যাতে অ্যাপ কোড dist/ তে কম্পাইল করতে পারে।
// tsconfig.json
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
এই বিভাগে, আমরা কয়েকটি ছোট TS ফাইল এবং একটি Solidity কন্ট্রাক্ট যোগ করব।
src/
├── appd.ts # @oasisprotocol/rofl-client এর উপর পাতলা wrapper
├── evm.ts # ethers helpers (provider, wallet, tx, deploy)
├── keys.ts # tiny helpers (checksum)
└── scripts/
├── deploy-contract.ts # কম্পাইল করা artifacts এর জন্য জেনেরিক ডিপ্লয় স্ক্রিপ্ট
└── smoke-test.ts # end-to-end ডেমো (logs)
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();
}
/**
* ROFL এর ভিতরে একটি secp256k1 কী তৈরি করে (বা নির্ধারণমূলকভাবে পুনরায় derive করে) এবং
* এটি একটি 0x-প্রিফিক্সড hex স্ট্রিং হিসাবে ফেরত দেয় (ethers.js Wallet এর জন্য)।
*
* শুধুমাত্র স্থানীয় ডেভেলপমেন্ট (ROFL এর বাইরে): যদি socket অনুপস্থিত থাকে এবং আপনি
* 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 helpers
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 — tiny helpers
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 — একক end‑to‑end ফ্লো
এটি একটি গুরুত্বপূর্ণ ধাপ কারণ এই স্ক্রিপ্টের একাধিক কাজ রয়েছে:
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 প্রভাইডারকে বিশ্বাস করে। উৎপাদনের জন্য, একটি
// light client (উদাহরণস্বরূপ, 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 — জেনেরিক deployer
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]'
* artifact অবশ্যই { 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 প্রভাইডারকে বিশ্বাস করে। উৎপাদনের জন্য, একটি
// light client (উদাহরণস্বরূপ, 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 version 0.8.24 এ আপডেট করতে পারেন।
npx hardhat compile
এটি একটি অপরিহার্য ধাপ। এখানে, আপনার একটি Dockerfile প্রয়োজন যা TS তৈরি করে এবং কন্ট্রাক্ট কম্পাইল করে। ফাইলটি smoke test একবার চালাবে, এবং তারপর আপনি লগ পরীক্ষা করার সময় নিষ্ক্রিয় থাকবে।
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"]
পরবর্তীতে, আপনাকে অবশ্যই ROFL দ্বারা প্রদত্ত appd socket মাউন্ট করতে হবে। নিশ্চিত থাকুন যে প্রক্রিয়ায় কোনও পাবলিক পোর্ট প্রকাশ করা হয় না।
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 পিন করতে হবে এবং compose.yaml এ image: …@sha256:… ব্যবহার করতে হবে।
oasis rofl build কমান্ড চালানোর আগে আপনাকে অবশ্যই একটি ধাপ নিতে হবে। যেহেতু ইমেজ সেগমেন্ট তৈরি করা কন্টেইনারাইজেশনের পরে আসে, আপনাকে compose.yaml এ services.demo.image আপনার তৈরি ইমেজে আপডেট করতে হবে।
সাধারণ TypeScript প্রজেক্টের জন্য, এটির মতো, কখনও কখনও সম্ভাবনা থাকে যে ইমেজ আকার প্রত্যাশিত থেকে বড়। তাই rofl.yaml resources বিভাগ অন্তত আপডেট করার পরামর্শ দেওয়া হয়: memory: 1024 এবং storage.size: 4096।
এখন, আপনি প্রস্তুত।
oasis rofl build
আপনি পরবর্তীতে enclave identities এবং কনফিগ প্রকাশ করতে পারেন।
oasis rofl update
এটি একটি যথেষ্ট সহজ ধাপ যেখানে আপনি একটি Testnet প্রভাইডারে ডিপ্লয় করেন।
oasis rofl deploy
এটি একটি 2-ধাপের প্রক্রিয়া, যদিও দ্বিতীয় ধাপ ঐচ্ছিক।
প্রথমে, আপনি smoke‑test লগ দেখুন।
oasis rofl machine logs
যদি আপনি এখন পর্যন্ত সমস্ত ধাপ সঠিকভাবে সম্পন্ন করে থাকেন, আপনি আউটপুটে দেখবেন:
পরবর্তী, স্থানীয় dev। এখানে, আপনাকে TypeScript কোড এবং Solidity কন্ট্রাক্ট কম্পাইল করতে npm run build:all চালাতে হবে। প্রয়োজন না হলে এই ধাপটি এড়িয়ে যান।
export ALLOW_LOCAL_DEV=true
export LOCAL_DEV_SK=0x<64-hex-dev-secret-key> # প্রোডাকশনে ব্যবহার করবেন না
npm run smoke-test
Oasis GitHub-এ একটি key generation demo আছে, যা আপনি এই টিউটোরিয়ালের উদাহরণ হিসাবে উল্লেখ করতে পারেন। https://github.com/oasisprotocol/demo-rofl-keygen
এখন যেহেতু আপনি সফলভাবে appd সহ ROFL এ একটি কী তৈরি করেছেন, বার্তা স্বাক্ষরিত করেছেন, একটি কন্ট্রাক্ট ডিপ্লয় করেছেন, এবং Base Sepolia তে ETH সরিয়েছেন, মন্তব্য বিভাগে আপনার প্রতিক্রিয়া জানান। নির্দিষ্ট সমস্যার সাহায্যের জন্য Oasis ইঞ্জিনিয়ারিং টিমের সাথে দ্রুত চ্যাট করার জন্য, আপনি অফিসিয়াল Discord-এ dev-central channel এ আপনার মন্তব্য রাখতে পারেন।
মূলত https://dev.to এ প্রকাশিত হয়েছে ফেব্রুয়ারি 20, 2026।
Guide To Cross-Chain Key Generation (EVM / Base) With Oasis ROFL মূলত Medium-এ Coinmonks-এ প্রকাশিত হয়েছিল, যেখানে মানুষ এই গল্পটি হাইলাইট এবং প্রতিক্রিয়া জানানোর মাধ্যমে কথোপকথন চালিয়ে যাচ্ছে।


