Encrypted vs Plain Calls
Every transaction and every eth_call on OXN is either encrypted or plain. This is a client-side decision, not a chain-side one. The chain accepts both. You should understand both, because sending a plain call when you meant to send an encrypted one is the single most common way apps accidentally leak data.
Default behavior by client
| Client | Default behavior |
|---|---|
Bare ethers.js (unwrapped) | Plain. Calldata on-chain in the clear. |
| ethers.js + OXN wrapper | Encrypted. Automatic X25519 + Deoxys-II end-to-end. |
Bare viem | Plain. |
| viem + OXN wrapper | Encrypted. |
Raw JSON-RPC (eth_sendRawTransaction) | Plain unless you crafted an encrypted envelope yourself. |
| MetaMask directly | Plain, until your dApp constructs the encrypted transaction. |
The rule: encryption never happens by accident on the client side. If you can't point to a specific wrapper or SDK function that encrypted the call, it went out in the clear.
What each mode looks like on-chain
Plain call: tx.data is the standard ABI encoding — 4-byte function selector followed by ABI-encoded arguments. Anyone reading the block sees transfer(0x…, 100) directly.
Encrypted call: tx.data is a CBOR envelope. Its first byte is in the range 0xa0–0xbf (CBOR map major type). The envelope contains the caller's ephemeral X25519 public key, a random nonce, and a Deoxys-II ciphertext. Nobody reading the block can extract the function selector or arguments.
You can verify which mode a transaction used by inspecting tx.data:
const tx = await provider.getTransaction(txHash);
const firstByte = parseInt(tx.data.slice(2, 4), 16);
const looksEncrypted = (firstByte & 0xe0) === 0xa0;
When to use each
Use encrypted for anything that must stay confidential. This is the default for most contract interactions on OXN. If in doubt, encrypt.
Use plain when:
- The call is inherently public (querying a public oracle, calling a plain relay contract).
- The receiving contract is not confidential and reading calldata is expected.
- You are debugging and want to inspect the exact bytes on-chain.
Use plain accidentally never. The most common mistake is initializing a Signer from raw ethers.js and forgetting to wrap it. The transaction goes through, and now sensitive arguments live in a public block forever.
Forcing encryption
import { ethers } from "ethers";
import { wrapEthersProvider, wrapEthersSigner } from "<oxn-ethers-wrapper>";
const raw = new ethers.JsonRpcProvider("https://rpc.bout.network");
const provider = wrapEthersProvider(raw);
const signer = wrapEthersSigner(new ethers.Wallet(PRIVATE_KEY, raw));
const contract = new ethers.Contract(addr, abi, signer);
await contract.transfer(recipient, amount); // Encrypted end-to-end
Every call through this wrapped signer is encrypted. No opt-in per function.
Forcing plain
Use the raw provider / signer without wrapping:
const raw = new ethers.JsonRpcProvider("https://rpc.bout.network");
const signer = new ethers.Wallet(PRIVATE_KEY, raw); // NOT wrapped
const contract = new ethers.Contract(addr, abi, signer);
await contract.publicMethod(...); // Plain
There is no in-between. A wrapped signer always encrypts; a bare signer always sends plain.
Common bugs
"My tests worked locally but leaked in production." Local Hardhat network is a plain EVM without a real enclave. Any test that "works" against Hardhat network says nothing about whether your production calls are encrypted. Always run integration tests against a real OXN network.
"I wrapped the provider but forgot the signer." wrapEthersProvider only encrypts read calls (eth_call). wrapEthersSigner encrypts transactions. You almost always need both.
"My library reconstructs a fresh signer internally." Some libraries create their own Wallet from a mnemonic under the hood. Even if you passed a wrapped signer in, the library might use its own bare one. Read the library source, or check that tx.data looks like a CBOR envelope after every transaction in your integration tests.
Next steps
- Encrypted Contract Storage — the other half of the story
- Signed Queries — authenticated encrypted reads