Software Development, Technology

Smart contract integration: test before users sign

By James KillickOctober 11, 2026

TL;DR: Smart contract integration comes down to four things: a wallet connection, the contract address with its ABI, a client library like viem, and a clean split between free reads and signed writes. Simulate every write before the wallet pops up. Version the ABI with the address. Test on a local chain, then a testnet, then mainnet.

Your app talks to a smart contract. The user clicks a button. Their wallet pops up. Money moves, or it doesn't.

That's the whole job, and it's where most dApp builds go wrong. Not in the contract. In the bit between the button and the chain.

Here's the short version. A frontend needs four things to talk to a contract:

  1. A wallet connection.
  2. The contract address and its ABI (the list of functions it offers).
  3. A client library, like viem or ethers.js, to encode the calls.
  4. A clear split between free reads and signed writes.

For most React builds, we use viem with wagmi, connected through MetaMask and WalletConnect. The rest of this post is the order we'd wire it in, and how we test each step before a user signs anything.

Step 1: Read state first

Reading is free. No gas. No signature. You call a view function and the node sends back a value.

Start here, because it proves your address, ABI and network all line up. If a read fails, nothing else will work.

  • Fetch the value. In viem, that's `readContract` with the address, ABI and function name. In wagmi, it's the `useReadContract` hook.
  • Format it. Chain values come back as big numbers. Use `formatUnits` with the token's decimals before you show them.
  • Cache it. Wrap reads in React Query or wagmi's hooks. Refetch on a user action, not on a timer.

Step 2: Simulate every write

A write changes state. It needs a signer, enough native currency for gas, and a correctly encoded transaction. The wallet asks the user to approve it first.

Here's the important bit. Simulate before you ask. A simulation runs the call without sending it, so you catch a revert before the user pays for it.

  1. Build the request with the function name and arguments.
  2. Simulate it (`simulateContract` in viem, or `eth_call` underneath).
  3. Ask the wallet to sign it.
  4. Wait for the receipt, then update your screen.

Skip step 2 and users hit the worst bug in dApps: a transaction that confirms and still fails. They pay the gas and get nothing.

One more tip. Re-simulate when a form input changes. Then the gas estimate stays true right up to the moment they sign.

Step 3: Connect wallets without surprises

Browser wallets like MetaMask follow the EIP-1193 standard. They sit at `window.ethereum`. Your app asks for access with `eth_requestAccounts`, and the familiar popup appears.

Phones are different. Many wallets don't inject anything into the page. Offer WalletConnect, with its QR flow, so desktop and mobile users both have a way in.

  • Listen for changes. Watch `accountsChanged` and `chainChanged`. Update your screen the moment either one fires.
  • Check the network. A wallet on the wrong chain causes more confusing failures than any other thing.
  • Use wagmi once you support two or more wallets. It holds the connection state in one place, so you're not copying provider logic into every component.

Step 4: Treat the ABI and address as one release

An ABI is a JSON list of every function and event a contract offers. Without the right one, your library can't turn a function name into the right call.

The trap is a redeploy. The contract gets a new address, and the app still points at the old one.

  • Version them together. Keep the ABI and address in the same repo or build artefact as the frontend.
  • Generate types from the ABI. Both viem and ethers can do it. A wrong function name then fails when you build, not when a user clicks.
  • Never copy the ABI in by hand. Make it part of your release pipeline. Our post on CI/CD for AI covers the same release discipline for AI changes.

Upgrades need extra care. Contracts can't be edited once deployed. So an upgrade means one of two things. Either you deploy a new contract and point the frontend at it, or you use a proxy pattern (transparent proxies or UUPS) where the address stays put and the logic changes.

With a proxy, the ABI can change between upgrades. Pull the ABI for the current implementation. Don't assume yesterday's still fits. A mismatch is the usual cause of the "reverted with empty data" error.

Step 5: Keep the screen in sync with events

Polling the chain works. Events are better. A `Transfer` event, for example, lets you refresh a balance the moment it changes.

  • Subscribe in the client. Use `useWatchContractEvent` in wagmi, or `contract.on` in ethers.js.
  • Use a backend for history. Scanning old logs in the browser doesn't scale. Send high-volume or historic queries through an indexer.
  • Poll only as a fallback. If event support is flaky, poll, and keep the gap long enough that you're not hammering your RPC endpoint.

Do you need a backend at all? Not for basic reads and writes. A client library can talk to the chain through the wallet. A backend earns its place when you need event history, webhooks or data pulled together from many contracts.

Step 6: Design for slow blocks

A block takes time to confirm. That gap is where most dApp experiences fall apart.

  1. Show the likely result first. Display the simulated outcome and gas estimate before the wallet prompt.
  2. Use real states. Idle, waiting for signature, pending, then confirmed or failed. One spinner for all of them confuses people.
  3. Be careful with optimistic updates. Use them only where a revert is truly unlikely. Otherwise show a pending placeholder until the receipt lands.
  4. Fail clearly. Link the transaction on a block explorer and give a retry button.

Map wallet errors to plain messages. MetaMask returns code 4001 when the user rejects a request (see the provider API docs). Show "Transaction cancelled", not a red "Failed" banner. Rejected, out of funds and wrong network each deserve their own line. People trust an app that tells them what happened.

Step 7: Test in three places

Before anything touches mainnet, run the full flow three times.

  1. On a local chain. Spin up Hardhat and iterate fast. The OpenZeppelin guide walks through deploying and interacting locally.
  2. With every write simulated. Confirm the reverts you expect actually revert.
  3. On a public testnet. Same flow, real network conditions.

Local chain, then testnet, then production. Same order as the contract itself. If you want a wider view on testing habits, our post on LLM unit testing covers a lot of the same thinking. And the contract itself? Read smart contract security: 4 verification steps before you deploy.

Build your own contract or use an existing one?

Build your own when the contract logic is your edge, or you need control over upgrades. Use an existing, audited contract when the job is standard, like moving a token.

Either way, the frontend work above is the same. If you're weighing up how blockchain fits with AI features, see blockchain and AI. For how this plugs into the rest of your stack, the post on the role of APIs in AI software is a good next read. Two more from our sister sites: AI system integration best practices and AI API integration gotchas.

Want this built for you?

We build the product around the contract, not just the contract. Wallet connection, read and write flows, events, and the testing that catches a bad ABI before your users do. That's app development with the blockchain parts handled.

Got a dApp idea and want to know what it'd take? See what we build or talk to James. Let's get cracking.

Frequently asked questions

How do I create a smart contract?

Write the logic in a language like Solidity, compile it, then deploy it to a network with a framework such as Hardhat. Hardhat also lets you test locally first. Most teams test locally, then on a public testnet, then deploy to mainnet.

What is a smart contract?

It's code that lives on a blockchain and runs by itself when set conditions are met. No middleman needed. It has functions you can read for free, and functions you call by sending a signed transaction that costs gas.

What are the types of smart contracts?

There's no single agreed list of four. Most people group them by job: token contracts, governance contracts, decentralised exchange contracts and NFT contracts.

How does blockchain technology work?

A blockchain is a shared ledger copied across many computers. Each new block is linked to the one before it by cryptography. Smart contracts run on top of that ledger, so no central party has to approve each step.

Do I need a backend to integrate a smart contract into my app?

Not for basic reads and writes. A client library can talk to the chain through the user's wallet. A backend helps once you need event history, webhook updates or data pulled together from many contracts.

About James Killick

10+ years building digital products · 200+ apps shipped since 2015

James is a co-founder of Devwiz and an AI product specialist. Since 2015 he has helped ship 200+ apps for founders, businesses and government, including work for NSW Government, Briometrix and Huskee. He builds AI-first platforms and writes about turning a proven program into software. He also hosts the Up in the AI podcast.

More articles by James · James's personal site · LinkedIn · AI Orchestrators

Tags: Blockchain, Smart Contracts, App Development

Browse all Devwiz articles·See our case studies