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