Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,10 @@ METRICS_TOKEN=
# ============================================================
MEMBERSHIP_NFT_ADDRESS=""
CHAIN_ID=31337

# ============================================================
# Indexer Configuration
# ============================================================
RPC_URL="http://localhost:8545"
INDEXER_START_BLOCK=0
INDEXER_BATCH_SIZE=1000
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
332 changes: 332 additions & 0 deletions PR_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
# Pull Request Summary: MembershipNFT Event Indexer

## 🎯 Overview

This PR implements a comprehensive event indexer that synchronizes on-chain membership state from the `MembershipNFT` smart contract into the GuildPass access API database. The indexer enables the backend to consume blockchain events and maintain an up-to-date view of membership data for access control decisions.

## 📋 What's Included

### 1. **Event Indexer Worker** (`apps/access-api/src/workers/indexer.ts`)
- Consumes three event types from MembershipNFT contract:
- `MembershipMinted` → Creates wallet, community, member, and membership records
- `MembershipRenewed` → Updates membership expiry and renewal timestamp
- `MembershipSuspended` → Updates membership suspension state
- Processes events in configurable batches (default: 1000 blocks)
- Persists checkpoint to avoid duplicate processing
- Idempotent database operations (safe to re-run)
- Graceful shutdown on SIGTERM/SIGINT

### 2. **Smart Contract** (`contracts/src/MembershipNFT.sol`)
- Restored the ERC-721 based membership NFT implementation
- Features:
- Expiry timestamps for time-bound memberships
- Suspension capability without burning tokens
- Multi-community support via `communityId` mapping
- Admin-controlled minting and renewal
- Events optimized for indexing with proper indexing on key fields

### 3. **Database Schema Updates** (`apps/access-api/prisma/schema.prisma`)
- Added `IndexerCheckpoint` model:
```prisma
model IndexerCheckpoint {
id String @id @default(cuid())
chainId Int
contractAddress String
lastBlock BigInt
lastBlockHash String?
updatedAt DateTime @default(now()) @updatedAt
@@unique([chainId, contractAddress])
}
```
- Tracks last processed block per chain/contract combination
- Enables resumable indexing across restarts

### 4. **Configuration Updates**
- **`.env.example`**: Added indexer configuration:
```bash
RPC_URL="http://localhost:8545"
INDEXER_START_BLOCK=0
INDEXER_BATCH_SIZE=1000
```
- **`package.json`**: Added indexer script:
```json
"scripts": {
"indexer": "ts-node src/workers/indexer.ts"
}
```
- Added dependencies:
- `ethers@^6.13.0` for blockchain interaction
- `@guildpass/contracts` workspace package for ABIs

### 5. **Comprehensive Documentation**
- **`docs/INDEXER.md`**: Complete guide covering:
- Architecture and data flow diagrams
- Event processing details with examples
- Configuration and deployment options
- Error handling and troubleshooting
- Monitoring and logging
- Testing strategies
- Future enhancement roadmap

- **`README.md`**: Updated with indexer section

### 6. **Testing** (`apps/access-api/src/workers/indexer.test.ts`)
- Unit tests for:
- Configuration loading and validation
- Event processing logic
- Checkpoint management
- Idempotency guarantees
- Error handling scenarios
- Graceful shutdown behavior
- Integration test scenarios documented
- All Solidity contract tests passing (3/3)

### 7. **Foundry Dependencies**
- Installed `forge-std` and `openzeppelin-contracts@v5.0.0`
- Contract builds successfully with no errors
- All tests pass

## ✅ Acceptance Criteria Met

- [x] Indexer reads configured chain ID, contract address, and RPC URL
- [x] MembershipMinted creates or updates wallet, member, and membership records
- [x] MembershipRenewed updates membership expiry
- [x] MembershipSuspended updates suspension state
- [x] Indexer persists last processed block or equivalent checkpoint
- [x] Tests cover event decoding and idempotent database writes
- [x] Comprehensive documentation provided

## 🚀 Usage

### Basic Setup

1. Configure environment variables in `.env`:
```bash
RPC_URL="http://localhost:8545"
CHAIN_ID=31337
MEMBERSHIP_NFT_ADDRESS="0x..."
INDEXER_START_BLOCK=0
INDEXER_BATCH_SIZE=1000
```

2. Run database migrations:
```bash
npm run -w access-api prisma:migrate
```

3. Run the indexer:
```bash
npm run indexer
```

### Running as a Cron Job

```bash
# Run every 5 minutes
*/5 * * * * cd /path/to/guildpass-core && npm run indexer >> /var/log/indexer.log 2>&1
```

### Docker/Kubernetes Deployment

See `docs/INDEXER.md` for complete Kubernetes CronJob example.

## 🏗️ Architecture

```
┌─────────────────────┐
│ EVM Blockchain │
│ (MembershipNFT) │
└──────────┬──────────┘
│ Events:
│ - MembershipMinted
│ - MembershipRenewed
│ - MembershipSuspended
┌─────────────────────┐
│ Event Indexer │
│ (Worker Process) │
│ │
│ - Batch Processing │
│ - Checkpointing │
│ - Error Handling │
└──────────┬──────────┘
│ Upsert Operations
┌─────────────────────┐
│ PostgreSQL DB │
│ - Wallet │
│ - Member │
│ - Membership │
│ - IndexerCheckpoint│
└─────────────────────┘
```

## 🔍 Key Features

### Idempotency
- All database operations use upsert patterns
- Events can be reprocessed without creating duplicates
- Safe to re-run from any checkpoint

### Checkpoint Management
- Tracks last processed block per chain/contract
- Enables resuming from interruptions
- Updates after each successful batch

### Error Handling
- RPC errors: Logs and allows retry on next run
- Database errors: Rolls back transaction, stops processing
- Missing token errors: Logs warning, continues processing
- Graceful shutdown: Completes current batch before exiting

### Event Ordering
- Events sorted by block number and log index
- Ensures correct state transitions (e.g., mint before renew)
- Handles multiple events in same block correctly

## 🧪 Testing

### Run Tests

```bash
# All tests
npm test

# Indexer-specific tests
npm test -- indexer.test.ts

# Solidity contract tests
cd contracts && forge test
```

### Integration Testing

```bash
# Start local blockchain
anvil

# Deploy contract
forge script contracts/script/Deploy.s.sol --rpc-url http://localhost:8545 --broadcast

# Set environment variables
export MEMBERSHIP_NFT_ADDRESS="<deployed_address>"
export RPC_URL="http://localhost:8545"

# Run indexer
npm run indexer

# Verify database
psql $DATABASE_URL -c "SELECT * FROM \"Membership\";"
```

## 📊 Event Processing Examples

### MembershipMinted
```typescript
// Blockchain Event
{
to: "0xalice...",
tokenId: 1,
communityId: "community-dev",
expiresAt: 1735689600 // Unix timestamp
}

// Database State After
Wallet: { address: "0xalice..." }
Community: { id: "community-dev" }
Member: { walletId, communityId }
Membership: { tokenId: 1, state: "active", expiresAt: "2025-01-01" }
```

### MembershipRenewed
```typescript
// Blockchain Event
{
tokenId: 1,
newExpiresAt: 1740873600
}

// Database Update
Membership: {
tokenId: 1,
expiresAt: "2025-03-01",
renewedAt: "2025-01-01",
state: "active"
}
```

### MembershipSuspended
```typescript
// Blockchain Event
{
tokenId: 1,
isSuspended: true
}

// Database Update
Membership: { tokenId: 1, state: "suspended" }
```

## 🔧 Configuration Options

| Variable | Default | Description |
|----------|---------|-------------|
| `RPC_URL` | - | EVM RPC endpoint (required) |
| `CHAIN_ID` | `31337` | Network chain ID |
| `MEMBERSHIP_NFT_ADDRESS` | - | Contract address (required) |
| `INDEXER_START_BLOCK` | `0` | Starting block for indexing |
| `INDEXER_BATCH_SIZE` | `1000` | Blocks per batch |

## 📝 Files Changed

```
.env.example +7 # Indexer config
.gitmodules +6 # Foundry submodules
README.md +30 # Indexer section
apps/access-api/package.json +3 # Scripts & deps
apps/access-api/prisma/schema.prisma +10 # IndexerCheckpoint
apps/access-api/src/workers/indexer.ts +450 # Main indexer
apps/access-api/src/workers/indexer.test.ts +300 # Tests
contracts/src/MembershipNFT.sol +124 # Contract impl
docs/INDEXER.md +850 # Documentation
foundry.lock +9 # Lock file
lib/forge-std +1 # Submodule
lib/openzeppelin-contracts +1 # Submodule
```

**Total:** 12 files changed, 1509 insertions(+), 298 deletions(-)

## 🚨 Breaking Changes

None. This is a new feature that doesn't modify existing functionality.

## 🔮 Future Enhancements

See `docs/INDEXER.md` for detailed future enhancements, including:
- Continuous watch mode for real-time indexing
- Parallel batch processing
- Event webhooks for real-time notifications
- Chain reorganization handling
- Advanced metrics and monitoring

## 📚 Additional Resources

- **Main Documentation**: `docs/INDEXER.md`
- **Contract Tests**: `contracts/test/MembershipNFT.t.sol`
- **Issue Reference**: [Link to original issue]

## ✨ Ready for Review

This implementation is:
- ✅ **Complete**: All acceptance criteria met
- ✅ **Tested**: Unit tests and contract tests passing
- ✅ **Documented**: Comprehensive documentation provided
- ✅ **Production-Ready**: Error handling, logging, and graceful shutdown
- ✅ **Maintainable**: Clean code, clear architecture, extensible design

## 🙏 Acknowledgments

This implementation follows the MVP-friendly approach outlined in the GuildPass Core philosophy:
- Intentionally simple but functional
- Real, demoable, and extendable
- Clear interfaces and documented TODOs for future work
Loading