Skip to main content

TypeScript / JavaScript SDK

The OXN TypeScript SDK wraps standard ethers.js and viem clients to automatically encrypt outgoing transactions and decrypt responses. This page is the reference for both wrapping patterns.

Package installation

npm install ethers@^6.16.0 @oasisprotocol/sapphire-ethers-v6@^6.0.1

Peer dependencies:

  • ethers v6.16 or later
  • Node 18 or later, or a modern browser

Older ethers v5 is not actively supported; migrate to v6 if you're on v5.

Wrapping a Provider (for reads)

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

const raw = new ethers.JsonRpcProvider("https://rpc.bout.network");
const provider = wrapEthersProvider(raw);

// All eth_call requests through this provider are encrypted
const network = await provider.getNetwork();
console.log("Chain ID:", network.chainId); // 186n

The wrapped provider transparently encrypts eth_call and decrypts the response. Standard ethers.js APIs work as expected.

Wrapping a Signer (for transactions)

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

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

// All transactions sent through this signer are encrypted
const tx = await signer.sendTransaction({
to: "0xRecipientAddress",
value: ethers.parseEther("0.1"),
gasLimit: 100_000n
});
await tx.wait();

Pass the unwrapped Wallet to wrapEthersSigner. The wrapper handles provider wrapping internally.

Working with contracts

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));

const abi = [ /* your ABI */ ];

// For reads (view functions, encrypted eth_call)
const readOnly = new ethers.Contract(address, abi, provider);
const value = await readOnly.someView();

// For writes and signed queries
const writable = new ethers.Contract(address, abi, signer);
await writable.someWrite(arg, { gasLimit: 500_000n });

// Signed queries — pass signer so msg.sender is populated inside view
const myBalance = await writable.myBalance();

Rule of thumb: use the signer-attached contract whenever you want msg.sender populated inside a view function. Provider-attached contracts do plain eth_call (with msg.sender = 0x0).

Deploying contracts

const factory = new ethers.ContractFactory(abi, bytecode, signer);
const contract = await factory.deploy(
...constructorArgs,
{ gasLimit: 3_000_000n }
);
await contract.waitForDeployment();
console.log("Deployed at:", await contract.getAddress());

Event listening

Events emitted on OXN are encrypted to the caller's session key. Listen with the wrapped provider to receive decrypted events:

const contract = new ethers.Contract(address, abi, provider);
contract.on("Transfer", (from, to, amount, event) => {
console.log("decrypted event:", from, to, amount);
});

Only events you caused (via calls from your session) will decrypt successfully. See Confidentiality Model for the caveat.

viem integration

If you use viem instead of ethers, there's a corresponding wrapper. Installation and API surface differ slightly but the wrapping model is the same. See the Oasis SDK for viem documentation for the latest API.

Common patterns

Detect if a signer is wrapped:

import { wrapEthersSigner } from "@oasisprotocol/sapphire-ethers-v6";
// Check by presence of the wrapped internal state — implementation-specific.
// Simpler: always wrap defensively; wrap-of-wrapped is a no-op.

Force plain calls when you need to:

const raw = new ethers.JsonRpcProvider("https://rpc.bout.network");
const rawSigner = new ethers.Wallet(PRIVATE_KEY, raw); // NOT wrapped
// Calls through rawSigner are plain — use for public reads or debugging.

Verify a transaction was encrypted:

const tx = await raw.getTransaction(txHash);
const firstByte = parseInt(tx!.data.slice(2, 4), 16);
if ((firstByte & 0xe0) !== 0xa0) {
throw new Error("Transaction was sent unencrypted — check wrapping");
}

Type safety with TypeChain

TypeChain generates typed contract bindings from ABIs:

npm install --save-dev typechain @typechain/ethers-v6
npx typechain --target ethers-v6 --out-dir types 'artifacts/**/*.json'

The generated types work with wrapped signers unchanged:

import { MyToken__factory } from "./types";
const token = MyToken__factory.connect(address, signer);
await token.transfer(recipient, amount, { gasLimit: 500_000n });

Next steps