- 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 lint
- and
- npm 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
- .wasm
- output 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
- (produces
- dist/
- 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
- 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-website
- skill
- — 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 one = runtime 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.md
docs/contracts-btc-runtime-contracts-op20-token.md
For OP721 NFTs, also read:
docs/contracts-btc-runtime-api-reference-op721.md
docs/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 safe
verifySignature
path.
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
, or
verifySchnorrSignature
directly. These are deprecated internal methods.
Read
guidelines/ethereum-migration-guidelines.md
Section 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.md
completely
I have read
docs/core-typescript-law-CompleteLaw.md
completely
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, while loops in contracts, number for 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 install alone 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 --noEmit if 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.ts for 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 typecheck or npx 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 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 runs
- npm install
- build/
- or
- dist/
- - recipient runs
- npm 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:
- bitcoinReserve
- and
- tokenReserve
- 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 Bitcoin's consensus rules, not OPNet's.
- Catastrophic scenario without reservations:
- You see token price is 0.01 BTC/token
- You create a transaction sending 1 BTC to buy 100 tokens
- During 10-20 minute confirmation time, other trades push price to 0.02 BTC/token
- On Ethereum, your transaction would revert and return your ETH
- On Bitcoin,
- your BTC transfer already happened
- - the contract can't send it back
- You lose your BTC and get zero tokens
- The reservation system (two-phase commit):
- Phase
- What Happens
- Phase 1: Reserve
- Prove you control BTC (UTXOs as inputs, sent back to yourself), pay small fee,
- price is locked in consensus state
- Phase 2: Execute
- Send exact BTC amount to providers (up to 200 addresses atomically), guaranteed execution at locked price
- Benefits:
- No slippage risk
-
- Price locked at reservation time
- No front-running
-
- Once price is locked in OPNet state, no one can change it
- Partial fills
-
- Automatically coordinate payments to up to 200 providers in single atomic transaction (impossible on any other Bitcoin protocol)
- Queue Impact: Accounting for Pending Sells
- Sellers queue tokens at the prevailing AMM price. The Queue Impact mechanism adjusts effective token reserve using
- logarithmic scaling
- :
- Why logarithmic?
- Markets process information multiplicatively
- Doubling queue from 100→200 tokens has same psychological impact as 1000→2000
- First sellers signal strong selling pressure; additional sellers have diminishing marginal impact
- Matches empirical observations from market microstructure research
- Slashing: Making Queue Manipulation Economically Irrational
- Without penalties, Queue Impact would be worthless:
- Attacker adds massive fake sell orders → crashes price via Queue Impact
- Attacker buys cheap tokens
- Attacker cancels their sells
- Profit from manipulation
- The slashing mechanism:
- Immediate cancellation
-
- 50% penalty (exceeds any realistic manipulation profit)
- Extended squatting
-
- Escalates to 90% penalty
- Slashed tokens return to pool
-
- Attempted attacks actually improve liquidity
- This makes manipulation economically irrational and ensures queue depth is a reliable market signal.
- Summary: Why Each Component Is Necessary
- Component
- Constraint It Addresses
- Virtual reserves
- Smart contracts can't custody BTC on Bitcoin
- Reservations (two-phase commit)
- OPNet controls contract state, not Bitcoin transfers
- Queue Impact
- Pending orders affect market psychology and pricing
- Slashing
- Queue Impact would be manipulable without penalties
- CSV timelocks
- UTXO chains are vulnerable to transaction pinning
- OPNet consensus
- Indexers can't provide binding state consistency
- Remove any component and the system either becomes exploitable or stops functioning as an AMM.
- ENFORCEMENT RULES (NON-NEGOTIABLE)
- ABI-Based Contract Interaction is MANDATORY
- ALL frontend websites that interact with OPNet smart contracts MUST use the
- opnet
- npm package with the contract's ABI definition.
- This is non-negotiable.
- ALWAYS
- import and use the contract's ABI (from
- opnet
- built-in ABIs or custom ABIs) when instantiating contracts
- NEVER
- use raw RPC calls, manual calldata encoding, or any other method to interact with contracts
- ALWAYS
- use typed contract instances created from ABIs for type-safe method calls and return value decoding
- Read
- docs/core-opnet-abi-reference-abi-overview.md
- and
- docs/core-opnet-abi-reference-custom-abis.md
- to understand ABI creation and usage
- // CORRECT - Use opnet package with ABI definition
- import
- {
- JSONRpcProvider
- }
- from
- 'opnet'
- ;
- import
- {
- MyContractABI
- }
- from
- './abi/MyContractABI.js'
- ;
- const
- provider
- =
- new
- JSONRpcProvider
- (
- 'https://regtest.opnet.org'
- )
- ;
- const
- contract
- =
- provider
- .
- getContract
- (
- contractAddress
- ,
- MyContractABI
- )
- ;
- const
- result
- =
- await
- contract
- .
- someMethod
- (
- param1
- ,
- param2
- )
- ;
- // WRONG - Raw RPC calls without ABI
- const
- result
- =
- await
- fetch
- (
- rpcUrl
- ,
- {
- body
- :
- JSON
- .
- stringify
- (
- {
- method
- :
- 'call'
- ,
- params
- :
- [
- ...
- ]
- }
- )
- }
- )
- ;
- // WRONG - Manual calldata encoding
- const
- calldata
- =
- new
- Uint8Array
- (
- [
- ...
- ]
- )
- ;
- If a contract does not have a pre-built ABI in
- opnet
- , you MUST create a custom ABI definition following
- docs/core-opnet-abi-reference-custom-abis.md
- .
- KNOWN FRONTEND MISTAKES — NEVER REPEAT THESE
- These are real bugs that have occurred. Every one of them is now
- forbidden
- .
- 1. WRONG: Using
- contract.execute()
- with raw selector bytes
- // WRONG - Raw execute with selector bytes. NEVER DO THIS.
- const
- selector
- =
- new
- Uint8Array
- (
- [
- 0x01
- ,
- 0x02
- ,
- 0x03
- ,
- 0x04
- ]
- )
- ;
- const
- result
- =
- await
- contract
- .
- execute
- (
- selector
- ,
- calldata
- )
- ;
- // CORRECT - Use ABI-defined typed method calls
- const
- result
- =
- await
- contract
- .
- claim
- (
- signature
- ,
- messageHash
- )
- ;
- Always define a proper ABI and call methods by name. Raw selectors bypass type safety and break silently.
- 2. WRONG: Missing
- getContract
- parameters
- getContract
- requires
- 5 parameters
- :
- (address, abi, provider, network, sender)
- .
- // WRONG - Missing params (will fail or return broken contract)
- const
- contract
- =
- getContract
- (
- address
- ,
- abi
- )
- ;
- const
- contract
- =
- getContract
- (
- address
- ,
- abi
- ,
- provider
- )
- ;
- // CORRECT - All 5 params
- const
- contract
- =
- getContract
- (
- address
- ,
- abi
- ,
- provider
- ,
- network
- ,
- senderAddress
- )
- ;
- Read
- docs/core-opnet-contracts-instantiating-contracts.md
- for the exact signature. Do NOT guess the params.
- 3. WRONG: Gating frontend actions on
- signer
- from walletconnect
- On a frontend, the wallet browser extension handles signing. The
- signer
- object from
- useWalletConnect()
- may be
- null
- /
- undefined
- initially — do NOT gate user actions on it.
- // WRONG - Throws "Wallet not connected" because signer is null on frontend
- if
- (
- !
- signer
- )
- throw
- new
- Error
- (
- 'Wallet not connected'
- )
- ;
- await
- contract
- .
- claim
- (
- signer
- ,
- ...
- )
- ;
- // CORRECT - Check wallet connection status, not signer object
- const
- {
- isConnected
- ,
- address
- }
- =
- useWalletConnect
- (
- )
- ;
- if
- (
- !
- isConnected
- ||
- !
- address
- )
- throw
- new
- Error
- (
- 'Wallet not connected'
- )
- ;
- 4. WRONG: Using walletconnect's provider for read calls
- Create a
- separate
- JSONRpcProvider
- for read-only contract calls. Do not reuse walletconnect's provider for reads.
- // WRONG - Using walletconnect provider for reads
- const
- {
- provider
- }
- =
- useWalletConnect
- (
- )
- ;
- const
- data
- =
- await
- provider
- .
- call
- (
- ...
- )
- ;
- // CORRECT - Dedicated read provider
- const
- readProvider
- =
- new
- JSONRpcProvider
- (
- 'https://regtest.opnet.org'
- )
- ;
- const
- contract
- =
- readProvider
- .
- getContract
- (
- address
- ,
- abi
- )
- ;
- const
- data
- =
- await
- contract
- .
- someReadMethod
- (
- )
- ;
- 5. WRONG: Importing from wrong packages
- Know where symbols live:
- Symbol
- Correct Package
- WRONG Package
- Address
- @btc-vision/transaction
- (also re-exported from
- opnet
- )
- ❌
- @btc-vision/bitcoin
- (not in browser bundle)
- ABIDataTypes
- @btc-vision/transaction
- (also re-exported from
- opnet
- )
- ❌
- @btc-vision/bitcoin
- JSONRpcProvider
- opnet
- ❌
- @btc-vision/provider
- networks
- @btc-vision/bitcoin
- —
- // CORRECT imports for frontend
- import
- {
- Address
- ,
- ABIDataTypes
- }
- from
- 'opnet'
- ;
- import
- {
- JSONRpcProvider
- }
- from
- 'opnet'
- ;
- // WRONG - Address is NOT in the browser bundle of @btc-vision/bitcoin
- import
- {
- Address
- }
- from
- '@btc-vision/bitcoin'
- ;
- // ❌ WILL FAIL IN BROWSER
- When in doubt, import from
- opnet
- — it re-exports the most commonly needed symbols.
- 6. WRONG: Passing a raw Bitcoin address (bc1q/bc1p) or raw ML-DSA public key to Address.fromString()
- Address.fromString()
- takes
- TWO parameters
- :
- First param
-
- The
- HASH
- of the ML-DSA public key (32 bytes, 0x-prefixed). This is NOT the raw ML-DSA public key (which is much larger). The walletconnect hook provides this as
- mldsaPublicKey
- — it is already hashed.
- Second param
- The Bitcoin tweaked public key (33 bytes compressed, 0x-prefixed). The walletconnect hook provides this as
publicKey
.
It does NOT accept raw Bitcoin addresses (bc1q/bc1p), and it does NOT accept only one parameter.
// WRONG - Passing a raw Bitcoin address. WILL CRASH.
const
sender
=
Address
.
fromString
(
walletAddress
)
;
// walletAddress = "bc1q..." ❌
// WRONG - Only one parameter. WILL CRASH.
const
sender
=
Address
.
fromString
(
publicKey
)
;
// ❌
// WRONG - Passing mldsaPublicKey (the RAW ML-DSA public key, ~2500 bytes).
// Address.fromString expects the 32-byte HASH, not the raw key. ❌
const
sender
=
Address
.
fromString
(
mldsaPublicKey
,
publicKey
)
;
// ❌ WRONG! mldsaPublicKey is raw
// CORRECT - ALWAYS pass hashedMLDSAKey (32-byte hash) + publicKey (Bitcoin tweaked pubkey)
const
sender
=
Address
.
fromString
(
hashedMLDSAKey
,
publicKey
)
;
// hashedMLDSAKey = "0xABCD..." (32-byte SHA256 hash of ML-DSA pubkey, from walletconnect's hashedMLDSAKey)
// publicKey = "0x0203..." (33-byte compressed Bitcoin tweaked pubkey, from walletconnect's publicKey)
On frontend with walletconnect:
const
{
publicKey
,
hashedMLDSAKey
}
=
useWalletConnect
(
)
;
// publicKey = Bitcoin tweaked public key (0x hex, 33 bytes compressed)
// hashedMLDSAKey = 32-byte SHA256 hash of ML-DSA public key (0x hex)
// NOTE: mldsaPublicKey is the RAW key (~2500 bytes) — do NOT use it for Address.fromString()
// CORRECT - Use hashedMLDSAKey (NOT mldsaPublicKey) + publicKey
const
senderAddress
=
Address
.
fromString
(
hashedMLDSAKey
,
publicKey
)
;
// Then use in getContract
const
contract
=
getContract
<
IMyContract
( contractAddr , abi , provider , network , senderAddress ) ; If mldsaPublicKey is not available (wallet doesn't support ML-DSA yet), use provider.getPublicKeyInfo(walletAddress) to resolve the public key info, then construct the Address. 7. WRONG: Using walletAddress (bc1q.../bc1p...) where a public key hex is needed The walletAddress from walletconnect is a bech32 Bitcoin address (bc1q... or bc1p...). It is NOT a public key. Many OPNet APIs need public key hex (0x-prefixed), not bech32 addresses. // WRONG - Passing bech32 address where public key is expected const sender = Address . fromString ( walletAddress ) ; // "bc1q..." is NOT a public key ❌ await contract . someMethod ( walletAddress ) ; // If it expects a pubkey ❌ // CORRECT - Use hashedMLDSAKey and publicKey from walletconnect const { publicKey , hashedMLDSAKey , mldsaPublicKey , address : walletAddress } = useWalletConnect ( ) ; // publicKey = hex string "0x0203..." (Bitcoin tweaked pubkey, 33 bytes compressed) // hashedMLDSAKey = hex string "0xABCD..." (32-byte SHA256 hash of ML-DSA pubkey) // mldsaPublicKey = raw ML-DSA public key (~2500 bytes) — for signing ONLY, NOT for Address.fromString // walletAddress = "bc1q..." (only use for display and refundTo) Rule of thumb: walletAddress (bc1q/bc1p) → ONLY for display to user and refundTo in sendTransaction publicKey (0x hex, 33 bytes) → Bitcoin tweaked public key, for Address.fromString second param hashedMLDSAKey (0x hex, 32 bytes) → SHA256 hash of ML-DSA public key, for Address.fromString first param mldsaPublicKey (0x hex, ~2500 bytes) → Raw ML-DSA public key, for MLDSA signing/verification ONLY. NEVER use for Address.fromString Before Writing ANY Code YOU MUST: READ docs/core-typescript-law-CompleteLaw.md COMPLETELY - These are strict TypeScript rules VERIFY project configuration matches standards in docs/ config files Configuration Files (in docs/ ) File Purpose eslint-contract.json ESLint for AssemblyScript contracts eslint-generic.json ESLint for TypeScript libraries eslint-react.json ESLint for React/Next.js frontends tsconfig-generic.json TypeScript config (NOT for contracts) asconfig.json AssemblyScript compiler config TypeScript is MANDATORY - NO EXCEPTIONS STOP - ABSOLUTE RULE YOU MUST NEVER, EVER WRITE RAW JAVASCRIPT CODE. NO EXCEPTIONS. All code MUST be TypeScript with proper tooling: Project Type Required Stack Frontend TypeScript + Vite + npm libraries Backend TypeScript + Node.js + npm libraries Contracts AssemblyScript (TypeScript-like) Tests TypeScript FORBIDDEN - NEVER DO THESE: ❌ Raw JavaScript files - NEVER write .js source files ❌ Inline