Once you deploy it, the bytecode stays fixed at that address — you change behaviour only by deploying a new contract or by routing calls through a proxy you designed for upgrades in advance.
Smart contract development follows eight stages:
Typical cost and timeline: a single production ERC-20 token contract takes about 32 hours of blockchain engineering. A staking contract with UI starts at $7,000. A full token launch platform runs $29,000–$41,000 over 2–3 months, plus a 2–3 week discovery phase.
Most guides on this topic stop at "write Solidity, deploy to testnet, done". That advice survives exactly until your first mainnet transaction fails. We build blockchain platforms for a living, and the contracts themselves are rarely what breaks — gas economics, node credentials, shared-wallet race conditions and undefined on-chain boundaries break first, and they break after you have already spent the budget.
This guide covers the process the way we actually run it, with the numbers from our own project estimates and three failure cases from production systems we operate.
Nick Szabo coined the term in 1994, describing self-executing agreements whose terms live in code rather than in prose. The mechanics matter more than the definition: a smart contract occupies an address on the blockchain exactly like a user wallet does, and it holds a balance. The difference is control. A user account signs transactions with a private key; a contract account acts only when its own code says it should.
That property is why you can send tokens to a contract and receive different tokens back without trusting a counterparty — the contract's logic is the counterparty. It is also why a bug is permanent. There is no patch, no rollback, no support ticket. You either designed an upgrade path before deployment, or you migrate users to a new address.
Contracts fall into three functional categories, and the category determines your legal exposure, your audit budget and your architecture. Getting this classification right at the requirements stage saves rework later.
These encode obligations that also exist as an enforceable legal agreement — a token sale, an escrow settlement, a tokenized property share. The code executes the transfer; the paper defines what happens when reality diverges from the code. This is the category where utility tokens vs security token classification stops being academic, because a US issuer who gets the Howey analysis wrong owns an unregistered securities offering.
Practically everything with money in it lands here: exchange settlement, the lending logic behind how to create a defi app, escrow marketplaces, and platforms that handle how to tokenize real estate holdings for retail investors.
Governance contracts hold rules the community votes on: proposal creation, voting weight, quorum, execution delay. The engineering challenge is not the voting math — it is the execution path. A governance contract that can call arbitrary functions on your treasury is a single compromised proposal away from a total loss, which is why every serious implementation puts a timelock between a passed vote and its execution.
These sit between your product and the chain: oracle consumers, bridges, the minting logic behind how to create an nft, staking accrual, IoT settlement. They carry the highest integration risk because they depend on data that originates off-chain. An oracle that returns a manipulated price turns a correctly written contract into a drain.
That contract count is typical once you move past a plain token into how to tokenize an asset territory. Each one had its own hour estimate. Anyone quoting you "a smart contract" as a single line item has not read your requirements.
This is the single largest cost lever in the entire project, and most teams skip it. Every function you push on-chain becomes immutable, gas-metered and audit-scoped. Every function you keep off-chain stays cheap to iterate on. The boundary is an architecture decision, not a philosophical one.
Our rule: value transfer and token emission go on-chain, because immutability is the point. Accrual math, leaderboards, referral accounting and user state stay off-chain, where a mistake is a migration instead of a redeployment.
Case: scoping a gamified token launch — Oleksandr Blinkov, Head of Project Management
Challenge. A client arrived wanting a multi-round seed and ICO structure plus a gamified staking layer: tiered yield-bearing assets, a referral mechanic paying +0.05% per referred user, asset degradation, burn-on-transaction and paid boost actions.
Both sides instinctively wanted all of it on-chain. That instinct turns a $30k scope into a $300k one and front-loads audit cost onto logic that never needed to be immutable. Compounding it, a Tier-1 security firm quoted approximately $25,000 for a full audit — real money for a product with zero users.
Solution. We inserted a mandatory decomposition step before anyone opened an editor: business logic → PM-translated technical requirements → an explicit on-chain/off-chain boundary. Emission and value transfer stayed on-chain. Staking accrual moved partially off-chain. We decomposed each asset tier into its own logic branch with explicit yield parameters and calculation formulas, so the contract surface was defined before estimation rather than discovered during it.
Then we architected three delivery tiers on one core: manual off-chain distribution; a BackOffice admin panel on a separate domain handling wallet-to-user_id mapping, multi-round management and rate control; and the full contract flow where a user sends a fixed amount and receives tokens at a deterministic rate with no backend in the critical path.
Result. The contract scope landed in the $10,000–$30,000 range with a defined surface instead of an open-ended vilka. The client shipped the manual tier in days and migrated to the contract tier without touching the core — the same staged approach we apply across crypto launchpad development engagements. We replaced the single $25k audit with layered review — LLM-assisted vulnerability scanning, internal audit, external developer review — and launched in beta under transaction caps.
Chain selection is a cost and talent decision, not a technology preference. The honest framing: EVM chains give you the deepest tooling, the largest auditor pool and the most audited libraries. Everything else buys you throughput or fees at the price of a smaller ecosystem.
| Ecosystem | Language | Primary toolchain | When we choose it | What it costs you |
| Ethereum mainnet | Solidity / Vyper | Foundry, Hardhat | High-value settlement, DeFi composability, institutional counterparties | Highest gas; deployment alone can exceed the cost of writing the contract |
| L2 rollups (Base, Arbitrum, Optimism, zkSync) | Solidity | Foundry, Hardhat | Consumer products needing EVM tooling at low fees | Sequencer centralisation; bridge dependency for withdrawals |
| BNB Chain, Polygon PoS | Solidity | Foundry, Hardhat | Cost-sensitive volume, existing BEP-20 liquidity | Gas estimation behaves differently from Ethereum under load |
| Solana | Rust | Anchor | High-frequency order flow, low per-transaction cost at scale | Smaller senior talent pool; account model unfamiliar to EVM teams |
| TON | FunC / Tolk | Blueprint | Telegram-native distribution and mini-app funnels | Asynchronous message model; fewer audited reference implementations |
| TRON | Solidity | TronBox, TronWeb | USDT-heavy payment flows in emerging markets | Energy and Bandwidth resource model instead of simple gas — see the case below |
If your requirements point toward a permissioned network rather than a public one, run the private blockchain vs public blockchain comparison before committing, because the operational burden differs by an order of magnitude.
Your toolchain determines how fast you find bugs. This is the stack we run on EVM work:
| Category | Tool | What it does | Alternative |
| Framework | Foundry | Compile, test and fuzz in Solidity; fastest test loop available | Hardhat (JS/TS ecosystem, richer plugins) |
| Contract library | OpenZeppelin Contracts | Audited ERC-20, ERC-721, ERC-1155, AccessControl, proxies | Solmate (leaner, less hand-holding) |
| Static analysis | Slither | Detects reentrancy, uninitialised storage, access control gaps | Mythril (symbolic execution) |
| Fuzzing | Foundry invariant tests, Echidna | Breaks assumptions you did not know you made | Medusa |
| Node access | Alchemy, QuickNode, Infura | RPC endpoints, archive data, mainnet forking | Self-hosted node (higher control, higher ops cost) |
| Frontend integration | viem + wagmi | Typed contract calls, wallet connection | ethers.js |
| Key custody | Gnosis Safe multisig | Removes single-signer risk on admin functions | Hardware wallet with timelock |
| Monitoring | Tenderly | Transaction simulation, alerting, debugging deployed calls | OpenZeppelin Defender |
The contract is only half the delivery. Wiring it to a working interface follows the same path we describe in ethereum dapp development — typed contract calls, wallet connection, and a transaction state machine on the frontend that survives a dropped RPC.
Inherit audited implementations. Writing your own ERC-20 transfer logic in 2026 is not engineering rigour, it is unbilled audit surface. A capped, ownable token looks like this:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract PlatformToken is ERC20, Ownable {
uint256 public constant MAX_SUPPLY = 100_000_000e18;
constructor(address initialOwner)
ERC20("Platform Token", "PLT")
Ownable(initialOwner)
{
_mint(initialOwner, 10_000_000e18);
}
function mint(address to, uint256 amount) external onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "cap exceeded");
_mint(to, amount);
}
}
Three things carry the weight here. The supply cap is enforced in code rather than promised in a whitepaper. Ownership sits behind an explicit constructor parameter, so you point it at a multisig instead of at a developer's hot wallet. And mint is the only privileged function, which keeps the access-control review surface to a single line.
Testing is where you buy back the immutability you accepted in Step 1. Four layers, in order:
Use current testnets: Sepolia and Holešky for Ethereum. Ropsten and Rinkeby have been discontinued, and any guide still recommending them was written for a network that no longer exists.
Audit pricing is the part of the budget clients consistently underestimate. A Tier-1 firm quoted us roughly $25,000 for a full audit on one project — more than the contract development itself cost on that scope. That does not mean skip the audit. It means match the audit tier to custodied value.
Security is a process with stages, not a certificate you buy once. If your contract will hold seven figures on day one, none of this substitutes for a top-tier audit — pay for it.
Case: transactions that passed on testnet and failed on mainnet — Yuriy Musienko, CTO
Challenge. On a multi-chain exchange platform, our withdrawal path executed cleanly against BNB Chain testnet through the entire QA cycle. In production, transactions failed. The contract interaction logic was byte-identical in both environments.
The divergence sat entirely in gas fee calculation: our estimator was tuned against testnet conditions, where there is effectively no mempool competition and no economic pressure on inclusion. Mainnet punished that assumption immediately, and every failure surfaced to a user as a failed withdrawal.
Solution. We stopped treating gas as a computed value and started treating it as a risk budget — moving from dynamic estimation to a fixed-and-elevated gas price model for value-bearing transactions. In parallel we found a second failure mode on the deposit path: the system applied a hardcoded fixed fee that could exceed the available hot wallet balance, leaving deposits in an indeterminate state.
We added a balance poller on a few-second interval that resumes the transaction automatically once the wallet is funded. We explicitly rejected the proposal to expose manual "force refresh" and "resend transaction" admin endpoints — with an automatic poller already running, a manual trigger is a duplicate-transaction generator.
Result. Failed mainnet withdrawals from gas underpricing stopped. Deposit stalls became self-healing instead of support tickets. Gas overpayment on a withdrawal is a rounding error; a lost transaction is a trust event.
Index your events, alert on balance thresholds, simulate suspicious transactions before they land, and keep a documented incident path — a pause function, a timelock, or a rehearsed migration. On one platform we audited, logs were written to ephemeral storage and vanished on every deploy, which made post-incident debugging impossible. Centralised log retention is not an optimisation for financial systems; it is the difference between an incident report and a shrug.
An escrow contract is the most instructive example because it exercises state machines, timing and the reentrancy pattern in about forty lines. In our P2P trading engine the escrow mechanism freezes funds for a fixed fifteen-minute window — the timer and the state machine carry the trust, not the transfer call:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract P2PEscrow {
enum State { None, Funded, Released, Refunded }
struct Deal {
address payable buyer;
address payable seller;
uint256 amount;
uint256 fundedAt;
State state;
}
uint256 public constant LOCK_WINDOW = 15 minutes;
mapping(uint256 => Deal) public deals;
event Funded(uint256 indexed dealId, uint256 amount);
event Released(uint256 indexed dealId, uint256 amount);
event Refunded(uint256 indexed dealId, uint256 amount);
function fund(uint256 dealId, address payable seller) external payable {
Deal storage d = deals[dealId];
require(d.state == State.None, "deal exists");
require(msg.value > 0, "zero amount");
deals[dealId] = Deal({
buyer: payable(msg.sender),
seller: seller,
amount: msg.value,
fundedAt: block.timestamp,
state: State.Funded
});
emit Funded(dealId, msg.value);
}
function release(uint256 dealId) external {
Deal storage d = deals[dealId];
require(msg.sender == d.buyer, "not buyer");
require(d.state == State.Funded, "bad state");
uint256 amount = d.amount;
d.amount = 0;
d.state = State.Released; // effects before interaction
(bool ok, ) = d.seller.call{value: amount}("");
require(ok, "transfer failed");
emit Released(dealId, amount);
}
function refundAfterTimeout(uint256 dealId) external {
Deal storage d = deals[dealId];
require(d.state == State.Funded, "bad state");
require(block.timestamp > d.fundedAt + LOCK_WINDOW, "still locked");
uint256 amount = d.amount;
d.amount = 0;
d.state = State.Refunded;
(bool ok, ) = d.buyer.call{value: amount}("");
require(ok, "refund failed");
emit Refunded(dealId, amount);
}
}
Read the ordering inside release. We zero the balance and set the state before sending funds. Reverse those two blocks and a malicious seller contract re-enters release during the transfer and drains the escrow. That ordering — checks, then effects, then interactions — is the single most valuable habit in Solidity, and it is why we never accept a contract that sends value before updating state.
Production contracts extend this pattern in predictable directions: adding ERC-20 support instead of native currency, a dispute path with an arbiter role, and per-deal fee accrual. If you are building a peer-to-peer marketplace on top of it, our breakdown of a working bitcoin escrow script covers the surrounding platform architecture.
Three failure classes account for most of the production incidents we get called into, and none of them are Solidity bugs.
Case: TRON energy allocation — Yuriy Musienko, CTO
Challenge. TRON does not price execution in a single gas unit. It splits execution into Energy and Bandwidth, each of which you can stake, rent or burn. That asymmetry broke our withdrawal path. On deposits, we allocate Energy to a specific target address, so consumption is deterministic. On withdrawals, Energy sits on a shared corporate hot wallet — which means any concurrent transaction, a user withdrawal competing with an admin withdrawal, can consume the allocation intended for another operation. A classic race condition on a shared resource, except the resource is money.
Solution. Three layers. We dropped Energy allocation on the withdrawal path entirely and moved to a burn-TRX path where consumption is unambiguous. We introduced a transaction queue with a data model binding each energy allocation to its parent withdrawal, explicit states (pending, processing) and locking of competing operations while a withdrawal is in flight. On top of that we added a cost-optimisation layer: a pre-flight resource check against the TRON API with conditional branching — rent Energy when staked capacity runs short, burn TRX for Bandwidth when a deposit burst has drained it.
Result. Resource conflicts and double-consumption on the shared wallet stopped, and transaction outcomes became predictable rather than probabilistic. The principle generalises past TRON: the moment a wallet is shared between operations, you need a queue, or you have chaos instead of a financial system.
We took over one Web3 platform where deposits, withdrawals and swaps were all dead and transactions were hanging in transit. None of it was contract code. The root causes were stale blockchain node credentials, changed node-provider tariff plans, and un-refreshed blockchain API access. We also found GitHub access held by exactly one developer — a single point of failure — while infrastructure documentation and transaction logs with hashes sat in the repository unused. In Web3 projects the code rarely falls over; the integration layer does.
Across our wallet architecture work we compare two models: self-hosted wallets on the client's own servers, or remote RPC with multisig. In both, one requirement is non-negotiable — private key management sits outside developer access. Large balances go to cold storage, hot wallets carry operational liquidity only, and withdrawals above a threshold keep a manual approval step — the same custody discipline that anchors crypto exchange security on any trading platform.
If your engineering team can reach the private keys, you already have the vulnerability, regardless of how clean the contract is. The same reasoning drives our broader position on security of blockchain technology at the platform level.
The bytecode at a deployed address cannot change. Your system's behaviour can — if you designed for it. That distinction has been true since proxy patterns matured, and "smart contracts are immutable, full stop" is now an outdated answer.
| Approach | How it works | Trade-off |
| Immutable deployment | No upgrade path; deploy a new contract and migrate users | Maximum trust, maximum migration pain |
| UUPS proxy | Upgrade logic lives in the implementation; proxy delegates calls | Cheaper than transparent proxy; you can brick it by removing the upgrade function |
| Transparent proxy | Admin and user call paths are separated at the proxy | Higher gas per call; simpler to reason about |
| Diamond (EIP-2535) | Multiple facets behind one address, upgraded individually | Handles the contract size limit; significantly harder to audit |
| Parameter governance | Contract stays fixed; fees, limits and addresses are settable | Covers most real change requests without any upgrade machinery |
Two warnings from experience. Storage layout collisions are the most common way teams brick a proxy — you cannot reorder or remove state variables between implementations, only append. And an upgradeable contract with a single EOA admin is not upgradeable, it is a rug pull waiting to be reported. Put the admin behind a multisig and a timelock, or the upgrade capability itself becomes your largest vulnerability.
Before reaching for a proxy, ask what you actually need to change. In most projects the answer is fees, limits and integration addresses — all of which a well-designed setter with access control handles, with none of the upgrade complexity.
These are line items from our own project estimates, not market averages. Our blockchain engineering rate is $40/hour, which is the figure behind every number below.
| Component | Blockchain hours | Cost |
| ERC-20 token contract | 32 | $1,280 |
| ERC-20 integration into a platform with an existing node | — | $200 |
| Factory contract for governance tokens | 48 | $1,920 |
| Vault contract (custody + reward accrual and distribution) | 48 | $1,920 |
| Oracle consumer contract | 48 | $1,920 |
| Participant registration contract | 40 | $1,600 |
| Halving / emission schedule algorithm | 160 | $6,400 |
| DAO minting wallet | 200 | $8,000 |
| Payment system integration layer | 148 | $5,920 |
| Genesis block prep with tokenomics contracts wired in | 56 | $2,240 |
| Staking module (contract + staking page + rate settings) | — | from $7,000 |
| ICO platform (token + contract + site + purchase flow) | — | $10,000–$20,000 |
Note the gap between writing a token contract (32 hours) and integrating an existing one where the node is already running ($200). That six-to-eight-times difference is exactly why we run the decomposition step from Step 1 before quoting anything.
For a worked example of how these line items assemble into a shipped product, our Case Study How We Built a Crypto Exchange with Own Token walks through the token contract, node layer and settlement together.
At the platform level, our delivered estimates look like this:
| Platform | Standard | Advanced | Timeline | Discovery |
| Token / ICO platform (Solidity token contract, Solidity staking contract, BTC/ETH/TRX/BSC nodes, USDT listing) | $29,000 | $41,000 | 2 months / 2.5–3 months | 2–3 weeks |
| Crypto escrow B2B marketplace (escrow contracts, contracts for 5 fiat-pegged tokens, KYC/AML, 3 bank APIs) | $64,000 | $75,000 | 2–3 months / 3 months | 1 month |
| NFT marketplace (minting, royalty and distribution contract, IPFS, fiat gateway) | $63,000 monolith / $75,000 microservices | $78,000 / $93,000 | 2–3 months | ~1 month |
The most instructive number we have comes from a project where the client wanted their own chain rather than contracts on an existing network: $202,700 total, of which $124,800 — 3,120 hours — went to the blockchain layer alone. Within that, the nine smart contracts accounted for roughly 385 hours. Everything else was node client, consensus, wallet system and synchronisation. Writing the contracts was about 12% of the on-chain cost.
If you are weighing that route, our guide to how to create own blockchain network covers what the other 88% consists of, and the broader how much does blockchain technology cost breakdown puts it in context.
All three are defensible. The wrong one is expensive in a way you discover late.
| Route | Realistic when | What it actually costs |
| Write it yourself from OpenZeppelin templates | Single-purpose token, no custody of third-party funds, founder can read Solidity | Days of work, plus the audit you will still need before anyone deposits money |
| Hire a dedicated team | Custom logic, multi-contract systems, ongoing product roadmap | 2–3 months to production plus a 2–4 week discovery phase |
| White-label plus custom modules | Time-to-market pressure, standard product category | Fastest launch; you inherit someone else's architecture and its limits |
The honest test: if your contract will ever hold funds belonging to people who are not you, the DIY route ends at the audit anyway. Budget for it from day one rather than discovering it two weeks before launch. Teams evaluating vendors can use our criteria for choosing a smart contract development company as a starting checklist, and teams building the full product around the contract will find the broader process in how to build a blockchain app.
We are engineers, not your counsel — get securities advice from a US attorney before a token sale. That said, three things reliably shape architecture for American issuers.
Second, if your platform touches fiat, FinCEN money services business registration and state money transmitter licensing enter scope, which pushes KYC and AML integration into the critical path. Third, OFAC screening applies at the address level, so your contract or your relayer needs a sanctions check somewhere in the flow.
Build these as parameters you can configure, not as constants you would need an upgrade to change.
Corporate structure matters too. Wyoming's DAO LLC framework and Delaware entities give governance contracts a legal wrapper, which materially reduces the personal liability exposure that unwrapped DAOs carry for their participants.
Run Slither one final time against the exact commit you are deploying. Deploy to Sepolia or Holešky first and execute the full user flow there, including the failure paths. Set up event indexing and balance alerting before the first user transaction, not after. Document the incident response path — who can pause, how long the timelock is, where the runbook lives.
Confirm every external address (oracle, router, treasury) is a mainnet address and not a testnet leftover.
A single production token contract takes about 32 hours of blockchain engineering, roughly one week including tests and review. A full platform with multiple interacting contracts runs 2–3 months plus a 2–4 week discovery phase. Anyone promising a production launch in three weeks is not counting the audit or the testnet cycle.
From our estimates: $1,280 for an ERC-20 token contract, from $7,000 for a staking module with UI, $10,000–$20,000 for a complete ICO platform. A Tier-1 external audit adds roughly $25,000. Integrating an already-written token where the node is running costs $200.
The bytecode at a given address cannot change. You can change system behaviour if you deployed behind an upgradeable proxy (UUPS, transparent or diamond) or exposed governed parameters. Retrofitting upgradeability onto a deployed immutable contract is impossible — you migrate to a new address instead.
The bytecode is always public because it lives on-chain. Publishing verified source on a block explorer is a separate, voluntary step, though users and integrators generally treat unverified contracts as untrustworthy. Assume any attacker can read and decompile your logic regardless of whether you publish it.
Solidity for every EVM chain, which covers Ethereum, its L2s, BNB Chain, Polygon and TRON. Rust with Anchor for Solana. FunC or Tolk for TON. Vyper is a defensible alternative to Solidity when you want a smaller language surface, but the auditor and library ecosystem around it is considerably thinner.
You need review proportional to custodied value. For a pre-revenue product, static analysis in CI plus an external developer review and a capped beta is a defensible position. Once the contract holds meaningful user funds, commission a full external audit — that threshold arrives faster than most teams plan for.
Almost always gas. Testnets have no mempool competition, so estimators tuned there underprice mainnet inclusion. Other common causes: the deployer or hot wallet lacks native currency to cover fees, an external address is still pointing at a testnet contract, or the mainnet version of a token behaves differently from your mock.
Access control, not reentrancy. Reentrancy gets the attention, but the recurring finding is a privileged function that anyone can call, or an admin role sitting on a single externally owned account with no multisig and no timelock behind it.
Emission and value transfer belong on-chain. Accrual math, tier parameters, referral accounting and leaderboards usually do not. Keeping accrual off-chain turns a yield-calculation bug into a hotfix rather than a redeployment and full position migration.
Your options are the ones you built in advance: pause the contract if you added a pause, upgrade the implementation if you deployed behind a proxy, or deploy a new contract and migrate state and users. With no pause and no proxy, migration is the only path — which is exactly why the upgrade question belongs in the design phase, not the incident.