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
{[[OPEN_1]]
"compilerOptions": {[[OPEN_2]]
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
इस खंड में, हम कुछ छोटी TS फाइलें और एक Solidity कॉन्ट्रैक्ट जोड़ेंगे।
src/
├── appd.ts # @oasisprotocol/rofl-client पर पतला रैपर
├── evm.ts # ethers हेल्पर्स (provider, wallet, tx, deploy)
├── keys.ts # छोटे हेल्पर्स (checksum)
└── scripts/
├── deploy-contract.ts # संकलित artifacts के लिए सामान्य डिप्लॉय स्क्रिप्ट
└── smoke-test.ts # एंड-टू-एंड डेमो (logs)
contracts/
└── Counter.sol # नमूना कॉन्ट्रैक्ट
src/appd.ts
import {existsSync} from 'node:fs';
import {[[OPEN_3]]
RoflClient,
KeyKind,
ROFL_SOCKET_PATH
} from '@oasisprotocol/rofl-client';
const client = new RoflClient(); // UDS: /run/rofl-appd.sock
export async function getAppId(): Promise<string> {[[OPEN_4]]
return client.getAppId();
}
/**
* ROFL के अंदर एक secp256k1 की जेनरेट करता है (या निर्धारक रूप से फिर से प्राप्त करता है) और
* इसे 0x-prefixed hex स्ट्रिंग के रूप में रिटर्न करता है (ethers.js Wallet के लिए)।
*
* केवल स्थानीय विकास (ROFL के बाहर): यदि सॉकेट गायब है और आप
* ALLOW_LOCAL_DEV=true और LOCAL_DEV_SK=0x<64-hex> सेट करते हैं, तो वह मान उपयोग किया जाता है।
*/
export async function getEvmSecretKey(keyId: string): Promise<string> {[[OPEN_5]]
if (existsSync(ROFL_SOCKET_PATH)) {[[OPEN_6]]
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 नहीं मिला और कोई LOCAL_DEV_SK प्रदान नहीं किया गया (केवल dev)।'
);
}
2. src/evm.ts — ethers हेल्पर्स
import {[[OPEN_7]]
JsonRpcProvider,
Wallet,
parseEther,
type TransactionReceipt,
ContractFactory
} from "ethers";
export function makeProvider(rpcUrl: string, chainId: number) {[[OPEN_8]]
return new JsonRpcProvider(rpcUrl, chainId);
}
export function connectWallet(
skHex: string,
rpcUrl: string,
chainId: number
): Wallet {[[OPEN_9]]
const w = new Wallet(skHex);
return w.connect(makeProvider(rpcUrl, chainId));
}
export async function signPersonalMessage(wallet: Wallet, msg: string) {[[OPEN_10]]
return wallet.signMessage(msg);
}
export async function sendEth(
wallet: Wallet,
to: string,
amountEth: string
): Promise<TransactionReceipt> {[[OPEN_11]]
const tx = await wallet.sendTransaction({[[OPEN_12]]
to,
value: parseEther(amountEth)
});
const receipt = await tx.wait();
if (receipt == null) {[[OPEN_13]]
throw new Error("ट्रांजैक्शन पुष्टि से पहले ड्रॉप या बदल दिया गया");
}
return receipt;
}
export async function deployContract(
wallet: Wallet,
abi: any[],
bytecode: string,
args: unknown[] = []
): Promise<{ address: string; receipt: TransactionReceipt }> {[[OPEN_14]]
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) {[[OPEN_15]]
throw new Error("डिप्लॉयमेंट TX माइन नहीं किया गया");
}
return { address: contract.target as string, receipt };
}
3. src/keys.ts — छोटे हेल्पर्स
import { Wallet, getAddress } from "ethers";
export function secretKeyToWallet(skHex: string): Wallet {[[OPEN_16]]
return new Wallet(skHex);
}
export function checksumAddress(addr: string): string {[[OPEN_17]]
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> {[[OPEN_18]]
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> {[[OPEN_19]]
const start = Date.now();
while (Date.now() - start < timeoutMs) {[[OPEN_20]]
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() {[[OPEN_21]]
const appId = await getAppId().catch(() => null);
console.log(`ROFL App ID: ${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 = "hello from 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) {[[OPEN_22]]
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) {[[OPEN_23]]
throw new Error("Counter artifact में abi/bytecode गायब है");
}
const { address: contractAddress, receipt: deployRcpt } =
await deployContract(wallet, artifact.abi, artifact.bytecode, []);
console.log(`${contractAddress} पर Counter डिप्लॉय किया गया (tx=${deployRcpt.hash})`);
console.log("Smoke test सफलतापूर्वक पूरा हुआ!");
}
main().catch((e) => {[[OPEN_24]]
console.error(e);
process.exit(1);
});
5. contracts/Counter.sol — न्यूनतम नमूना
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Counter {[[OPEN_25]]
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]'
* artifact में { abi, bytecode } होना चाहिए।
*/
async function main() {[[OPEN_26]]
const [artifactPath, ctorJson = "[]"] = process.argv.slice(2);
if (!artifactPath) {[[OPEN_27]]
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) {[[OPEN_28]]
throw new Error("Artifact में { abi, bytecode } होना चाहिए");
}
let args: unknown[];
try {[[OPEN_29]]
args = JSON.parse(ctorJson);
if (!Array.isArray(args)) throw new Error("constructor args एक JSON array होना चाहिए");
} catch (e) {[[OPEN_30]]
throw new Error(`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) => {[[OPEN_31]]
console.error(e);
process.exit(1);
});
इस चरण में, हमें Counter.sol को संकलित करने के लिए न्यूनतम कॉन्फ़िगरेशन की आवश्यकता होगी
hardhat.config.ts
import type { HardhatUserConfig } from "hardhat/config";
const config: HardhatUserConfig = {[[OPEN_32]]
solidity: {[[OPEN_33]]
version: "0.8.24",
settings: {[[OPEN_34]]
optimizer: { enabled: true, runs: 200 }
}
},
paths: {[[OPEN_35]]
sources: "./contracts",
artifacts: "./artifacts",
cache: "./cache"
}
};
export default config;
ध्यान देने योग्य बात यह है कि स्थानीय संकलन वैकल्पिक है, इसलिए यदि आप चाहें तो इसे छोड़ सकते हैं। अगला कदम एक विकल्प है — या तो मौजूदा contracts/Lock.sol फाइल को हटा दें या आप इसे Solidity संस्करण 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 कमांड चलाने से पहले एक कदम है जो आपको उठाना होगा। चूंकि इमेज सेगमेंट बनाना containerization के बाद आता है, आपको compose.yaml में services.demo.image को अपडेट करना होगा जो आपने बनाई थी।
इस तरह के सरल TypeScript प्रोजेक्ट्स के लिए, कभी-कभी संभावना है कि इमेज का आकार अनुमान से बड़ा हो। इसलिए rofl.yaml resources सेक्शन को कम से कम इसमें अपडेट करने की सलाह दी जाती है: memory: 1024 और storage.size: 4096।
अब, आप तैयार हैं।
oasis rofl build
आप अगले enclave identities और config प्रकाशित कर सकते हैं।
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> # PROD में उपयोग न करें
npm run smoke-test
Oasis GitHub में एक की जनरेशन डेमो है, जिसे आप इस ट्यूटोरियल के उदाहरण के रूप में संदर्भित कर सकते हैं। https://github.com/oasisprotocol/demo-rofl-keygen
अब जब आपने ROFL में appd के साथ सफलतापूर्वक एक की जेनरेट की है, संदेशों पर हस्ताक्षर किए हैं, एक कॉन्ट्रैक्ट डिप्लॉय किया है, और Base Sepolia पर ETH ट्रांसफर किया है, तो टिप्पणी अनुभाग में हमें अपनी प्रतिक्रिया बताएं। विशिष्ट मुद्दों में मदद के लिए Oasis इंजीनियरिंग टीम के साथ त्वरित चैट के लिए, आप आधिकारिक Discord में dev-central चैनल में अपनी टिप्पणियां छोड़ सकते हैं।
मूल रूप से https://dev.to पर 20 फरवरी, 2026 को प्रकाशित।
Guide To Cross-Chain Key Generation (EVM / Base) With Oasis ROFL मूल रूप से Medium पर Coinmonks में प्रकाशित किया गया था, जहां लोग इस कहानी को हाइलाइट और जवाब देकर बातचीत जारी रख रहे हैं।

