@btc-vision/btc-runtime
Version:
Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.
631 lines (510 loc) • 16 kB
Markdown
This guide explains the standard project structure for OP_NET smart contract development and how the btc-runtime library is organized.
This diagram visualizes the complete project structure and file relationships:
```mermaid
---
config:
theme: dark
---
graph LR
subgraph root["Project Root"]
ROOT["my-opnet-project/"]
end
subgraph source["Source Code - src/"]
SRC["src/"]
TOKEN_DIR["token/"]
NFT_DIR["nft/"]
TOKEN["MyToken.ts"]
TOKEN_INDEX["index.ts"]
NFT["MyNFT.ts"]
NFT_INDEX["index.ts"]
end
subgraph build["Build Output - build/"]
BUILD["build/"]
TOKEN_WASM["MyToken.wasm"]
NFT_WASM["MyNFT.wasm"]
end
subgraph tests["Tests - tests/"]
TESTS["tests/"]
SPEC["MyToken.spec.ts"]
end
subgraph config["Configuration"]
ASCONFIG["asconfig.json"]
PKG["package.json"]
TSCONFIG["tsconfig.json"]
end
ROOT --> SRC
ROOT --> BUILD
ROOT --> TESTS
ROOT --> ASCONFIG
ROOT --> PKG
ROOT --> TSCONFIG
SRC --> TOKEN_DIR
SRC --> NFT_DIR
TOKEN_DIR --> TOKEN
TOKEN_DIR --> TOKEN_INDEX
NFT_DIR --> NFT
NFT_DIR --> NFT_INDEX
TOKEN -.->|"imported by"| TOKEN_INDEX
NFT -.->|"imported by"| NFT_INDEX
TOKEN_INDEX -.->|"compiled by"| ASCONFIG
NFT_INDEX -.->|"compiled by"| ASCONFIG
ASCONFIG -.->|"produces"| TOKEN_WASM
ASCONFIG -.->|"produces"| NFT_WASM
TOKEN -.->|"tested by"| SPEC
```
A typical OP_NET contract project looks like this:
```
my-opnet-project/
├── src/
│ ├── token/
│ │ ├── MyToken.ts
│ │ └── index.ts
│ ├── nft/
│ │ ├── MyNFT.ts
│ │ └── index.ts
│ └── tsconfig.json
├── build/
│ ├── MyToken.wasm
│ └── MyNFT.wasm
├── tests/
│ └── MyToken.spec.ts
├── asconfig.json
├── package.json
└── tsconfig.json
```
The entry point for each contract that sets up the contract instance:
```typescript
import { Blockchain } from '@btc-vision/btc-runtime/runtime';
import { revertOnError } from '@btc-vision/btc-runtime/runtime/abort/abort';
import { MyToken } from './MyToken';
// DO NOT TOUCH TO THIS.
Blockchain.contract = () => {
// ONLY CHANGE THE CONTRACT CLASS NAME.
// DO NOT ADD CUSTOM LOGIC HERE.
return new MyToken();
};
// VERY IMPORTANT
export * from '@btc-vision/btc-runtime/runtime/exports';
// VERY IMPORTANT
export function abort(message: string, fileName: string, line: u32, column: u32): void {
revertOnError(message, fileName, line, column);
}
```
AssemblyScript compiler configuration with per-contract targets:
```json
{
"targets": {
"token": {
"outFile": "build/MyToken.wasm",
"use": ["abort=src/token/index/abort"]
},
"nft": {
"outFile": "build/MyNFT.wasm",
"use": ["abort=src/nft/index/abort"]
}
},
"options": {
"sourceMap": false,
"optimizeLevel": 3,
"shrinkLevel": 1,
"converge": true,
"noAssert": false,
"enable": [
"sign-extension",
"mutable-globals",
"nontrapping-f2i",
"bulk-memory",
"simd",
"reference-types",
"multi-value"
],
"runtime": "stub",
"memoryBase": 0,
"initialMemory": 1,
"exportStart": "start",
"transform": "@btc-vision/opnet-transform"
}
}
```
| Option | Description |
|--------|-------------|
| `targets` | Per-contract build targets with output paths |
| `use` | Links custom abort function for error handling |
| `optimizeLevel` | Optimization level (0-3), higher = faster but larger |
| `shrinkLevel` | Code size reduction (0-2) |
| `transform` | OP_NET transform for decorator processing |
| `runtime: "stub"` | Minimal runtime (OP_NET provides its own) |
### package.json Scripts
Recommended scripts for your `package.json`:
```json
{
"scripts": {
"build:token": "asc src/token/index.ts --target token --measure --uncheckedBehavior never",
"build:nft": "asc src/nft/index.ts --target nft --measure --uncheckedBehavior never"
}
}
```
This diagram shows the internal organization of the @btc-vision/btc-runtime package:
```mermaid
---
config:
theme: dark
---
graph LR
subgraph runtime["btc-runtime Package"]
RUNTIME["runtime/"]
INDEX["index.ts<br/>Main Exports"]
end
subgraph contracts["Contract Base Classes"]
OPNET["OP_NET.ts<br/>Base Contract"]
OP20["OP20.ts<br/>Fungible Token"]
OP721["OP721.ts<br/>NFT Standard"]
GUARD["ReentrancyGuard.ts<br/>Security"]
end
subgraph storage["Storage System"]
STORED["StoredU256.ts<br/>Persistent Values"]
STOREDSTR["StoredString.ts<br/>String Storage"]
STOREDMAP["StoredMapU256.ts<br/>Key-Value Maps"]
STOREDARRAY["StoredU256Array.ts<br/>Dynamic Arrays"]
end
subgraph memory["Memory Management"]
ADDRMAP["AddressMemoryMap.ts<br/>Address Mappings"]
MAPOFMAP["MapOfMap.ts<br/>Nested Maps"]
KEYMERGE["KeyMerger.ts<br/>Key Utilities"]
end
subgraph types["Types & Utilities"]
ADDR["Address.ts<br/>32-byte Addresses"]
CALLDATA["Calldata.ts<br/>Input Parsing"]
BYTESW["BytesWriter.ts<br/>Output Builder"]
BYTESR["BytesReader.ts<br/>Data Reader"]
SAFE["SafeMath.ts<br/>Overflow Protection"]
REV["Revert.ts<br/>Error Handling"]
end
subgraph blockchain["Blockchain Environment"]
BLOCKCHAIN["BlockchainEnvironment.ts<br/>Runtime Context"]
TX["Transaction.ts<br/>TX Data"]
BLOCK["Block.ts<br/>Block Info"]
end
subgraph events["Event System"]
EVENT["NetEvent.ts<br/>Event Base"]
TRANSFER["TransferredEvent.ts"]
APPROVAL["ApprovedEvent.ts"]
MINT["MintedEvent.ts"]
BURN["BurnedEvent.ts"]
end
RUNTIME --> INDEX
INDEX --> OPNET
INDEX --> OP20
INDEX --> OP721
INDEX --> GUARD
INDEX --> STORED
INDEX --> STOREDSTR
INDEX --> STOREDMAP
INDEX --> STOREDARRAY
INDEX --> ADDRMAP
INDEX --> MAPOFMAP
INDEX --> ADDR
INDEX --> CALLDATA
INDEX --> BYTESW
INDEX --> BYTESR
INDEX --> SAFE
INDEX --> REV
INDEX --> BLOCKCHAIN
INDEX --> EVENT
OPNET -.->|"uses"| BLOCKCHAIN
OPNET -.->|"uses"| EVENT
OP20 -.->|"extends"| GUARD
GUARD -.->|"extends"| OPNET
OP20 -.->|"uses"| STORED
OP20 -.->|"uses"| ADDRMAP
OP20 -.->|"uses"| SAFE
BLOCKCHAIN --> TX
BLOCKCHAIN --> BLOCK
EVENT --> TRANSFER
EVENT --> APPROVAL
EVENT --> MINT
EVENT --> BURN
```
Understanding the btc-runtime structure helps you find the right imports:
```
@btc-vision/btc-runtime/
├── runtime/
│ ├── index.ts
│ ├── contracts/
│ │ ├── OP_NET.ts
│ │ ├── OP20.ts
│ │ ├── OP20S.ts
│ │ ├── OP721.ts
│ │ └── ReentrancyGuard.ts
│ ├── storage/
│ │ ├── StoredU256.ts
│ │ ├── StoredString.ts
│ │ ├── StoredArray.ts
│ │ ├── StoredMap.ts
│ │ └── ...
│ ├── math/
│ │ ├── SafeMath.ts
│ │ └── SafeMathI128.ts
│ ├── types/
│ │ ├── Address.ts
│ │ ├── Calldata.ts
│ │ ├── BytesWriter.ts
│ │ ├── BytesReader.ts
│ │ └── Selector.ts
│ ├── events/
│ │ ├── NetEvent.ts
│ │ ├── predefined/
│ │ └── ...
│ ├── env/
│ │ └── BlockchainEnvironment.ts
│ └── interfaces/
```
```typescript
// Core runtime - contracts and utilities
import {
OP_NET,
OP20,
OP721,
Blockchain,
Address,
Calldata,
BytesWriter,
BytesReader,
SafeMath,
Revert,
} from '@btc-vision/btc-runtime/runtime';
// Big numbers
import { u128, u256 } from '@btc-vision/as-bignum/assembly';
```
```typescript
import {
StoredU256,
StoredString,
StoredBoolean,
StoredAddress,
StoredU256Array,
StoredAddressArray,
StoredMapU256,
AddressMemoryMap,
} from '@btc-vision/btc-runtime/runtime';
```
```typescript
import {
NetEvent,
TransferEvent,
ApprovalEvent,
MintEvent,
BurnEvent,
} from '@btc-vision/btc-runtime/runtime';
```
```typescript
import {
OP20InitParameters,
OP721InitParameters,
} from '@btc-vision/btc-runtime/runtime';
```
This diagram illustrates how OP_NET manages persistent storage using pointers:
```mermaid
---
config:
theme: dark
---
flowchart LR
subgraph contract["Contract Storage Model"]
CONTRACT["MyToken Contract"]
end
subgraph pointers["Storage Pointers"]
P1["Pointer 0<br/>Total Supply"]
P2["Pointer 1<br/>Token Name"]
P3["Pointer 2<br/>Token Symbol"]
P4["Pointer 3<br/>Decimals"]
P5["Pointer 4<br/>Balance Map"]
P6["Pointer 5<br/>Allowance Map"]
end
subgraph state["Blockchain State"]
STATE[("Persistent Storage")]
subgraph values["Stored Values"]
V1["0x00...01<br/>1000000 tokens"]
V2["0x00...02<br/>MyToken"]
V3["0x00...03<br/>MTK"]
V4["0x00...04<br/>18"]
end
subgraph balances["Balance Mapping"]
B1["SHA256(pointer 4 + address1)<br/>→ 1000 tokens"]
B2["SHA256(pointer 4 + address2)<br/>→ 500 tokens"]
B3["SHA256(pointer 4 + address3)<br/>→ 250 tokens"]
end
subgraph allowances["Allowance Nested Map"]
A1["SHA256(pointer 5 + owner + spender)<br/>→ 100 tokens"]
end
end
CONTRACT --> P1
CONTRACT --> P2
CONTRACT --> P3
CONTRACT --> P4
CONTRACT --> P5
CONTRACT --> P6
P1 -.->|"reads/writes"| V1
P2 -.->|"reads/writes"| V2
P3 -.->|"reads/writes"| V3
P4 -.->|"reads/writes"| V4
P5 -.->|"with address key"| B1
P5 -.->|"with address key"| B2
P5 -.->|"with address key"| B3
P6 -.->|"with owner+spender"| A1
V1 --> STATE
V2 --> STATE
V3 --> STATE
V4 --> STATE
B1 --> STATE
B2 --> STATE
B3 --> STATE
A1 --> STATE
```
For simple projects with one contract:
```
src/
├── token/
│ ├── MyToken.ts
│ └── index.ts
└── tsconfig.json
```
For larger projects with multiple contracts:
```
src/
├── token/
│ ├── MyToken.ts
│ └── index.ts
├── stablecoin/
│ ├── MyStablecoin.ts
│ └── index.ts
├── nft/
│ ├── MyNFT.ts
│ └── index.ts
├── shared/
│ ├── CustomTypes.ts
│ └── Helpers.ts
└── tsconfig.json
```
For contracts sharing common functionality:
```typescript
// src/shared/Pausable.ts
import { Blockchain, OP_NET, Revert, StoredBoolean } from '@btc-vision/btc-runtime/runtime';
const pausedPointer: u16 = Blockchain.nextPointer;
export abstract class Pausable extends OP_NET {
private _paused: StoredBoolean = new StoredBoolean(pausedPointer, false);
protected whenNotPaused(): void {
if (this._paused.value) {
throw new Revert('Contract is paused');
}
}
protected pause(): void {
this.onlyDeployer(Blockchain.tx.sender);
this._paused.value = true;
}
protected unpause(): void {
this.onlyDeployer(Blockchain.tx.sender);
this._paused.value = false;
}
}
// src/token/MyToken.ts
import { Pausable } from '../shared/Pausable';
export class MyToken extends Pausable {
// Now has pause functionality
}
```
| Solidity | OP_NET | Notes |
|----------|-------|-------|
| `contracts/` | `src/token/`, `src/nft/` | Contract source files (one folder per contract) |
| `interfaces/` | `src/shared/` | Type definitions and shared logic |
| `libraries/` | `src/shared/` | Shared utilities |
| `test/` | `tests/` | Test files |
| `artifacts/` | `build/` | Compiled output |
| `hardhat.config.js` | `asconfig.json` | Build configuration |
## Best Practices
### 1. Organize by Feature
```
src/
├── token/ # Token contract
├── stablecoin/ # Stablecoin contract
├── nft/ # NFT contract
├── governance/ # Governance contract
└── shared/ # Shared utilities and types
```
### 2. Use Consistent Naming
```typescript
// Contract files: PascalCase.ts
MyToken.ts
MyStablecoin.ts
// Type files: PascalCase.ts
CustomTypes.ts
// Utility files: camelCase.ts
mathHelpers.ts
```
### 3. Entry Point Pattern
Each contract should have its own index.ts with the proper pattern:
```typescript
// src/token/index.ts
import { Blockchain } from '@btc-vision/btc-runtime/runtime';
import { revertOnError } from '@btc-vision/btc-runtime/runtime/abort/abort';
import { MyToken } from './MyToken';
Blockchain.contract = () => {
return new MyToken();
};
export * from '@btc-vision/btc-runtime/runtime/exports';
export function abort(message: string, fileName: string, line: u32, column: u32): void {
revertOnError(message, fileName, line, column);
}
```
```typescript
// Good: Each contract has a single responsibility
export class TokenContract extends OP20 { /* token logic */ }
export class GovernanceContract extends OP_NET { /* governance logic */ }
// Bad: One contract doing too much
export class EverythingContract extends OP_NET {
// token logic
// governance logic
// oracle logic
// etc.
}
```
```
tests/
├── unit/
│ ├── MyToken.spec.ts
│ └── SafeMath.spec.ts
├── integration/
│ └── TokenTransfer.spec.ts
└── fixtures/
└── testData.ts
```
Now that you understand the project structure:
1. [Learn about the blockchain environment](../core-concepts/blockchain-environment.md)
2. [Understand the storage system](../core-concepts/storage-system.md)
3. [See complete examples](../examples/basic-token.md)
---
**Navigation:**
- Previous: [First Contract](./first-contract.md)
- Next: [Blockchain Environment](../core-concepts/blockchain-environment.md)