Security patterns for Web3 and blockchain applications — Solana wallet signature verification, transaction validation, smart contract interaction security, and checklist for DeFi/NFT features.
From clarcnpx claudepluginhub marvinrichter/clarc --plugin clarcThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Security patterns for applications that interact with blockchains, wallets, and smart contracts.
For general security (injection, auth, secrets, OWASP Top 10) — see skill
security-review. For auth patterns (JWT, OAuth, sessions) — see skillauth-patterns.
import { verify } from '@solana/web3.js'
async function verifyWalletOwnership(
publicKey: string,
signature: string,
message: string
) {
try {
const isValid = verify(
Buffer.from(message),
Buffer.from(signature, 'base64'),
Buffer.from(publicKey, 'base64')
)
return isValid
} catch (error) {
return false
}
}
async function verifyTransaction(transaction: Transaction) {
// Verify recipient
if (transaction.to !== expectedRecipient) {
throw new Error('Invalid recipient')
}
// Verify amount
if (transaction.amount > maxAmount) {
throw new Error('Amount exceeds limit')
}
// Verify user has sufficient balance
const balance = await getBalance(transaction.from)
if (balance < transaction.amount) {
throw new Error('Insufficient balance')
}
return true
}
| Attack | Description | Fix |
|---|---|---|
| Replay attack | Reusing a valid signed message | Include nonce + expiry in signed payload |
| Spoofed recipient | User-supplied destination address | Pin recipient addresses server-side |
| Amount overflow | Integer overflow in token math | Use BN.js or BigInt for all amounts |
| Reentrancy (EVM) | Callback executes before state update | Update state before external calls |
| Front-running | Transaction ordering manipulation | Use commit-reveal schemes |
contract VulnerableVault {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient");
// ❌ External call BEFORE state update — attacker can re-enter here
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount; // State updated AFTER call
}
}
Attack vector: Malicious contract's receive() calls withdraw() again before balances is decremented. Attacker can drain the vault.
contract SafeVault {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) external {
// ✅ 1. Checks
require(balances[msg.sender] >= amount, "Insufficient");
// ✅ 2. Effects (state update BEFORE external call)
balances[msg.sender] -= amount;
// ✅ 3. Interactions (external call LAST)
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Rule: Always follow Checks → Effects → Interactions order. For complex cases, add ReentrancyGuard from OpenZeppelin:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SafeVault is ReentrancyGuard {
function withdraw(uint256 amount) external nonReentrant { ... }
}