Migrating from ethers
Method-by-method mapping from ethers v5/v6 to Oviato's evm.* namespace, with adapters for the response-envelope pattern.
Oviato borrows viem's naming for most methods. If you're on ethers, the mapping is still straightforward — every ethers method has a counterpart, and we ship aliases where the names differ.
Quick map
| ethers v5 / v6 | Oviato | Notes |
|---|---|---|
provider.getBlockNumber() | evm.getBlockNumber() | bigint return |
provider.getBlock(n) | evm.getBlock({ blockNumber: n }) | key-arg signature |
provider.getTransaction(hash) | evm.getTransaction({ hash }) | |
provider.getTransactionReceipt(hash) | evm.getTransactionReceipt({ hash }) | |
provider.getTransactionCount(addr) | evm.getTransactionCount({ address }) | |
provider.getCode(addr) | evm.getCode({ address }) | |
provider.getStorageAt(addr, slot) | evm.getStorageAt({ address, slot }) | |
provider.getGasPrice() | evm.getGasPrice() | |
provider.estimateGas(tx) | evm.estimateGas(tx) | |
provider.getFeeData() | evm.getFeeData() | alias of estimateFeesPerGas |
provider.getBalance(addr) | getBalance({ address }) | chain-agnostic, top-level |
provider.waitForTransaction(h, c, t) | evm.waitForTransaction(h, c, t) | positional alias |
contract.<fn>(args) (read) | evm.readContract({ abi, functionName, args }) | explicit form |
contract.<fn>(args) (write) | evm.writeContract({ abi, functionName, args }) | returns envelope |
Multicall contract.aggregate(...) | evm.multicall({ contracts }) | Multicall3 default |
signer.sendTransaction(tx) | evm.sendTransaction(tx) | returns envelope |
signer.signMessage(msg) | signMessage(msg) | chain-agnostic, top-level |
signer.signTypedData(d, t, v) | evm.signTypedData({ domain, types, primaryType, message }) | params bundled |
verifyMessage(msg, sig) | evm.verifyMessage({ address, message, signature }) | explicit address |
verifyTypedData(d, t, v, sig) | evm.verifyTypedData({ address, domain, types, primaryType, message, signature }) | |
formatUnits, parseUnits | formatUnits, parseUnits (top-level) | identical semantics |
formatEther, parseEther | use formatUnits(x, 18) / parseUnits(x, 18) | no dedicated export |
Provider / Signer — gone
Ethers is built around a Provider (reads) + Signer (writes) model. Oviato doesn't expose either — the session is the signer, and reads go through our backend RPC.
ethers v6
import { BrowserProvider, parseEther } from 'ethers';
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const balance = await provider.getBalance(address);
const tx = await signer.sendTransaction({ to, value: parseEther('0.01') });
await tx.wait();Oviato
import { evm, getBalance, parseUnits } from '@oviato/connect';
const { balance } = await getBalance({ address });
const r = await evm.sendTransaction({ to, value: parseUnits('0.01', 18) });
if (r.status === 'success') {
await evm.waitForTransactionReceipt({ hash: r.data.hash });
}Contract instance → explicit ABI calls
Ethers' Contract class wraps a contract with ABI-derived methods. Oviato's equivalent is passing the ABI to readContract / writeContract explicitly.
ethers
const usdc = new Contract(USDC_ADDRESS, ERC20_ABI, signer);
const bal = await usdc.balanceOf(address);
await usdc.transfer(to, amount);Oviato
const bal = await evm.readContract<bigint>({
address: USDC_ADDRESS,
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [address],
});
await evm.writeContract({
address: USDC_ADDRESS,
abi: ERC20_ABI,
functionName: 'transfer',
args: [to, amount],
});A touch more verbose per call, but it keeps the ABI at the call site — easier to audit + tree-shake.
If you miss the contract.fn() style, wrap it yourself:
function tokenContract(address: `0x${string}`, abi: readonly AbiItem[]) {
return new Proxy({}, {
get(_, fn: string) {
return (...args: unknown[]) =>
evm.readContract({ address, abi, functionName: fn, args });
},
});
}
const usdc = tokenContract(USDC_ADDRESS, ERC20_ABI);
const bal = await usdc.balanceOf(address); // works(Reads only — for writes you'd branch to evm.writeContract. Not recommended for production.)
The envelope adapter
Ethers methods throw on failure and return raw values. Oviato returns a discriminated envelope. Drop-in adapter:
async function unwrap<T extends { status: string; data?: any; message?: string }>(p: Promise<T>) {
const r = await p;
if (r.status === 'success') return r.data;
throw new Error(r.message ?? 'Unknown error');
}
// Now looks like ethers
const { hash } = await unwrap(evm.sendTransaction({ to, value }));BigNumber → bigint
Ethers v5 used BigNumber (custom class). Ethers v6 moved to native bigint. Oviato is bigint-native — no class wrappers, no .toBigInt() calls.
If you're on v5, the conversion is:
// v5
const b = BigNumber.from('1000000');
b.add(500).toBigInt();
// Oviato
const b = 1_000_000n;
b + 500n;Signatures: split vs joined
Ethers v5 had both split ({ r, s, v }) and joined (string) signatures depending on the API. Oviato returns joined hex strings (0x + 130 bytes) — matching v6 + viem.
// Oviato
const r = await evm.signTypedData({ domain, types, primaryType, message });
const sig = r.data.signature; // '0x...' (130 bytes)
// If you need split (for a permit() call)
const v = parseInt(sig.slice(130, 132), 16);
const r_ = `0x${sig.slice(2, 66)}`;
const s_ = `0x${sig.slice(66, 130)}`;signTypedData: domain/types/message bundle
Ethers passes domain, types, and value (ethers v5) or message (ethers v6) as three positional args. Oviato bundles them:
ethers v6
const sig = await signer.signTypedData(domain, types, message);Oviato
const r = await evm.signTypedData({
domain,
types,
primaryType: 'YourType',
message,
});Oviato requires primaryType explicitly. Ethers infers it from the single
top-level type in types. If your ethers code relied on that inference,
declare the primary type directly.
Also: don't include EIP712Domain in types. Oviato computes the domain hash from the domain object directly. Adding it manually changes the hash.
Chain switching
ethers doesn't have a standard chain-switch API — you'd call window.ethereum.request({ method: 'wallet_switchEthereumChain' }).
Oviato
import { switchNetwork, Network } from '@oviato/connect';
await switchNetwork(Network.BASEMAINNET);
// Also available as evm.switchChain(...) for familiarity
await evm.switchChain(Network.BASEMAINNET);Switching requires a passkey tap (server re-issues the JWT for the new chain). Different from ethers' UX where switching is silent.
What's different by design
- No custom Provider/Signer objects. The session is implicit global state. You don't thread a provider through your code.
- No local key management. Oviato holds keys via WebAuthn passkeys.
sendTransactiontriggers a bridge popup, not a local signer. - No ABI-level type inference. Ethers v6 types
contract.fn()return types from the ABI. Oviato'sreadContract<T>asks you to declare<T>explicitly — the ABI travels to the server for encoding. - Server-side nonce tracker. You can't override the nonce by default — the tracker handles it. Pass
nonce:explicitly if you really need to.
What Oviato adds over ethers
Same list as for viem — passkey custody, server-side nonce tracking, specialized bridge UI, multi-chain session, curated token registry.
Side-by-side: a typical tx flow
ethers v6
import { BrowserProvider, parseEther } from 'ethers';
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
try {
const tx = await signer.sendTransaction({
to: '0x742d35Cc...',
value: parseEther('0.01'),
});
const rx = await tx.wait();
console.log('Mined in block', rx?.blockNumber);
} catch (err) {
console.error('Tx failed', err);
}Oviato
import { evm, parseUnits } from '@oviato/connect';
const r = await evm.sendTransaction({
to: '0x742d35Cc...',
value: parseUnits('0.01', 18),
});
if (r.status === 'success') {
const rx = await evm.waitForTransactionReceipt({ hash: r.data.hash });
console.log('Mined in block', rx?.blockNumber);
} else {
console.error('Tx failed', r.message);
}Related
Migrating from viem
Method-by-method mapping from viem clients to Oviato's evm.* namespace, plus adapters for the response-envelope pattern.
UTXO Overview
Oviato's UTXO surface — PSBT signing for Bitcoin, scoped under utxo.*. Everything else lives at the top level (sendTransfer, signMessage, getBalance).