
{"id":136925,"date":"2026-02-20T13:16:48","date_gmt":"2026-02-20T13:16:48","guid":{"rendered":"https:\/\/mycryptomania.com\/?p=136925"},"modified":"2026-02-20T13:16:48","modified_gmt":"2026-02-20T13:16:48","slug":"guide-to-cross-chain-key-generation-evm-base-with-oasis-rofl","status":"publish","type":"post","link":"https:\/\/mycryptomania.com\/?p=136925","title":{"rendered":"Guide To Cross-Chain Key Generation (EVM \/ Base) With Oasis ROFL"},"content":{"rendered":"<p>Oasis introduced the framework for runtime off-chain logic ( <a href=\"https:\/\/oasis.net\/decentralized-ai\">ROFL<\/a>) to help build and run apps off-chain while ensuring privacy and maintaining trust with on-chain verifiability. There are many moving parts to building with ROFL. <br \/>In this tutorial, I will demonstrate how to build a tiny TypeScript app, <strong>generating a secp256k1 key inside ROFL<\/strong>. It will be using the <strong>@oasisprotocol\/rofl-client TypeScript SDK<\/strong>, which talks to the <a href=\"https:\/\/docs.oasis.io\/build\/rofl\/features\/appd\/\"><strong>appd REST API<\/strong><\/a> under the hood. The TypeScript app will\u00a0also:<\/p>\n<p>There will be a simple <strong>smoke test<\/strong> that prints to\u00a0logs.<\/p>\n<h3>Prerequisites<\/h3>\n<p>To do the steps described in this guide, you will\u00a0need:<\/p>\n<p><strong>Node.js 20+<\/strong> and <strong>Docker<\/strong> (or\u00a0Podman)<strong>Oasis CLI<\/strong> and a minimum of 120 TEST tokens in your wallet (<a href=\"https:\/\/faucet.testnet.oasis.io\/\">Oasis Testnet\u00a0faucet<\/a>)Some Base Sepiola test ETH (<a href=\"https:\/\/docs.base.org\/base-chain\/tools\/network-faucets\">Base Sepiola\u00a0faucet<\/a>)<\/p>\n<p>For the setup details, please refer to the documentation on <a href=\"https:\/\/docs.oasis.io\/build\/tools\/cli\/setup\">Quickstart Prerequisites<\/a>.<\/p>\n<h3>Init App<\/h3>\n<p>The first step is to initialize a new app using the Oasis\u00a0CLI.<\/p>\n<p>oasis rofl init rofl-keygen<br \/>cd rofl-keygen<\/p>\n<h3>Create App<\/h3>\n<p>At the time of creating the app on the Testnet, you will be required to deposit tokens. Assign 100 TEST tokens at this\u00a0point.<\/p>\n<p>oasis rofl create &#8211;network testnet<\/p>\n<p>As output, the CLI will produce the <strong>App ID<\/strong>, denoted by\u00a0rofl1\u2026.<\/p>\n<h3>Init a Hardhat (TypeScript) project<\/h3>\n<p>Now, you are ready to kickstart the\u00a0project.<\/p>\n<p>npx hardhat init<\/p>\n<p>Since we are showcasing a TypeScript app, <strong>choose TypeScript<\/strong> when prompted, and then accept the defaults.<br \/>The nextstep would be to add the small runtime deps for use outside of\u00a0Hardhat.<\/p>\n<p>npm i @oasisprotocol\/rofl-client ethers dotenv @types\/node<br \/>npm i -D tsx<\/p>\n<p>Hardhat\u2019s TypeScript template automatically creates a <strong>tsconfig.json<\/strong>. We need to add a small script so that the app code can compile to\u00a0<strong>dist\/<\/strong>.<\/p>\n<p>\/\/ tsconfig.json<br \/>{<br \/>  &#8220;compilerOptions&#8221;: {<br \/>    &#8220;rootDir&#8221;: &#8220;.\/src&#8221;,<br \/>    &#8220;outDir&#8221;: &#8220;.\/dist&#8221;<br \/>  },<br \/>  &#8220;include&#8221;: [&#8220;src&#8221;]<br \/>}<\/p>\n<h3>App structure<\/h3>\n<p>In this section, we will add a few small TS files and one Solidity contract.<\/p>\n<p>src\/<br \/>\u251c\u2500\u2500 appd.ts               # thin wrapper over @oasisprotocol\/rofl-client<br \/>\u251c\u2500\u2500 evm.ts                # ethers helpers (provider, wallet, tx, deploy)<br \/>\u251c\u2500\u2500 keys.ts               # tiny helpers (checksum)<br \/>\u2514\u2500\u2500 scripts\/<br \/>    \u251c\u2500\u2500 deploy-contract.ts  # generic deploy script for compiled artifacts<br \/>    \u2514\u2500\u2500 smoke-test.ts       # end-to-end demo (logs)<br \/>contracts\/<br \/>\u2514\u2500\u2500 Counter.sol           # sample contract<strong>src\/appd.ts<\/strong>\u200a\u2014\u200athin wrapper over the SDK Here, you will need to use the official client to talk to <strong>appd<\/strong> (UNIX socket). We will also need to keep an explicit <strong>local\u2011dev fallback<\/strong> when running outside\u00a0ROFL.<\/p>\n<p><strong>src\/appd.ts<\/strong><\/p>\n<p>import {existsSync} from &#8216;node:fs&#8217;;<br \/>import {<br \/>  RoflClient,<br \/>  KeyKind,<br \/>  ROFL_SOCKET_PATH<br \/>} from &#8216;@oasisprotocol\/rofl-client&#8217;;<\/p>\n<p>const client = new RoflClient(); \/\/ UDS: \/run\/rofl-appd.sock<\/p>\n<p>export async function getAppId(): Promise&lt;string&gt; {<br \/>  return client.getAppId();<br \/>}<\/p>\n<p>\/**<br \/> * Generates (or deterministically re-derives) a secp256k1 key inside ROFL and<br \/> * returns it as a 0x-prefixed hex string (for ethers.js Wallet).<br \/> *<br \/> * Local development ONLY (outside ROFL): If the socket is missing and you set<br \/> * ALLOW_LOCAL_DEV=true and LOCAL_DEV_SK=0x&lt;64-hex&gt;, that value is used.<br \/> *\/<br \/>export async function getEvmSecretKey(keyId: string): Promise&lt;string&gt; {<br \/>  if (existsSync(ROFL_SOCKET_PATH)) {<br \/>    const hex = await client.generateKey(keyId, KeyKind.SECP256K1);<br \/>    return hex.startsWith(&#8216;0x&#8217;) ? hex : `0x${hex}`;<br \/>  }<br \/>  const allow = process.env.ALLOW_LOCAL_DEV === &#8216;true&#8217;;<br \/>  const pk = process.env.LOCAL_DEV_SK;<br \/>  if (allow &amp;&amp; pk &amp;&amp; \/^0x[0-9a-fA-F]{64}$\/.test(pk)) return pk;<br \/>  throw new Error(<br \/>    &#8216;rofl-appd socket not found and no LOCAL_DEV_SK provided (dev only).&#8217;<br \/>  );<br \/>}<\/p>\n<p>2. <strong>src\/evm.ts<\/strong>\u200a\u2014\u200aethers\u00a0helpers<\/p>\n<p>import {<br \/>  JsonRpcProvider,<br \/>  Wallet,<br \/>  parseEther,<br \/>  type TransactionReceipt,<br \/>  ContractFactory<br \/>} from &#8220;ethers&#8221;;<\/p>\n<p>export function makeProvider(rpcUrl: string, chainId: number) {<br \/>  return new JsonRpcProvider(rpcUrl, chainId);<br \/>}<\/p>\n<p>export function connectWallet(<br \/>  skHex: string,<br \/>  rpcUrl: string,<br \/>  chainId: number<br \/>): Wallet {<br \/>  const w = new Wallet(skHex);<br \/>  return w.connect(makeProvider(rpcUrl, chainId));<br \/>}<\/p>\n<p>export async function signPersonalMessage(wallet: Wallet, msg: string) {<br \/>  return wallet.signMessage(msg);<br \/>}<\/p>\n<p>export async function sendEth(<br \/>  wallet: Wallet,<br \/>  to: string,<br \/>  amountEth: string<br \/>): Promise&lt;TransactionReceipt&gt; {<br \/>  const tx = await wallet.sendTransaction({<br \/>    to,<br \/>    value: parseEther(amountEth)<br \/>  });<br \/>  const receipt = await tx.wait();<br \/>  if (receipt == null) {<br \/>    throw new Error(&#8220;Transaction dropped or replaced before confirmation&#8221;);<br \/>  }<br \/>  return receipt;<br \/>}<\/p>\n<p>export async function deployContract(<br \/>  wallet: Wallet,<br \/>  abi: any[],<br \/>  bytecode: string,<br \/>  args: unknown[] = []<br \/>): Promise&lt;{ address: string; receipt: TransactionReceipt }&gt; {<br \/>  const factory = new ContractFactory(abi, bytecode, wallet);<br \/>  const contract = await factory.deploy(&#8230;args);<br \/>  const deployTx = contract.deploymentTransaction();<br \/>  const receipt = await deployTx?.wait();<br \/>  await contract.waitForDeployment();<br \/>  if (!receipt) {<br \/>    throw new Error(&#8220;Deployment TX not mined&#8221;);<br \/>  }<br \/>  return { address: contract.target as string, receipt };<br \/>}<\/p>\n<p>3. <strong>src\/keys.ts<\/strong>\u200a\u2014\u200atiny\u00a0helpers<\/p>\n<p>import { Wallet, getAddress } from &#8220;ethers&#8221;;<\/p>\n<p>export function secretKeyToWallet(skHex: string): Wallet {<br \/>  return new Wallet(skHex);<br \/>}<\/p>\n<p>export function checksumAddress(addr: string): string {<br \/>  return getAddress(addr);<br \/>}<\/p>\n<p>4. <strong>src\/scripts\/smoke-test.ts<\/strong>\u200a\u2014\u200asingle end\u2011to\u2011end flow<\/p>\n<p>This is an important step as this script has multiple functions:<\/p>\n<p>print the App ID (inside ROFL), address, and a signed\u00a0messagewait for\u00a0fundingdeploy the counter\u00a0contractimport &#8220;dotenv\/config&#8221;;<br \/>import { readFileSync } from &#8220;node:fs&#8221;;<br \/>import { join } from &#8220;node:path&#8221;;<br \/>import { getAppId, getEvmSecretKey } from &#8220;..\/appd.js&#8221;;<br \/>import { secretKeyToWallet, checksumAddress } from &#8220;..\/keys.js&#8221;;<br \/>import { makeProvider, signPersonalMessage, sendEth, deployContract } from &#8220;..\/evm.js&#8221;;<br \/>import { formatEther, JsonRpcProvider } from &#8220;ethers&#8221;;<\/p>\n<p>const RPC_URL = process.env.BASE_RPC_URL ?? &#8220;https:\/\/sepolia.base.org&#8221;;<br \/>const CHAIN_ID = Number(process.env.BASE_CHAIN_ID ?? &#8220;84532&#8221;);<br \/>const KEY_ID = process.env.KEY_ID ?? &#8220;evm:base:sepolia&#8221;;<\/p>\n<p>function sleep(ms: number): Promise&lt;void&gt; {<br \/>  return new Promise((r) =&gt; setTimeout(r, ms));<br \/>}<\/p>\n<p>async function waitForFunding(<br \/>  provider: JsonRpcProvider,<br \/>  addr: string,<br \/>  minWei: bigint = 1n,<br \/>  timeoutMs = 15 * 60 * 1000,<br \/>  pollMs = 5_000<br \/>): Promise&lt;bigint&gt; {<br \/>  const start = Date.now();<br \/>  while (Date.now() &#8211; start &lt; timeoutMs) {<br \/>    const bal = await provider.getBalance(addr);<br \/>    if (bal &gt;= minWei) return bal;<br \/>    console.log(`Waiting for funding&#8230; current balance=${formatEther(bal)} ETH`);<br \/>    await sleep(pollMs);<br \/>  }<br \/>  throw new Error(&#8220;Timed out waiting for funding.&#8221;);<br \/>}<\/p>\n<p>async function main() {<br \/>  const appId = await getAppId().catch(() =&gt; null);<br \/>  console.log(`ROFL App ID: ${appId ?? &#8220;(unavailable outside ROFL)&#8221;}`);<\/p>\n<p>  const sk = await getEvmSecretKey(KEY_ID);<br \/>  \/\/ NOTE: This demo trusts the configured RPC provider. For production, prefer a<br \/>  \/\/ light client (for example, Helios) so you can verify remote chain state.<br \/>  const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));<br \/>  const addr = checksumAddress(await wallet.getAddress());<br \/>  console.log(`EVM address (Base Sepolia): ${addr}`);<\/p>\n<p>  const msg = &#8220;hello from rofl&#8221;;<br \/>  const sig = await signPersonalMessage(wallet, msg);<br \/>  console.log(`Signed message: &#8220;${msg}&#8221;`);<br \/>  console.log(`Signature: ${sig}`);<\/p>\n<p>  const provider = wallet.provider as JsonRpcProvider;<\/p>\n<p>  let bal = await provider.getBalance(addr);<br \/>  if (bal === 0n) {<br \/>    console.log(&#8220;Please fund the above address with Base Sepolia ETH to continue.&#8221;);<br \/>    bal = await waitForFunding(provider, addr);<br \/>  }<br \/>  console.log(`Balance detected: ${formatEther(bal)} ETH`);<\/p>\n<p>  const artifactPath = join(process.cwd(), &#8220;artifacts&#8221;, &#8220;contracts&#8221;, &#8220;Counter.sol&#8221;, &#8220;Counter.json&#8221;);<br \/>  const artifact = JSON.parse(readFileSync(artifactPath, &#8220;utf8&#8221;));<br \/>  if (!artifact?.abi || !artifact?.bytecode) {<br \/>    throw new Error(&#8220;Counter artifact missing abi\/bytecode&#8221;);<br \/>  }<br \/>  const { address: contractAddress, receipt: deployRcpt } =<br \/>    await deployContract(wallet, artifact.abi, artifact.bytecode, []);<br \/>  console.log(`Deployed Counter at ${contractAddress} (tx=${deployRcpt.hash})`);<\/p>\n<p>  console.log(&#8220;Smoke test completed successfully!&#8221;);<br \/>}<\/p>\n<p>main().catch((e) =&gt; {<br \/>  console.error(e);<br \/>  process.exit(1);<br \/>});<\/p>\n<p>5. <strong>contracts\/Counter.sol<\/strong>\u200a\u2014\u200aminimal\u00a0sample<\/p>\n<p>\/\/ SPDX-License-Identifier: MIT<br \/>pragma solidity ^0.8.24;<\/p>\n<p>contract Counter {<br \/>    uint256 private _value;<br \/>    event Incremented(uint256 v);<br \/>    event Set(uint256 v);<\/p>\n<p>    function current() external view returns (uint256) { return _value; }<br \/>    function inc() external { unchecked { _value += 1; } emit Incremented(_value); }<br \/>    function set(uint256 v) external { _value = v; emit Set(v); }<br \/>}<\/p>\n<p>6. <strong>src\/scripts\/deploy-contract.ts<\/strong>\u200a\u2014\u200ageneric\u00a0deployer<\/p>\n<p>import &#8220;dotenv\/config&#8221;;<br \/>import { readFileSync } from &#8220;node:fs&#8221;;<br \/>import { getEvmSecretKey } from &#8220;..\/appd.js&#8221;;<br \/>import { secretKeyToWallet } from &#8220;..\/keys.js&#8221;;<br \/>import { makeProvider, deployContract } from &#8220;..\/evm.js&#8221;;<\/p>\n<p>const KEY_ID = process.env.KEY_ID ?? &#8220;evm:base:sepolia&#8221;;<br \/>const RPC_URL = process.env.BASE_RPC_URL ?? &#8220;https:\/\/sepolia.base.org&#8221;;<br \/>const CHAIN_ID = Number(process.env.BASE_CHAIN_ID ?? &#8220;84532&#8221;);<\/p>\n<p>\/**<br \/> * Usage:<br \/> *   npm run deploy-contract &#8212; .\/artifacts\/MyContract.json &#8216;[arg0, arg1]&#8217;<br \/> * The artifact must contain { abi, bytecode }.<br \/> *\/<br \/>async function main() {<br \/>  const [artifactPath, ctorJson = &#8220;[]&#8221;] = process.argv.slice(2);<br \/>  if (!artifactPath) {<br \/>    console.error(&#8220;Usage: npm run deploy-contract &#8212; &lt;artifact.json&gt; &#8216;[constructorArgsJson]'&#8221;);<br \/>    process.exit(2);<br \/>  }<\/p>\n<p>  const artifactRaw = readFileSync(artifactPath, &#8220;utf8&#8221;);<br \/>  const artifact = JSON.parse(artifactRaw);<br \/>  const { abi, bytecode } = artifact ?? {};<br \/>  if (!abi || !bytecode) {<br \/>    throw new Error(&#8220;Artifact must contain { abi, bytecode }&#8221;);<br \/>  }<\/p>\n<p>  let args: unknown[];<br \/>  try {<br \/>    args = JSON.parse(ctorJson);<br \/>    if (!Array.isArray(args)) throw new Error(&#8220;constructor args must be a JSON array&#8221;);<br \/>  } catch (e) {<br \/>    throw new Error(`Failed to parse constructor args JSON: ${String(e)}`);<br \/>  }<\/p>\n<p>  const sk = await getEvmSecretKey(KEY_ID);<br \/>  \/\/ NOTE: This demo trusts the configured RPC provider. For production, prefer a<br \/>  \/\/ light client (for example, Helios) so you can verify remote chain state.<br \/>  const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));<br \/>  const { address, receipt } = await deployContract(wallet, abi, bytecode, args);<\/p>\n<p>  console.log(JSON.stringify({ contractAddress: address, txHash: receipt.hash, status: receipt.status }, null, 2));<br \/>}<\/p>\n<p>main().catch((e) =&gt; {<br \/>  console.error(e);<br \/>  process.exit(1);<br \/>});<\/p>\n<h3>Hardhat (contracts only)<\/h3>\n<p>At this stage, we will need minimal config to compile <strong>Counter.sol<\/strong><\/p>\n<p><strong>hardhat.config.ts<\/strong><\/p>\n<p>import type { HardhatUserConfig } from &#8220;hardhat\/config&#8221;;<\/p>\n<p>const config: HardhatUserConfig = {<br \/>  solidity: {<br \/>    version: &#8220;0.8.24&#8221;,<br \/>    settings: {<br \/>      optimizer: { enabled: true, runs: 200 }<br \/>    }<br \/>  },<br \/>  paths: {<br \/>    sources: &#8220;.\/contracts&#8221;,<br \/>    artifacts: &#8220;.\/artifacts&#8221;,<br \/>    cache: &#8220;.\/cache&#8221;<br \/>  }<br \/>};<\/p>\n<p>export default config;<\/p>\n<p>Point to note is that local compilation is optional, so you can skip it if you want. Next step is a choice\u200a\u2014\u200aeither delete the existing <strong>contracts\/Lock.sol<\/strong> file or you can update it to Solidity <strong>version\u00a00.8.24<\/strong>.<\/p>\n<p>npx hardhat compile<\/p>\n<h3>Containerize<\/h3>\n<p>This is an essential step. Here, you need to a Dockerfile that builds TS and compiles the contract. The file will also run the <strong>smoke test<\/strong> once, and then stand idle while you inspect\u00a0logs.<\/p>\n<p><strong>Dockerfile<\/strong><\/p>\n<p>FROM node:20-alpine<br \/>WORKDIR \/app<\/p>\n<p>COPY package.json package-lock.json* .\/<br \/>RUN npm ci<\/p>\n<p>COPY tsconfig.json .\/<br \/>COPY src .\/src<br \/>COPY contracts .\/contracts<br \/>COPY hardhat.config.ts .\/<br \/>RUN npm run build &amp;&amp; npx hardhat compile &amp;&amp; npm prune &#8211;omit=dev<\/p>\n<p>ENV NODE_ENV=production<br \/>CMD [&#8220;sh&#8221;, &#8220;-c&#8221;, &#8220;node dist\/scripts\/smoke-test.js || true; tail -f \/dev\/null&#8221;]<\/p>\n<p>Next, you must mount <strong>appd socket<\/strong> provided by ROFL. Rest assured that no public ports are exposed in the\u00a0process.<\/p>\n<p><strong>compose.yaml<\/strong><\/p>\n<p>services:<br \/>  demo:<br \/>    image: docker.io\/YOURUSER\/rofl-keygen:0.1.0<br \/>    platform: linux\/amd64<br \/>    environment:<br \/>      &#8211; KEY_ID=${KEY_ID:-evm:base:sepolia}<br \/>      &#8211; BASE_RPC_URL=${BASE_RPC_URL:-https:\/\/sepolia.base.org}<br \/>      &#8211; BASE_CHAIN_ID=${BASE_CHAIN_ID:-84532}<br \/>    volumes:<br \/>      &#8211; \/run\/rofl-appd.sock:\/run\/rofl-appd.sock<\/p>\n<h3>Build the\u00a0image<\/h3>\n<p>It is important to remember that ROFL only runs on Intel TDX-enabled hardware. So, if you\u2019re compiling images on a different host, such as macOS, then passing the\u200a<strong>\u2014\u200aplatform linux\/amd64<\/strong> parameter is an essential extra\u00a0step.<\/p>\n<p>docker buildx build &#8211;platform linux\/amd64 <br \/>  -t docker.io\/YOURUSER\/rofl-keygen:0.1.0 &#8211;push .<\/p>\n<p>An interesting point to note here is that you can opt for extra security and verifiability. You just need to pin the digest and use <strong>image:\u00a0\u2026<\/strong><a href=\"https:\/\/dev.to\/sha256\"><strong>@sha256<\/strong><\/a><strong>:\u2026<\/strong> in <strong>compose.yaml<\/strong>.<\/p>\n<h3>Build ROFL\u00a0bundle<\/h3>\n<p>There is a step that you must take before running the <strong>oasis rofl build<\/strong> command. Since building the image segment comes after containerization, you will need to update the <strong>services.demo.image<\/strong> in <strong>compose.yaml<\/strong> to the image you built.<br \/>For simple TypeScript projects, like this one, there is sometimes a possibility that the image size is larger than anticipated. It is thus advisable to update the <strong>rofl.yaml<\/strong> <strong>resources<\/strong> section to at least: <strong>memory: 1024<\/strong> and <strong>storage.size: 4096<\/strong>.<br \/>Now, you are\u00a0ready.<\/p>\n<p>oasis rofl build<\/p>\n<p>You can next publish the enclave identities and\u00a0config.<\/p>\n<p>oasis rofl update<\/p>\n<h3>Deploy<\/h3>\n<p>This is an easy enough step where you deploy to a Testnet provider.<\/p>\n<p>oasis rofl deploy<\/p>\n<h3>End\u2011to\u2011end (Base\u00a0Sepolia)<\/h3>\n<p>This is a 2-step process, although the second step is optional.<br \/> First, you view smoke\u2011test logs.<\/p>\n<p>oasis rofl machine logs<\/p>\n<p>If you have completed all the steps till now correctly, you will see in the\u00a0output:<\/p>\n<p>App IDEVM address and a signed\u00a0messageA prompt to fund the\u00a0addressOnce funding is done, a Counter.sol deployment<\/p>\n<p>Next, local dev. Here, you need to run <strong>npm run build:all<\/strong> to compile the TypeScript code and the Solidity contract. Skip this step if not\u00a0needed.<\/p>\n<p> export ALLOW_LOCAL_DEV=true<br \/> export LOCAL_DEV_SK=0x&lt;64-hex-dev-secret-key&gt;   # DO NOT USE IN PROD<br \/> npm run smoke-test<\/p>\n<h3>Security &amp; notes to\u00a0remember<\/h3>\n<p>Provider logs are not encrypted at rest. So, <strong>never<\/strong> log secret\u00a0keys.The appd socket <strong>\/run\/rofl-appd.sock<\/strong> exists <strong>only inside\u00a0ROFL<\/strong>.There may be rate limits in public RPCs. So, it is advisable to opt for a dedicated Base RPC\u00a0URL.<\/p>\n<p>There is a <strong>key generation demo<\/strong> in the Oasis GitHub, which you can refer to as an example of this tutorial. <a href=\"https:\/\/github.com\/oasisprotocol\/demo-rofl-keygen\">https:\/\/github.com\/oasisprotocol\/demo-rofl-keygen<\/a><\/p>\n<p>Now that you have successfully generated a key in ROFL with <strong>appd<\/strong>, signed messages, deployed a contract, and moved ETH on Base Sepolia, let us know in the comments section your feedback. For a quick chat with the Oasis engineering team for help with specific issues, you can drop your comments in the <strong>dev-central channel<\/strong> in the official\u00a0<a href=\"https:\/\/discord.com\/invite\/BQCxwhT5wS\">Discord<\/a>.<\/p>\n<p><em>Originally published at <\/em><a href=\"https:\/\/dev.to\/dc600\/guide-to-cross-chain-key-generation-evm-base-with-oasis-rofl-38c8\"><em>https:\/\/dev.to<\/em><\/a><em> on February 20,\u00a02026.<\/em><\/p>\n<p><a href=\"https:\/\/medium.com\/coinmonks\/guide-to-cross-chain-key-generation-evm-base-with-oasis-rofl-09d76200ddc0\">Guide To Cross-Chain Key Generation (EVM \/ Base) With Oasis ROFL<\/a> was originally published in <a href=\"https:\/\/medium.com\/coinmonks\">Coinmonks<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>","protected":false},"excerpt":{"rendered":"<p>Oasis introduced the framework for runtime off-chain logic ( ROFL) to help build and run apps off-chain while ensuring privacy and maintaining trust with on-chain verifiability. There are many moving parts to building with ROFL. In this tutorial, I will demonstrate how to build a tiny TypeScript app, generating a secp256k1 key inside ROFL. It [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":136926,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-136925","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-interesting"],"_links":{"self":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/136925"}],"collection":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=136925"}],"version-history":[{"count":0,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/136925\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/media\/136926"}],"wp:attachment":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=136925"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=136925"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=136925"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}