Chain-Agnostic Overview
Methods that work identically across Bitcoin + EVM with one API. Oviato's unique value proposition.
This is what Oviato does that no other wallet SDK does cleanly: one API that works across chain families. Same function names, same return shapes — the SDK routes by the session's active network.
import { signMessage, sendTransfer, switchNetwork, getBalance } from '@oviato/connect';
// Same code, different chain each run
const bal = await getBalance({ address: session.address });
await signMessage('Hello from Oviato');
await sendTransfer({ recipient: { address, amount } });No branches on network === 'eth' vs network === 'bitcoin'. The SDK dispatches internally.
What's chain-agnostic
| Method | Works on | Dispatches by |
|---|---|---|
signMessage | BTC + EVM | session network |
sendTransfer | BTC + EVM (native only) | session network |
switchNetwork | any → any | target network |
getBalance | BTC + EVM (via rpc) | session network |
Auth: login, register, authenticate | any | n/a — WebAuthn is universal |
Session: getSession, clearSession, isAuthenticated | any | n/a |
What's chain-specific (lives elsewhere)
When you're doing something that genuinely only works on one chain family, use the dedicated namespace:
The namespaces exist precisely so you don't accidentally call an EVM method on a Bitcoin session. Runtime errors become impossible compile-time mistakes.
The dispatch model
Every chain-agnostic method inspects the session's network and chooses the right backend path:
sendTransfer({ recipient })
↓
session.network
├─ 'bitcoin' / 'bitcointestnet' → UTXO prepare path (PSBT, fee_rate, multi-recipient supported)
└─ 'eth' / 'base' / 'basesepolia' / 'ethsepolia' → EVM prepare path (nonce, gas, value as wei)Return shapes are aligned where possible:
sendTransfer→{ txid, network, explorerUrl }— same across both chain familiesgetBalance→ discriminated union:{ chainType: 'utxo', raw: number, confirmed, unconfirmed, ... }OR{ chainType: 'evm', raw: bigint, ... }so your code narrows explicitly
Amount unit conventions
Different chains have different native units. The SDK uses whatever's overflow-safe for the chain:
| Chain family | Unit | Type |
|---|---|---|
| Bitcoin + other UTXO | satoshis (1 BTC = 10⁸ sats) | number |
| Ethereum + EVM | wei (1 ETH = 10¹⁸ wei) | bigint |
number overflows past 2⁵³−1 (~9×10¹⁵). UTXO amounts never get that
big (max BTC supply × 10⁸ ≈ 2.1×10¹⁵). EVM amounts easily exceed it, so
we use bigint there. sendTransfer accepts both — pass number or
bigint based on the active chain.
// Bitcoin — satoshis as number
await sendTransfer({
recipient: { address: 'bc1q...', amount: 10000 }, // 0.0001 BTC
});
// Ethereum — wei as bigint
await sendTransfer({
recipient: { address: '0x...', amount: 10n ** 15n }, // 0.001 ETH
});When "chain-agnostic" isn't quite true
A few caveats worth calling out:
sendTransferon EVM sends native coin (ETH/Base ETH/etc.) only. For ERC-20 / NFT / contract calls, useevm.writeContract.sendTransferwith multiple recipients works only on UTXO (Bitcoin supports multi-output natively; EVM doesn't in one tx).signMessageproduces different signature formats: EIP-191 on EVM, Bitcoin message signing on UTXO. Verification needs to know which.
When to use chain-agnostic vs chain-specific
Chain-agnostic when you're writing code that should work across chain families — a generic "connect + send" UI, a faucet, a multi-chain dashboard.
Chain-specific when you're doing something that genuinely only makes sense for one chain — a DeFi app (EVM), a PSBT-heavy Bitcoin flow, a DEX swap.
Mixing is fine:
import { getBalance, signMessage, evm } from '@oviato/connect';
async function sendUsdcWithAuthProof(amount: bigint, recipient: `0x${string}`) {
// Chain-agnostic: fetch balance for a pre-check
const { balance } = await getBalance({ address: session.address });
// Chain-agnostic: sign an auth message for off-chain logging
await signMessage(`Authorize transfer of ${amount} at ${Date.now()}`);
// Chain-specific: the actual USDC transfer (EVM-only)
return evm.writeContract({
address: USDC,
abi: ERC20_ABI,
functionName: 'transfer',
args: [recipient, amount],
});
}