Here's a puzzle: how do you run smart contracts on a blockchain that doesn't have a concept of computational gas? Bitcoin fees are based on bytes, not cycles. You can't just add a metering system. That would require changing the protocol. Yet here we are, with a mainnet live and developers deploying contracts through standard Bitcoin transactions. The solution required rethinking what a smart contract platform actually needs from its base layer. This article is the technical deep dive.
After months of iterating on this architecture, we landed on a design that I think other Bitcoin developers will find useful. Here are the core pieces, explained the way I would have wanted them explained to me.
- The architectural model (what lives on Bitcoin vs what executes off-chain);
- The execution pipeline (how nodes deterministically reach the same state);
- The developer-facing primitives you’ll recognize immediately (deployment, calldata, storage, events).
It’s time to open the hood.
Bitcoin as a data availability and finality layer
OP_NET treats Bitcoin as the place to anchor code and outcomes, and moves computation into a deterministic runtime that any node can replay.
The key mechanism: P2OP outputs & deterministic Wasm execution
OP_NET follows a simple three-part loop: publish code on Bitcoin, execute it deterministically off-chain, then anchor the result back to Bitcoin. Deployment starts with compiling your contract to WebAssembly (Wasm) and embedding that bytecode into a P2OP output (a SegWit v16 address type used by OP_NET). From Bitcoin’s perspective, this is just a normal transaction that carries data in a recognizable format.
From there, an opnet-node watches the Bitcoin chain for P2OP outputs, retrieves the relevant Wasm module, and executes contract calls inside its local Wasm VM. The important property is determinism: given the same inputs, every honest node should produce the same outputs and end up with the same state.
Finally, OP_NET closes the loop by committing outcomes back to Bitcoin. After execution, the resulting state updates (and any asset movements) are submitted as standard Bitcoin transactions. The chain does not need to understand the computation, it only records the ordering and the anchored result under normal Bitcoin miner finality, without any change to consensus rules.
Here’s a quick visualization:
Deploy Wasm – BTC tx with P2OP output;
Call method – BTC tx referencing contract and calldata;
opnet-node – detects P2OP, runs Wasm deterministically;
Commit – BTC tx anchors state/asset updates.
Essentially, every OP_NET transaction is just a Bitcoin transaction. OP_NET is not a Layer 2, sidechain, or rollup – there’s no separate consensus, and no bridge or wrapped-asset requirement. In the base layer’s view, it’s still Bitcoin transactions and UTXOs.
What P2OP means at the Bitcoin level
P2OP addresses start with op1… and Bitcoin treats P2OP outputs as anyone-can-spend at the script layer; OP_NET’s off-chain execution rules are what enforce access control and valid state transitions. The docs also note automatic cleanup, where miners drop spent P2OP UTXOs to avoid chain bloat.
The Technical Stack: AssemblyScript → WebAssembly
OP_NET’s contract surface is the @btc-vision/btc-runtime runtime: you write contracts in AssemblyScript (TypeScript-like), compile to WebAssembly (Wasm), and the network executes that Wasm deterministically across nodes.
Determinism (where every honest OP_NET node will compute the exact same result) is key, which is why the runtime is explicit about constraints:
- Floating-point operations are prohibited (f32, f64). The stated reason is non-determinism across CPU architectures / compilers / platform rounding differences. The intended pattern is integer math only (u128, u256) plus fixed-point (store values in smallest units, satoshis-style).
- The runtime gives you a consistent blockchain context (block metadata, tx metadata, inputs/outputs) and EVM-familiar caller semantics:
- Blockchain.tx.sender = immediate caller
- Blockchain.tx.origin = original transaction signer (typed as an ExtendedAddress, with notes about broader key support)
That last point is important for security. Using origin for authorization is specifically called out as risky (phishing-by-proxy style patterns), so most access control should key off sender.
Deploy and Call without Leaving Bitcoin txs
For developers, OP_NET keeps the workflow transaction-centric. You construct, sign, and broadcast standard Bitcoin transactions (inputs/outputs/UTXOs) that carry contract deploy/call data, instead of switching to a separate network with its own account rules and tooling.
Pick a network and connect a provider
OP_NET exposes node RPC via JSONRpcProvider (from the opnet package), and you pass a network config from @btc-vision/bitcoin.
For now, Mainnet is listed as disabled in the official networks table, so your practical entry points are Testnet or Regtest.
UTXOs → Wasm bytecode → signed raw tx → broadcast
The documented deployment flow is:
- Fetch funding UTXOs via provider.utxoManager.getUTXOs({ address: wallet.p2tr })
- Load compiled contract bytecode from a .wasm file
- Fetch network parameters (gasParameters) and a challenge from the node
- Sign with TransactionFactory.signDeployment(...)
- Broadcast with provider.sendRawTransactions(...) and read back the tweaked contract pubkey and P2OP address
Calls: simulate first, then broadcast
For contract interactions, the docs emphasize gas simulation as a default part of the provider flow: providers simulate transactions to estimate gas usage, and fees can shift block-to-block, so you check estimates before broadcasting.
In other words, deploy and call flows are still ‘build → sign → broadcast Bitcoin transactions,’ with the OP_NET toolchain handling the extra mechanics (bytecode packaging, calldata encoding, gas estimation, and deterministic execution rules).
Building an OP_20 Token
OP_20 is OP_NET’s fungible token standard – conceptually equivalent to ERC-20, but implemented in the OP_NET runtime (AssemblyScript → Wasm). You get the familiar surface area (balances, transfers, approvals), plus a few runtime-enforced constraints (for example: decimals are capped at 32, and max supply is enforced at instantiation).
Here’s a minimal first token contract, where onDeployment() runs once to initialize metadata and mint supply:
Two details are worth internalizing early. Blockchain.tx.sender is the immediate caller (it changes as contracts call other contracts), while Blockchain.tx.origin is the original transaction signer and stays constant through the call chain. So for access control you should almost always check sender, because relying on origin can enable phishing-by-proxy patterns.
On the token side, OP_20 setup is done by passing an OP20InitParameters object into instantiate(): you define the token’s name, symbol, decimals, and max supply, and you can also supply an optional icon URL for wallets/indexers that support it.
Solidity → OP_NET cheat sheet
Solidity / EVM | OP_NET / btc-runtime | Notes |
contract X | class X extends OP_NET / extends OP20 | Single inheritance is the norm. |
constructor() | onDeployment(calldata) | One-time initialization hook. |
msg.sender | Blockchain.tx.sender | Immediate caller. |
tx.origin | Blockchain.tx.origin | Original signer; avoid for auth. |
block.number | Blockchain.block.number | Block height. |
block.timestamp | Blockchain.block.medianTimestamp | Uses Median Time Past (MTP). |
mapping(a => uint) | AddressMemoryMap<..., StoredU256> | Backed by pointer storage (see below). |
uint256 | u256 | Deterministic integer math (no floats). |
Pointers on the storage model
OP_NET storage is built on explicit u16 pointers you allocate in-contract. A storage key is derived as SHA256(pointer || subPointer), which makes layout deterministic, auditable, and predictable.
As a rule of thumb, allocate pointers once (class-level), never reuse them, and document your pointer layout because pointer order defines storage layout.
Some extra things to bear in mind
In terms of how the state converges, at runtime, OP_NET nodes detect contract-related Bitcoin transactions (P2OP outputs and calldata), execute the Wasm deterministically, update local state, then anchor outcomes back via standard Bitcoin transactions. Bitcoin records the inputs/outputs and ordering that let nodes agree on the same state transitions.
OP_NET also charges a gas fee for execution (compute and data footprint) and lets you add an optional priority fee when you want faster processing under congestion. OP_NET’s docs say both gas and priority fees are burned, sent to “dead” contract addresses and permanently removed from circulation, with no redistribution to operators or any fee pool.
Security checklist
- Use tx.sender for authorization, not tx.origin. Origin-based authorization is a known footgun and the runtime docs warn about it directly.
- Turn on reentrancy protection when you do external calls. OP_NET ships a ReentrancyGuard with STANDARD and CALLBACK modes; protection is applied broadly (opt-out by selector).
- Time-based logic should use MTP (Blockchain.block.medianTimestamp), not raw timestamps.
- Stick to integer math (u256) and fixed-point representations; avoid nondeterministic patterns.
Final thoughts
OP_NET gives you an EVM-like development surface, but your storage layout is explicit (pointers) and your execution rules are stricter because every node has to reproduce the exact same state transition from the same on-chain inputs.
This constraint is also what makes the payoff interesting. Once you can deploy tokens and contract logic directly through standard Bitcoin transactions, the usual DeFi building blocks become normal application design.
In the OP_NET ecosystem, that translates into things like a Bitcoin-native DEX surface (MotoSwap), yield/farming primitives (MotoChef), and one-click deployment paths for tokens and farms via the Launchpad. Also, BTC-native yield concepts like Motochef’s Proof-of-Hodl model (that’s designed around self-custody rather than wrapped BTC workflows) become possible.
Underneath it all, contracts are still anchored to Bitcoin Layer 1, inheriting Bitcoin’s security guarantees and decentralization properties.
This article was penned by Samuel Patt, also known as Chad Master, is the Co Founder of OP_NET and a long time Bitcoin enthusiast and trader. Coming from a punk and anti establishment background, he believes strongly in Bitcoin’s ethos of decentralisation and the removal of intermediaries. In 2023, he co founded OP_NET with the mission to transform Bitcoin from a passive store of value into a fully programmable financial system. His work focuses on enabling smart contracts, DeFi, stablecoins, and native yield directly on Bitcoin Layer 1. He is committed to delivering this without bridges, custodians, or synthetic versions of Bitcoin.