
Software Crypto Web3
- 390 installs
- 73 repo stars
- Updated July 13, 2026
- vasilyu1983/ai-agents-public
software-crypto-web3 is a Claude Code skill that guides secure blockchain development across EVM, Solana, Cosmos, and TON stacks for developers who implement smart contracts, audits, fuzz tests, and Web3 backend integrat
About
software-crypto-web3 is a vasilyu1983/ai-agents-public skill for designing and reviewing blockchain systems with security-first defaults, explicit threat models, and layered testing across unit, integration, fork, and fuzz or invariant suites. It maps toolchain choices such as Foundry and Hardhat for EVM Solidity, Anchor for Solana Rust programs, CosmWasm for Cosmos contracts, and Tact or FunC with Blueprint on TON, plus fuzzers like Echidna and Medusa and static analyzers including Slither and Aderyn. Coverage extends to ERC-20 and ERC-721 token standards, upgradeable proxy patterns, oracle integrations, subgraph indexing, account abstraction, L2 deployments, custody webhook validation, and backend transaction lifecycle monitoring. Developers reach for software-crypto-web3 when implementing DeFi protocols, writing audit-ready contracts, configuring Foundry fork tests, or connecting .NET or Node services to RPC providers and custodial signing flows. Triggers cover Solidity, Foundry, Anchor, CosmWasm, smart contract audits, DeFi protocols, and custodial webhook integration requests during blockchain feature work.
- software-crypto-web3
- AI & Agent Building
- AI-coding skill
Software Crypto Web3 by the numbers
- 390 all-time installs (skills.sh)
- +13 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #2,029 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vasilyu1983/ai-agents-public --skill software-crypto-web3Add your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 390 |
|---|---|
| repo stars | ★ 73 |
| Last updated | July 13, 2026 |
| Repository | vasilyu1983/ai-agents-public ↗ |
How do you build secure multi-chain smart contracts?
Helps with ai & agent building tasks.
Who is it for?
Developers building EVM, Solana, Cosmos, or TON applications who need audit-oriented smart contract patterns, fuzz testing, and production Web3 integration guidance.
Skip if: Developers working on centralized web apps without on-chain components who do not need wallet, RPC, or smart contract tooling.
When should I use this skill?
The user asks about Solidity, Foundry, Anchor, CosmWasm, smart contract audits, DeFi protocols, or Web3 backend RPC and custody integration.
What you get
Threat-modeled contract code, Foundry or Hardhat test suites, fuzz configs, deployment scripts, and Web3 backend integration patterns.
- smart contract source
- test and fuzz configs
- deployment and verification scripts
By the numbers
- Covers 4 blockchain ecosystems: EVM, Solana, Cosmos, and TON
- Documents Foundry, Hardhat, Echidna, and Medusa in testing workflows
- Includes ERC-20, ERC-721, ERC-1155, and SPL token standard patterns
Files
Software Crypto/Web3 Engineering
Use this skill to design, implement, and review secure blockchain systems: smart contracts, on-chain/off-chain integration, custody and signing, testing, audits, and production operations.
Defaults to: security-first development, explicit threat models, comprehensive testing (unit + integration + fork + fuzz/invariants), formal methods when high-value, upgrade safety (timelocks, governance, rollback plans), and defense-in-depth for key custody and signing.
---
Quick Reference
| Task | Tool/Framework | Command | When to Use |
|---|---|---|---|
| Solidity Development | Hardhat/Foundry | npx hardhat init or forge init | Ethereum/EVM smart contracts |
| Solana Programs | Anchor | anchor init | Solana blockchain development |
| Cosmos Contracts | CosmWasm | cargo generate --git cosmwasm-template | Cosmos ecosystem contracts |
| TON Contracts | Tact/FunC + Blueprint | npm create ton@latest | TON blockchain development |
| Testing (Solidity) | Foundry/Hardhat | forge test or npx hardhat test | Unit, fork, invariant tests |
| Security Audit | Slither/Aderyn/Echidna | slither . or aderyn . | Static analysis, fuzzing |
| AI-Assisted Review | AI scanners (optional) | N/A | Pre-audit preparation (verify findings manually) |
| Fuzzing | Echidna/Medusa | echidna . or medusa fuzz | Property-based fuzzing |
| Gas Optimization | Foundry Gas Snapshots | forge snapshot | Benchmark and optimize gas |
| Deployment | Hardhat Deploy/Forge Script | npx hardhat deploy | Mainnet/testnet deployment |
| Verification | Etherscan API | npx hardhat verify | Source code verification |
| Upgradeable Contracts | OpenZeppelin Upgrades | @openzeppelin/hardhat-upgrades | Proxy-based upgrades |
| Smart Wallets | ERC-4337, EIP-7702 | Account abstraction SDKs | Smart accounts and sponsored gas (verify network support) |
Scope
Use this skill when you need:
- Smart contract development (Solidity, Rust, CosmWasm)
- DeFi protocol implementation (AMM, lending, staking, yield farming)
- NFT and token standards (ERC20, ERC721, ERC1155, SPL tokens)
- DAO governance systems
- Cross-chain bridges and interoperability
- Gas optimization and storage patterns
- Smart contract security audits
- Testing strategies (Foundry, Hardhat, Anchor)
- Oracle integration (Chainlink, Pyth)
- Upgradeable contract patterns (proxies, diamonds)
- Web3 frontend integration (ethers.js, web3.js, @solana/web3.js)
- Blockchain indexing (The Graph, subgraphs)
- MEV protection and flashbots
- Layer 2 scaling solutions (Base, Arbitrum, Optimism, zkSync)
- Account abstraction (ERC-4337, EIP-7702, smart wallets)
- Backend crypto integration (.NET/C#, multi-provider architecture, CQRS)
- Webhook handling and signature validation (Fireblocks, custodial providers)
- Event-driven architecture with Kafka for crypto payments
- Transaction lifecycle management and monitoring
- Wallet management (custodial vs non-custodial)
Decision Tree: Blockchain Platform Selection
Project needs: [Use Case]
- EVM-compatible smart contracts?
- Complex testing needs -> Foundry (fuzzing, invariants, gas snapshots)
- TypeScript ecosystem -> Hardhat (plugins, TS, Ethers.js/Viem)
- Enterprise features -> NestJS + Hardhat
- High throughput / low fees?
- Rust-based -> Solana (Anchor)
- EVM L2 -> Arbitrum/Optimism/Base (Ethereum security, lower gas)
- Telegram distribution -> TON (Tact/FunC)
- Interoperability across chains?
- Cosmos ecosystem -> CosmWasm (IBC)
- Multi-chain apps -> LayerZero or Wormhole (verify trust assumptions)
- Bridge development -> custom (high risk; threat model required)
- Token standard implementation?
- Fungible tokens -> ERC20 (OpenZeppelin), SPL Token (Solana)
- NFTs -> ERC721/ERC1155 (OpenZeppelin), Metaplex (Solana)
- Semi-fungible -> ERC1155 (gaming, fractionalized NFTs)
- DeFi protocol development?
- AMM/DEX -> Uniswap V3 fork or custom (concentrated liquidity)
- Lending -> Compound/Aave fork (collateralized borrowing)
- Staking/yield -> custom reward distribution contracts
- Upgradeable contracts required?
- Transparent proxy -> OpenZeppelin (admin/user separation)
- UUPS -> upgrade logic in implementation
- Diamond -> modular functionality (EIP-2535)
- Backend integration?
- .NET/C# -> multi-provider architecture (see backend integration references)
- Node.js -> Ethers.js/Viem + durable queues
- Python -> Web3.py + FastAPIChain-Specific Considerations:
- Ethereum/EVM: Security-first, higher gas costs, largest ecosystem
- Solana: Performance-first, Rust required, lower fees
- Cosmos: Interoperability-first, IBC native, growing ecosystem
- TON: Telegram-first, async contracts, unique architecture
See references/ for chain-specific best practices.
---
Security-First Patterns (Jan 2026)
Security baseline: Assume an adversarial environment. Treat contracts and signing infrastructure as public, attackable APIs.
Custody, Keys, and Signing (Core)
Key management is a dominant risk driver in production crypto systems. Use a real key management standard as baseline (for example, NIST SP 800-57).
| Model | Who holds keys | Typical use | Primary risks | Default controls |
|---|---|---|---|---|
| Non-custodial | End user wallet | Consumer apps, self-custody | Phishing, approvals, UX errors | Hardware wallet support, clear signing UX, allowlists |
| Custodial | Your service (HSM/MPC) | Exchanges, payments, B2B | Key theft, insider threat, ops mistakes | HSM/MPC, separation of duties, limits/approvals, audit logs |
| Hybrid | Split responsibility | Enterprises | Complex failure modes | Explicit recovery/override paths, runbooks |
BEST:
- Separate hot/warm/cold signing paths with limits and approvals [Inference]
- Require dual control for high-value transfers (policy engine + human approval) [Inference]
- Keep an immutable audit trail for signing requests (who/what/when/why) [Inference]
AVOID:
- Storing private keys in databases or application config
- Reusing signing keys across environments (dev/staging/prod)
- Hot-wallet automation without rate limits and circuit breakers [Inference]
Checks-Effects-Interactions (CEI) Pattern
Mandatory for all state-changing functions.
// Correct: CEI pattern
function withdraw(uint256 amount) external {
// 1. CHECKS: Validate conditions
require(balances[msg.sender] >= amount, "Insufficient balance");
// 2. EFFECTS: Update state BEFORE external calls
balances[msg.sender] -= amount;
// 3. INTERACTIONS: External calls LAST
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
// Wrong: External call before state update (reentrancy risk)
function withdrawUnsafe(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] -= amount; // Too late!
}Security Tools (Jan 2026)
| Category | Tool | Purpose | When to Use |
|---|---|---|---|
| Static Analysis | Slither | Vulnerability detection, 92+ detectors | Every contract |
| Static Analysis | Aderyn | Rust-based, faster for large codebases | Large projects |
| Fuzzing | Echidna | Property-based fuzzing | Complex state |
| Fuzzing | Medusa | Parallelized Go fuzzer | CI/CD pipelines |
| Formal Verification | SMTChecker | Built-in Solidity checker | Every contract |
| Formal Verification | Certora | Property-based proofs (CVL) | DeFi, high-value |
| Formal Verification | Halmos | Symbolic testing | Complex invariants |
| AI-Assisted | Sherlock AI | ML vulnerability detection | Pre-audit prep |
| AI-Assisted | Olympix | DevSecOps integration | CI/CD security |
| AI-Assisted | AuditBase | 423+ detectors, LLM-powered | Business logic |
| Mutation Testing | SuMo | Test suite quality assessment | Test validation |
// Certora CVL rule example
rule balanceNeverNegative(address user) {
env e;
require balances[user] >= 0;
deposit(e);
assert balances[user] >= 0;
}AI-assisted review: Use AI tooling for pre-audit preparation and coverage, not for final security decisions. Treat outputs as untrusted and reproduce findings with deterministic tools, tests, and manual review.
MEV Protection
| Strategy | Implementation |
|---|---|
| Private mempool | Flashbots Protect, MEV Blocker |
| Commit-reveal | Hash commitment, reveal after deadline |
| Batch auctions | CoW Protocol, Gnosis Protocol |
| Encrypted mempools | Shutter Network |
// Commit-reveal pattern
mapping(address => bytes32) public commitments;
function commit(bytes32 hash) external {
commitments[msg.sender] = hash;
}
function reveal(uint256 value, bytes32 salt) external {
require(
keccak256(abi.encodePacked(value, salt)) == commitments[msg.sender],
"Invalid reveal"
);
// Process revealed value
}---
Account Abstraction (Jan 2026)
Note: Adoption numbers and upgrade timelines change quickly. Verify current ERC-4337 ecosystem state and any EIP-7702 activation details with WebSearch before making recommendations.
ERC-4337 vs EIP-7702
| Standard | Type | Key Feature | Use Case |
|---|---|---|---|
| ERC-4337 | Smart contract wallets | Full AA without protocol changes | New wallets, DeFi, gaming |
| EIP-7702 | EOA enhancement | EOAs execute smart contract code | Existing wallets, batch txns |
| ERC-6900 | Modular accounts | Plugin management for AA wallets | Extensible wallet features |
ERC-4337 Architecture:
User -> UserOperation -> Bundler -> EntryPoint -> Smart Account -> Target Contract
|
v
Paymaster (gas sponsorship)EIP-7702 (Pectra Upgrade):
- EOAs can temporarily delegate to smart contracts
- Enables batch transactions, sponsored gas for existing addresses
- Complementary to ERC-4337 (uses same bundler/paymaster infra)
- Supported by Ambire, Trust Wallet, and growing
Key Capabilities:
- Gasless transactions: Paymasters sponsor gas in ERC-20 or fiat
- Batch operations: Multiple actions in single transaction
- Social recovery: Multi-sig or guardian-based key recovery
- Session keys: Limited permissions for dApps without full wallet access
Smart Wallet Development
// Minimal ERC-4337 Account (simplified)
import "@account-abstraction/contracts/core/BaseAccount.sol";
contract SimpleAccount is BaseAccount {
address public owner;
function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external override returns (uint256 validationData) {
// Verify signature
require(_validateSignature(userOp, userOpHash), "Invalid sig");
// Pay prefund if needed
if (missingAccountFunds > 0) {
(bool success,) = payable(msg.sender).call{value: missingAccountFunds}("");
require(success);
}
return 0; // Valid
}
}---
Layer 2 Development (Jan 2026)
Note: L2 market share and risk stages change quickly. Use current data (for example, L2Beat and ecosystem dashboards) before stating rankings, TVL, or stage classifications.
L2 Selection Guide
| L2 | Type | Best For | Key Feature |
|---|---|---|---|
| Base | Optimistic | Consumer apps, mainstream adoption | Coinbase integration, low fees |
| Arbitrum | Optimistic | DeFi, mature ecosystem | Largest TVL, DAO grants |
| Optimism | Optimistic | Public goods, Superchain | OP Stack, grant programs |
| zkSync Era | ZK-Rollup | Fast finality, native AA | zkEVM, no withdrawal delay |
| StarkNet | ZK-Rollup | Cairo development, ZK-native | STARK proofs, custom VM |
Enterprise Rollups (2025-2026 Trend)
Major institutions launching L2s on OP Stack:
- Kraken INK - Exchange-native L2
- Uniswap UniChain - DeFi-optimized
- Sony Soneium - Gaming and media
- Robinhood - Arbitrum integration
EIP-4844 Blob Optimization
Since March 2024, rollups use blob-based data posting:
Before: calldata posting -> expensive
After: blob posting -> lower data availability costOptimism, zkSync optimized batching for blobs in 2025.
---
Common Mistakes (2025-2026)
Reality check: Exploits regularly cause large losses. Access control, signing/custody, and integration bugs remain top incident drivers.
| Mistake | Impact | Prevention |
|---|---|---|
| Missing access control | Unauthorized admin actions | Use OpenZeppelin Ownable2Step, AccessControl |
| Reentrancy | Drain funds via callback | CEI pattern, ReentrancyGuard, Slither checks |
| Unchecked external calls | Silent failures | Always check return values, use SafeERC20 |
| Integer overflow (pre-0.8) | Arbitrary value manipulation | Use Solidity 0.8.x+ (built-in checks) |
| Frontrunning | MEV extraction, sandwich attacks | Commit-reveal, Flashbots Protect, private mempool |
| Oracle manipulation | Price feed attacks | TWAP, multiple oracles, sanity bounds |
| Improper initialization | Proxy takeover | Use initializer modifier, _disableInitializers() |
| Storage collision (proxies) | Data corruption | Follow EIP-1967 slots, use OpenZeppelin upgrades |
Anti-Patterns to Avoid
AVOID:
- Using
tx.originfor authorization (phishing risk) - Storing secrets on-chain (all data is public)
- Using
block.timestampfor randomness (miner/validator influence) - Ignoring return values from
transfer/send - Using deprecated tooling (Truffle/Ganache/Brownie)
BEST:
- Run static analysis on every change (for example, Slither and Aderyn)
- Add fuzz/invariant tests before any audit
- Use formal methods for high-value DeFi (for example, Certora and symbolic testing)
---
LLM Limitations in Smart Contracts
Do not rely on LLMs for:
- Security-critical logic verification
- Gas optimization calculations
- Complex mathematical proofs
Use LLMs for:
- Boilerplate generation (tests, docs)
- Code explanation and review prep
- Initial vulnerability hypotheses (verify manually)
---
When NOT to Use This Skill
- Traditional backend without blockchain -> Use software-backend
- Pure API design without Web3 -> Use dev-api-design
- General security without smart contracts -> Use software-security-appsec
- Frontend-only dApp UI -> Use software-frontend + Web3 libraries
---
Navigation
Resources
- references/blockchain-best-practices.md - Universal blockchain patterns and security
- references/backend-integration-best-practices.md - .NET/C# crypto integration patterns (CQRS, Kafka, multi-provider)
- references/solidity-best-practices.md - Solidity/EVM-specific guidance
- references/rust-solana-best-practices.md - Solana + Anchor patterns
- references/cosmwasm-best-practices.md - Cosmos/CosmWasm guidance
- references/ton-best-practices.md - TON contracts (Tact/Fift/FunC) and deployment
- references/defi-protocol-patterns.md - AMM, lending, staking, yield farming, flash loans, oracle integration
- references/nft-token-standards.md - ERC-20/721/1155, SPL Token, Metaplex, token gating, metadata
- references/cross-chain-bridges.md - Bridge architectures, LayerZero, Wormhole, IBC, security analysis
- ../software-security-appsec/references/smart-contract-security-auditing.md - Smart contract audit workflows and tools (see software-security-appsec skill)
- data/sources.json - Curated external references per chain
- Shared secure review checklist: ../software-clean-code-standard/assets/checklists/secure-code-review-checklist.md
Templates
- Ethereum/EVM: assets/ethereum/template-solidity-hardhat.md, assets/ethereum/template-solidity-foundry.md
- Solana: assets/solana/template-rust-anchor.md
- Cosmos: assets/cosmos/template-cosmwasm.md
- TON: assets/ton/template-tact-blueprint.md, assets/ton/template-func-blueprint.md
- Bitcoin: assets/bitcoin/template-bitcoin-core.md
Related Skills
- ../software-security-appsec/SKILL.md - Security hardening, threat modeling, OWASP vulnerabilities
- ../software-architecture-design/SKILL.md - System decomposition, modularity, dependency design
- ../ops-devops-platform/SKILL.md - Infrastructure, CI/CD, observability for blockchain nodes
- ../software-backend/SKILL.md - API integration with smart contracts, RPC nodes, indexers
- ../qa-resilience/SKILL.md - Resilience, circuit breakers, retry logic for chains
- ../software-code-review/SKILL.md - Code review patterns and quality gates
- ../dev-api-design/SKILL.md - RESTful design for Web3 APIs and dApp backends
---
Trend Awareness Protocol
IMPORTANT: When users ask recommendation questions about Web3/crypto development, you MUST use WebSearch to check current trends before answering.
Trigger Conditions
- "What's the best blockchain for [use case]?"
- "What should I use for [smart contracts/DeFi/NFTs]?"
- "What's the latest in Web3 development?"
- "Current best practices for [Solidity/auditing/gas optimization]?"
- "Is [chain/protocol] still relevant in 2026?"
- "[Ethereum] vs [Solana] vs [other L1/L2]?"
- "Best framework for [smart contract development]?"
Required Searches
1. Search: "Web3 development best practices 2026" 2. Search: "[Ethereum/Solana/Base] development updates 2026" 3. Search: "smart contract security 2026" 4. Search: "[Hardhat/Foundry] comparison 2026"
What to Report
After searching, provide:
- Current landscape: What chains/tools are popular NOW
- Emerging trends: New protocols or patterns gaining traction
- Deprecated/declining: Chains or approaches losing relevance
- Recommendation: Based on fresh data and ecosystem activity
Example Topics (verify with fresh search)
- L2 ecosystem growth (Base, Arbitrum, Optimism)
- Solidity vs Rust for smart contracts
- Foundry vs Hardhat tooling
- Account abstraction (ERC-4337) adoption
- Cross-chain bridges and interoperability
- DeFi security patterns and audit practices
---
Operational Playbooks
- references/operational-playbook.md - Smart contract architecture, security-first workflows, and platform-specific patterns
Fact-Checking
- Use web search/web fetch to verify current external facts, versions, pricing, deadlines, regulations, or platform behavior before final answers.
- Prefer primary sources; report source links and dates for volatile information.
- If web access is unavailable, state the limitation and mark guidance as unverified.
Bitcoin Development — Bitcoin Core & Lightning Network Template
Production-grade Bitcoin development with Bitcoin Core, Lightning Network, and scripting.
---
Project Overview
This template provides guidance for Bitcoin development using:
- Bitcoin Core - Reference implementation of Bitcoin
- Bitcoin Script - Stack-based scripting language
- Lightning Network - Layer 2 payment channels
- BDK (Bitcoin Dev Kit) - Rust library for wallet development
- Electrum/Electrs - Server for SPV wallets
- BTCPay Server - Self-hosted payment processor
Use cases: Wallets, payment processors, Lightning apps, multisig, timelock contracts, DLCs
---
Project Structure
bitcoin-project/
├── bitcoin-core/
│ ├── bitcoin.conf # Node configuration
│ └── scripts/
│ ├── multisig.sh # Multisig wallet scripts
│ └── timelock.sh # Timelock scripts
├── wallet/
│ ├── src/
│ │ ├── main.rs # BDK wallet implementation
│ │ ├── descriptors.rs # Output descriptors
│ │ └── psbt.rs # PSBT handling
│ └── Cargo.toml
├── lightning/
│ ├── lnd.conf # LND configuration
│ └── node/
│ ├── channels.ts # Channel management
│ └── invoices.ts # Invoice handling
└── scripts/
├── deploy-node.sh # Node deployment
└── backup.sh # Backup scripts---
Environment Setup
1. Install Bitcoin Core
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install bitcoind bitcoin-cli
# macOS (Homebrew)
brew install bitcoin
# Or download from bitcoin.org
wget https://bitcoincore.org/bin/bitcoin-core-25.0/bitcoin-25.0-x86_64-linux-gnu.tar.gz
tar -xzf bitcoin-25.0-x86_64-linux-gnu.tar.gz
sudo install -m 0755 -o root -g root -t /usr/local/bin bitcoin-25.0/bin/*2. Configure Bitcoin Core
~/.bitcoin/bitcoin.conf:
# Network
testnet=1 # Use testnet (remove for mainnet)
# signet=1 # Or use signet for testing
# RPC
server=1
rpcuser=your_username
rpcpassword=your_secure_password
rpcallowip=127.0.0.1
rpcport=18332 # 8332 for mainnet
# Indexing (optional, needed for some features)
txindex=1 # Index all transactions
addressindex=1 # Index addresses
timestampindex=1 # Index timestamps
spentindex=1 # Index spent outputs
# Mempool
maxmempool=300 # MB
mempoolexpiry=72 # hours
# Pruning (for space-constrained nodes)
# prune=550 # Keep only 550MB of blocks3. Start Bitcoin Node
# Start daemon
bitcoind -daemon
# Check status
bitcoin-cli getblockchaininfo
# Stop daemon
bitcoin-cli stop---
Bitcoin Scripting
Basic Scripts
Pay to Public Key Hash (P2PKH):
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIGPay to Script Hash (P2SH):
OP_HASH160 <scriptHash> OP_EQUALPay to Witness Public Key Hash (P2WPKH - SegWit):
OP_0 <pubKeyHash>Pay to Taproot (P2TR):
OP_1 <taproot_output_key>Multisig Script (2-of-3)
#!/bin/bash
# Create 2-of-3 multisig address
# Generate 3 addresses
ADDR1=$(bitcoin-cli getnewaddress)
ADDR2=$(bitcoin-cli getnewaddress)
ADDR3=$(bitcoin-cli getnewaddress)
# Get public keys
PUBKEY1=$(bitcoin-cli getaddressinfo $ADDR1 | jq -r '.pubkey')
PUBKEY2=$(bitcoin-cli getaddressinfo $ADDR2 | jq -r '.pubkey')
PUBKEY3=$(bitcoin-cli getaddressinfo $ADDR3 | jq -r '.pubkey')
# Create multisig address
bitcoin-cli createmultisig 2 "[\"$PUBKEY1\",\"$PUBKEY2\",\"$PUBKEY3\"]"Output:
{
"address": "2N...",
"redeemScript": "5221...53ae",
"descriptor": "wsh(multi(2,[...],...))#..."
}Timelock Script (CSV - CheckSequenceVerify)
# Script: Coins can be spent after 144 blocks (~24 hours)
<144> OP_CHECKSEQUENCEVERIFY OP_DROP
<pubKey> OP_CHECKSIGCreating timelock transaction:
# Create raw transaction with sequence number
bitcoin-cli createrawtransaction \
'[{"txid":"<txid>","vout":0,"sequence":144}]' \
'{"<recipient_address>":0.01}'
# Sign and broadcast
bitcoin-cli signrawtransactionwithwallet <raw_tx>
bitcoin-cli sendrawtransaction <signed_tx>---
Wallet Development with BDK
Rust Wallet Implementation
Cargo.toml:
[dependencies]
bdk = { version = "0.29", features = ["electrum"] }
bitcoin = "0.30"src/main.rs:
use bdk::{
bitcoin::{Address, Network},
blockchain::ElectrumBlockchain,
database::MemoryDatabase,
electrum_client::Client,
wallet::AddressIndex,
KeychainKind, SyncOptions, Wallet,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create wallet with descriptor
let external_descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubD...)";
let internal_descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubD...)";
let wallet = Wallet::new(
external_descriptor,
Some(internal_descriptor),
Network::Testnet,
MemoryDatabase::default(),
)?;
// Connect to Electrum server
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
let blockchain = ElectrumBlockchain::from(client);
// Sync wallet
wallet.sync(&blockchain, SyncOptions::default())?;
// Get balance
let balance = wallet.get_balance()?;
println!("Balance: {} sats", balance);
// Get new address
let address = wallet.get_address(AddressIndex::New)?;
println!("New address: {}", address);
// Create transaction
let mut tx_builder = wallet.build_tx();
tx_builder
.add_recipient(Address::from_str("tb1...")?.script_pubkey(), 50_000)
.fee_rate(bdk::FeeRate::from_sat_per_vb(1.0));
let (mut psbt, _) = tx_builder.finish()?;
// Sign transaction
let finalized = wallet.sign(&mut psbt, Default::default())?;
println!("Transaction signed: {}", finalized);
// Extract and broadcast
if finalized {
let tx = psbt.extract_tx();
blockchain.broadcast(&tx)?;
println!("Transaction broadcast: {}", tx.txid());
}
Ok(())
}---
Lightning Network Integration
LND Setup
Install LND:
# Download LND
wget https://github.com/lightningnetwork/lnd/releases/download/v0.17.0/lnd-linux-amd64-v0.17.0.tar.gz
tar -xzf lnd-linux-amd64-v0.17.0.tar.gz
sudo install -m 0755 -o root -g root -t /usr/local/bin lnd-linux-amd64-v0.17.0/*lnd.conf:
[Application Options]
debuglevel=info
alias=MyLightningNode
color=#3399FF
[Bitcoin]
bitcoin.active=1
bitcoin.testnet=1
bitcoin.node=bitcoind
[Bitcoind]
bitcoind.rpcuser=your_username
bitcoind.rpcpass=your_password
bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332
bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333Start LND:
lndChannel Management
# Create wallet
lncli create
# Get node info
lncli getinfo
# Connect to peer
lncli connect 03..@host:port
# Open channel (1,000,000 sats)
lncli openchannel --node_key=03... --local_amt=1000000
# List channels
lncli listchannels
# Close channel
lncli closechannel <funding_txid> <output_index>Invoice and Payment
# Create invoice (10,000 sats)
lncli addinvoice --amt=10000 --memo="Coffee"
# Decode invoice
lncli decodepayreq <payment_request>
# Pay invoice
lncli payinvoice <payment_request>
# List invoices
lncli listinvoices
# List payments
lncli listpaymentsLND gRPC Client (TypeScript)
import * as fs from 'fs';
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
const lndCert = fs.readFileSync('/path/to/tls.cert');
const macaroon = fs.readFileSync('/path/to/admin.macaroon').toString('hex');
const packageDefinition = protoLoader.loadSync('lightning.proto');
const lnrpc = grpc.loadPackageDefinition(packageDefinition).lnrpc;
const credentials = grpc.credentials.createSsl(lndCert);
const macaroonCreds = grpc.credentials.createFromMetadataGenerator((args, callback) => {
const metadata = new grpc.Metadata();
metadata.add('macaroon', macaroon);
callback(null, metadata);
});
const combinedCreds = grpc.credentials.combineChannelCredentials(
credentials,
macaroonCreds
);
const lightning = new lnrpc.Lightning('localhost:10009', combinedCreds);
// Get node info
lightning.getInfo({}, (err, response) => {
if (err) console.error(err);
else console.log('Node info:', response);
});
// Create invoice
lightning.addInvoice({ value: 10000, memo: 'Coffee' }, (err, response) => {
if (err) console.error(err);
else console.log('Invoice:', response.payment_request);
});
// Pay invoice
lightning.sendPaymentSync({
payment_request: 'lnbc...'
}, (err, response) => {
if (err) console.error(err);
else console.log('Payment sent:', response);
});---
PSBT (Partially Signed Bitcoin Transactions)
Creating and Signing PSBT
# Create PSBT
bitcoin-cli walletcreatefundedpsbt \
'[]' \
'[{"<address>":0.01}]' \
| jq -r '.psbt' > unsigned.psbt
# Sign PSBT (wallet 1)
bitcoin-cli -rpcwallet=wallet1 walletprocesspsbt $(cat unsigned.psbt) \
| jq -r '.psbt' > partially_signed.psbt
# Sign PSBT (wallet 2)
bitcoin-cli -rpcwallet=wallet2 walletprocesspsbt $(cat partially_signed.psbt) \
| jq -r '.psbt' > fully_signed.psbt
# Finalize and broadcast
bitcoin-cli finalizepsbt $(cat fully_signed.psbt) \
| jq -r '.hex' | xargs bitcoin-cli sendrawtransaction---
Output Descriptors
Descriptor Types
# Single key P2WPKH
wpkh(xpub.../0/*)
# Multisig P2WSH (2-of-3)
wsh(multi(2,xpub1.../0/*,xpub2.../0/*,xpub3.../0/*))
# Nested SegWit (P2SH-P2WPKH)
sh(wpkh(xpub.../0/*))
# Taproot
tr(xpub.../0/*)
# With checksum
wpkh([fingerprint/derivation]xpub...)#checksumImport Descriptor
# Import watch-only descriptor
bitcoin-cli importdescriptors '[{
"desc": "wpkh([c258d2e4/84h/1h/0h]tpubD...)#checksum",
"timestamp": "now",
"range": [0, 1000],
"watchonly": true
}]'---
Useful Commands
Node Operations
# Get blockchain info
bitcoin-cli getblockchaininfo
# Get mempool info
bitcoin-cli getmempoolinfo
# Get network info
bitcoin-cli getnetworkinfo
# Get peer info
bitcoin-cli getpeerinfo
# Add node
bitcoin-cli addnode "<ip>:port" "add"
# Generate blocks (regtest only)
bitcoin-cli -regtest generatetoaddress 101 <address>Wallet Operations
# Create wallet
bitcoin-cli createwallet "my_wallet"
# Load wallet
bitcoin-cli loadwallet "my_wallet"
# Get new address
bitcoin-cli getnewaddress "" "bech32"
# Get balance
bitcoin-cli getbalance
# Send transaction
bitcoin-cli sendtoaddress <address> 0.01
# List transactions
bitcoin-cli listtransactions
# List unspent outputs
bitcoin-cli listunspent
# Dump private key
bitcoin-cli dumpprivkey <address>
# Import private key
bitcoin-cli importprivkey <privkey>Transaction Operations
# Get raw transaction
bitcoin-cli getrawtransaction <txid> true
# Decode raw transaction
bitcoin-cli decoderawtransaction <hex>
# Test mempool accept
bitcoin-cli testmempoolaccept '["<hex>"]'
# Get transaction out
bitcoin-cli gettxout <txid> <vout>---
Testing with Regtest
# Start regtest node
bitcoind -regtest -daemon
# Create wallet
bitcoin-cli -regtest createwallet "test"
# Get new address
ADDR=$(bitcoin-cli -regtest getnewaddress)
# Mine 101 blocks (coinbase maturity)
bitcoin-cli -regtest generatetoaddress 101 $ADDR
# Check balance
bitcoin-cli -regtest getbalance
# Send transaction
bitcoin-cli -regtest sendtoaddress <recipient> 1.0
# Mine block to confirm
bitcoin-cli -regtest generatetoaddress 1 $ADDR---
Production Checklist
Before running in production:
Security:
- [ ] Use strong RPC password
- [ ] Firewall configured (only allow localhost for RPC)
- [ ] Regular backups of wallet.dat
- [ ] Encrypted wallet (
bitcoin-cli encryptwallet) - [ ] Monitor for security updates
Performance:
- [ ] Sufficient disk space (500GB+ for full node)
- [ ] 8GB+ RAM recommended
- [ ] SSD preferred for chainstate
- [ ] Monitor sync status
Reliability:
- [ ] Automated backups
- [ ] Monitoring and alerting
- [ ] Redundant peers configured
- [ ] Regular software updates
Lightning (if applicable):
- [ ] Channel backups automated
- [ ] Watchtower configured
- [ ] Sufficient inbound liquidity
- [ ] Fee policies set appropriately
---
Resources
CosmWasm Smart Contract Development Template
Production-grade smart contract development for the Cosmos ecosystem using CosmWasm and Rust.
---
Project Overview
This template provides a complete development environment for building, testing, and deploying CosmWasm smart contracts using:
- CosmWasm - Smart contract platform for Cosmos
- Rust - Systems programming language
- cargo-generate - Project scaffolding
- cw-multi-test - Multi-contract testing
- CosmJS - JavaScript library for Cosmos
Use cases: DeFi protocols, DAOs, NFT marketplaces, governance systems, IBC-enabled contracts
---
Project Structure
cosmwasm-contract/
├── src/
│ ├── contract.rs # Entry points (instantiate, execute, query, migrate)
│ ├── state.rs # State definitions and storage
│ ├── msg.rs # Message types (Instantiate, Execute, Query)
│ ├── error.rs # Custom error types
│ ├── helpers.rs # Utility functions
│ └── lib.rs # Library exports
├── examples/
│ └── schema.rs # JSON schema generator
├── tests/
│ ├── integration.rs # Integration tests with cw-multi-test
│ └── helpers/
│ └── mock.rs # Mock contracts and helpers
├── schema/ # Generated JSON schemas
│ ├── instantiate_msg.json
│ ├── execute_msg.json
│ ├── query_msg.json
│ └── state.json
├── Cargo.toml
├── .cargo/
│ └── config # Cargo configuration
└── README.md---
Environment Setup
1. Install Prerequisites
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default stable
rustup target add wasm32-unknown-unknown
# Install cargo-generate (for project templates)
cargo install cargo-generate --features vendored-openssl
# Install cargo-run-script (for custom scripts)
cargo install cargo-run-script2. Create New Project
# Using CosmWasm template
cargo generate --git https://github.com/CosmWasm/cw-template.git --name my-contract
cd my-contract
# Or manually create project
cargo new --lib my-contract
cd my-contract3. Configure Cargo.toml
Cargo.toml:
[package]
name = "my-contract"
version = "0.1.0"
authors = ["Your Name <your.email@example.com>"]
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = 'abort'
incremental = false
overflow-checks = true
[features]
# Use library feature to disable all instantiate/execute/query exports
library = []
[dependencies]
cosmwasm-std = "1.5"
cosmwasm-storage = "1.5"
cw-storage-plus = "1.2"
cw2 = "1.1"
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = "1.0"
[dev-dependencies]
cw-multi-test = "0.20"
cosmwasm-schema = "1.5"---
Basic Contract Implementation
Entry Points (contract.rs)
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo,
Response, StdResult,
};
use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, CountResponse};
use crate::state::{State, CONFIG};
// Version info for migration
const CONTRACT_NAME: &str = "crates.io:my-contract";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
#[entry_point]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
// Set contract version for migration
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
let state = State {
count: msg.count,
owner: info.sender.clone(),
};
CONFIG.save(deps.storage, &state)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("owner", info.sender)
.add_attribute("count", msg.count.to_string()))
}
#[entry_point]
pub fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::Increment {} => execute_increment(deps),
ExecuteMsg::Reset { count } => execute_reset(deps, info, count),
ExecuteMsg::Transfer { recipient, amount } => {
execute_transfer(deps, info, recipient, amount)
}
}
}
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetCount {} => to_binary(&query_count(deps)?),
QueryMsg::GetOwner {} => to_binary(&query_owner(deps)?),
}
}
#[entry_point]
pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
// Perform migration logic here
let version = cw2::get_contract_version(deps.storage)?;
if version.contract != CONTRACT_NAME {
return Err(ContractError::InvalidContractName {
name: version.contract,
});
}
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Ok(Response::new()
.add_attribute("method", "migrate")
.add_attribute("new_version", CONTRACT_VERSION))
}
// Execute handlers
pub fn execute_increment(deps: DepsMut) -> Result<Response, ContractError> {
CONFIG.update(deps.storage, |mut state| -> Result<_, ContractError> {
state.count += 1;
Ok(state)
})?;
Ok(Response::new().add_attribute("action", "increment"))
}
pub fn execute_reset(
deps: DepsMut,
info: MessageInfo,
count: i32,
) -> Result<Response, ContractError> {
CONFIG.update(deps.storage, |mut state| -> Result<_, ContractError> {
if info.sender != state.owner {
return Err(ContractError::Unauthorized {});
}
state.count = count;
Ok(state)
})?;
Ok(Response::new()
.add_attribute("action", "reset")
.add_attribute("count", count.to_string()))
}
// Query handlers
fn query_count(deps: Deps) -> StdResult<CountResponse> {
let state = CONFIG.load(deps.storage)?;
Ok(CountResponse { count: state.count })
}
fn query_owner(deps: Deps) -> StdResult<OwnerResponse> {
let state = CONFIG.load(deps.storage)?;
Ok(OwnerResponse {
owner: state.owner.to_string(),
})
}Message Types (msg.rs)
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::Uint128;
#[cw_serde]
pub struct InstantiateMsg {
pub count: i32,
}
#[cw_serde]
pub enum ExecuteMsg {
Increment {},
Reset { count: i32 },
Transfer { recipient: String, amount: Uint128 },
}
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
#[returns(CountResponse)]
GetCount {},
#[returns(OwnerResponse)]
GetOwner {},
}
#[cw_serde]
pub struct CountResponse {
pub count: i32,
}
#[cw_serde]
pub struct OwnerResponse {
pub owner: String,
}
#[cw_serde]
pub struct MigrateMsg {}State Management (state.rs)
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use cw_storage_plus::Item;
#[cw_serde]
pub struct State {
pub count: i32,
pub owner: Addr,
}
pub const CONFIG: Item<State> = Item::new("config");Error Handling (error.rs)
use cosmwasm_std::StdError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Unauthorized")]
Unauthorized {},
#[error("Invalid contract name: {name}")]
InvalidContractName { name: String },
#[error("Semver parsing error: {0}")]
SemVer(String),
}
impl From<semver::Error> for ContractError {
fn from(err: semver::Error) -> Self {
Self::SemVer(err.to_string())
}
}---
Testing
Unit Tests (contract.rs)
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
#[test]
fn proper_initialization() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg { count: 17 };
let info = mock_info("creator", &coins(1000, "earth"));
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(0, res.messages.len());
// Query count
let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap();
let value: CountResponse = from_binary(&res).unwrap();
assert_eq!(17, value.count);
}
#[test]
fn increment() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg { count: 17 };
let info = mock_info("creator", &coins(2, "token"));
let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
// Increment
let info = mock_info("anyone", &coins(2, "token"));
let msg = ExecuteMsg::Increment {};
let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
// Verify
let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap();
let value: CountResponse = from_binary(&res).unwrap();
assert_eq!(18, value.count);
}
#[test]
fn reset() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg { count: 17 };
let info = mock_info("creator", &coins(2, "token"));
let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
// Reset as owner
let info = mock_info("creator", &coins(2, "token"));
let msg = ExecuteMsg::Reset { count: 5 };
let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
// Verify
let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap();
let value: CountResponse = from_binary(&res).unwrap();
assert_eq!(5, value.count);
}
#[test]
fn unauthorized_reset() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg { count: 17 };
let info = mock_info("creator", &coins(2, "token"));
let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
// Unauthorized reset
let info = mock_info("anyone", &coins(2, "token"));
let msg = ExecuteMsg::Reset { count: 5 };
let res = execute(deps.as_mut(), mock_env(), info, msg);
match res {
Err(ContractError::Unauthorized {}) => {}
_ => panic!("Must return unauthorized error"),
}
}
}Integration Tests (tests/integration.rs)
use cosmwasm_std::{Addr, Coin, Empty, Uint128};
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor};
use my_contract::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg};
pub fn contract_template() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
my_contract::contract::execute,
my_contract::contract::instantiate,
my_contract::contract::query,
)
.with_migrate(my_contract::contract::migrate);
Box::new(contract)
}
const USER: &str = "user";
const ADMIN: &str = "admin";
const NATIVE_DENOM: &str = "denom";
fn mock_app() -> App {
AppBuilder::new().build(|router, _, storage| {
router
.bank
.init_balance(
storage,
&Addr::unchecked(USER),
vec![Coin {
denom: NATIVE_DENOM.to_string(),
amount: Uint128::new(1000),
}],
)
.unwrap();
})
}
fn proper_instantiate() -> (App, Addr) {
let mut app = mock_app();
let code_id = app.store_code(contract_template());
let msg = InstantiateMsg { count: 1 };
let contract_addr = app
.instantiate_contract(
code_id,
Addr::unchecked(ADMIN),
&msg,
&[],
"test",
None,
)
.unwrap();
(app, contract_addr)
}
#[test]
fn count() {
let (mut app, contract_addr) = proper_instantiate();
let msg = ExecuteMsg::Increment {};
let cosmos_msg = my_contract::msg::ExecuteMsg::Increment {};
app.execute_contract(Addr::unchecked(USER), contract_addr.clone(), &cosmos_msg, &[])
.unwrap();
let res: CountResponse = app
.wrap()
.query_wasm_smart(contract_addr, &QueryMsg::GetCount {})
.unwrap();
assert_eq!(res.count, 2);
}
#[test]
fn reset() {
let (mut app, contract_addr) = proper_instantiate();
let msg = ExecuteMsg::Reset { count: 5 };
app.execute_contract(Addr::unchecked(ADMIN), contract_addr.clone(), &msg, &[])
.unwrap();
let res: CountResponse = app
.wrap()
.query_wasm_smart(contract_addr, &QueryMsg::GetCount {})
.unwrap();
assert_eq!(res.count, 5);
}---
Building and Deployment
Build Contract
# Optimize for production
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.15.0
# This produces optimized wasm in ./artifacts/Deploy to Testnet
# Set up wasmd CLI
CHAIN_ID="uni-6"
TESTNET_NAME="uni-6"
RPC="https://rpc.uni.junomint.com:443"
TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0.025ujunox --gas auto --gas-adjustment 1.3"
# Store contract
RES=$(junod tx wasm store artifacts/my_contract.wasm --from wallet $TXFLAG -y --output json -b block)
CODE_ID=$(echo $RES | jq -r '.logs[0].events[-1].attributes[0].value')
echo "Code ID: $CODE_ID"
# Instantiate contract
INIT='{"count":100}'
junod tx wasm instantiate $CODE_ID "$INIT" \
--from wallet --label "my contract" $TXFLAG -y --no-admin
# Get contract address
CONTRACT=$(junod query wasm list-contract-by-code $CODE_ID --output json | jq -r '.contracts[-1]')
echo "Contract address: $CONTRACT"
# Query contract
junod query wasm contract-state smart $CONTRACT '{"get_count":{}}'
# Execute contract
junod tx wasm execute $CONTRACT '{"increment":{}}' \
--from wallet $TXFLAG -y---
CosmJS Client Integration
TypeScript Client
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { GasPrice } from "@cosmjs/stargate";
import fs from "fs";
const RPC_ENDPOINT = "https://rpc.uni.junomint.com:443";
const MNEMONIC = "your mnemonic here"; // NEVER commit this
async function main() {
// Create wallet from mnemonic
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(MNEMONIC, {
prefix: "juno",
});
const [account] = await wallet.getAccounts();
console.log("Wallet address:", account.address);
// Connect to chain
const client = await SigningCosmWasmClient.connectWithSigner(
RPC_ENDPOINT,
wallet,
{
gasPrice: GasPrice.fromString("0.025ujunox"),
}
);
// Upload contract
const wasmCode = fs.readFileSync("./artifacts/my_contract.wasm");
const uploadResult = await client.upload(
account.address,
wasmCode,
"auto"
);
console.log("Code ID:", uploadResult.codeId);
// Instantiate contract
const instantiateMsg = { count: 100 };
const instantiateResult = await client.instantiate(
account.address,
uploadResult.codeId,
instantiateMsg,
"My Contract",
"auto"
);
console.log("Contract address:", instantiateResult.contractAddress);
// Query contract
const queryResult = await client.queryContractSmart(
instantiateResult.contractAddress,
{ get_count: {} }
);
console.log("Count:", queryResult.count);
// Execute contract
const executeMsg = { increment: {} };
const executeResult = await client.execute(
account.address,
instantiateResult.contractAddress,
executeMsg,
"auto"
);
console.log("Transaction hash:", executeResult.transactionHash);
}
main().catch(console.error);---
Useful Commands
Development
# Build contract
cargo build
# Run tests
cargo test
# Run clippy
cargo clippy -- -D warnings
# Format code
cargo fmt
# Generate schema
cargo run --example schema
# Check wasm size
ls -lh target/wasm32-unknown-unknown/release/*.wasmOptimization
# Using rust-optimizer (recommended)
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.15.0
# Using workspace-optimizer (for workspaces)
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/workspace-optimizer:0.15.0---
Production Checklist
Before deploying to mainnet:
Security:
- [ ] All addresses validated with
addr_validate() - [ ] Checked arithmetic (Uint128/Decimal)
- [ ] Custom errors for all failure cases
- [ ] Access control on privileged functions
- [ ] No state changes in query functions
- [ ] Migration function secured
- [ ] Professional security audit completed
Testing:
- [ ] 100% unit test coverage
- [ ] Integration tests with cw-multi-test
- [ ] Negative test cases
- [ ] Migration tests
- [ ] Gas benchmarks documented
Deployment:
- [ ] Contract optimized with rust-optimizer
- [ ] Schema generated and documented
- [ ] Admin/migration authority secured
- [ ] Deployment tested on testnet
- [ ] Contract verified on chain explorer
Documentation:
- [ ] README with usage instructions
- [ ] API documentation
- [ ] Architecture diagram
- [ ] Known limitations documented
---
Resources
Ethereum Smart Contract Development - Foundry Template
Modern, blazingly fast Ethereum development with Foundry, Solidity, and native Solidity testing.
---
Project Overview
This template provides a complete development environment for building, testing, and deploying Ethereum smart contracts using:
- Foundry - Fast smart contract development framework (Forge, Cast, Anvil)
- Forge - Ethereum testing framework with improved fuzzer and counterexample minimization
- Cast - Swiss army knife for interacting with EVM contracts
- Anvil - Local Ethereum node (instant mining, forking)
- Solidity - Write tests in Solidity (no JavaScript required)
- OpenZeppelin - Battle-tested contract library
Use cases: DeFi protocols, token contracts, NFT projects, DAOs, upgradeable systems
---
Project Structure
foundry-project/
├── src/
│ ├── Token.sol
│ ├── NFT.sol
│ └── interfaces/
│ └── IToken.sol
├── test/
│ ├── Token.t.sol
│ ├── NFT.t.sol
│ └── mocks/
│ └── MockERC20.sol
├── script/
│ ├── Deploy.s.sol
│ └── Upgrade.s.sol
├── lib/
│ ├── forge-std/
│ └── openzeppelin-contracts/
├── foundry.toml
├── .env.example
└── README.md---
Environment Setup
1. Install Foundry
# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Verify installation
forge --version
cast --version
anvil --version2. Initialize Project
# Create new project
forge init my-project
cd my-project
# Install OpenZeppelin
forge install OpenZeppelin/openzeppelin-contracts
# Install OpenZeppelin Upgradeable
forge install OpenZeppelin/openzeppelin-contracts-upgradeable
# Install Chainlink (for price feeds)
forge install smartcontractkit/chainlink-brownie-contracts3. Configure Environment
.env.example:
# Network RPC URLs
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
# Private Keys (NEVER commit .env)
DEPLOYER_PRIVATE_KEY=0x...
# Etherscan API Key
ETHERSCAN_API_KEY=YOUR_ETHERSCAN_API_KEY---
Foundry Configuration
foundry.toml:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.20"
optimizer = true
optimizer_runs = 200
via_ir = false
# Etherscan verification
[etherscan]
sepolia = { key = "${ETHERSCAN_API_KEY}" }
mainnet = { key = "${ETHERSCAN_API_KEY}" }
# RPC endpoints
[rpc_endpoints]
sepolia = "${SEPOLIA_RPC_URL}"
mainnet = "${MAINNET_RPC_URL}"
# Testing configuration
[profile.default.fuzz]
runs = 256
max_test_rejects = 65536
[profile.default.invariant]
runs = 256
depth = 15
fail_on_revert = false
# CI profile (more thorough)
[profile.ci]
fuzz_runs = 10000
invariant_runs = 1000
# Gas reporting
[profile.default.gas_reports]
contracts = ["*"]---
Smart Contract Examples
ERC20 Token
src/Token.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
uint256 public constant MAX_SUPPLY = 1_000_000 * 10**18;
constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {
_mint(msg.sender, 1000 * 10**18);
}
function mint(address to, uint256 amount) public onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
_mint(to, amount);
}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
}ERC721 NFT
src/NFT.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721URIStorage, Ownable {
uint256 private _tokenIdCounter;
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.01 ether;
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function mint(string memory uri) public payable {
require(_tokenIdCounter < MAX_SUPPLY, "Max supply reached");
require(msg.value >= MINT_PRICE, "Insufficient payment");
uint256 tokenId = _tokenIdCounter++;
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, uri);
}
function withdraw() public onlyOwner {
(bool success, ) = owner().call{value: address(this).balance}("");
require(success, "Transfer failed");
}
}---
Testing with Forge
Basic Test Structure
test/Token.t.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "forge-std/Test.sol";
import "../src/Token.sol";
contract TokenTest is Test {
MyToken public token;
address public owner = address(this);
address public alice = address(0x1);
address public bob = address(0x2);
function setUp() public {
token = new MyToken();
// Give Alice and Bob some ETH
vm.deal(alice, 100 ether);
vm.deal(bob, 100 ether);
}
function testInitialSupply() public {
assertEq(token.totalSupply(), 1000 * 10**18);
assertEq(token.balanceOf(owner), 1000 * 10**18);
}
function testMint() public {
uint256 amount = 100 * 10**18;
token.mint(alice, amount);
assertEq(token.balanceOf(alice), amount);
assertEq(token.totalSupply(), 1100 * 10**18);
}
function testMintFailsWhenNotOwner() public {
vm.prank(alice);
vm.expectRevert(
abi.encodeWithSelector(
Ownable.OwnableUnauthorizedAccount.selector,
alice
)
);
token.mint(bob, 100 * 10**18);
}
function testMintFailsWhenExceedsMaxSupply() public {
uint256 maxSupply = token.MAX_SUPPLY();
uint256 toMint = maxSupply + 1;
vm.expectRevert("Exceeds max supply");
token.mint(alice, toMint);
}
function testBurn() public {
uint256 amount = 100 * 10**18;
token.burn(amount);
assertEq(token.balanceOf(owner), 900 * 10**18);
assertEq(token.totalSupply(), 900 * 10**18);
}
function testTransfer() public {
uint256 amount = 50 * 10**18;
token.transfer(alice, amount);
assertEq(token.balanceOf(alice), amount);
assertEq(token.balanceOf(owner), 950 * 10**18);
vm.prank(alice);
token.transfer(bob, 25 * 10**18);
assertEq(token.balanceOf(bob), 25 * 10**18);
assertEq(token.balanceOf(alice), 25 * 10**18);
}
}Fuzz Testing
contract TokenFuzzTest is Test {
MyToken public token;
function setUp() public {
token = new MyToken();
}
/// @notice Fuzz test: mint amount should never exceed max supply
function testFuzzMint(address to, uint256 amount) public {
vm.assume(to != address(0));
vm.assume(amount <= token.MAX_SUPPLY());
uint256 currentSupply = token.totalSupply();
if (currentSupply + amount <= token.MAX_SUPPLY()) {
token.mint(to, amount);
assertEq(token.balanceOf(to), amount);
} else {
vm.expectRevert("Exceeds max supply");
token.mint(to, amount);
}
}
/// @notice Fuzz test: transfers preserve total supply
function testFuzzTransfer(address from, address to, uint256 amount) public {
vm.assume(from != address(0));
vm.assume(to != address(0));
vm.assume(from != to);
vm.assume(amount <= token.MAX_SUPPLY());
// Setup: mint to 'from' address
token.mint(from, amount);
uint256 totalBefore = token.totalSupply();
// Execute transfer
vm.prank(from);
token.transfer(to, amount);
// Verify invariant: total supply unchanged
assertEq(token.totalSupply(), totalBefore);
assertEq(token.balanceOf(to), amount);
}
}Invariant Testing
contract TokenInvariantTest is Test {
MyToken public token;
TokenHandler public handler;
function setUp() public {
token = new MyToken();
handler = new TokenHandler(token);
// Set handler as target for invariant tests
targetContract(address(handler));
}
/// @notice Invariant: Total supply never exceeds MAX_SUPPLY
function invariant_totalSupplyNeverExceedsMax() public {
assertLe(token.totalSupply(), token.MAX_SUPPLY());
}
/// @notice Invariant: Sum of all balances equals total supply
function invariant_sumOfBalancesEqualsTotalSupply() public {
uint256 sum = 0;
address[] memory users = handler.getUsers();
for (uint256 i = 0; i < users.length; i++) {
sum += token.balanceOf(users[i]);
}
assertEq(sum, token.totalSupply());
}
}
/// @notice Handler contract for invariant testing
contract TokenHandler {
MyToken public token;
address[] public users;
constructor(MyToken _token) {
token = _token;
}
function mint(uint256 seed, uint256 amount) public {
address user = _getRandomUser(seed);
amount = bound(amount, 0, token.MAX_SUPPLY());
try token.mint(user, amount) {
if (!_isKnownUser(user)) {
users.push(user);
}
} catch {}
}
function transfer(uint256 fromSeed, uint256 toSeed, uint256 amount) public {
if (users.length == 0) return;
address from = users[fromSeed % users.length];
address to = _getRandomUser(toSeed);
amount = bound(amount, 0, token.balanceOf(from));
vm.prank(from);
try token.transfer(to, amount) {
if (!_isKnownUser(to)) {
users.push(to);
}
} catch {}
}
function getUsers() external view returns (address[] memory) {
return users;
}
function _getRandomUser(uint256 seed) internal pure returns (address) {
return address(uint160(seed));
}
function _isKnownUser(address user) internal view returns (bool) {
for (uint256 i = 0; i < users.length; i++) {
if (users[i] == user) return true;
}
return false;
}
}Fork Testing
contract ForkTest is Test {
uint256 mainnetFork;
IERC20 constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
function setUp() public {
// Fork mainnet at latest block
mainnetFork = vm.createFork(vm.envString("MAINNET_RPC_URL"));
vm.selectFork(mainnetFork);
}
function testForkDAI() public {
assertEq(DAI.decimals(), 18);
assertEq(DAI.name(), "Dai Stablecoin");
}
function testForkSwap() public {
// Fork at specific block for deterministic testing
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"), 18_000_000);
// Impersonate whale address
address whale = 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643;
vm.prank(whale);
// Test swap logic
// ...
}
}---
Deployment Scripts
Basic Deployment
script/Deploy.s.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "forge-std/Script.sol";
import "../src/Token.sol";
import "../src/NFT.sol";
contract DeployScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
// Deploy Token
MyToken token = new MyToken();
console.log("Token deployed at:", address(token));
// Deploy NFT
MyNFT nft = new MyNFT();
console.log("NFT deployed at:", address(nft));
vm.stopBroadcast();
}
}Deploy to network:
# Deploy to Sepolia
forge script script/Deploy.s.sol:DeployScript --rpc-url sepolia --broadcast --verify
# Deploy to Mainnet (with additional confirmations)
forge script script/Deploy.s.sol:DeployScript --rpc-url mainnet --broadcast --verify --slowDeterministic Deployment (CREATE2)
contract DeterministicDeploy is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
bytes32 salt = keccak256("MyToken_v1");
vm.startBroadcast(deployerPrivateKey);
// Predict address
address predicted = vm.computeCreate2Address(
salt,
keccak256(type(MyToken).creationCode)
);
console.log("Predicted address:", predicted);
// Deploy with CREATE2
MyToken token = new MyToken{salt: salt}();
console.log("Deployed at:", address(token));
require(address(token) == predicted, "Address mismatch");
vm.stopBroadcast();
}
}---
Useful Commands
Compilation & Testing
# Build contracts
forge build
# Run tests
forge test
# Run tests with verbosity
forge test -vvv
# Run specific test
forge test --match-test testMint
# Run tests in specific contract
forge test --match-contract TokenTest
# Gas report
forge test --gas-report
# Coverage report
forge coverage
# Coverage with LCOV output
forge coverage --report lcovFuzzing & Invariants
# Fuzz testing with custom runs
forge test --fuzz-runs 10000
# Invariant testing
forge test --match-test invariant
# Invariant testing with custom depth
forge test --match-test invariant --depth 50Deployment & Verification
# Deploy to Sepolia
forge script script/Deploy.s.sol --rpc-url sepolia --broadcast
# Deploy with verification
forge script script/Deploy.s.sol --rpc-url sepolia --broadcast --verify
# Verify existing contract
forge verify-contract <CONTRACT_ADDRESS> src/Token.sol:MyToken --chain sepolia
# Flatten contract (for manual verification)
forge flatten src/Token.solLocal Node (Anvil)
# Start local node
anvil
# Fork mainnet locally
anvil --fork-url $MAINNET_RPC_URL
# Fork at specific block
anvil --fork-url $MAINNET_RPC_URL --fork-block-number 18000000Cast (Interaction)
# Get balance
cast balance <ADDRESS>
# Call view function
cast call <CONTRACT> "totalSupply()(uint256)"
# Send transaction
cast send <CONTRACT> "mint(address,uint256)" <ADDRESS> 1000000000000000000 --private-key <KEY>
# Get transaction receipt
cast receipt <TX_HASH>
# Estimate gas
cast estimate <CONTRACT> "mint(address,uint256)" <ADDRESS> 1000000000000000000
# Get storage slot
cast storage <CONTRACT> <SLOT>
# Convert to checksummed address
cast --to-checksum-address 0x...
# Convert wei to ether
cast --from-wei 1000000000000000000
# Compute keccak256
cast keccak "mint(address,uint256)"
# Decode calldata
cast 4byte-decode 0x40c10f19...---
Production Checklist
Before deploying to mainnet:
Security:
- [ ] All tests passing (
forge test) - [ ] High test coverage (
forge coverage) - [ ] Fuzz tests for critical functions (
forge test --fuzz-runs 10000) - [ ] Invariant tests passing (
forge test --match-test invariant) - [ ] No compiler warnings (
forge build) - [ ] OpenZeppelin contracts for standard functionality
- [ ] Slither analysis clean (
slither .) - [ ] Professional audit completed
- [ ] Bug bounty program prepared
Configuration:
- [ ] Solidity version locked (no
^) - [ ] Optimizer enabled with appropriate runs (200 for general, 1000+ for libraries)
- [ ] All environment variables documented
- [ ] Private keys secured (hardware wallet/MPC)
Deployment:
- [ ] Deployed to testnet first
- [ ] Multi-sig wallet set as owner
- [ ] Timelock for critical functions
- [ ] Verified on Etherscan (
forge verify-contract) - [ ] Monitoring and alerting configured
- [ ] Emergency pause mechanism tested
Documentation:
- [ ] README with usage instructions
- [ ] NatSpec comments on all public functions
- [ ] Architecture diagram
- [ ] Known limitations documented
---
Advanced Patterns
Gas Snapshots
# Create gas snapshot
forge snapshot
# Compare gas changes
forge snapshot --diff .gas-snapshotFormal Verification (Halmos)
# Install Halmos
pip install halmos
# Run symbolic tests
halmos --contract TokenTest --function testMintDeployment to Multiple Networks
# Deploy to all testnets
forge script script/Deploy.s.sol --rpc-url sepolia --broadcast
forge script script/Deploy.s.sol --rpc-url goerli --broadcast
forge script script/Deploy.s.sol --rpc-url mumbai --broadcast---
Resources
Ethereum Smart Contract Development - Hardhat Template
Production-grade Ethereum smart contract development with Hardhat, OpenZeppelin, and Ethers.js.
---
Project Overview
This template provides a complete development environment for building, testing, and deploying Ethereum smart contracts using:
- Hardhat - Ethereum development environment (tests, deployment, plugins)
- Solidity 0.8.x - Smart contract language (pin an exact version in your toolchain)
- OpenZeppelin - Battle-tested contract library
- Ethers.js - Ethereum library for JavaScript
- TypeScript - Type-safe development
- Chai - Testing framework
Use cases: DeFi protocols, NFT collections, DAOs, token contracts, upgradeable systems
---
Project Structure
hardhat-project/
├── contracts/
│ ├── Token.sol
│ ├── NFT.sol
│ └── interfaces/
│ └── IToken.sol
├── test/
│ ├── Token.test.ts
│ └── NFT.test.ts
├── scripts/
│ ├── deploy.ts
│ └── verify.ts
├── hardhat.config.ts
├── package.json
├── tsconfig.json
├── .env.example
└── README.md---
Environment Setup
1. Initialize Project
mkdir my-hardhat-project && cd my-hardhat-project
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat initSelect: "Create a TypeScript project"
2. Install Dependencies
npm install --save-dev \
@openzeppelin/contracts \
@openzeppelin/contracts-upgradeable \
@nomicfoundation/hardhat-verify \
hardhat-gas-reporter \
solidity-coverage \
dotenv3. Configure Environment
.env.example:
# Network RPC URLs
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
# Private Keys (NEVER commit .env)
DEPLOYER_PRIVATE_KEY=0x...
# Etherscan API Key
ETHERSCAN_API_KEY=YOUR_ETHERSCAN_API_KEY
# Coinmarketcap API Key (for gas reporter)
COINMARKETCAP_API_KEY=YOUR_CMC_API_KEY---
Hardhat Configuration
hardhat.config.ts:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@nomicfoundation/hardhat-verify";
import "hardhat-gas-reporter";
import "solidity-coverage";
import * as dotenv from "dotenv";
dotenv.config();
const config: HardhatUserConfig = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
hardhat: {
chainId: 31337,
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.DEPLOYER_PRIVATE_KEY
? [process.env.DEPLOYER_PRIVATE_KEY]
: [],
chainId: 11155111,
},
mainnet: {
url: process.env.MAINNET_RPC_URL || "",
accounts: process.env.DEPLOYER_PRIVATE_KEY
? [process.env.DEPLOYER_PRIVATE_KEY]
: [],
chainId: 1,
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
gasReporter: {
enabled: process.env.REPORT_GAS === "true",
currency: "USD",
coinmarketcap: process.env.COINMARKETCAP_API_KEY,
},
};
export default config;---
Smart Contract Examples
ERC20 Token
contracts/Token.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
uint256 public constant MAX_SUPPLY = 1_000_000 * 10**18;
constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {
_mint(msg.sender, 1000 * 10**18); // Initial supply
}
function mint(address to, uint256 amount) public onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
_mint(to, amount);
}
}ERC721 NFT
contracts/NFT.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721URIStorage, Ownable {
uint256 private _tokenIdCounter;
uint256 public constant MAX_SUPPLY = 10000;
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function mint(address to, string memory uri) public onlyOwner {
require(_tokenIdCounter < MAX_SUPPLY, "Max supply reached");
uint256 tokenId = _tokenIdCounter++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
}---
Testing
test/Token.test.ts:
import { expect } from "chai";
import { ethers } from "hardhat";
import { MyToken } from "../typechain-types";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
describe("MyToken", function () {
let token: MyToken;
let owner: SignerWithAddress;
let addr1: SignerWithAddress;
let addr2: SignerWithAddress;
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
token = await Token.deploy();
await token.waitForDeployment();
});
describe("Deployment", function () {
it("Should set the right owner", async function () {
expect(await token.owner()).to.equal(owner.address);
});
it("Should assign initial supply to owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(ownerBalance).to.equal(ethers.parseEther("1000"));
});
});
describe("Minting", function () {
it("Should mint tokens to address", async function () {
await token.mint(addr1.address, ethers.parseEther("100"));
expect(await token.balanceOf(addr1.address)).to.equal(
ethers.parseEther("100")
);
});
it("Should fail if non-owner tries to mint", async function () {
await expect(
token.connect(addr1).mint(addr2.address, ethers.parseEther("100"))
).to.be.revertedWithCustomError(token, "OwnableUnauthorizedAccount");
});
it("Should not exceed max supply", async function () {
const maxSupply = await token.MAX_SUPPLY();
const toMint = maxSupply + ethers.parseEther("1");
await expect(
token.mint(addr1.address, toMint)
).to.be.revertedWith("Exceeds max supply");
});
});
describe("Transfers", function () {
it("Should transfer tokens between accounts", async function () {
await token.transfer(addr1.address, ethers.parseEther("50"));
expect(await token.balanceOf(addr1.address)).to.equal(
ethers.parseEther("50")
);
await token.connect(addr1).transfer(addr2.address, ethers.parseEther("25"));
expect(await token.balanceOf(addr2.address)).to.equal(
ethers.parseEther("25")
);
});
});
});Run tests:
npx hardhat test
npx hardhat test --grep "Minting" # Run specific suite
REPORT_GAS=true npx hardhat test # With gas reporting
npx hardhat coverage # Coverage report---
Deployment Scripts
scripts/deploy.ts:
import { ethers } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with account:", deployer.address);
console.log("Account balance:", (await ethers.provider.getBalance(deployer.address)).toString());
// Deploy Token
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy();
await token.waitForDeployment();
const tokenAddress = await token.getAddress();
console.log("Token deployed to:", tokenAddress);
// Deploy NFT
const NFT = await ethers.getContractFactory("MyNFT");
const nft = await NFT.deploy();
await nft.waitForDeployment();
const nftAddress = await nft.getAddress();
console.log("NFT deployed to:", nftAddress);
// Save deployment info
const deploymentInfo = {
network: (await ethers.provider.getNetwork()).name,
token: tokenAddress,
nft: nftAddress,
deployer: deployer.address,
timestamp: new Date().toISOString(),
};
console.log("\nDeployment Info:", JSON.stringify(deploymentInfo, null, 2));
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});Deploy to network:
npx hardhat run scripts/deploy.ts --network sepolia
npx hardhat run scripts/deploy.ts --network mainnet---
Verification
scripts/verify.ts:
import { run } from "hardhat";
async function main() {
const TOKEN_ADDRESS = "0x..."; // Your deployed address
console.log("Verifying contract...");
try {
await run("verify:verify", {
address: TOKEN_ADDRESS,
constructorArguments: [],
});
console.log("Contract verified successfully");
} catch (error: any) {
if (error.message.includes("Already Verified")) {
console.log("Contract already verified");
} else {
console.error("Verification failed:", error);
}
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});Verify:
npx hardhat verify --network sepolia 0xYOUR_CONTRACT_ADDRESS---
Testing on Local Fork
test/Fork.test.ts:
import { expect } from "chai";
import { ethers } from "hardhat";
describe("Fork Tests", function () {
beforeEach(async function () {
// Fork mainnet
await ethers.provider.send("hardhat_reset", [
{
forking: {
jsonRpcUrl: process.env.MAINNET_RPC_URL,
blockNumber: 18000000, // Optional: pin to specific block
},
},
]);
});
it("Should interact with mainnet DAI", async function () {
const DAI_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const dai = await ethers.getContractAt("IERC20", DAI_ADDRESS);
expect(await dai.decimals()).to.equal(18);
});
});---
Production Checklist
Before deploying to mainnet:
Security:
- [ ] All tests passing (100% coverage)
- [ ] No compiler warnings
- [ ] OpenZeppelin contracts for standard functionality
- [ ] Access control properly implemented
- [ ] Reentrancy guards where needed
- [ ] Professional audit completed
- [ ] Bug bounty program prepared
Configuration:
- [ ] Solidity version locked (no
^) - [ ] Optimizer enabled with appropriate runs
- [ ] All environment variables documented
- [ ] Private keys secured (hardware wallet/MPC)
Deployment:
- [ ] Deployed to testnet first
- [ ] Multi-sig wallet set as owner
- [ ] Timelock for critical functions
- [ ] Verified on Etherscan
- [ ] Monitoring and alerting configured
- [ ] Emergency pause mechanism tested
Documentation:
- [ ] README with usage instructions
- [ ] NatSpec comments on all public functions
- [ ] Architecture diagram
- [ ] Known limitations documented
---
Useful Commands
# Compile contracts
npx hardhat compile
# Run tests
npx hardhat test
REPORT_GAS=true npx hardhat test
# Test coverage
npx hardhat coverage
# Local node
npx hardhat node
# Console
npx hardhat console --network sepolia
# Clean build artifacts
npx hardhat clean
# Check contract size
npx hardhat size-contracts---
Resources
Solana Program Development — Anchor Framework Template
Production-grade Solana program development with Anchor, Rust, and TypeScript testing.
---
Project Overview
This template provides a complete development environment for building, testing, and deploying Solana programs using:
- Anchor - Solana development framework (v0.29+)
- Rust - Systems programming language for on-chain programs
- TypeScript - Client-side testing and deployment
- Solana CLI - Command-line tools for Solana
- SPL Token - Token program integration
Use cases: DeFi protocols, NFT projects, gaming, DAOs, token contracts, staking systems
---
Project Structure
anchor-project/
├── programs/
│ └── my_program/
│ ├── src/
│ │ ├── lib.rs
│ │ ├── state.rs
│ │ ├── instructions/
│ │ │ ├── mod.rs
│ │ │ ├── initialize.rs
│ │ │ └── update.rs
│ │ └── errors.rs
│ └── Cargo.toml
├── tests/
│ └── my_program.ts
├── app/
│ └── client.ts
├── target/
├── migrations/
│ └── deploy.ts
├── Anchor.toml
└── package.json---
Environment Setup
1. Install Prerequisites
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default stable
rustup update
# Install Solana CLI
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
# Verify Solana installation
solana --version
# Install Anchor
cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
avm install latest
avm use latest
# Verify Anchor installation
anchor --version
# Install Node.js dependencies
npm install -g yarn2. Initialize Project
# Create new Anchor project
anchor init my_project
cd my_project
# Install dependencies
yarn install
# Build project
anchor build
# Test project
anchor test3. Configure Cluster
# Set cluster to devnet
solana config set --url devnet
# Create wallet (or use existing)
solana-keygen new --outfile ~/.config/solana/id.json
# Check balance
solana balance
# Airdrop SOL (devnet only)
solana airdrop 2Anchor.toml:
[features]
seeds = false
skip-lint = false
[programs.localnet]
my_program = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[programs.devnet]
my_program = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[programs.mainnet]
my_program = "YOUR_MAINNET_PROGRAM_ID"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "Devnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"---
Basic Program Structure
Main Program File
programs/my_program/src/lib.rs:
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
let account = &mut ctx.accounts.account;
account.data = data;
account.authority = ctx.accounts.authority.key();
account.bump = ctx.bumps.account;
msg!("Initialized with data: {}", data);
Ok(())
}
pub fn update(ctx: Context<Update>, new_data: u64) -> Result<()> {
let account = &mut ctx.accounts.account;
account.data = new_data;
msg!("Updated data to: {}", new_data);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + 8 + 32 + 1,
seeds = [b"account", authority.key().as_ref()],
bump
)]
pub account: Account<'info, MyAccount>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(
mut,
seeds = [b"account", authority.key().as_ref()],
bump = account.bump,
has_one = authority
)]
pub account: Account<'info, MyAccount>,
pub authority: Signer<'info>,
}
#[account]
pub struct MyAccount {
pub data: u64,
pub authority: Pubkey,
pub bump: u8,
}---
Token Program Integration
SPL Token Mint and Transfer
programs/my_program/src/lib.rs:
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Mint, MintTo, Transfer};
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod token_program {
use super::*;
pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
token::mint_to(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
amount,
)?;
msg!("Minted {} tokens", amount);
Ok(())
}
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
amount,
)?;
msg!("Transferred {} tokens", amount);
Ok(())
}
}
#[derive(Accounts)]
pub struct MintTokens<'info> {
#[account(mut)]
pub mint: Account<'info, Mint>,
#[account(mut)]
pub token_account: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(
mut,
constraint = from.mint == to.mint @ ErrorCode::MintMismatch
)]
pub from: Account<'info, TokenAccount>,
#[account(mut)]
pub to: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}---
NFT (Metaplex) Integration
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token::{Mint, Token, TokenAccount},
};
use mpl_token_metadata::{
instructions::{CreateMetadataAccountV3Cpi, CreateMetadataAccountV3CpiAccounts, CreateMetadataAccountV3InstructionArgs},
types::DataV2,
};
#[program]
pub mod nft_program {
use super::*;
pub fn create_nft(
ctx: Context<CreateNFT>,
name: String,
symbol: String,
uri: String,
) -> Result<()> {
// Create metadata account
let data_v2 = DataV2 {
name,
symbol,
uri,
seller_fee_basis_points: 500, // 5%
creators: Some(vec![mpl_token_metadata::types::Creator {
address: ctx.accounts.authority.key(),
verified: true,
share: 100,
}]),
collection: None,
uses: None,
};
let create_metadata_ix = CreateMetadataAccountV3Cpi::new(
&ctx.accounts.metadata_program,
CreateMetadataAccountV3CpiAccounts {
metadata: &ctx.accounts.metadata,
mint: &ctx.accounts.mint,
mint_authority: &ctx.accounts.authority,
payer: &ctx.accounts.authority,
update_authority: (&ctx.accounts.authority, true),
system_program: &ctx.accounts.system_program,
rent: None,
},
CreateMetadataAccountV3InstructionArgs {
data: data_v2,
is_mutable: true,
collection_details: None,
},
);
create_metadata_ix.invoke()?;
msg!("NFT created successfully");
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateNFT<'info> {
#[account(
init,
payer = authority,
mint::decimals = 0,
mint::authority = authority,
)]
pub mint: Account<'info, Mint>,
/// CHECK: Validated by Metaplex program
#[account(mut)]
pub metadata: UncheckedAccount<'info>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
/// CHECK: Metaplex metadata program
pub metadata_program: UncheckedAccount<'info>,
pub rent: Sysvar<'info, Rent>,
}---
Error Handling
programs/my_program/src/errors.rs:
use anchor_lang::prelude::*;
#[error_code]
pub enum ErrorCode {
#[msg("The provided amount exceeds the maximum allowed")]
AmountTooLarge,
#[msg("Insufficient funds in the vault")]
InsufficientFunds,
#[msg("The game has already started")]
GameAlreadyStarted,
#[msg("Only the authority can perform this action")]
Unauthorized,
#[msg("Invalid mint provided")]
InvalidMint,
#[msg("Mint mismatch between accounts")]
MintMismatch,
}Usage:
use crate::errors::ErrorCode;
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
require!(
ctx.accounts.vault.balance >= amount,
ErrorCode::InsufficientFunds
);
require!(
amount <= MAX_WITHDRAWAL,
ErrorCode::AmountTooLarge
);
// Process withdrawal
Ok(())
}---
TypeScript Testing
tests/my_program.ts:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { MyProgram } from "../target/types/my_program";
import { expect } from "chai";
describe("my_program", () => {
// Configure the client to use the local cluster
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.MyProgram as Program<MyProgram>;
it("Initializes account", async () => {
const [accountPDA] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("account"), provider.wallet.publicKey.toBuffer()],
program.programId
);
const tx = await program.methods
.initialize(new anchor.BN(1000))
.accounts({
account: accountPDA,
authority: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
console.log("Transaction signature:", tx);
// Fetch account data
const accountData = await program.account.myAccount.fetch(accountPDA);
expect(accountData.data.toNumber()).to.equal(1000);
expect(accountData.authority.toString()).to.equal(
provider.wallet.publicKey.toString()
);
});
it("Updates account data", async () => {
const [accountPDA] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("account"), provider.wallet.publicKey.toBuffer()],
program.programId
);
await program.methods
.update(new anchor.BN(2000))
.accounts({
account: accountPDA,
authority: provider.wallet.publicKey,
})
.rpc();
const accountData = await program.account.myAccount.fetch(accountPDA);
expect(accountData.data.toNumber()).to.equal(2000);
});
it("Fails when unauthorized", async () => {
const [accountPDA] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("account"), provider.wallet.publicKey.toBuffer()],
program.programId
);
const unauthorized = anchor.web3.Keypair.generate();
// Airdrop to unauthorized user
await provider.connection.requestAirdrop(
unauthorized.publicKey,
2 * anchor.web3.LAMPORTS_PER_SOL
);
try {
await program.methods
.update(new anchor.BN(3000))
.accounts({
account: accountPDA,
authority: unauthorized.publicKey,
})
.signers([unauthorized])
.rpc();
expect.fail("Expected error not thrown");
} catch (err) {
expect(err.error.errorCode.code).to.equal("ConstraintHasOne");
}
});
});---
Client-Side Integration
app/client.ts:
import * as anchor from "@coral-xyz/anchor";
import { Program, AnchorProvider, web3 } from "@coral-xyz/anchor";
import { MyProgram } from "../target/types/my_program";
import fs from "fs";
async function main() {
// Configure the client
const connection = new web3.Connection("https://api.devnet.solana.com", "confirmed");
// Load wallet
const keypair = web3.Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(fs.readFileSync("/path/to/wallet.json", "utf-8")))
);
const wallet = new anchor.Wallet(keypair);
const provider = new AnchorProvider(connection, wallet, {
commitment: "confirmed",
});
// Load program
const programId = new web3.PublicKey("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
const idl = JSON.parse(fs.readFileSync("./target/idl/my_program.json", "utf-8"));
const program = new Program(idl, programId, provider) as Program<MyProgram>;
// Derive PDA
const [accountPDA, bump] = web3.PublicKey.findProgramAddressSync(
[Buffer.from("account"), provider.wallet.publicKey.toBuffer()],
program.programId
);
console.log("Account PDA:", accountPDA.toString());
console.log("Bump:", bump);
// Initialize account
const tx = await program.methods
.initialize(new anchor.BN(1000))
.accounts({
account: accountPDA,
authority: provider.wallet.publicKey,
systemProgram: web3.SystemProgram.programId,
})
.rpc();
console.log("Transaction signature:", tx);
// Fetch account data
const accountData = await program.account.myAccount.fetch(accountPDA);
console.log("Account data:", accountData);
}
main().catch(console.error);---
Deployment
Local Deployment (Validator)
# Start local validator
solana-test-validator
# In another terminal, deploy
anchor build
anchor deploy
# Test against local validator
anchor test --skip-local-validatorDevnet Deployment
# Set cluster to devnet
solana config set --url devnet
# Build program
anchor build
# Get program ID
solana address -k target/deploy/my_program-keypair.json
# Update Anchor.toml and lib.rs with program ID
# Rebuild with correct ID
anchor build
# Deploy to devnet
anchor deploy
# Verify deployment
solana program show <PROGRAM_ID>Mainnet Deployment
# Set cluster to mainnet
solana config set --url mainnet-beta
# Ensure sufficient SOL for deployment
solana balance
# Deploy to mainnet
anchor deploy --provider.cluster mainnet
# IMPORTANT: Verify program after deployment
anchor verify <PROGRAM_ID>---
Program Upgrade
# Build new version
anchor build
# Upgrade program
solana program deploy --program-id <PROGRAM_KEYPAIR> target/deploy/my_program.so
# Close old buffer (reclaim rent)
solana program close <BUFFER_ADDRESS>---
Useful Commands
Anchor Commands
# Build program
anchor build
# Test program
anchor test
# Test with logs
anchor test --skip-local-validator -- --nocapture
# Deploy program
anchor deploy
# Verify program
anchor verify <PROGRAM_ID>
# Clean build artifacts
anchor clean
# Create new program
anchor new <PROGRAM_NAME>Solana CLI Commands
# Check balance
solana balance
# Airdrop SOL (devnet/testnet only)
solana airdrop 2
# Get program info
solana program show <PROGRAM_ID>
# Get account info
solana account <ADDRESS>
# Transfer SOL
solana transfer <RECIPIENT> <AMOUNT>
# Create keypair
solana-keygen new --outfile wallet.json
# Get public key from keypair
solana-keygen pubkey wallet.jsonSPL Token Commands
# Create token mint
spl-token create-token
# Create token account
spl-token create-account <MINT_ADDRESS>
# Mint tokens
spl-token mint <MINT_ADDRESS> <AMOUNT>
# Transfer tokens
spl-token transfer <MINT_ADDRESS> <AMOUNT> <RECIPIENT>
# Get token balance
spl-token balance <MINT_ADDRESS>---
Production Checklist
Before deploying to mainnet:
Security:
- [ ] All accounts validated (owner, signer, program ID)
- [ ] Checked arithmetic for all operations
- [ ] Custom errors for all failure cases
- [ ] Access control on privileged instructions
- [ ] PDA seeds unique and collision-resistant
- [ ] Professional security audit completed
- [ ] Bug bounty program launched
Testing:
- [ ] 100% instruction coverage
- [ ] Negative test cases (unauthorized, invalid inputs)
- [ ] Integration tests with TypeScript client
- [ ] Fuzz testing for critical functions
- [ ] Compute unit benchmarks documented
Deployment:
- [ ] Program verified on Solana Explorer
- [ ] Upgrade authority secured with multi-sig
- [ ] Emergency pause mechanism implemented
- [ ] Monitoring and alerting configured
- [ ] Deployment procedure tested on devnet
Documentation:
- [ ] README with usage instructions
- [ ] API documentation (rustdoc)
- [ ] Architecture diagram
- [ ] Known limitations documented
---
Resources
TON Smart Contract Development — FunC + Blueprint Template
Production-grade TON smart contract development using FunC language and Blueprint framework.
---
Project Overview
This template provides a complete development environment for building, testing, and deploying TON blockchain smart contracts using:
- FunC - TON's smart contract language (similar to C)
- Blueprint - Modern development framework for TON
- TypeScript - Testing and deployment scripts
- TON SDK - JavaScript/TypeScript library for TON
- Sandbox - Local blockchain for testing
Use cases: Jettons (tokens), NFTs, DeFi protocols, wallets, DAOs, games
---
Project Structure
ton-contract/
├── contracts/
│ ├── main.fc # Main contract code
│ ├── imports/
│ │ ├── stdlib.fc # Standard library
│ │ └── utils.fc # Utility functions
│ └── jetton.fc # Jetton (token) example
├── wrappers/
│ ├── MainContract.ts # TypeScript wrapper
│ └── JettonMinter.ts # Jetton wrapper
├── tests/
│ ├── MainContract.spec.ts # Contract tests
│ └── JettonMinter.spec.ts # Token tests
├── scripts/
│ ├── deploy.ts # Deployment script
│ └── interact.ts # Interaction script
├── build/
│ └── main.compiled.json # Compiled contract
├── blueprint.config.ts
├── tsconfig.json
└── package.json---
Environment Setup
1. Install Prerequisites
# Install Node.js (v18+)
# Install npm or yarn
# Create new Blueprint project
npm create ton@latest
# Or manually:
npm init
npm install --save-dev @ton/blueprint @ton/core @ton/crypto @ton/ton @ton/test-utils
npm install --save-dev @types/node typescript ts-node2. Initialize Blueprint Project
# Create new contract
npx blueprint create Counter
# This creates:
# - contracts/counter.fc
# - wrappers/Counter.ts
# - tests/Counter.spec.ts3. Configure Blueprint
blueprint.config.ts:
import { Config } from '@ton/blueprint';
export const config: Config = {
network: {
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
apiKey: process.env.TONCENTER_API_KEY,
},
};---
Basic Counter Contract (FunC)
Contract Code (contracts/counter.fc)
;; Counter contract - stores and manages a counter value
#include "imports/stdlib.fc";
;; Storage layout:
;; counter: uint64
;; owner: MsgAddressInt
(int, slice) load_data() inline {
slice ds = get_data().begin_parse();
return (
ds~load_uint(64), ;; counter
ds~load_msg_addr() ;; owner
);
}
() save_data(int counter, slice owner) impure inline {
set_data(begin_cell()
.store_uint(counter, 64)
.store_slice(owner)
.end_cell());
}
;; recv_internal is the main entry point for internal messages
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_empty?()) { ;; Ignore empty messages
return ();
}
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) { ;; Bounced messages
return ();
}
slice sender_address = cs~load_msg_addr();
int op = in_msg_body~load_uint(32);
int query_id = in_msg_body~load_uint(64);
(int counter, slice owner) = load_data();
if (op == 1) { ;; Increment
counter += 1;
save_data(counter, owner);
return ();
}
if (op == 2) { ;; Reset (owner only)
throw_unless(401, equal_slices(sender_address, owner));
int new_counter = in_msg_body~load_uint(64);
save_data(new_counter, owner);
return ();
}
if (op == 3) { ;; Get counter (query)
;; Send response with counter value
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(sender_address)
.store_coins(0)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op, 32)
.store_uint(query_id, 64)
.store_uint(counter, 64)
.end_cell();
send_raw_message(msg, 64); ;; Send all remaining balance
return ();
}
throw(0xffff); ;; Unknown operation
}
;; Get methods (for off-chain queries)
int get_counter() method_id {
(int counter, _) = load_data();
return counter;
}
slice get_owner() method_id {
(_, slice owner) = load_data();
return owner;
}---
Jetton (Token) Contract Example
Jetton Minter (contracts/jetton-minter.fc)
#include "imports/stdlib.fc";
#include "imports/params.fc";
#include "imports/jetton-utils.fc";
;; Storage:
;; total_supply: Coins
;; admin_address: MsgAddressInt
;; content: Cell (metadata)
;; jetton_wallet_code: Cell
(int, slice, cell, cell) load_data() inline {
slice ds = get_data().begin_parse();
return (
ds~load_coins(), ;; total_supply
ds~load_msg_addr(), ;; admin_address
ds~load_ref(), ;; content
ds~load_ref() ;; jetton_wallet_code
);
}
() save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline {
set_data(begin_cell()
.store_coins(total_supply)
.store_slice(admin_address)
.store_ref(content)
.store_ref(jetton_wallet_code)
.end_cell());
}
() mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) impure {
cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code);
slice to_wallet_address = calculate_address_by_state_init(state_init);
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(to_wallet_address)
.store_coins(amount)
.store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)
.store_ref(state_init)
.store_ref(master_msg)
.end_cell();
send_raw_message(msg, 1); ;; Pay transfer fees separately
}
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_empty?()) {
return ();
}
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) {
return ();
}
slice sender_address = cs~load_msg_addr();
int op = in_msg_body~load_uint(32);
int query_id = in_msg_body~load_uint(64);
(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data();
if (op == op::mint()) { ;; Mint tokens
throw_unless(73, equal_slices(sender_address, admin_address));
slice to_address = in_msg_body~load_msg_addr();
int amount = in_msg_body~load_coins();
cell master_msg = in_msg_body~load_ref();
mint_tokens(to_address, jetton_wallet_code, amount, master_msg);
save_data(total_supply + amount, admin_address, content, jetton_wallet_code);
return ();
}
if (op == op::burn_notification()) { ;; Burn notification from wallet
int jetton_amount = in_msg_body~load_coins();
slice from_address = in_msg_body~load_msg_addr();
;; Verify sender is a valid jetton wallet
cell state_init = calculate_jetton_wallet_state_init(from_address, my_address(), jetton_wallet_code);
slice expected_wallet = calculate_address_by_state_init(state_init);
throw_unless(74, equal_slices(sender_address, expected_wallet));
save_data(total_supply - jetton_amount, admin_address, content, jetton_wallet_code);
return ();
}
if (op == op::change_admin()) { ;; Change admin
throw_unless(73, equal_slices(sender_address, admin_address));
slice new_admin_address = in_msg_body~load_msg_addr();
save_data(total_supply, new_admin_address, content, jetton_wallet_code);
return ();
}
throw(0xffff);
}
;; Get methods
(int, int, slice, cell, cell) get_jetton_data() method_id {
(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data();
return (total_supply, -1, admin_address, content, jetton_wallet_code);
}
slice get_wallet_address(slice owner_address) method_id {
(_, _, _, cell jetton_wallet_code) = load_data();
return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code);
}---
TypeScript Wrapper
Counter Wrapper (wrappers/Counter.ts)
import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';
export type CounterConfig = {
counter: number;
owner: Address;
};
export function counterConfigToCell(config: CounterConfig): Cell {
return beginCell()
.storeUint(config.counter, 64)
.storeAddress(config.owner)
.endCell();
}
export class Counter implements Contract {
constructor(
readonly address: Address,
readonly init?: { code: Cell; data: Cell }
) {}
static createFromAddress(address: Address) {
return new Counter(address);
}
static createFromConfig(config: CounterConfig, code: Cell, workchain = 0) {
const data = counterConfigToCell(config);
const init = { code, data };
return new Counter(contractAddress(workchain, init), init);
}
async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().endCell(),
});
}
async sendIncrement(
provider: ContractProvider,
via: Sender,
opts: {
value: bigint;
queryID?: number;
}
) {
await provider.internal(via, {
value: opts.value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell()
.storeUint(1, 32) // op
.storeUint(opts.queryID ?? 0, 64)
.endCell(),
});
}
async sendReset(
provider: ContractProvider,
via: Sender,
opts: {
value: bigint;
newCounter: number;
queryID?: number;
}
) {
await provider.internal(via, {
value: opts.value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell()
.storeUint(2, 32) // op
.storeUint(opts.queryID ?? 0, 64)
.storeUint(opts.newCounter, 64)
.endCell(),
});
}
async getCounter(provider: ContractProvider): Promise<number> {
const result = await provider.get('get_counter', []);
return result.stack.readNumber();
}
async getOwner(provider: ContractProvider): Promise<Address> {
const result = await provider.get('get_owner', []);
return result.stack.readAddress();
}
}---
Testing
Test File (tests/Counter.spec.ts)
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { Cell, toNano } from '@ton/core';
import { Counter } from '../wrappers/Counter';
import '@ton/test-utils';
import { compile } from '@ton/blueprint';
describe('Counter', () => {
let code: Cell;
beforeAll(async () => {
code = await compile('Counter');
});
let blockchain: Blockchain;
let deployer: SandboxContract<TreasuryContract>;
let counter: SandboxContract<Counter>;
beforeEach(async () => {
blockchain = await Blockchain.create();
deployer = await blockchain.treasury('deployer');
counter = blockchain.openContract(
Counter.createFromConfig(
{
counter: 0,
owner: deployer.address,
},
code
)
);
const deployResult = await counter.sendDeploy(deployer.getSender(), toNano('0.05'));
expect(deployResult.transactions).toHaveTransaction({
from: deployer.address,
to: counter.address,
deploy: true,
success: true,
});
});
it('should deploy', async () => {
// Already checked in beforeEach
});
it('should increment counter', async () => {
const counterBefore = await counter.getCounter();
expect(counterBefore).toBe(0);
await counter.sendIncrement(deployer.getSender(), {
value: toNano('0.05'),
});
const counterAfter = await counter.getCounter();
expect(counterAfter).toBe(1);
});
it('should reset counter (owner)', async () => {
await counter.sendIncrement(deployer.getSender(), { value: toNano('0.05') });
await counter.sendIncrement(deployer.getSender(), { value: toNano('0.05') });
expect(await counter.getCounter()).toBe(2);
await counter.sendReset(deployer.getSender(), {
value: toNano('0.05'),
newCounter: 10,
});
expect(await counter.getCounter()).toBe(10);
});
it('should reject reset from non-owner', async () => {
const notOwner = await blockchain.treasury('notOwner');
const result = await counter.sendReset(notOwner.getSender(), {
value: toNano('0.05'),
newCounter: 100,
});
expect(result.transactions).toHaveTransaction({
from: notOwner.address,
to: counter.address,
success: false,
exitCode: 401, // Unauthorized
});
});
});---
Deployment
Deploy Script (scripts/deploy.ts)
import { toNano } from '@ton/core';
import { Counter } from '../wrappers/Counter';
import { compile, NetworkProvider } from '@ton/blueprint';
export async function run(provider: NetworkProvider) {
const counter = provider.open(
Counter.createFromConfig(
{
counter: 0,
owner: provider.sender().address!,
},
await compile('Counter')
)
);
await counter.sendDeploy(provider.sender(), toNano('0.05'));
await provider.waitForDeploy(counter.address);
console.log('Counter deployed at:', counter.address);
console.log('Initial counter:', await counter.getCounter());
}Run deployment:
# Deploy to testnet
npx blueprint run
# Deploy to mainnet
npx blueprint run --mainnet
# Deploy to custom network
npx blueprint run --custom https://your-node.com/api---
Useful Commands
Development
# Compile contracts
npx blueprint build
# Run tests
npx blueprint test
# Run specific test
npx blueprint test Counter
# Watch mode
npx blueprint test --watch
# Deploy
npx blueprint run
# Interact with deployed contract
npx blueprint run interactTON CLI
# Install TON CLI
npm install -g ton
# Get account info
ton account <address>
# Send transaction
ton send <from> <to> <amount>
# Get transaction
ton gettx <address> <lt>---
Production Checklist
Before deploying to mainnet:
Security:
- [ ] All operations have proper authorization checks
- [ ] Bounce message handling implemented
- [ ] Gas limits considered
- [ ] Integer overflow protection
- [ ] Professional security audit completed
Testing:
- [ ] 100% test coverage
- [ ] Negative test cases
- [ ] Gas consumption measured
- [ ] Tested on testnet
Deployment:
- [ ] Contract optimized and compiled
- [ ] Initial state correctly configured
- [ ] Deployment tested on testnet
- [ ] Owner/admin keys secured
- [ ] Monitoring setup
Documentation:
- [ ] README with usage instructions
- [ ] API documentation
- [ ] Known limitations documented
---
Resources
Blockchain Best Practices - Universal Patterns
Chain-agnostic best practices for building secure, scalable blockchain applications.
---
Table of Contents
1. Architecture Patterns 2. Security Principles 3. State Management 4. Economic Security 5. Gas Optimization (Universal) 6. Cross-Chain Patterns 7. Testing Strategies 8. Deployment Best Practices 9. Monitoring & Observability 10. Governance Patterns 11. Common Pitfalls (All Chains) 12. Documentation Standards 13. Production Checklist 14. Resources
Architecture Patterns
Separation of Concerns
Contracts should follow single responsibility principle:
Protocol Architecture:
├── Core Logic (business rules)
├── Storage Layer (state management)
├── Access Control (permissions)
├── Treasury (funds management)
└── Governance (upgrades/parameters)Example (Solidity):
// BEST: Separated concerns
contract TokenLogic {
function transfer(address to, uint amount) external;
}
contract TokenStorage {
mapping(address => uint) public balances;
}
contract TokenGovernance {
function updateParameters() external onlyGovernor;
}
// AVOID: God contract
contract MonolithicToken {
// All logic, storage, governance in one contract
}Fail-Safe Defaults
Whitelist over blacklist:
// BEST: Whitelist approved addresses
mapping(address => bool) public approvedUsers;
function transfer(address to) public {
require(approvedUsers[to], "Not approved");
}
// AVOID: Blacklist default allows everyone
mapping(address => bool) public blockedUsers;
function transfer(address to) public {
require(!blockedUsers[to], "Blocked"); // Default allows everyone
}---
Security Principles
Defense in Depth
Multiple layers of security:
1. Input Validation
function withdraw(uint amount) public {
require(amount > 0, "Zero amount");
require(amount <= balances[msg.sender], "Insufficient balance");
require(amount <= withdrawalLimit, "Exceeds limit");
}2. State Protection
modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}3. Access Control
modifier onlyAuthorized() {
require(hasRole(AUTHORIZED_ROLE, msg.sender), "Unauthorized");
_;
}4. Circuit Breakers
bool public paused;
modifier whenNotPaused() {
require(!paused, "Contract paused");
_;
}Least Privilege Principle
Grant minimum necessary permissions:
// GOOD: GOOD: Role-based access
bytes32 public constant MINTER_ROLE = keccak256("MINTER");
bytes32 public constant BURNER_ROLE = keccak256("BURNER");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER");
function mint(address to, uint amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
// BAD: BAD: Single admin has all powers
address public admin;
function mint(address to, uint amount) public {
require(msg.sender == admin);
_mint(to, amount);
}---
State Management
Atomic Transactions
Ensure all-or-nothing operations:
// GOOD: GOOD: Atomic swap
function atomicSwap(address tokenA, address tokenB, uint amountA, uint amountB) public {
require(IERC20(tokenA).transferFrom(msg.sender, address(this), amountA));
require(IERC20(tokenB).transfer(msg.sender, amountB));
// Both succeed or both revert
}
// BAD: BAD: Partial state changes possible
function partialSwap(address tokenA, address tokenB) public {
IERC20(tokenA).transferFrom(msg.sender, address(this), 100);
// If next line fails, first transfer already happened
IERC20(tokenB).transfer(msg.sender, 100);
}Immutability Where Possible
// GOOD: Critical parameters immutable
uint256 public immutable VESTING_DURATION;
address public immutable TREASURY;
constructor(uint256 duration, address treasury) {
VESTING_DURATION = duration;
TREASURY = treasury;
}
// [WARNING] Mutable parameters need governance
uint256 public feePercentage; // Can be changed by governance---
Economic Security
Sybil Resistance
Prevent spam/DoS with economic costs:
// GOOD: Require minimum stake
mapping(address => uint) public stakes;
uint public constant MIN_STAKE = 1 ether;
function propose(bytes memory data) public {
require(stakes[msg.sender] >= MIN_STAKE, "Insufficient stake");
// Process proposal
}
// GOOD: Charge fees for operations
function create() public payable {
require(msg.value >= CREATION_FEE, "Insufficient fee");
// Create resource
}Flash Loan Protection
// GOOD: Validate balances at transaction end
uint256 private constant SNAPSHOT_ID = type(uint256).max;
modifier noFlashLoan() {
uint256 balanceBefore = token.balanceOf(address(this));
_;
require(
token.balanceOf(address(this)) >= balanceBefore,
"Flash loan detected"
);
}
// GOOD: Use TWAP instead of spot prices
function getPrice() public view returns (uint256) {
return oracle.getTWAP(3600); // 1-hour average
}---
Gas Optimization (Universal)
Minimize Storage Operations
Storage is expensive across all chains:
// BAD: EXPENSIVE: Multiple storage writes
function badUpdate(uint[] calldata values) external {
for (uint i = 0; i < values.length; i++) {
data[i] = values[i]; // SSTORE each iteration
}
}
// GOOD: OPTIMIZED: Batch operations
function goodUpdate(uint[] calldata values) external {
uint length = values.length;
for (uint i = 0; i < length;) {
data[i] = values[i];
unchecked { ++i; }
}
}Event Over Storage
For historical data, use events:
// BAD: EXPENSIVE: Store entire history
struct Trade {
address buyer;
uint amount;
uint timestamp;
}
Trade[] public trades; // Growing array in storage
function recordTrade(address buyer, uint amount) internal {
trades.push(Trade(buyer, amount, block.timestamp));
}
// GOOD: CHEAP: Emit events
event TradeRecorded(address indexed buyer, uint amount, uint timestamp);
function recordTrade(address buyer, uint amount) internal {
emit TradeRecorded(buyer, amount, block.timestamp);
}---
Cross-Chain Patterns
Message Verification
// GOOD: Verify cross-chain messages
function processMessage(
bytes memory message,
bytes memory signatures
) external {
bytes32 messageHash = keccak256(message);
require(
verifySignatures(messageHash, signatures),
"Invalid signatures"
);
// Process verified message
}Lock-and-Mint Bridge
// Chain A: Lock tokens
function lockTokens(uint amount, bytes32 destinationChain) external {
token.transferFrom(msg.sender, address(this), amount);
lockedBalance += amount;
emit TokensLocked(msg.sender, amount, destinationChain);
}
// Chain B: Mint wrapped tokens
function mintWrapped(address to, uint amount, bytes memory proof) external {
require(verifyBridgeProof(proof), "Invalid proof");
wrappedToken.mint(to, amount);
}---
Testing Strategies
Invariant Testing
Define and test protocol invariants:
// Invariant: Total supply equals sum of all balances
function invariant_totalSupply() public {
uint sum = 0;
for (uint i = 0; i < users.length; i++) {
sum += balanceOf(users[i]);
}
assertEq(totalSupply(), sum);
}
// Invariant: Reserves maintain constant product
function invariant_constantProduct() public {
uint k = reserve0 * reserve1;
assertGe(k, MINIMUM_LIQUIDITY ** 2);
}Fork Testing
Test against live protocols:
// Test interaction with mainnet Uniswap
function testForkSwap() public {
vm.createSelectFork("mainnet", 18000000);
IUniswapV2Router router = IUniswapV2Router(UNISWAP_ROUTER);
// Test swap logic
}---
Deployment Best Practices
Deterministic Deployment
Use CREATE2 for predictable addresses:
function deploy(bytes32 salt) public returns (address) {
return address(new Contract{salt: salt}());
}
// Address can be predicted before deployment
function predictAddress(bytes32 salt) public view returns (address) {
return address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(type(Contract).creationCode)
)))));
}Multi-Signature Deployment
// GOOD: Deploy with multi-sig as owner
constructor() {
transferOwnership(MULTISIG_ADDRESS);
}
// GOOD: Timelock for critical operations
uint256 public constant TIMELOCK_DELAY = 2 days;
mapping(bytes32 => uint256) public queuedTransactions;
function queueTransaction(bytes memory data) public onlyOwner {
bytes32 txHash = keccak256(data);
queuedTransactions[txHash] = block.timestamp + TIMELOCK_DELAY;
}
function executeTransaction(bytes memory data) public onlyOwner {
bytes32 txHash = keccak256(data);
require(
queuedTransactions[txHash] != 0 &&
block.timestamp >= queuedTransactions[txHash],
"Too early"
);
// Execute
}---
Monitoring & Observability
Event Design
Emit comprehensive events:
// GOOD: GOOD: Indexed fields for filtering, all data included
event Transfer(
address indexed from,
address indexed to,
uint256 amount,
bytes32 indexed txId,
uint256 timestamp
);
// BAD: BAD: Missing critical data
event Transfer(address from, address to);State Validation
// GOOD: Internal accounting checks
modifier validateState() {
uint balanceBefore = address(this).balance;
_;
uint balanceAfter = address(this).balance;
// Ensure internal accounting matches actual balance
require(
balanceAfter >= internalBalance,
"State mismatch"
);
}---
Governance Patterns
Proposal-Vote-Execute
enum ProposalState { Pending, Active, Succeeded, Executed, Canceled }
struct Proposal {
uint256 id;
address proposer;
bytes calldatas;
uint256 forVotes;
uint256 againstVotes;
ProposalState state;
uint256 deadline;
}
function propose(bytes memory calldata_) public returns (uint256) {
require(votingPower[msg.sender] >= PROPOSAL_THRESHOLD);
uint256 proposalId = nextProposalId++;
proposals[proposalId] = Proposal({
id: proposalId,
proposer: msg.sender,
calldatas: calldata_,
forVotes: 0,
againstVotes: 0,
state: ProposalState.Active,
deadline: block.timestamp + VOTING_PERIOD
});
return proposalId;
}Quorum & Voting Power
// GOOD: Quadratic voting to reduce whale power
function vote(uint256 proposalId, bool support) public {
uint256 votes = sqrt(votingPower[msg.sender]);
if (support) {
proposals[proposalId].forVotes += votes;
} else {
proposals[proposalId].againstVotes += votes;
}
}
// GOOD: Quorum requirement
function execute(uint256 proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(proposal.state == ProposalState.Succeeded);
uint256 totalVotes = proposal.forVotes + proposal.againstVotes;
require(totalVotes >= QUORUM, "Quorum not reached");
require(proposal.forVotes > proposal.againstVotes, "Votes failed");
// Execute proposal
}---
Common Pitfalls (All Chains)
Rounding Errors
// BAD: VULNERABLE: Always rounds down in user's favor
uint256 fee = (amount * FEE_RATE) / 10000;
// GOOD: SECURE: Round up for fees
uint256 fee = (amount * FEE_RATE + 9999) / 10000;Precision Loss
// BAD: VULNERABLE: Division before multiplication
uint256 result = (amount / price) * multiplier;
// GOOD: SECURE: Multiplication before division
uint256 result = (amount * multiplier) / price;External Dependency Risk
// BAD: RISKY: Direct dependency
function getPrice() public view returns (uint256) {
return externalOracle.latestPrice(); // What if it fails?
}
// GOOD: RESILIENT: Try-catch with fallback
function getPrice() public view returns (uint256) {
try externalOracle.latestPrice() returns (uint256 price) {
return price;
} catch {
return fallbackOracle.getPrice();
}
}---
Documentation Standards
NatSpec Comments
/// @title Token Contract
/// @author Your Name
/// @notice Implements ERC20 with additional features
/// @dev Uses OpenZeppelin's ERC20 implementation
/**
* @notice Transfer tokens to a recipient
* @dev Emits Transfer event on success
* @param to The recipient address
* @param amount The amount to transfer
* @return success True if transfer succeeded
*/
function transfer(address to, uint256 amount) public returns (bool success) {
// Implementation
}Architecture Documentation
Every protocol should document: 1. System architecture diagram 2. Contract interaction flows 3. Access control matrix 4. Economic model (tokenomics) 5. Upgrade procedures 6. Emergency procedures 7. Known limitations
---
Production Checklist
Before mainnet deployment:
- [ ] Professional security audit completed
- [ ] All tests passing (100% coverage)
- [ ] Fuzz tests for critical functions
- [ ] Fork tests for external integrations
- [ ] Multi-sig wallet as owner
- [ ] Timelock on critical functions
- [ ] Circuit breaker implemented
- [ ] Monitoring and alerting configured
- [ ] Incident response plan documented
- [ ] Bug bounty program prepared
- [ ] Contract verified on explorer
- [ ] Documentation complete
- [ ] Deployment procedure tested on testnet
---
Resources
Related skills
How it compares
Choose software-crypto-web3 for multi-chain contract and backend integration guidance rather than generic backend API skills without on-chain security patterns.
FAQ
Which blockchains does software-crypto-web3 cover?
software-crypto-web3 spans EVM chains with Solidity, Solana with Anchor and Rust, Cosmos with CosmWasm, and TON with Tact or FunC plus Blueprint tooling for contract development and review.
What testing tools does software-crypto-web3 recommend?
software-crypto-web3 lists Foundry and Hardhat for EVM suites, Anchor tests for Solana, and Echidna or Medusa property fuzzing alongside static analyzers like Slither and Aderyn for pre-audit checks.
Does software-crypto-web3 include backend integration guidance?
software-crypto-web3 covers RPC providers, subgraph indexing, custodial webhook signature validation, Kafka-style event flows, and transaction lifecycle monitoring for production Web3 backends.