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.

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-ethers

The 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.deriveWalletsraw @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

Capabilitywative-coreWeb3.jsviemethers@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✓—partialpartialmanual
One-call transfer (intent → build → sign → send → track)✓————
ERC-20 / SPL transfer from one intent✓partialpartialpartialpartial
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.

Operationwative-coreWeb3.jsviemethers
Wallet from mnemonicws.accounts.create(name, pw, m) → acc.deriveWallets(N) → wallet.evm— (use ethers/hdkey)mnemonicToAccount(m)Wallet.fromPhrase(m)
Wallet from private keyws.accounts.create(name, pw, pk, undefined, { kind: "PK" }) → wallet.evmweb3.eth.accounts.privateKeyToAccount(pk)privateKeyToAccount(pk)new Wallet(pk)
Sign messagewallet.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_v4walletClient.signTypedData({...})wallet.signTypedData(domain, types, value)
Sign raw 32-byte digestwallet.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 transferwallet.evm.transfer({ to, amount, asset: { address } })erc20.methods.transfer(to, amt).send({ from })walletClient.writeContract({ ...'transfer' })erc20.transfer(to, amt)
Encode calldatanew Contract(name, abi).encode(fn, args)web3.eth.abi.encodeFunctionCall(item, args)encodeFunctionData({ abi, functionName, args })iface.encodeFunctionData(fn, args)
Decode return datanew 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 balancenew ChainClient(net).readBalance(owner)web3.eth.getBalance(addr)publicClient.getBalance({ address })provider.getBalance(addr)
ERC-20 balancenew 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 pricenew ChainClient(net).getGasPrice()web3.eth.getGasPrice()publicClient.getGasPrice()provider.getFeeData()
Estimate gas / feesauto (filled by buildTransaction / send)web3.eth.estimateGas(tx)publicClient.estimateGas(tx)provider.estimateGas(tx)
Tx receipt / waitawait …transfer(...).whenConfirmed() · ChainClient.getTransactionReceipt(h)web3.eth.getTransactionReceipt(h)publicClient.waitForTransactionReceipt({ hash })tx.wait()
Contract bytecodenew 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.

Operationwative-core@solana/web3.js (+ spl-token)
Keypair from mnemonicws.accounts.create(name, pw, m) → acc.deriveWallets(N) → wallet.svmderive seed (ed25519-hdkey) → Keypair.fromSeed
Keypair from secretws.accounts.create(name, pw, pk, undefined, { kind: "PK" }) → wallet.svmKeypair.fromSecretKey(bytes)
Sign byteswallet.svm.signBytes(bytes)nacl.sign.detached(msg, sk)
Native SOL transferwallet.svm.transfer({ to, amount })SystemProgram.transfer(...) + sendAndConfirmTransaction
SPL transferwallet.svm.transfer({ to, amount, asset: { address: mint } }) · or buildTransaction({ recipient, amount, tokenMint })spl-token createTransferInstruction(...) + send
Arbitrary instructionswallet.svm.buildTransaction({ instructions }) (pass web3.js instructions straight in)new TransactionInstruction(...)
Recent blockhashauto (filled by buildTransaction) · ChainClient.getRecentBlockhash()connection.getLatestBlockhash()
Account infonew ChainClient(net).getAccountInfo(address)connection.getAccountInfo(pk)
Signature statusChainClient.getSignatureStatus(sig) · await …transfer(...).whenConfirmed()connection.getSignatureStatuses([sig])
SPL balancenew 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",
          },
        ],
      }],
    },
  },
];

Last updated on