Skip to main content

Migrating an Ethereum dApp to OXN

Most Ethereum dApps run on OXN with minimal changes: two lines of compiler config, one Hardhat plugin, and explicit gas limits in your scripts. This page walks through the checklist and highlights the differences.

Overview

The migration is essentially:

  1. Compile with evmVersion: "paris".
  2. Install the OXN SDK.
  3. Wrap providers and signers for encrypted transactions.
  4. Set gas limits explicitly.
  5. Redeploy.

Nothing about your Solidity source has to change. Standard ERC-20, ERC-721, ERC-1155, DEX, and lending patterns all work.

1. Compiler settings

Update hardhat.config.js:

module.exports = {
solidity: {
version: "0.8.24",
settings: {
evmVersion: "paris", // required for OXN
optimizer: { enabled: true, runs: 200 },
},
},
networks: {
oxn_testnet: {
url: "https://rpc.bout.network",
chainId: 186,
accounts: [process.env.PRIVATE_KEY],
},
},
};

For Foundry, foundry.toml:

[profile.default]
evm_version = "paris"

2. Install the SDK

npm install --save-dev @oasisprotocol/sapphire-hardhat
npm install ethers@^6.16.0 @oasisprotocol/sapphire-ethers-v6

Add to hardhat.config.js:

require("@nomicfoundation/hardhat-toolbox");
require("@oasisprotocol/sapphire-hardhat"); // AFTER toolbox

3. Wrap providers and signers

In deployment and interaction scripts, wrap for encryption:

import { ethers } from "ethers";
import { wrapEthersProvider, wrapEthersSigner } from "@oasisprotocol/sapphire-ethers-v6";

const raw = new ethers.JsonRpcProvider("https://rpc.bout.network");
const provider = wrapEthersProvider(raw);
const signer = wrapEthersSigner(new ethers.Wallet(PRIVATE_KEY, raw));

For Hardhat scripts using hre.ethers, the Hardhat plugin does this automatically — no code change needed.

4. Set gas limits explicitly

Every transaction needs an explicit gasLimit:

// Before (Ethereum): estimateGas handled implicitly
await token.transfer(recipient, amount);

// After (OXN): explicit gasLimit
await token.transfer(recipient, amount, { gasLimit: 500_000n });

Common values: see Gas and Fees.

5. Redeploy

Contract addresses on Ethereum do not carry over to OXN. Deploy fresh:

npx hardhat run scripts/deploy.js --network oxn_testnet

Persist addresses in a deployments/ folder — see Deploying Contracts.

Confidentiality — what changes for users

For a straight port (no confidentiality features added), your users get:

  • Encrypted calldata — the arguments to their transactions are no longer public.
  • Encrypted events — logs from their transactions are only visible to them.
  • Same UX otherwise — MetaMask flow, gas costs, transaction times are nearly identical to Ethereum.

To take advantage of confidentiality, add features that only make sense in a confidential setting:

  • Hide balances in an ERC-20 (see Building a Confidential ERC-20).
  • Add sealed-bid auction mechanics.
  • Use Sapphire.randomBytes for fair randomness in games.

Testing

Local Hardhat tests continue to work for logic. Add integration tests against OXN Testnet for encryption paths — see Testing Confidential Behavior.

What breaks (unlikely but check for)

  • Contracts using PUSH0 — fix with evmVersion: "paris".
  • Contracts using TLOAD/TSTORE — Cancun-era transient storage isn't supported. Refactor to normal storage.
  • Contracts using BLOBHASH — not supported; OXN has no blobs.
  • Contracts relying on PREVRANDAO for randomness — replace with Sapphire.randomBytes.
  • Scripts using estimateGas — replace with explicit gasLimit.

Migration checklist

  • evmVersion: "paris" in compiler config
  • @oasisprotocol/sapphire-hardhat installed and required in order
  • Deployment scripts have explicit gasLimit
  • Interaction scripts wrap providers/signers
  • Integration tests hit real OXN, not Hardhat network
  • Contracts redeployed
  • Addresses persisted in deployments/
  • Frontend chain ID updated to 186 (or parameterized)
  • Frontend RPC URL updated to https://rpc.bout.network
  • Wallet integration tested with MetaMask + OXN Testnet

Next steps