OPNet Development Skill
A comprehensive skill for building on OPNet - Bitcoin's L1 consensus layer for trustless smart contracts.
STEP 1: UNDERSTAND WHAT THE USER WANTS FIRST
BEFORE reading any docs or guidelines, you MUST understand the user's request.
- Read the user's message carefully
- Identify what type of project they need (contract, frontend, backend, plugin, audit, question)
- Ask clarifying questions if the request is ambiguous
- ONLY THEN proceed to read the docs listed for that project type
Do NOT start reading all docs blindly. Understand the task first, then read only the relevant docs.
STEP 2: MANDATORY READING BEFORE ANY CODE
IF YOU WRITE CODE WITHOUT READING THE REQUIRED DOCS, YOU WILL CREATE BROKEN, EXPLOITABLE CODE.
CRITICAL RULES FOR AI AGENTS
ABSOLUTE RULE - NO RAW JAVASCRIPT
NEVER write raw JavaScript. ALWAYS use:
- Frontend: TypeScript + Vite + npm packages
- Backend: TypeScript + Node.js + npm packages
- NO EXCEPTIONS. NOT EVEN FOR "QUICK EXAMPLES".
DO NOT:
- Read a few files and then say "I have enough context"
- Read random skills or guidelines not listed for your project type
- Start writing code after skimming 2-3 files
- Assume you know OPNet patterns from other frameworks
- Skip files because they "seem similar" to ones you read
- Create zip files or deliverables WITHOUT running
npm run lintandnpm run typecheck - Say "run npm run lint to verify" instead of ACTUALLY running it
YOU MUST:
- Read EVERY SINGLE FILE listed for your project type below
- Read them IN ORDER - later files depend on earlier ones
- Read the COMPLETE FILE, not just the first few sections
- ONLY read files listed in this skill - no random exploration
- Confirm you read all files before writing ANY code
- ACTUALLY RUN
npm install,npm run lint,npm run typecheck,npm run build - FIX ALL ERRORS before creating any deliverable (zip, PR, etc.)
MANDATORY WORKFLOW RULES
1. CONTRACTS FIRST, THEN FRONTEND
When a user asks for a website/dApp that interacts with a smart contract, you MUST build the .wasm contract FIRST before building the frontend.
Workflow order:
- Build and compile the AssemblyScript smart contract → verify
.wasmoutput exists - Run contract tests to ensure correctness
- THEN build the frontend that interacts with that contract
Never build a frontend that references a contract that doesn't exist yet.
2. WALLET INTEGRATION: ALWAYS USE @btc-vision/opwallet
All OPNet websites MUST use @btc-vision/opwallet for OP_WALLET integration.
OP_WALLET is the official and ONLY wallet that fully supports OPNet features (MLDSA signatures, quantum-resistant keys, full OPNet integration). Every frontend dApp MUST integrate it.
3. VITE IS MANDATORY — NO EXCEPTIONS
All frontend projects MUST use Vite as the build tool. No webpack, no parcel, no rollup standalone, no esbuild standalone. Vite only.
4. IPFS DEPLOYMENT: USE OPNET-CLI ONLY
When publishing to IPFS:
- Build with Vite:
npm run build(producesdist/folder) - The output is static HTML + JS + CSS files
- Upload using the opnet-cli IPFS upload script:
- IPFS-only upload (no domain):
bash /root/openclaw/skills/opnet-cli/scripts/ipfs-upload.sh ./dist - Upload + publish to .btc domain:
bash /root/openclaw/skills/opnet-cli/scripts/ipfs-upload.sh ./dist mysite - Dry-run (upload but don't publish on-chain):
bash /root/openclaw/skills/opnet-cli/scripts/ipfs-upload.sh ./dist mysite --dry-run
- IPFS-only upload (no domain):
- The site must work as a fully static site — no server-side rendering, no API routes
- All contract interactions happen client-side via the OPNet provider
The dist/ folder after vite build contains everything needed for IPFS deployment.
The upload script returns JSON with the CID and gateway URL:
{
"cid": "QmXyz...",
"files": 12,
"totalSize": 245000,
"gateway": "https://ipfs.opnet.org/ipfs/QmXyz..."
}
FORBIDDEN IPFS methods — NEVER use any of these:
- ❌ Local IPFS daemon (Kubo) — do NOT run
ipfs add,ipfs pin, or any local Kubo commands - ❌
ipfs-websiteskill — this skill is deprecated and deleted - ❌ Direct HTTP calls to IPFS API — do NOT use
curl,fetch, or any raw HTTP to IPFS endpoints - ❌ Uploading files one by one — the script handles directory uploads with proper chunking
- ❌ Any IPFS upload method other than the opnet-cli script above
ALWAYS use bash /root/openclaw/skills/opnet-cli/scripts/ipfs-upload.sh for ALL IPFS uploads. No exceptions.
COMMON AGENT MISTAKES — DO NOT REPEAT THESE
These are real mistakes observed across multiple AI agents. If you make any of these, your code is broken.
| Mistake | Why It's Wrong | Correct Approach |
|---|---|---|
| Using Keccak256 selectors (EVM style) | OPNet uses SHA256, not Keccak256. This is Bitcoin, not Ethereum. | Use SHA256 for all hashing and method selectors |
Calling approve() on OP-20 tokens |
OP-20 does NOT have approve(). It uses increaseAllowance() / decreaseAllowance() to prevent the well-known approve race condition. |
Use increaseAllowance(spender, amount) and decreaseAllowance(spender, amount) |
Passing bc1p... addresses to Address.fromString() |
Address.fromString() takes TWO hex pubkey parameters, not a Bech32 address string |
Address.fromString(hashedMLDSAKey, tweakedPublicKey) — both are hex strings |
Using bitcoinjs-lib |
OPNet has its own Bitcoin library with critical patches and 709x faster PSBT | Use @btc-vision/bitcoin — never bitcoinjs-lib |
| Factory-deploys-child-contract pattern | OPNet contracts cannot deploy other contracts. There is no CREATE or CREATE2 opcode. |
Deploy each contract separately, then link them via stored addresses |
Skipping simulation before sendTransaction() |
Bitcoin transfers are irreversible. If the contract reverts, your BTC is gone. | ALWAYS simulate first: contract.method() to simulate, then result.sendTransaction(params) only after confirming success |
| Using Express/Fastify/Koa for backends | These frameworks are forbidden. They are significantly slower. | Use @btc-vision/hyper-express and @btc-vision/uwebsocket.js only |
Not running npm-check-updates after setup |
Package versions drift constantly. Stale versions = build failures. | ALWAYS run npx npm-check-updates -u && npm i eslint@^9.39.2 @eslint/js@^9.39.2 ... (see Install Commands section) |
Giving users verifyECDSASignature or verifySchnorrSignature without deprecation warning |
Both ECDSA and Schnorr are DEPRECATED. ECDSA is gated behind UNSAFE_QUANTUM_SIGNATURES_ALLOWED and will break when consensus flips the flag. Schnorr is also deprecated but still works through the safe verifySignature path. Handing users the direct deprecated methods without warning sets them up for contract failure. |
ALWAYS warn first: ECDSA and Schnorr are deprecated. Show Blockchain.verifySignature(address, signature, hash) as the ONLY correct approach — it is consensus-aware and auto-selects the right algorithm (Schnorr today, ML-DSA when enforced). NEVER point users to verifyECDSASignature, verifyBitcoinECDSASignature, or verifySchnorrSignature directly. Read guidelines/ethereum-migration-guidelines.md Section 3. |
Using MessageSigner.signMessage() or signMLDSAMessage() instead of Auto methods |
The non-Auto methods are environment-specific. signMessage() only works with local keypairs (backend), signMLDSAMessage() only works with OP_WALLET (browser). Using the wrong crash. |
ALWAYS use Auto methods: MessageSigner.signMessageAuto(), tweakAndSignMessageAuto(), signMLDSAMessageAuto(). These auto-detect whether you're in browser (OP_WALLET) or backend (local keypair) and call the right underlying method. NEVER use the non-Auto variants unless you have an explicit reason and know your environment. |
If you catch yourself doing any of the above, STOP and fix it immediately.
WHY THIS MATTERS
OPNet development has:
- Beta package versions that change frequently - guessing versions = build failures
- Strict TypeScript rules - violations = security vulnerabilities
- Platform-specific patterns - wrong patterns = exploits, gas attacks, data loss
Guidelines are SUMMARIES. The docs/ folder contains the ACTUAL implementation patterns.
Reading 3-4 files is NOT enough. You MUST read ALL files for your project type.
MANDATORY READING ORDER BY PROJECT TYPE
For ALL Projects (Read First, Every Time)
| Order | File | Why |
|---|---|---|
| 1 | docs/core-typescript-law-CompleteLaw.md |
THE LAW - Type system rules, forbidden constructs, required patterns |
| 2 | guidelines/setup-guidelines.md |
Package versions, tsconfig, ESLint configs |
For Smart Contracts (AssemblyScript)
Read ALL of these BEFORE writing contract code:
| Order | File | Contains |
|---|---|---|
| 1 | docs/core-typescript-law-CompleteLaw.md |
Type rules (applies to AS too) |
| 2 | guidelines/setup-guidelines.md |
Package versions, asconfig.json |
| 3 | guidelines/contracts-guidelines.md |
Summary of contract patterns |
| 4 | docs/contracts-btc-runtime-README.md |
Runtime overview |
| 5 | docs/contracts-btc-runtime-getting-started-installation.md |
Setup |
| 6 | docs/contracts-btc-runtime-getting-started-first-contract.md |
Entry point, factory pattern |
| 7 | docs/contracts-btc-runtime-getting-started-project-structure.md |
Directory layout |
| 8 | docs/contracts-btc-runtime-core-concepts-storage-system.md |
Storage types, pointers |
| 9 | docs/contracts-btc-runtime-core-concepts-pointers.md |
Pointer allocation |
| 10 | docs/contracts-btc-runtime-api-reference-safe-math.md |
SafeMath (MANDATORY for u256) |
| 11 | docs/contracts-btc-runtime-gas-optimization.md |
Gas patterns, forbidden loops |
| 12 | docs/contracts-btc-runtime-core-concepts-security.md |
Security checklist |
For OP20 tokens, also read:
docs/contracts-btc-runtime-api-reference-op20.mddocs/contracts-btc-runtime-contracts-op20-token.md
For OP721 NFTs, also read:
docs/contracts-btc-runtime-api-reference-op721.mddocs/contracts-btc-runtime-contracts-op721-nft.md
For Frontend (React/Vite)
Read ALL of these BEFORE writing frontend code:
| Order | File | Contains |
|---|---|---|
| 1 | docs/core-typescript-law-CompleteLaw.md |
Type rules, forbidden constructs |
| 2 | guidelines/setup-guidelines.md |
Package versions, vite config |
| 3 | guidelines/frontend-guidelines.md |
Summary of frontend patterns |
| 4 | docs/core-opnet-README.md |
Client library overview |
| 5 | docs/core-opnet-getting-started-installation.md |
Installation |
| 6 | docs/core-opnet-getting-started-quick-start.md |
Quick start |
| 7 | docs/core-opnet-providers-json-rpc-provider.md |
Provider setup |
| 8 | docs/core-opnet-providers-internal-caching.md |
Caching (MANDATORY) |
| 9 | docs/core-opnet-contracts-instantiating-contracts.md |
Contract instances |
| 10 | docs/core-opnet-contracts-simulating-calls.md |
Read operations |
| 11 | docs/core-opnet-contracts-sending-transactions.md |
Write operations |
| 12 | docs/core-opnet-contracts-transaction-configuration.md |
Transaction options |
| 13 | docs/core-transaction-transaction-factory-interfaces.md |
Advanced TX params (fees, notes, anchors) |
| 14 | docs/clients-walletconnect-README.md |
Wallet connection |
| 15 | docs/frontend-motoswap-ui-README.md |
THE STANDARD - Reference implementation |
For Backend/API (Node.js)
Read ALL of these BEFORE writing backend code:
| Order | File | Contains |
|---|---|---|
| 1 | docs/core-typescript-law-CompleteLaw.md |
Type rules, forbidden constructs |
| 2 | guidelines/setup-guidelines.md |
Package versions |
| 3 | guidelines/backend-guidelines.md |
Summary of backend patterns |
| 4 | docs/core-opnet-backend-api.md |
REQUIRED FRAMEWORKS - hyper-express, uWebSockets.js |
| 5 | docs/core-opnet-providers-json-rpc-provider.md |
Provider setup |
| 6 | docs/core-opnet-providers-threaded-http.md |
Threading (MANDATORY) |
| 7 | docs/core-opnet-providers-internal-caching.md |
Caching (MANDATORY) |
| 8 | docs/core-opnet-contracts-instantiating-contracts.md |
Contract instances |
| 9 | docs/core-opnet-contracts-sending-transactions.md |
Sending transactions |
| 10 | docs/core-transaction-transaction-factory-interfaces.md |
Advanced TX params (fees, notes, anchors) |
FORBIDDEN FRAMEWORKS: Express, Fastify, Koa, Hapi, Socket.io - use hyper-express and uWebSockets.js only.
For Plugins (OPNet Node)
Read ALL of these BEFORE writing plugin code:
| Order | File | Contains |
|---|---|---|
| 1 | docs/core-typescript-law-CompleteLaw.md |
Type rules, forbidden constructs |
| 2 | guidelines/setup-guidelines.md |
Package versions |
| 3 | guidelines/plugin-guidelines.md |
Summary of plugin patterns |
| 4 | docs/core-OIP-OIP-0003.md |
PLUGIN SPECIFICATION - Full spec |
| 5 | docs/plugins-plugin-sdk-README.md |
SDK reference |
| 6 | docs/plugins-opnet-node-README.md |
Node integration |
CRITICAL: You MUST implement onReorg() to handle chain reorganizations or your data will be inconsistent.
For Unit Tests (TypeScript)
Read ALL of these BEFORE writing test code:
| Order | File | Contains |
|---|---|---|
| 1 | docs/core-typescript-law-CompleteLaw.md |
Type rules |
| 2 | guidelines/setup-guidelines.md |
Package versions |
| 3 | guidelines/unit-testing-guidelines.md |
Summary of test patterns |
| 4 | docs/testing-unit-test-framework-README.md |
Framework overview |
| 5 | docs/testing-opnet-unit-test-README.md |
Test setup |
| 6 | docs/testing-opnet-unit-test-docs-Blockchain.md |
Blockchain mocking |
| 7 | docs/testing-opnet-unit-test-docs-ContractRuntime.md |
Contract runtime |
CRITICAL: Unit tests are TypeScript (NOT AssemblyScript). They have a SEPARATE package.json.
For Generic Questions (Architecture, Concepts, Best Practices)
Read: guidelines/generic-questions-guidelines.md
For questions like:
- "How does OPNet work?"
- "Can OPNet survive 51% attacks?"
- "How does airdrop work on OPNet?"
- "What's the difference between OPNet and Runes/Ordinals?"
- "Why can't contracts hold BTC?"
- "What is transaction pinning?"
- "How is OPNet different from Ethereum?"
- "How do I port my Solidity contract to OPNet?"
- "What's the equivalent of ecrecover on OPNet?"
See the full guideline for complete topic mappings and what docs to read for each question type.
For Ethereum migration questions: Read guidelines/ethereum-migration-guidelines.md -- covers concept mapping from Ethereum to OPNet (addresses, signatures, tokens, wallets, transactions, DEX patterns).
CRITICAL -- ecrecover / ECDSA / Schnorr / signature verification questions:
When a user asks about ecrecover, ECDSA, Schnorr, or signature verification, you MUST:
- Warn FIRST that both ECDSA and Schnorr are DEPRECATED on OPNet. ECDSA will break when consensus disables
UNSAFE_QUANTUM_SIGNATURES_ALLOWED. Schnorr is also deprecated but still works through the safeverifySignaturepath. - Show the ONLY correct approach:
Blockchain.verifySignature(address, signature, hash)-- this is consensus-aware, auto-selects the right algorithm (Schnorr today, ML-DSA when enforced), and is the ONLY future-proof method. - Show client-side signing with AUTO methods ONLY:
MessageSigner.signMessageAuto()/tweakAndSignMessageAuto()/signMLDSAMessageAuto(). These auto-detect browser (OP_WALLET) vs backend (local keypair) and call the right method. NEVER use the non-Auto variants (signMessage(),signMLDSAMessage(),tweakAndSignMessage()) -- they are environment-specific and will crash in the wrong context. - NEVER point users to
verifyECDSASignature,verifyBitcoinECDSASignature, orverifySchnorrSignaturedirectly. These are deprecated internal methods. - Read
guidelines/ethereum-migration-guidelines.mdSection 3 BEFORE answering.
Do NOT just hand users deprecated APIs or environment-specific signing methods. Their code WILL break. Show them verifySignature for contract-side and *Auto() methods for client-side.
IMPORTANT: For conceptual questions, read the relevant docs/sections BEFORE answering. Do not guess or make assumptions about how OPNet works.
For Security Auditing
STOP - MANDATORY READING BEFORE ANY AUDIT
IF YOU AUDIT CODE WITHOUT READING THE REQUIRED DOCS, YOU WILL MISS CRITICAL VULNERABILITIES.
OPNet audits require understanding:
- AssemblyScript runtime internals - serialization, storage, cache coherence
- Bitcoin-specific attack vectors - transaction pinning, malleability, reorgs
- Critical vulnerability patterns - that standard audits miss completely
You MUST read guidelines/audit-guidelines.md COMPLETELY before auditing ANY code.
Skipping the audit guidelines = missing vulnerabilities. There are no shortcuts.
DISCLAIMER (MANDATORY IN EVERY REPORT)
IMPORTANT DISCLAIMER: This audit is AI-assisted and may contain errors,
false positives, or miss critical vulnerabilities. This is NOT a substitute
for a professional security audit by experienced human auditors.
Do NOT deploy to production based solely on this review.
Always engage professional auditors for contracts handling real value.
Mandatory Reading Order for Audits
Read ALL of these IN ORDER before starting ANY audit:
| Order | File | Why Required |
|---|---|---|
| 1 | docs/core-typescript-law-CompleteLaw.md |
Type rules that define secure code |
| 2 | guidelines/audit-guidelines.md |
COMPLETE AUDIT GUIDE - vulnerability patterns, checklists, detection methods |
Then read based on code type:
| Code Type | Additional Required Reading |
|---|---|
| Smart Contracts | docs/contracts-btc-runtime-core-concepts-security.md, docs/contracts-btc-runtime-gas-optimization.md, docs/contracts-btc-runtime-api-reference-safe-math.md, docs/contracts-btc-runtime-types-bytes-writer-reader.md |
| DEX/Swap Code | This SKILL.md - CSV, NativeSwap, Slashing sections |
| Frontend | guidelines/frontend-guidelines.md |
| Backend | guidelines/backend-guidelines.md |
| Plugins | guidelines/plugin-guidelines.md - Reorg Handling section |
AUDIT VERIFICATION CHECKPOINT
BEFORE writing ANY audit findings, confirm:
- I have read
guidelines/audit-guidelines.mdcompletely - I have read
docs/core-typescript-law-CompleteLaw.mdcompletely - I have read ALL additional docs for this code type
- I understand the Critical Runtime Vulnerability Patterns section
- I understand serialization/deserialization consistency requirements
- I understand storage system cache coherence issues
- I understand Bitcoin-specific attack vectors (CSV, pinning, reorgs)
If you cannot check ALL boxes, GO BACK AND READ THE DOCS.
Smart Contract Audit Checklist (Summary)
See guidelines/audit-guidelines.md for COMPLETE checklists with detection patterns.
| Category | Check For |
|---|---|
| Arithmetic | All u256 operations use SafeMath (no raw +, -, *, /) |
| Overflow/Underflow | SafeMath.add(), SafeMath.sub(), SafeMath.mul(), SafeMath.div() |
| Access Control | onlyOwner checks, authorization on sensitive methods |
| Reentrancy | State changes BEFORE external calls (checks-effects-interactions) |
| Gas/Loops | No while loops, all for loops bounded, no unbounded iterations |
| Storage | No iterating all map keys, stored aggregates for totals |
| Input Validation | All user inputs validated, bounds checked |
| Integer Handling | u256 created via fromString() for large values, not arithmetic |
| Serialization | Write/read type consistency, sizeof() mapping correct |
| Cache Coherence | Setters load from storage before comparison |
| Deletion Markers | Storage deletion uses 32-byte EMPTY_BUFFER |
| Bounds Checking | >= not > for max index checks |
TypeScript/Frontend/Backend Audit Checklist (Summary)
| Category | Check For |
|---|---|
| Type Safety | No any, no non-null assertions (the ! operator), no @ts-ignore |
| Null Safety | Explicit null checks, optional chaining used correctly |
| BigInt | All satoshi/token amounts use bigint, not number |
| No Floats | No floating point for financial calculations |
| Caching | Provider/contract instances cached, not recreated |
| Input Validation | Address validation, amount validation, bounds checking |
| Error Handling | Errors caught and handled, no silent failures |
| Provider Type | Use JSONRpcProvider (WebSocketProvider is experimental) |
Bitcoin-Specific Audit Checklist (Summary)
| Category | Check For |
|---|---|
| CSV Timelocks | All swap recipient addresses use CSV (anti-pinning) |
| UTXO Handling | Proper UTXO selection, dust outputs avoided |
| Transaction Malleability | Signatures not assumed immutable before confirmation |
| Fee Sniping | Proper locktime handling |
| Reorg Handling | Data deleted/reverted for reorged blocks |
| P2WPKH | Only compressed pubkeys (33 bytes), reject uncompressed |
| Witness Script Size | Validate against 3,600 byte standard limit |
DEX/Swap Audit Checklist (Summary)
| Category | Check For |
|---|---|
| Reservation System | Prices locked at reservation, not execution |
| Slippage Protection | Maximum slippage enforced |
| Front-Running | Reservation system prevents front-running |
| Queue Manipulation | Slashing penalties for queue abuse |
| Partial Fills | Atomic coordination of multi-provider payments |
Critical Runtime Vulnerability Patterns (Summary)
See guidelines/audit-guidelines.md for COMPLETE patterns with code examples.
| ID | Vulnerability | Impact |
|---|---|---|
| C-07 | Serialization Mismatch (write u16, read u32) | Data corruption |
| C-08 | sizeof() bytes treated as bits | Truncated data |
| C-09 | Signed/unsigned type confusion (i8 → u8) | Sign loss |
| C-10 | Cache coherence (setter compares to unloaded cache) | Silent state corruption |
| C-11 | Wrong deletion marker size | has() returns wrong result |
| C-12 | Generic integer truncation (only reading first byte) | Data loss |
| H-06 | Index out of bounds | Memory corruption |
| H-07 | Off-by-one (> instead of >=) |
Buffer overflow |
| H-08 | Pointer collision (truncate without hash) | Storage overwrites |
| M-05 | Taylor series divergence | Incorrect math |
ALWAYS end audit reports with the disclaimer. NEVER claim the audit is complete or guarantees security.
VERIFICATION CHECKPOINT
BEFORE writing ANY code, you MUST be able to answer:
What project type am I building? (Contract / Frontend / Backend / Plugin / Tests)
Did I read EVERY file listed for this project type?
- List each file you read
- If you cannot list them all, GO BACK AND READ
What are the exact package versions? (from
guidelines/setup-guidelines.md)- If you don't know, GO BACK AND READ
What constructs are FORBIDDEN?
any, non-null assertion operator,whileloops in contracts,numberfor satoshis, etc.- If you can't list them, GO BACK AND READ
What patterns are REQUIRED?
- Caching, SafeMath, threading, etc.
- If you can't list them, GO BACK AND READ
STOP - DO NOT PROCEED IF:
- ❌ You read fewer than 5 docs files
- ❌ You skipped any file in the mandatory reading list
- ❌ You read files not listed for your project type
- ❌ You cannot list the exact package versions
- ❌ You're about to write "I have enough context" after reading 2-3 files
GO BACK AND READ ALL THE REQUIRED FILES. NO SHORTCUTS.
CODE VERIFICATION ORDER (MANDATORY)
You MUST actually RUN these commands, not just mention them.
Step 1: Install Dependencies
For Non-Contract Projects:
npx npm-check-updates -u && npm i eslint@^9.39.2 @eslint/js@^9.39.2 @btc-vision/bitcoin@rc @btc-vision/transaction@rc opnet@rc @btc-vision/bip32 @btc-vision/ecpair --prefer-online
For Contract Projects:
npx npm-check-updates -u && npm i eslint@^9.39.2 @eslint/js@^9.39.2 @btc-vision/opnet-transform@1.1.0 @btc-vision/assemblyscript@^0.29.2 @btc-vision/as-bignum@0.1.2 @btc-vision/btc-runtime@rc --prefer-online
- Run the appropriate command FIRST before any other commands
- If package.json was created or modified, this is REQUIRED
- Do NOT skip this step - do NOT just run
npm installalone
Step 2: Run Prettier
npm run format
- Formats all code consistently
- Run BEFORE linting to avoid formatting conflicts
- Do NOT skip this step
Step 3: Run ESLint
npm run lint
- MUST pass with zero errors
- If errors exist, FIX THEM before proceeding
- Do NOT say "you should run lint" - actually RUN IT
Step 4: Run TypeScript Check
npm run typecheck
- Or
npx tsc --noEmitif no script exists - MUST pass with zero errors
- Fix all type errors before proceeding
Step 5: Build
npm run build
- Only run AFTER format, lint, and typecheck pass
- For contracts: verify .wasm file is generated
Step 6: Run Tests
npm run test
- Or
npx ts-node --esm tests/*.test.tsfor unit tests - All tests MUST pass
- If tests fail, FIX THE CODE
STOP - YOU MUST ACTUALLY RUN THESE COMMANDS
- ❌ Do NOT just write "run npm run lint to verify"
- ❌ Do NOT skip steps because "it should work"
- ❌ Do NOT consider code complete without running ALL steps
- ✅ Actually execute each command
- ✅ Verify each command passes
- ✅ Fix any errors before proceeding to next step
If you wrote code but didn't run these commands, YOUR TASK IS NOT COMPLETE.
TASK COMPLETION CHECKPOINT
BEFORE considering ANY task complete, ask yourself these questions:
Have I performed all the steps required by the task?
- Did I address everything the user asked for?
- Did I miss any requirements?
Did I actually run the verification commands?
- Did I run
npm install? (REQUIRED if package.json exists/changed) - Did I run
npm run format? (REQUIRED - Prettier formatting) - Did I run
npm run lint? (REQUIRED - must pass with zero errors) - Did I run
npm run typecheckornpx tsc --noEmit? (REQUIRED) - Did I run
npm run build? (REQUIRED for deployable code) - Did I run
npm run test? (REQUIRED if tests exist) - If ANY command failed, did I fix the errors?
- "I should run lint" is NOT the same as actually running it
- Did I run
Did I review and reconsider my work?
- Re-read what I produced - is it correct?
- Did I make any assumptions that could be wrong?
- Are there edge cases I didn't consider?
- Could my code/answer introduce bugs or vulnerabilities?
- For smart contracts: SafeMath, bounds checking, access control, serialization
- For TypeScript: type safety, null handling, caching patterns
- For answers: Is the information accurate? Did I verify it?
Did I write complete, production-ready code?
- NEVER add comments like "in a production environment, you would..."
- NEVER add stubs, placeholders, or TODO comments
- NEVER write partial implementations with "add your logic here"
- If you can't implement something fully, explain why and ask the user
- All code must be ready to use immediately, not a starting point
Is it appropriate to adjust non-code files?
- Did I update documentation if behavior changed?
- Did I update config files if dependencies changed?
Should new tests be written?
- If I added new functionality, are there tests for it?
- Do the tests cover security patterns from the guidelines?
Is this just exploration/research?
- If yes, tests, linting, and auditing are not required
- Focus on providing accurate information
This self-reflection prevents incomplete work, missed requirements, and security vulnerabilities.
PROJECT DELIVERY
STOP - BEFORE CREATING ANY ZIP OR DELIVERABLE
You MUST run and PASS all verification commands BEFORE packaging:
# 1. Install dependencies (use the appropriate command for your project type)
# Non-contract:
npx npm-check-updates -u && npm i eslint@^9.39.2 @eslint/js@^9.39.2 @btc-vision/bitcoin@rc @btc-vision/transaction@rc opnet@rc @btc-vision/bip32 @btc-vision/ecpair --prefer-online
# Contract:
npx npm-check-updates -u && npm i eslint@^9.39.2 @eslint/js@^9.39.2 @btc-vision/opnet-transform@1.1.0 @btc-vision/assemblyscript@^0.29.2 @btc-vision/as-bignum@0.1.2 @btc-vision/btc-runtime@rc --prefer-online
# 2. Format code
npm run format
# 3. Lint (MUST PASS)
npm run lint
# 4. TypeScript check (MUST PASS)
npm run typecheck
# OR: npx tsc --noEmit
# 5. Build (MUST SUCCEED)
npm run build
# 6. Tests (MUST PASS)
npm run test
If ANY command fails, DO NOT create the zip. Fix the errors first.
❌ FORBIDDEN: Creating zip without running verification commands ❌ FORBIDDEN: Saying "run npm run lint to verify" instead of actually running it ❌ FORBIDDEN: Skipping typecheck because "it should work"
When creating zip files for delivery:
NEVER include:
node_modules/- recipient runsnpm installbuild/ordist/- recipient runsnpm run build.git/- repository history.env- contains secrets
Correct zip command (only AFTER all checks pass):
zip -r project.zip . -x "node_modules/*" -x "build/*" -x "dist/*" -x ".git/*" -x "*.wasm" -x ".env"
What is OPNet
OPNet is a Bitcoin L1 consensus layer enabling smart contracts directly on Bitcoin. It is:
- NOT a metaprotocol - It's a full consensus layer
- Fully trustless - No centralized components
- Permissionless - Anyone can participate
- Decentralized - Relies on Bitcoin PoW + OPNet epoch SHA1 mining
Security Model
After 20 blocks, an epoch is buried deep enough that changing it requires rewriting Bitcoin history at millions of dollars per hour, making OPNet state more final than Bitcoin's 6-confirmation security.
The checksum root for each epoch is a cryptographic fingerprint of the entire state. If even one bit differs, the checksum changes completely and proof fails, making silent state corruption impossible.
Key Principles
- Contracts are WebAssembly (AssemblyScript) - Deterministic execution
- NON-CUSTODIAL - Contracts NEVER hold BTC
- Verify-don't-custody - Contracts verify L1 tx outputs, not hold funds
- Partial reverts - Only consensus layer execution reverts; Bitcoin transfers are ALWAYS valid
- No gas token - Uses Bitcoin directly
- CSV timelocks are MANDATORY - All addresses receiving BTC in swaps MUST use CSV (CheckSequenceVerify) to prevent transaction pinning attacks
Why OPNet Requires Consensus (Not Just Indexing)
OPNet is fundamentally different from indexer-based protocols like Runes, Ordinals/BRC-20, or Alkanes:
| Protocol | State Consistency | Can Different Nodes Disagree? |
|---|---|---|
| Runes | Indexer-dependent | Yes - no consensus mechanism |
| BRC-20 | Indexer-dependent | Yes - no consensus mechanism |
| Alkanes | Indexer-dependent | Yes - WASM is deterministic but no consensus |
| OPNet | Cryptographic consensus | No - proof-of-work + attestations enforce agreement |
Why this matters: For applications requiring binding state consistency (like DEXs, escrows, or any multi-party coordination), indexer disagreement is catastrophic. When NativeSwap locks a reservation at a specific price, EVERY node must agree that reservation exists. OPNet's consensus layer guarantees this through cryptographic proofs.
CSV: The Critical Anti-Pinning Mechanism (MANDATORY)
What is Transaction Pinning?
Transaction pinning is a catastrophic attack vector that most Bitcoin protocols ignore. Here's how it works:
- When you send Bitcoin to someone, they can immediately create a transaction spending that Bitcoin, even before the first transaction is confirmed
- An attacker creates massive chains of unconfirmed transactions, all dependent on each other
- This makes it impossible for miners to include your legitimate transaction in a block
- Your transaction is stuck in mempool limbo while the attacker manipulates contract state
Why Pinning Destroys DEXs Without Protection
Attack scenario on an unprotected DEX:
- Attacker pins your swap transaction, preventing confirmation
- Your reservation expires, but your BTC is stuck in mempool
- Attacker cancels their sell orders or manipulates pool state
- When your transaction finally confirms (if ever), no tokens remain
- Result: Attacker gets free money, you lose everything
This vulnerability exists in every Bitcoin protocol that doesn't mandate CSV:
- Multisig bridges can be frozen entirely with one malicious unwrap request
- Ordinals marketplaces are vulnerable
- Runes trading platforms are vulnerable
- BRC-20 exchanges are vulnerable
The CSV Solution
CSV (CheckSequenceVerify, BIP 112) completely eliminates pinning attacks:
Without CSV: Maximum unconfirmed chain length = UNLIMITED (attackers can pin forever)
With CSV: Maximum unconfirmed chain length = ZERO (must wait for confirmation)
By requiring all seller addresses to have a 1-block CSV timelock, once Bitcoin arrives at those addresses, it cannot be spent again for at least one block. This is mathematically provable and completely closes the attack vector.
Implementation Requirement
ALL addresses receiving BTC in OPNet swaps MUST use CSV timelocks.
This is enforced at the protocol level in NativeSwap. If you're building any application that coordinates BTC transfers with smart contract state, you MUST implement CSV protection.
The Two Address Systems (CRITICAL for Airdrops)
Why OPNet Airdrops Work Differently Than Ethereum
On Ethereum, there's one address format. You can loop through addresses and call transfer(to, amount) because ERC20 contracts just decrement sender balance and increment recipient balance using the same address type.
On OPNet, contract balances are keyed by ML-DSA public key hashes (32-byte quantum-resistant addresses), but users are typically known externally by their Bitcoin addresses (Taproot P2TR, tweaked public keys). These are completely different cryptographic systems with no inherent link between them.
| Address System | Format | Used For |
|---|---|---|
| Bitcoin Address | Taproot P2TR (bc1p...) |
External identity, what you have in your airdrop list |
| OPNet Address | ML-DSA public key hash (32 bytes) | Contract balances, internal state |
WHY YOU CANNOT JUST LOOP AND TRANSFER
If you have a list of Bitcoin addresses from token holders or snapshot participants:
// WRONG - THIS DOES NOT WORK
for (const btcAddress of airdropList) {
await token.transfer(btcAddress, amount); // IMPOSSIBLE
}
The contract literally cannot credit tokens to a Bitcoin address directly. The contract storage uses ML-DSA addresses, not Bitcoin addresses. The mapping between them only exists once a user explicitly proves ownership of both keys together.
THE CORRECT SOLUTION: Claim-Based Airdrop Contract
Airdrops on OPNet are done via a smart contract with a claim pattern:
1. Deploy an airdrop contract that stores allocations keyed by tweaked public key:
// Contract storage
mapping(tweakedPubKey => amount) // Store: which Bitcoin addresses get how much
mapping(tweakedPubKey => claimed) // Track: has this allocation been claimed
2. Users call claim() providing a signature that proves they control that Bitcoin address:
// User's frontend (browser) - OP_WALLET signs automatically
const message = `Claim airdrop for ${contractAddress}`;
const signed = await MessageSigner.tweakAndSignMessageAuto(message);
// Submit to contract with signature in calldata
await airdropContract.claim(signature, messageHash);
3. Contract verifies and transfers:
// Contract logic
public claim(calldata: Calldata): BytesWriter {
const signature = calldata.readBytes(64);
const messageHash = calldata.readBytes(32);
// Verify signature proves caller owns the tweaked public key
if (!Blockchain.verifySignature(Blockchain.tx.origin, signature, messageHash, false)) {
throw new Revert('Invalid signature');
}
// Get allocation for this tweaked public key
const tweakedKey = Blockchain.tx.origin.tweakedPublicKey;
const amount = this.allocations.get(tweakedKey);
// Transfer to caller's ML-DSA address (now linked!)
this._mint(Blockchain.tx.sender, amount);
this.claimed.set(tweakedKey, true);
}
This is the "unlock transaction" - the moment where the user proves ownership of both identities (Bitcoin address AND ML-DSA address), allowing the contract to link them and credit the tokens.
The Banana Locker Analogy
- You know 300 monkeys by their face (Bitcoin address)
- Lockers open with a secret handshake (ML-DSA key)
- You label lockers with faces and put bananas inside
- When a monkey shows up, they show face AND do handshake
- System learns: "this face = this handshake" and gives them their banana
- The banana was always "theirs" but they couldn't access it until they linked face to handshake
NativeSwap: How to Build a Real DEX on Bitcoin
NativeSwap answers the biggest unanswered question in BitcoinFi: How do you build an actual AMM that trades native BTC for tokens, trustlessly, without custody?
The Fundamental Problem
Traditional AMMs (like Uniswap) hold both assets in a pool and use math to set prices. Bitcoin cannot do this - you literally cannot have a smart contract hold and programmatically transfer BTC.
Why common "solutions" fail:
- Multisig wallets: Trusted parties can collude or disappear
- Wrapped BTC: Bridges become honeypots (billions stolen from bridges)
- Pure order books: Terrible liquidity without market makers holding inventory
Virtual Reserves: The Solution
NativeSwap realizes that an AMM doesn't need to physically hold assets - it just needs to track the economic effect of trades. This is similar to:
- Banks updating ledger entries without moving physical cash
- Futures markets trading billions in commodities without touching a barrel of oil
- Clearinghouses settling trillions without holding underlying assets
How it works:
- The contract maintains two numbers:
bitcoinReserveandtokenReserve - When someone buys tokens with BTC, the system records that bitcoin reserve increased and token reserve decreased
- The actual BTC goes directly to sellers, not to the contract
- AMM pricing only depends on the ratio between reserves
- The constant product formula (
bitcoinReserve × tokenReserve = k) works identically whether reserves are physical or virtual
Two-Phase Commit: Why Reservations Are Necessary
Problem: OPNet can revert smart contract execution, but it cannot reverse Bitcoin transfers. Once you send BTC, that transfer is governed by Bit
…(truncated)