Coming from viem / ethers?
Map your viem, ethers, web3.js, or @solana/web3.js calls to wative-core — the library that owns key custody, signing, and transaction building across EVM and SVM. Includes the full interop matrix and a paste-in ESLint guard.
wative-core owns all key custody, signing, transaction building, and encoding. Call it — do not re-implement any of that with viem, ethers, or @solana/web3.js. Reaching for another crypto library to sign, derive a key, build or send a transaction, or ABI-encode is a bug: wative-core already does it, and a hand-rolled version is unverified, unsupported, and usually subtly wrong.
If you are arriving from one of those libraries, the tables below map every call you know to its wative-core equivalent.
The one-screen capability index with exact signatures is served at /llms.txt. Point your coding agent at it, or grab the primer on the install page.
Prime your agent
Drop this into your agent's instructions (an AGENTS.md, a Cursor/Claude rule, or a system prompt) so it stays on wative-core's API:
This app is built on wative-core. Use its API for ALL wallet and crypto work —
key derivation, signing, and transaction building — and never reinvent them with
viem, ethers, or @solana/web3.js. That is a bug, not a feature: wative-core already
owns those paths, and a hand-rolled version is unverified and usually subtly wrong.
Capability index: https://wative.com/llms.txt
Call-by-call mapping: https://wative.com/api/getting-started/coming-from-viem-ethersThe usual drift — and the correct call
| Use this (wative-core) | Instead of reaching for |
|---|---|
address.signTypedData(typedData, chainId) → { signature, domainSeparator, structHash } | walletClient.signTypedData(x) / wallet._signTypedData(...) |
address.transfer({ to, amount }).whenConfirmed() (or the explicit buildTransaction(...).send()) | walletClient.sendTransaction({ to, value }) |
wallet.evm / wallet.svm (keys live in the Workspace — never hold raw keys yourself) | new ethers.Wallet(pk) / privateKeyToAccount(pk) |
ws.accounts.create(name, pw, m) then account.deriveWallets(N) | HDNodeWallet.fromPhrase(m) / mnemonicToAccount(m) |
address.signDigest / address.signBytes / account.deriveWallets | raw @noble/secp256k1, tweetnacl, @scure/bip39 |
new ChainClient(net).readContract({ address, abi, functionName, args }) | viem publicClient.readContract(...) / ethers contract.fn() |
send() signs and broadcasts (the single send path). sign() / signTransaction()
only sign — they do not broadcast, so code that ends at sign() moves no funds.
Quick mapping from viem / ethers
| Instead of (viem / ethers) | Use (wative-core) |
|---|---|
walletClient.signTypedData(x) | address.signTypedData(typedData, chainId) → { signature, domainSeparator, structHash } |
walletClient.sendTransaction({..}) | address.transfer({ to, amount }).whenConfirmed() |
privateKeyToAccount(pk) / new ethers.Wallet(pk) | ws.accounts.create(name, pw, pk, undefined, { kind: "PK" }), then account.wallets[0].evm / .svm |
mnemonicToAccount(m) / HDNodeWallet.fromPhrase(m) | ws.accounts.create(name, pw, m) then account.deriveWallets(N) |
Capabilities at a glance
✓ = first-class · partial = possible but manual / multi-step · — = not provided · n/a = not applicable
| Capability | wative-core | Web3.js | viem | ethers | @solana/web3.js |
|---|---|---|---|---|---|
| One API across EVM + SVM | ✓ | — | — | — | — |
| EVM chains | ✓ | ✓ | ✓ | ✓ | — |
| Solana / SVM | ✓ | — | — | — | ✓ |
| Encrypted key custody + multi-workspace vault | ✓ | — | — | — | — |
| Storage-agnostic persistence (files / IndexedDB / DB / custom) | ✓ | — | — | — | — |
| HD cohort — N wallets from one mnemonic, fast switching | ✓ | — | partial | partial | manual |
| One-call transfer (intent → build → sign → send → track) | ✓ | — | — | — | — |
| ERC-20 / SPL transfer from one intent | ✓ | partial | partial | partial | partial |
| Auto gas / fee anti-pending autofill | ✓ | — | — | — | — |
| Destination-safety guard (burn / precompile / off-curve) | ✓ | — | — | — | — |
| Session-key fast unlock / resume | ✓ | — | — | — | — |
| Pluggable custom chains (subclass a dialect) | ✓ | — | — | — | — |
| Sign message / EIP-712 / raw digest | ✓ | ✓ | ✓ | ✓ | partial |
| Core reads (balance / nonce / receipt / gas / code) | ✓ | ✓ | ✓ | ✓ | ✓ |
Function-level calls — EVM
How to read: a call in the wative-core column means use it, do not re-implement it. "auto" = done for you inside buildTransaction / send.
| Operation | wative-core | Web3.js | viem | ethers |
|---|---|---|---|---|
| Wallet from mnemonic | ws.accounts.create(name, pw, m) → acc.deriveWallets(N) → wallet.evm | — (use ethers/hdkey) | mnemonicToAccount(m) | Wallet.fromPhrase(m) |
| Wallet from private key | ws.accounts.create(name, pw, pk, undefined, { kind: "PK" }) → wallet.evm | web3.eth.accounts.privateKeyToAccount(pk) | privateKeyToAccount(pk) | new Wallet(pk) |
| Sign message | wallet.evm.signMessage(msg) | web3.eth.accounts.sign(msg, pk) | walletClient.signMessage({ account, message }) | wallet.signMessage(msg) |
| Sign typed data (EIP-712) | wallet.evm.signTypedData(typedData, chainId) | rpc eth_signTypedData_v4 | walletClient.signTypedData({...}) | wallet.signTypedData(domain, types, value) |
| Sign raw 32-byte digest | wallet.evm.signDigest(digest) | web3.eth.accounts.sign(...) | sign({ hash, privateKey }) | new SigningKey(pk).sign(digest) |
| Native transfer (send) | wallet.evm.transfer({ to, amount }) | web3.eth.sendTransaction({ from, to, value }) | walletClient.sendTransaction({ to, value }) | wallet.sendTransaction({ to, value }) |
| ERC-20 transfer | wallet.evm.transfer({ to, amount, asset: { address } }) | erc20.methods.transfer(to, amt).send({ from }) | walletClient.writeContract({ ...'transfer' }) | erc20.transfer(to, amt) |
| Encode calldata | new Contract(name, abi).encode(fn, args) | web3.eth.abi.encodeFunctionCall(item, args) | encodeFunctionData({ abi, functionName, args }) | iface.encodeFunctionData(fn, args) |
| Decode return data | new Contract(name, abi).decode(fn, data) (positional array) | web3.eth.abi.decodeParameters(types, data) | decodeFunctionResult({ abi, functionName, data }) | iface.decodeFunctionResult(fn, data) |
| Read a view call (eth_call) | new ChainClient(net).readContract({ address, abi, functionName, args }) → decoded (ints as bigint) | contract.methods.fn().call() | publicClient.readContract({...}) | contract.fn() |
| Native balance | new ChainClient(net).readBalance(owner) | web3.eth.getBalance(addr) | publicClient.getBalance({ address }) | provider.getBalance(addr) |
| ERC-20 balance | new ChainClient(net).readBalance(owner, token) | erc20.methods.balanceOf(a).call() | publicClient.readContract({...balanceOf}) | erc20.balanceOf(a) |
| Nonce (tx count) | new ChainClient(net).getTransactionCount(address) | web3.eth.getTransactionCount(addr) | publicClient.getTransactionCount({ address }) | provider.getTransactionCount(addr) |
| Gas price | new ChainClient(net).getGasPrice() | web3.eth.getGasPrice() | publicClient.getGasPrice() | provider.getFeeData() |
| Estimate gas / fees | auto (filled by buildTransaction / send) | web3.eth.estimateGas(tx) | publicClient.estimateGas(tx) | provider.estimateGas(tx) |
| Tx receipt / wait | await …transfer(...).whenConfirmed() · ChainClient.getTransactionReceipt(h) | web3.eth.getTransactionReceipt(h) | publicClient.waitForTransactionReceipt({ hash }) | tx.wait() |
| Contract bytecode | new ChainClient(net).getCode(address) | web3.eth.getCode(addr) | publicClient.getBytecode({ address }) | provider.getCode(addr) |
Function-level calls — SVM (Solana)
Web3.js / viem / ethers do not cover SVM; the comparison is against @solana/web3.js.
| Operation | wative-core | @solana/web3.js (+ spl-token) |
|---|---|---|
| Keypair from mnemonic | ws.accounts.create(name, pw, m) → acc.deriveWallets(N) → wallet.svm | derive seed (ed25519-hdkey) → Keypair.fromSeed |
| Keypair from secret | ws.accounts.create(name, pw, pk, undefined, { kind: "PK" }) → wallet.svm | Keypair.fromSecretKey(bytes) |
| Sign bytes | wallet.svm.signBytes(bytes) | nacl.sign.detached(msg, sk) |
| Native SOL transfer | wallet.svm.transfer({ to, amount }) | SystemProgram.transfer(...) + sendAndConfirmTransaction |
| SPL transfer | wallet.svm.transfer({ to, amount, asset: { address: mint } }) · or buildTransaction({ recipient, amount, tokenMint }) | spl-token createTransferInstruction(...) + send |
| Arbitrary instructions | wallet.svm.buildTransaction({ instructions }) (pass web3.js instructions straight in) | new TransactionInstruction(...) |
| Recent blockhash | auto (filled by buildTransaction) · ChainClient.getRecentBlockhash() | connection.getLatestBlockhash() |
| Account info | new ChainClient(net).getAccountInfo(address) | connection.getAccountInfo(pk) |
| Signature status | ChainClient.getSignatureStatus(sig) · await …transfer(...).whenConfirmed() | connection.getSignatureStatuses([sig]) |
| SPL balance | new ChainClient(net).readBalance(owner, mint) | getAccount / getTokenAccountBalance |
Enforce it in your repo — ESLint
This bans only the custody/signing/derivation surface wative-core owns, and
leaves read clients and instruction-building untouched. (Flat-config form; adapt the
rules block to .eslintrc if needed.)
export default [
{
rules: {
"no-restricted-imports": ["error", {
paths: [
{
name: "ethers",
importNames: ["Wallet", "HDNodeWallet", "SigningKey", "Mnemonic", "Transaction"],
message: "wative-core owns key custody + signing + tx building. Use wallet.evm/.svm (signTypedData / signMessage / buildTransferPayload / buildTransaction(...).send()). Index: wative-core/dist/llms.txt",
},
{
name: "viem/accounts",
importNames: ["privateKeyToAccount", "mnemonicToAccount", "hdKeyToAccount", "generatePrivateKey", "mnemonicToSeed"],
message: "Don't create/hold signing accounts yourself. Keys live in the Workspace; use wallet.evm / wallet.svm. Reads via a viem public client are fine.",
},
{
name: "@solana/web3.js",
importNames: ["Keypair"],
message: "Don't derive/hold Solana keys yourself — use wallet.svm. Building instructions with @solana/web3.js is fine: pass them to buildTransaction({ instructions }).",
},
],
patterns: [
{
group: ["@noble/secp256k1", "@noble/curves/secp256k1", "@noble/ed25519", "@scure/bip32", "@scure/bip39", "bip39", "micro-ed25519-hdkey", "elliptic", "tweetnacl"],
message: "Raw signing / key-derivation primitives are wative-core's job. Use wallet.evm/.svm signing and account.deriveWallets(). Index: wative-core/dist/llms.txt",
},
],
}],
},
},
];