Oasis wprowadził framework dla logiki off-chain w czasie wykonywania (ROFL), aby pomóc w budowaniu i uruchamianiu aplikacji off-chain, zapewniając jednocześnie prywatność i utrzymując zaufanie do on-chainOasis wprowadził framework dla logiki off-chain w czasie wykonywania (ROFL), aby pomóc w budowaniu i uruchamianiu aplikacji off-chain, zapewniając jednocześnie prywatność i utrzymując zaufanie do on-chain

Przewodnik po generowaniu kluczy Cross-Chain (EVM / Base) z Oasis ROFL

2026/02/20 21:16
9 min. lektury
W przypadku uwag lub wątpliwości dotyczących niniejszej treści skontaktuj się z nami pod adresem crypto.news@mexc.com

Oasis wprowadził framework dla logiki off-chain w czasie wykonywania (ROFL), aby pomóc w tworzeniu i uruchamianiu aplikacji poza łańcuchem, zapewniając jednocześnie prywatność i utrzymując zaufanie dzięki weryfikowalności on-chain. Budowanie z ROFL obejmuje wiele elementów.
W tym tutorialu pokażę, jak zbudować małą aplikację TypeScript, generującą klucz secp256k1 wewnątrz ROFL. Będzie ona wykorzystywać @oasisprotocol/rofl-client TypeScript SDK, który komunikuje się z appd REST API pod spodem. Aplikacja TypeScript będzie również:

Będzie prosty smoke test, który wypisuje do logów.

Wymagania wstępne

Aby wykonać kroki opisane w tym przewodniku, będziesz potrzebować:

  • Node.js 20+ i Docker (lub Podman)
  • Oasis CLI i minimum 120 tokenów TEST w swoim portfelu (Oasis Testnet faucet)
  • Trochę testowego ETH Base Sepolia (Base Sepolia faucet)

Szczegóły konfiguracji można znaleźć w dokumentacji dotyczącej Wymagań wstępnych Quickstart.

Init App

Pierwszym krokiem jest zainicjowanie nowej aplikacji przy użyciu Oasis CLI.

oasis rofl init rofl-keygen
cd rofl-keygen

Create App

W momencie tworzenia aplikacji w Testnet będziesz musiał zdeponować tokeny. Przypisz 100 tokenów TEST w tym momencie.

oasis rofl create --network testnet

Jako wynik, CLI wygeneruje App ID, oznaczony przez rofl1….

Init projektu Hardhat (TypeScript)

Teraz jesteś gotowy, aby rozpocząć projekt.

npx hardhat init

Ponieważ prezentujemy aplikację TypeScript, wybierz TypeScript po wyświetleniu monitu, a następnie zaakceptuj wartości domyślne.
Kolejnym krokiem będzie dodanie małych zależności runtime do użytku poza Hardhat.

npm i @oasisprotocol/rofl-client ethers dotenv @types/node
npm i -D tsx

Szablon TypeScript Hardhat automatycznie tworzy tsconfig.json. Musimy dodać mały skrypt, aby kod aplikacji mógł się skompilować do dist/.

// tsconfig.json
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}

Struktura aplikacji

W tej sekcji dodamy kilka małych plików TS i jeden kontrakt Solidity.

src/
├── appd.ts # cienka warstwa nad @oasisprotocol/rofl-client
├── evm.ts # pomocniki ethers (provider, wallet, tx, deploy)
├── keys.ts # małe pomocniki (checksum)
└── scripts/
├── deploy-contract.ts # ogólny skrypt wdrażania dla skompilowanych artefaktów
└── smoke-test.ts # demo end-to-end (logi)
contracts/
└── Counter.sol # przykładowy kontrakt

  1. src/appd.ts — cienka warstwa nad SDK Tutaj będziesz musiał użyć oficjalnego klienta do komunikacji z appd (gniazdo UNIX). Będziemy również potrzebować jawnego local‑dev fallback podczas uruchamiania poza 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();
}
/**
* Generuje (lub deterministycznie ponownie wyprowadza) klucz secp256k1 wewnątrz ROFL i
* zwraca go jako ciąg hex z prefiksem 0x (dla ethers.js Wallet).
*
* TYLKO rozwój lokalny (poza ROFL): Jeśli gniazdo jest brakujące i ustawisz
* ALLOW_LOCAL_DEV=true i LOCAL_DEV_SK=0x<64-hex>, ta wartość jest używana.
*/
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 — pomocniki 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 — małe pomocniki

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 — pojedynczy przepływ end‑to‑end

To ważny krok, ponieważ ten skrypt ma wiele funkcji:

  • wypisz App ID (wewnątrz ROFL), adres i podpisaną wiadomość
  • czekaj na finansowanie
  • wdróż kontrakt licznika

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);
// UWAGA: To demo ufa skonfigurowanemu dostawcy RPC. W produkcji preferuj
// lekkiego klienta (na przykład Helios), abyś mógł weryfikować zdalny stan łańcucha.
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 — minimalny przykład

// 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 — ogólny 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");
/**
* Użycie:
* npm run deploy-contract -- ./artifacts/MyContract.json '[arg0, arg1]'
* Artefakt musi zawierać { 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);
// UWAGA: To demo ufa skonfigurowanemu dostawcy RPC. W produkcji preferuj
// lekkiego klienta (na przykład Helios), abyś mógł weryfikować zdalny stan łańcucha.
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 (tylko kontrakty)

Na tym etapie będziemy potrzebować minimalnej konfiguracji do skompilowania 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;

Warto zauważyć, że lokalna kompilacja jest opcjonalna, więc możesz ją pominąć, jeśli chcesz. Następnym krokiem jest wybór — możesz usunąć istniejący plik contracts/Lock.sol lub zaktualizować go do Solidity wersji 0.8.24.

npx hardhat compile

Konteneryzacja

To istotny krok. Tutaj musisz utworzyć Dockerfile, który buduje TS i kompiluje kontrakt. Plik uruchomi również smoke test raz, a następnie będzie bezczynny, podczas gdy sprawdzasz logi.

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"]

Następnie musisz zamontować gniazdo appd dostarczone przez ROFL. Zapewniam, że żadne publiczne porty nie są eksponowane w tym procesie.

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

Budowanie obrazu

Ważne jest, aby pamiętać, że ROFL działa tylko na sprzęcie z włączoną technologią Intel TDX. Więc jeśli kompilujesz obrazy na innym hoście, takim jak macOS, to przekazanie parametru — platform linux/amd64 jest niezbędnym dodatkowym krokiem.

docker buildx build --platform linux/amd64 \
-t docker.io/YOURUSER/rofl-keygen:0.1.0 --push .

Interesującym punktem do zauważenia jest to, że możesz wybrać dodatkowe bezpieczeństwo i weryfikowalność. Wystarczy przypąć digest i użyć image: …@sha256:… w compose.yaml.

Budowanie pakietu ROFL

Jest krok, który musisz wykonać przed uruchomieniem polecenia oasis rofl build. Ponieważ budowanie segmentu obrazu następuje po konteneryzacji, będziesz musiał zaktualizować services.demo.image w compose.yaml do obrazu, który zbudowałeś.
W przypadku prostych projektów TypeScript, takich jak ten, czasami istnieje możliwość, że rozmiar obrazu jest większy niż oczekiwano. Zaleca się zatem aktualizację sekcji rofl.yaml resources do co najmniej: memory: 1024 i storage.size: 4096.
Teraz jesteś gotowy.

oasis rofl build

Następnie możesz opublikować tożsamości enclave i konfigurację.

oasis rofl update

Wdrożenie

To dość łatwy krok, w którym wdrażasz do dostawcy Testnet.

oasis rofl deploy

End‑to‑end (Base Sepolia)

To proces 2-etapowy, chociaż drugi krok jest opcjonalny.
Najpierw przeglądasz logi smoke‑test.

oasis rofl machine logs

Jeśli do tej pory wykonałeś wszystkie kroki poprawnie, zobaczysz w wyniku:

  • App ID
  • Adres EVM i podpisaną wiadomość
  • Monit o dofinansowanie adresu
  • Po zakończeniu finansowania, wdrożenie Counter.sol

Następnie, lokalny dev. Tutaj musisz uruchomić npm run build:all, aby skompilować kod TypeScript i kontrakt Solidity. Pomiń ten krok, jeśli nie jest potrzebny.

export ALLOW_LOCAL_DEV=true
export LOCAL_DEV_SK=0x<64-hex-dev-secret-key> # NIE UŻYWAJ W PRODUKCJI
npm run smoke-test

Bezpieczeństwo i uwagi do zapamiętania

  • Logi dostawcy nie są szyfrowane w spoczynku. Więc nigdy nie loguj kluczy tajnych.
  • Gniazdo appd /run/rofl-appd.sock istnieje tylko wewnątrz ROFL.
  • W publicznych RPC mogą występować limity szybkości. Zaleca się wybranie dedykowanego adresu URL Base RPC.

Na GitHubie Oasis znajduje się demo generowania klucza, do którego możesz się odnieść jako przykład tego tutoriala. https://github.com/oasisprotocol/demo-rofl-keygen

Teraz, gdy pomyślnie wygenerowałeś klucz w ROFL za pomocą appd, podpisałeś wiadomości, wdrożyłeś kontrakt i przeniosłeś ETH na Base Sepolia, daj nam znać w sekcji komentarzy swoją opinię. Aby szybko porozmawiać z zespołem inżynieryjnym Oasis w celu uzyskania pomocy w konkretnych problemach, możesz zostawić swoje komentarze w kanale dev-central na oficjalnym Discordzie.

Pierwotnie opublikowano na https://dev.to 20 lutego 2026.


Guide To Cross-Chain Key Generation (EVM / Base) With Oasis ROFL został pierwotnie opublikowany w Coinmonks na Medium, gdzie ludzie kontynuują rozmowę, podkreślając i reagując na tę historię.

Okazja rynkowa
Logo CROSS
Cena CROSS(CROSS)
$0.06588
$0.06588$0.06588
-1.94%
USD
CROSS (CROSS) Wykres Ceny na Żywo
Zastrzeżenie: Artykuły udostępnione na tej stronie pochodzą z platform publicznych i służą wyłącznie celom informacyjnym. Niekoniecznie odzwierciedlają poglądy MEXC. Wszystkie prawa pozostają przy pierwotnych autorach. Jeśli uważasz, że jakakolwiek treść narusza prawa stron trzecich, skontaktuj się z crypto.news@mexc.com w celu jej usunięcia. MEXC nie gwarantuje dokładności, kompletności ani aktualności treści i nie ponosi odpowiedzialności za jakiekolwiek działania podjęte na podstawie dostarczonych informacji. Treść nie stanowi porady finansowej, prawnej ani innej profesjonalnej porady, ani nie powinna być traktowana jako rekomendacja lub poparcie ze strony MEXC.

$30,000 in PRL + 15,000 USDT

$30,000 in PRL + 15,000 USDT$30,000 in PRL + 15,000 USDT

Deposit & trade PRL to boost your rewards!