Migrating from viem
Method-by-method mapping from viem clients to Oviato's evm.* namespace, plus adapters for the response-envelope pattern.
If you're comfortable with viem, you'll find Oviato's EVM surface shaped almost identically — method names, parameter keys, and return shapes all match the viem convention where possible.
The one-minute map
| viem | Oviato | Notes |
|---|---|---|
publicClient.readContract | evm.readContract | identical params |
publicClient.multicall | evm.multicall | identical — Multicall3 default |
publicClient.getBalance | getBalance (top level) | chain-agnostic return |
publicClient.getBlockNumber | evm.getBlockNumber | |
publicClient.getBlock | evm.getBlock | |
publicClient.getTransaction | evm.getTransaction | |
publicClient.getTransactionReceipt | evm.getTransactionReceipt | |
publicClient.getTransactionCount | evm.getTransactionCount | pending is tracker-adjusted |
publicClient.getCode | evm.getCode | |
publicClient.getStorageAt | evm.getStorageAt | |
publicClient.getGasPrice | evm.getGasPrice | |
publicClient.estimateGas | evm.estimateGas | |
publicClient.estimateFeesPerGas | evm.estimateFeesPerGas | |
publicClient.estimateMaxPriorityFeePerGas | evm.estimateMaxPriorityFeePerGas | |
publicClient.getFeeHistory | evm.getFeeHistory | |
publicClient.waitForTransactionReceipt | evm.waitForTransactionReceipt | |
publicClient.verifyMessage | evm.verifyMessage | |
publicClient.verifyTypedData | evm.verifyTypedData | |
walletClient.sendTransaction | evm.sendTransaction | returns envelope, not hash |
walletClient.writeContract | evm.writeContract | returns envelope, not hash |
walletClient.signTypedData | evm.signTypedData | returns envelope, not signature |
walletClient.signMessage | signMessage (top level) | chain-agnostic |
walletClient.switchChain | evm.switchChain | also exported as switchNetwork |
walletClient.watchAsset | — | not implemented; use evm.getTokens |
publicClient.simulateContract | — | v2 roadmap |
publicClient.watchBlocks | — | v2 roadmap |
publicClient.watchEvent | — | v2 roadmap |
Clients vs namespace
viem:
const publicClient = createPublicClient({ chain: mainnet, transport: http() });
const walletClient = createWalletClient({ chain: mainnet, transport: custom(window.ethereum) });
const balance = await publicClient.getBalance({ address });
const hash = await walletClient.sendTransaction({ to, value });Oviato:
import { evm } from '@oviato/connect';
// No client to create — the namespace IS the client. Chain comes from the user's session.
const balance = await getBalance({ address }); // chain-agnostic
const r = await evm.sendTransaction({ to, value });Oviato's chain is controlled by the user (via switchNetwork), not by the
developer. You don't pass chain: on every call — the session already
knows. This is deliberate: multi-chain UX without the dev having to juggle
configs.
The envelope adapter
The biggest DX difference: Oviato returns a discriminated envelope, viem returns a raw value and throws on error.
try {
const hash = await walletClient.sendTransaction({ to, value });
console.log('Hash:', hash);
} catch (err) {
console.error('Failed:', err);
}const r = await evm.sendTransaction({ to, value });
if (r.status === 'success' && r.type === 'sendTransactionSuccess') {
console.log('Hash:', r.data.hash);
} else if (r.status === 'error') {
console.error('Failed:', r.message);
}// One-line adapter — drop-in replacement for viem-style code
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');
}
const { hash } = await unwrap(evm.sendTransaction({ to, value }));We keep the envelope by default because it makes "user cancelled popup" a first-class state instead of an exception — far easier to handle in UI code. Use the adapter if your codebase assumes the viem style.
ABI compatibility
viem's Abi type and ours are structurally compatible. Pass viem ABIs verbatim:
import { erc20Abi } from 'viem';
import { evm } from '@oviato/connect';
const balance = await evm.readContract<bigint>({
address: USDC,
abi: erc20Abi,
functionName: 'balanceOf',
args: [address],
});Address + Hex types
Identical:
// viem
import type { Address, Hex } from 'viem';
// Oviato
import type { Address, Hex } from '@oviato/connect';Both resolve to the TypeScript template literal type for 0x-prefixed hex strings.
bigint conventions
Identical — wei is always bigint, overflow-safe.
const value = 10n ** 18n; // 1 ETH
// parseUnits / formatUnits match viem's semantics exactly
import { parseUnits, formatUnits } from '@oviato/connect';
parseUnits('1.5', 18); // 1500000000000000000n
formatUnits(1_234_500n, 6); // '1.2345'Chains
viem has explicit chain objects (mainnet, base, sepolia). Oviato uses string slugs on the session.
| viem chain | Oviato Network |
|---|---|
mainnet | Network.ETHMAINNET ('eth') |
sepolia | Network.ETHSEPOLIA ('ethsepolia') |
base | Network.BASEMAINNET ('base') |
baseSepolia | Network.BASESEPOLIA ('basesepolia') |
You don't pass the chain on each call — it's implicit from the session. Switch via switchNetwork(Network.X).
What Oviato adds over viem
- Account custody. Oviato manages keys via WebAuthn passkeys — no private-key juggling, no seed phrase UX.
- Server-side nonce tracker. Rapid-fire sends just work; no "pending count lag" race.
- Specialized confirmation sheets.
writeContractwith an ERC-20 ABI shows a "Transfer Token" sheet, not raw calldata. - Bridge UI. The popup/iframe handles approval — you don't ship wallet UI.
- Multi-chain session. Same API on Bitcoin + EVM via
sendTransfer/signMessage/getBalance. - Curated token registry.
evm.getTokens()returns canonical ERC-20s per chain, server-maintained.
What Oviato doesn't do (yet)
simulateContract— v2. For now, callreadContractwith the same ABI to preview.watchBlocks/watchEvent/ subscriptions — v2. Poll viagetBlockNumber+getLogsif you need it now.- Local ABI encoding. The SDK ships ABIs to the server for encoding. If you need pure-client encoding, wrap viem's
encodeFunctionDataand pass the result asdata:tosendTransaction. - Custom chains not in our supported list. See EVM Overview → Supported networks.
Side-by-side: a typical swap
viem
import { createWalletClient, http, parseEther } from 'viem';
import { mainnet } from 'viem/chains';
const walletClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum!),
});
const hash = await walletClient.sendTransaction({
to: '0x742d35Cc...',
value: parseEther('0.01'),
});
const rx = await publicClient.waitForTransactionReceipt({ hash });Oviato
import { evm, parseUnits } from '@oviato/connect';
const r = await evm.sendTransaction({
to: '0x742d35Cc...',
value: parseUnits('0.01', 18),
});
if (r.status !== 'success') return;
const rx = await evm.waitForTransactionReceipt({ hash: r.data.hash });No client to set up, no wallet to connect — the session handles it.
Related
- EVM Overview — the mental model
- Migrating from ethers — if you're coming from ethers v5/v6 instead