@dilukangelo/web3-ai-skills 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -0
- package/bin/cli.js +74 -0
- package/package.json +50 -0
- package/template/.agent/ARCHITECTURE.md +129 -0
- package/template/.agent/GEMINI.md +135 -0
- package/template/.agent/agents/contract-auditor.md +161 -0
- package/template/.agent/agents/rust-web3.md +153 -0
- package/template/.agent/agents/solidity-expert.md +164 -0
- package/template/.agent/agents/web3-frontend.md +192 -0
- package/template/.agent/agents/web3-infra.md +155 -0
- package/template/.agent/agents/web3-orchestrator.md +145 -0
- package/template/.agent/skills/clean-code/SKILL.md +142 -0
- package/template/.agent/skills/dapp-patterns/SKILL.md +184 -0
- package/template/.agent/skills/rainbowkit-wagmi/SKILL.md +262 -0
- package/template/.agent/skills/rpc-optimization/SKILL.md +194 -0
- package/template/.agent/skills/rust-smart-contracts/SKILL.md +202 -0
- package/template/.agent/skills/smart-contract-auditing/SKILL.md +198 -0
- package/template/.agent/skills/solidity-patterns/SKILL.md +192 -0
- package/template/.agent/skills/subgraph-indexing/SKILL.md +225 -0
- package/template/.agent/workflows/audit.md +126 -0
- package/template/.agent/workflows/create-contract.md +134 -0
- package/template/.agent/workflows/create-dapp.md +109 -0
- package/template/.agent/workflows/deploy-contract.md +120 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: smart-contract-auditing
|
|
3
|
+
description: Smart contract security audit methodology. Slither, Mythril, Aderyn, Echidna, manual review patterns. Vulnerability classification, attack vectors, DeFi exploit patterns.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Smart Contract Auditing — Security Analysis Methodology
|
|
7
|
+
|
|
8
|
+
Expert knowledge for systematically identifying vulnerabilities in smart contracts.
|
|
9
|
+
|
|
10
|
+
## Use this skill when
|
|
11
|
+
|
|
12
|
+
- Performing pre-deployment security audits
|
|
13
|
+
- Reviewing smart contract code for vulnerabilities
|
|
14
|
+
- Analyzing DeFi protocol security
|
|
15
|
+
- Investigating post-exploit incidents
|
|
16
|
+
- Preparing contracts for bug bounty programs
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Audit Workflow
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
1. SCOPE DEFINITION
|
|
24
|
+
└── Identify contracts, dependencies, trust assumptions
|
|
25
|
+
|
|
26
|
+
2. AUTOMATED ANALYSIS
|
|
27
|
+
└── Slither → Mythril → Aderyn → Echidna
|
|
28
|
+
|
|
29
|
+
3. MANUAL REVIEW
|
|
30
|
+
└── Line-by-line, state machine analysis
|
|
31
|
+
|
|
32
|
+
4. EXPLOIT DEVELOPMENT
|
|
33
|
+
└── PoC for each finding
|
|
34
|
+
|
|
35
|
+
5. REPORT
|
|
36
|
+
└── Findings, severity, recommendations
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Automated Tools
|
|
42
|
+
|
|
43
|
+
### Slither (Static Analysis)
|
|
44
|
+
```bash
|
|
45
|
+
# Full analysis
|
|
46
|
+
slither . --detect all --exclude-low
|
|
47
|
+
|
|
48
|
+
# Specific detectors
|
|
49
|
+
slither . --detect reentrancy-eth,reentrancy-no-eth
|
|
50
|
+
|
|
51
|
+
# Print contract summary
|
|
52
|
+
slither . --print contract-summary
|
|
53
|
+
|
|
54
|
+
# Check for upgradeable issues
|
|
55
|
+
slither . --detect delegatecall-loop
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Mythril (Symbolic Execution)
|
|
59
|
+
```bash
|
|
60
|
+
# Analyze single contract
|
|
61
|
+
myth analyze contracts/Token.sol --solv 0.8.24
|
|
62
|
+
|
|
63
|
+
# Deep analysis with more solvers
|
|
64
|
+
myth analyze contracts/Token.sol --execution-timeout 300 --solver-timeout 60
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Aderyn (Rust-Based)
|
|
68
|
+
```bash
|
|
69
|
+
# Fast Rust-based analysis
|
|
70
|
+
aderyn .
|
|
71
|
+
|
|
72
|
+
# With specific output
|
|
73
|
+
aderyn . --output report.md
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Echidna (Fuzzing)
|
|
77
|
+
```yaml
|
|
78
|
+
# echidna.config.yaml
|
|
79
|
+
testMode: assertion
|
|
80
|
+
testLimit: 50000
|
|
81
|
+
shrinkLimit: 5000
|
|
82
|
+
```
|
|
83
|
+
```solidity
|
|
84
|
+
// Property test
|
|
85
|
+
function echidna_total_supply_invariant() public view returns (bool) {
|
|
86
|
+
return token.totalSupply() <= MAX_SUPPLY;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Vulnerability Taxonomy
|
|
93
|
+
|
|
94
|
+
### Critical (Immediate fund loss)
|
|
95
|
+
|
|
96
|
+
| Vulnerability | Pattern | Check |
|
|
97
|
+
|---------------|---------|-------|
|
|
98
|
+
| **Reentrancy** | External call before state update | CEI pattern enforced? |
|
|
99
|
+
| **Access Control Bypass** | Missing `onlyOwner` / role check | All admin functions protected? |
|
|
100
|
+
| **Uninitialized Proxy** | Proxy without `_disableInitializers()` | Constructor blocks init? |
|
|
101
|
+
| **Oracle Manipulation** | Spot price as oracle | TWAP with sufficient window? |
|
|
102
|
+
| **Arbitrary External Call** | User-controlled `target.call(data)` | Target address validated? |
|
|
103
|
+
|
|
104
|
+
### High (Conditional fund loss)
|
|
105
|
+
|
|
106
|
+
| Vulnerability | Pattern | Check |
|
|
107
|
+
|---------------|---------|-------|
|
|
108
|
+
| **Flash Loan Attack** | State manipulable in single tx | Same-block protections? |
|
|
109
|
+
| **Front-Running** | Profitable tx ordering | Commit-reveal or MEV protection? |
|
|
110
|
+
| **Signature Replay** | Missing nonce/chainId in signature | EIP-712 with full domain? |
|
|
111
|
+
| **Integer Overflow** | Unsafe `unchecked` blocks | Bounds validated before unchecked? |
|
|
112
|
+
| **Delegate Call Injection** | User-controlled delegatecall target | Target hardcoded or validated? |
|
|
113
|
+
|
|
114
|
+
### Medium (Limited impact)
|
|
115
|
+
|
|
116
|
+
| Vulnerability | Pattern | Check |
|
|
117
|
+
|---------------|---------|-------|
|
|
118
|
+
| **Centralization Risk** | Single admin key controls funds | Timelock + multisig? |
|
|
119
|
+
| **Precision Loss** | Division before multiplication | Correct order of operations? |
|
|
120
|
+
| **DoS via Revert** | Loop depends on external call | Pull pattern used? |
|
|
121
|
+
| **Timestamp Dependence** | Business logic on `block.timestamp` | Acceptable tolerance? |
|
|
122
|
+
| **Storage Collision** | Proxy upgrade changes layout | Storage gap pattern? |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## DeFi-Specific Attack Vectors
|
|
127
|
+
|
|
128
|
+
### Price Oracle Attacks
|
|
129
|
+
```solidity
|
|
130
|
+
// ❌ VULNERABLE: Spot price manipulation
|
|
131
|
+
uint256 price = pair.getReserves()[0] / pair.getReserves()[1];
|
|
132
|
+
|
|
133
|
+
// ✅ SAFE: TWAP oracle with sufficient window
|
|
134
|
+
uint256 price = oracle.consult(token, 1e18, TWAP_PERIOD);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### ERC-4626 Inflation Attack
|
|
138
|
+
```solidity
|
|
139
|
+
// ❌ VULNERABLE: First depositor can inflate share price
|
|
140
|
+
function deposit(uint256 assets) {
|
|
141
|
+
uint256 shares = (totalSupply == 0) ? assets : assets * totalSupply / totalAssets;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ✅ SAFE: Virtual shares and assets
|
|
145
|
+
function deposit(uint256 assets) {
|
|
146
|
+
uint256 shares = assets * (totalSupply + 1) / (totalAssets + 1);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Governance Attack
|
|
151
|
+
- Flash-borrow governance tokens
|
|
152
|
+
- Create and vote on malicious proposal
|
|
153
|
+
- Execute in same block
|
|
154
|
+
|
|
155
|
+
### Cross-Chain Bridge Exploits
|
|
156
|
+
- Message verification failures
|
|
157
|
+
- Replay across chains
|
|
158
|
+
- Race conditions in finality
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Report Template
|
|
163
|
+
|
|
164
|
+
```markdown
|
|
165
|
+
# Security Audit Report
|
|
166
|
+
|
|
167
|
+
## Summary
|
|
168
|
+
- **Project:** [Name]
|
|
169
|
+
- **Commit:** [Hash]
|
|
170
|
+
- **Scope:** [Contracts audited]
|
|
171
|
+
- **Duration:** [Days]
|
|
172
|
+
|
|
173
|
+
## Findings Summary
|
|
174
|
+
|
|
175
|
+
| ID | Title | Severity | Status |
|
|
176
|
+
|----|-------|----------|--------|
|
|
177
|
+
| H-01 | [Title] | High | Open |
|
|
178
|
+
| M-01 | [Title] | Medium | Fixed |
|
|
179
|
+
|
|
180
|
+
## Detailed Findings
|
|
181
|
+
|
|
182
|
+
### [H-01] Title
|
|
183
|
+
|
|
184
|
+
**Severity:** High
|
|
185
|
+
**Location:** `Contract.sol#L45-L62`
|
|
186
|
+
|
|
187
|
+
**Description:**
|
|
188
|
+
[Detailed description]
|
|
189
|
+
|
|
190
|
+
**Impact:**
|
|
191
|
+
[What an attacker can achieve]
|
|
192
|
+
|
|
193
|
+
**Proof of Concept:**
|
|
194
|
+
[Exploit code or test]
|
|
195
|
+
|
|
196
|
+
**Recommendation:**
|
|
197
|
+
[How to fix]
|
|
198
|
+
```
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: solidity-patterns
|
|
3
|
+
description: Solidity 0.8.x+ patterns, ERC standards, gas optimization, upgradeable contracts, DeFi integrations. Use when writing or reviewing Solidity smart contracts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Solidity Patterns — EVM Smart Contract Mastery
|
|
7
|
+
|
|
8
|
+
Expert knowledge for writing secure, gas-efficient Solidity smart contracts on EVM chains.
|
|
9
|
+
|
|
10
|
+
## Use this skill when
|
|
11
|
+
|
|
12
|
+
- Writing new Solidity contracts
|
|
13
|
+
- Optimizing gas consumption
|
|
14
|
+
- Implementing ERC standards
|
|
15
|
+
- Creating upgradeable proxy contracts
|
|
16
|
+
- Integrating with DeFi protocols
|
|
17
|
+
|
|
18
|
+
## Do not use this skill when
|
|
19
|
+
|
|
20
|
+
- Working with Solana or non-EVM chains (use `rust-smart-contracts`)
|
|
21
|
+
- Building frontend code (use `rainbowkit-wagmi`)
|
|
22
|
+
|
|
23
|
+
## Instructions
|
|
24
|
+
|
|
25
|
+
1. Identify the contract type and ERC standard needed
|
|
26
|
+
2. Apply security patterns (CEI, access control, reentrancy guards)
|
|
27
|
+
3. Optimize gas with storage packing, calldata usage, custom errors
|
|
28
|
+
4. Write comprehensive Foundry tests (unit, fuzz, invariant)
|
|
29
|
+
5. Create deployment scripts with verification
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Core Patterns
|
|
34
|
+
|
|
35
|
+
### Storage Optimization
|
|
36
|
+
```solidity
|
|
37
|
+
// ❌ BAD: 3 storage slots (96 bytes used, 96 bytes allocated)
|
|
38
|
+
uint256 amount; // slot 0
|
|
39
|
+
address owner; // slot 1
|
|
40
|
+
bool active; // slot 2
|
|
41
|
+
|
|
42
|
+
// ✅ GOOD: 2 storage slots (53 bytes used, 64 bytes allocated)
|
|
43
|
+
uint256 amount; // slot 0
|
|
44
|
+
address owner; // slot 1 (20 bytes)
|
|
45
|
+
bool active; // slot 1 (1 byte, packed with address)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Custom Errors (Gas Efficient)
|
|
49
|
+
```solidity
|
|
50
|
+
// ❌ BAD: ~50+ bytes for string
|
|
51
|
+
require(balance >= amount, "Insufficient balance");
|
|
52
|
+
|
|
53
|
+
// ✅ GOOD: 4 bytes selector only
|
|
54
|
+
error InsufficientBalance(uint256 available, uint256 required);
|
|
55
|
+
if (balance < amount) revert InsufficientBalance(balance, amount);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Check-Effects-Interactions (CEI)
|
|
59
|
+
```solidity
|
|
60
|
+
function withdraw(uint256 amount) external {
|
|
61
|
+
// 1. CHECK
|
|
62
|
+
if (balances[msg.sender] < amount) revert InsufficientBalance();
|
|
63
|
+
|
|
64
|
+
// 2. EFFECTS (state changes BEFORE external calls)
|
|
65
|
+
balances[msg.sender] -= amount;
|
|
66
|
+
|
|
67
|
+
// 3. INTERACTIONS (external calls LAST)
|
|
68
|
+
(bool success, ) = msg.sender.call{value: amount}("");
|
|
69
|
+
if (!success) revert TransferFailed();
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Transient Storage (EIP-1153, Solidity 0.8.24+)
|
|
74
|
+
```solidity
|
|
75
|
+
// Gas-efficient reentrancy guard using transient storage
|
|
76
|
+
modifier nonReentrant() {
|
|
77
|
+
assembly {
|
|
78
|
+
if tload(0) { revert(0, 0) }
|
|
79
|
+
tstore(0, 1)
|
|
80
|
+
}
|
|
81
|
+
_;
|
|
82
|
+
assembly {
|
|
83
|
+
tstore(0, 0)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Upgradeable Contracts (UUPS)
|
|
89
|
+
```solidity
|
|
90
|
+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
91
|
+
|
|
92
|
+
contract MyContract is UUPSUpgradeable {
|
|
93
|
+
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
94
|
+
constructor() { _disableInitializers(); }
|
|
95
|
+
|
|
96
|
+
function initialize() public initializer {
|
|
97
|
+
__UUPSUpgradeable_init();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function _authorizeUpgrade(address) internal override onlyOwner {}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## ERC Implementation Quick Reference
|
|
107
|
+
|
|
108
|
+
### ERC-20 (Fungible Token)
|
|
109
|
+
- Use OpenZeppelin `ERC20` base
|
|
110
|
+
- Add `permit` for gasless approvals (ERC-2612)
|
|
111
|
+
- Consider `ERC20Votes` for governance tokens
|
|
112
|
+
- Handle fee-on-transfer and rebasing edge cases
|
|
113
|
+
|
|
114
|
+
### ERC-721 (NFT)
|
|
115
|
+
- Use `ERC721Enumerable` only if on-chain enumeration needed (expensive)
|
|
116
|
+
- Implement `ERC2981` for royalties
|
|
117
|
+
- Use `_safeMint` to check receiver compatibility
|
|
118
|
+
- Consider `ERC721A` for batch minting optimization
|
|
119
|
+
|
|
120
|
+
### ERC-1155 (Multi-Token)
|
|
121
|
+
- Ideal for gaming assets and mixed fungible/non-fungible
|
|
122
|
+
- Batch operations reduce gas significantly
|
|
123
|
+
- Implement `uri()` with token ID substitution
|
|
124
|
+
|
|
125
|
+
### ERC-4626 (Tokenized Vault)
|
|
126
|
+
- Standard for yield-bearing vaults
|
|
127
|
+
- Beware of inflation attack on first deposit
|
|
128
|
+
- Always round against the user (deposit up, withdraw down)
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Gas Optimization Checklist
|
|
133
|
+
|
|
134
|
+
| Technique | Savings |
|
|
135
|
+
|-----------|---------|
|
|
136
|
+
| Custom errors | ~50 gas per revert |
|
|
137
|
+
| `calldata` instead of `memory` | ~60 gas per param |
|
|
138
|
+
| `unchecked` (safe arithmetic) | ~20-40 gas per op |
|
|
139
|
+
| Storage packing | ~20,000 gas per slot saved |
|
|
140
|
+
| `immutable` / `constant` | ~2,100 gas on read |
|
|
141
|
+
| Caching `.length` in loops | ~6 gas per iteration |
|
|
142
|
+
| `bytes32` instead of `string` | ~100+ gas |
|
|
143
|
+
| `mapping` instead of `array` | Variable, often significant |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Testing Patterns (Foundry)
|
|
148
|
+
|
|
149
|
+
```solidity
|
|
150
|
+
// Unit Test
|
|
151
|
+
function test_Transfer() public {
|
|
152
|
+
token.mint(alice, 1000);
|
|
153
|
+
vm.prank(alice);
|
|
154
|
+
token.transfer(bob, 500);
|
|
155
|
+
assertEq(token.balanceOf(bob), 500);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Fuzz Test
|
|
159
|
+
function testFuzz_Transfer(uint256 amount) public {
|
|
160
|
+
amount = bound(amount, 1, MAX_SUPPLY);
|
|
161
|
+
token.mint(alice, amount);
|
|
162
|
+
vm.prank(alice);
|
|
163
|
+
token.transfer(bob, amount);
|
|
164
|
+
assertEq(token.balanceOf(bob), amount);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Invariant Test
|
|
168
|
+
function invariant_TotalSupplyMatchesBalances() public {
|
|
169
|
+
uint256 sum = token.balanceOf(alice) + token.balanceOf(bob);
|
|
170
|
+
assertEq(token.totalSupply(), sum);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Deployment Script (Foundry)
|
|
177
|
+
```solidity
|
|
178
|
+
// script/Deploy.s.sol
|
|
179
|
+
contract DeployScript is Script {
|
|
180
|
+
function run() public {
|
|
181
|
+
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
|
|
182
|
+
vm.startBroadcast(deployerKey);
|
|
183
|
+
|
|
184
|
+
MyContract c = new MyContract();
|
|
185
|
+
|
|
186
|
+
vm.stopBroadcast();
|
|
187
|
+
|
|
188
|
+
// Verify
|
|
189
|
+
// forge verify-contract <address> MyContract --chain <chain-id>
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: subgraph-indexing
|
|
3
|
+
description: Chain indexing with The Graph, Ponder, Envio, and custom indexers. Event-driven data, GraphQL APIs, entity relationships, and real-time sync.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Subgraph & Indexing — Blockchain Data Access
|
|
7
|
+
|
|
8
|
+
Expert knowledge for building efficient blockchain data indexing solutions.
|
|
9
|
+
|
|
10
|
+
## Use this skill when
|
|
11
|
+
|
|
12
|
+
- Building subgraphs for The Graph
|
|
13
|
+
- Setting up Ponder or Envio indexers
|
|
14
|
+
- Creating real-time data feeds from on-chain events
|
|
15
|
+
- Designing entity schemas for blockchain data
|
|
16
|
+
- Querying historical blockchain data efficiently
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The Graph (Subgraph)
|
|
21
|
+
|
|
22
|
+
### Project Structure
|
|
23
|
+
```
|
|
24
|
+
subgraph/
|
|
25
|
+
├── schema.graphql # Entity definitions
|
|
26
|
+
├── subgraph.yaml # Data source manifest
|
|
27
|
+
├── src/
|
|
28
|
+
│ ├── token.ts # Event handlers
|
|
29
|
+
│ └── utils.ts # Helper functions
|
|
30
|
+
├── tests/
|
|
31
|
+
│ └── token.test.ts # Matchstick unit tests
|
|
32
|
+
├── package.json
|
|
33
|
+
└── tsconfig.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Schema Definition
|
|
37
|
+
```graphql
|
|
38
|
+
# schema.graphql
|
|
39
|
+
type Token @entity {
|
|
40
|
+
id: Bytes!
|
|
41
|
+
name: String!
|
|
42
|
+
symbol: String!
|
|
43
|
+
totalSupply: BigInt!
|
|
44
|
+
holders: [TokenHolder!]! @derivedFrom(field: "token")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type TokenHolder @entity {
|
|
48
|
+
id: Bytes! # address
|
|
49
|
+
token: Token!
|
|
50
|
+
balance: BigInt!
|
|
51
|
+
transferCount: BigInt!
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type Transfer @entity(immutable: true) {
|
|
55
|
+
id: Bytes!
|
|
56
|
+
from: Bytes!
|
|
57
|
+
to: Bytes!
|
|
58
|
+
value: BigInt!
|
|
59
|
+
blockNumber: BigInt!
|
|
60
|
+
timestamp: BigInt!
|
|
61
|
+
transactionHash: Bytes!
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Event Handlers
|
|
66
|
+
```typescript
|
|
67
|
+
// src/token.ts
|
|
68
|
+
import { Transfer as TransferEvent } from '../generated/Token/Token'
|
|
69
|
+
import { Token, TokenHolder, Transfer } from '../generated/schema'
|
|
70
|
+
|
|
71
|
+
export function handleTransfer(event: TransferEvent): void {
|
|
72
|
+
// Create Transfer entity (immutable — never updated)
|
|
73
|
+
let transfer = new Transfer(event.transaction.hash.concatI32(event.logIndex.toI32()))
|
|
74
|
+
transfer.from = event.params.from
|
|
75
|
+
transfer.to = event.params.to
|
|
76
|
+
transfer.value = event.params.value
|
|
77
|
+
transfer.blockNumber = event.block.number
|
|
78
|
+
transfer.timestamp = event.block.timestamp
|
|
79
|
+
transfer.transactionHash = event.transaction.hash
|
|
80
|
+
transfer.save()
|
|
81
|
+
|
|
82
|
+
// Update holder balances
|
|
83
|
+
updateHolder(event.params.from, event.params.value.neg())
|
|
84
|
+
updateHolder(event.params.to, event.params.value)
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Deployment
|
|
89
|
+
```bash
|
|
90
|
+
# Build
|
|
91
|
+
graph codegen && graph build
|
|
92
|
+
|
|
93
|
+
# Deploy to Subgraph Studio
|
|
94
|
+
graph deploy --studio my-subgraph
|
|
95
|
+
|
|
96
|
+
# Deploy to hosted service
|
|
97
|
+
graph deploy --node https://api.thegraph.com/deploy/ my-org/my-subgraph
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Ponder (TypeScript Indexer)
|
|
103
|
+
|
|
104
|
+
### Configuration
|
|
105
|
+
```typescript
|
|
106
|
+
// ponder.config.ts
|
|
107
|
+
import { createConfig } from '@ponder/core'
|
|
108
|
+
import { http } from 'viem'
|
|
109
|
+
import { TokenAbi } from './abis/Token'
|
|
110
|
+
|
|
111
|
+
export default createConfig({
|
|
112
|
+
networks: {
|
|
113
|
+
mainnet: {
|
|
114
|
+
chainId: 1,
|
|
115
|
+
transport: http(process.env.PONDER_RPC_URL_1),
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
contracts: {
|
|
119
|
+
Token: {
|
|
120
|
+
network: 'mainnet',
|
|
121
|
+
abi: TokenAbi,
|
|
122
|
+
address: '0x...',
|
|
123
|
+
startBlock: 18_000_000,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Schema
|
|
130
|
+
```typescript
|
|
131
|
+
// ponder.schema.ts
|
|
132
|
+
import { createSchema } from '@ponder/core'
|
|
133
|
+
|
|
134
|
+
export default createSchema((p) => ({
|
|
135
|
+
Token: p.createTable({
|
|
136
|
+
id: p.hex(),
|
|
137
|
+
name: p.string(),
|
|
138
|
+
symbol: p.string(),
|
|
139
|
+
totalSupply: p.bigint(),
|
|
140
|
+
}),
|
|
141
|
+
Transfer: p.createTable({
|
|
142
|
+
id: p.string(),
|
|
143
|
+
from: p.hex(),
|
|
144
|
+
to: p.hex(),
|
|
145
|
+
value: p.bigint(),
|
|
146
|
+
timestamp: p.int(),
|
|
147
|
+
}),
|
|
148
|
+
}))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Event Handlers
|
|
152
|
+
```typescript
|
|
153
|
+
// src/Token.ts
|
|
154
|
+
import { ponder } from '@/generated'
|
|
155
|
+
|
|
156
|
+
ponder.on('Token:Transfer', async ({ event, context }) => {
|
|
157
|
+
const { from, to, value } = event.args
|
|
158
|
+
|
|
159
|
+
await context.db.Transfer.create({
|
|
160
|
+
id: `${event.transaction.hash}-${event.log.logIndex}`,
|
|
161
|
+
data: { from, to, value, timestamp: Number(event.block.timestamp) },
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Indexer Selection Guide
|
|
169
|
+
|
|
170
|
+
| Tool | Best For | Language | Hosting |
|
|
171
|
+
|------|----------|----------|---------|
|
|
172
|
+
| **The Graph** | Decentralized, production | AssemblyScript | Decentralized |
|
|
173
|
+
| **Ponder** | TypeScript-native, fast dev | TypeScript | Self-hosted |
|
|
174
|
+
| **Envio** | High-performance, HyperSync | TypeScript | Managed |
|
|
175
|
+
| **Goldsky** | Mirror + Subgraphs | GraphQL | Managed |
|
|
176
|
+
| **Subsquid** | Multi-chain, archive data | TypeScript | Managed |
|
|
177
|
+
| **Custom** | Full control | Any | Self-hosted |
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## GraphQL Query Patterns
|
|
182
|
+
|
|
183
|
+
```graphql
|
|
184
|
+
# Get recent transfers
|
|
185
|
+
query RecentTransfers {
|
|
186
|
+
transfers(
|
|
187
|
+
first: 10
|
|
188
|
+
orderBy: blockNumber
|
|
189
|
+
orderDirection: desc
|
|
190
|
+
where: { value_gt: "1000000000000000000" }
|
|
191
|
+
) {
|
|
192
|
+
from
|
|
193
|
+
to
|
|
194
|
+
value
|
|
195
|
+
blockNumber
|
|
196
|
+
transactionHash
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# Get top holders
|
|
201
|
+
query TopHolders($token: Bytes!) {
|
|
202
|
+
tokenHolders(
|
|
203
|
+
first: 100
|
|
204
|
+
orderBy: balance
|
|
205
|
+
orderDirection: desc
|
|
206
|
+
where: { token: $token }
|
|
207
|
+
) {
|
|
208
|
+
id
|
|
209
|
+
balance
|
|
210
|
+
transferCount
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Best Practices
|
|
218
|
+
|
|
219
|
+
- Use `immutable: true` for event entities (never need updates)
|
|
220
|
+
- Cache derived values instead of computing in queries
|
|
221
|
+
- Use `@derivedFrom` for reverse lookups
|
|
222
|
+
- Index only the data you need — less storage, faster sync
|
|
223
|
+
- Handle chain reorgs gracefully
|
|
224
|
+
- Test handlers with Matchstick or local tests
|
|
225
|
+
- Monitor indexing health and sync lag
|