A comprehensive backend service for managing airdrop eligibility data using Merkle Patricia Tries with PostgreSQL storage, NATS object storage, AWS KMS encryption, and Ethereum blockchain integration.
- Merkle Patricia Trie: Ethereum-compatible trie implementation for eligibility data
- Multi-round Support: Separate tries per round with versioning
- PostgreSQL Storage: Primary database backend with audit trails
- NATS Object Storage: Secondary storage for data availability and backup
- AWS KMS Encryption: Secure private key management with envelope encryption
- Smart Contract Integration: Ethereum contract interaction using Alloy 1.0.25
- Dual ABI Support: Both JSON ABI files and inline Solidity interfaces
- External Data Integration: Fetch and compare data with other backends
- Multi-format Support: JSON, CSV, hex, and base64 data formats
- RESTful API: Comprehensive HTTP API with proper error handling
- Rust 1.70+
- PostgreSQL 13+
- NATS Server with JetStream
- AWS KMS access
- Ethereum RPC endpoint
- Clone the repository:
git clone https://github.com/your-org/airdrop-backend.git
cd airdrop-backend- Install dependencies:
cargo build --release- Setup environment:
cp config.yaml.example config.yaml
# Edit config.yaml with your settings- Start services with Docker Compose:
docker-compose up -d postgres nats- Run the application:
cargo runThe service will be available at http://localhost:3000
The service uses a YAML configuration file (config.yaml):
server:
bind_address: "0.0.0.0:3000"
max_upload_size: 52428800 # 50MB
database:
url: "postgresql://username:password@localhost/airdrop_db"
max_connections: 10
blockchain:
rpc_url: "https://rpc.polygon-cdk-chain.example"
contract_address: "0x1234567890123456789012345678901234567890"
chain_id: 1
contract_interface:
type: "json_abi" # or "inline_sol"
abi_path: "abi/AirdropContract.json"
aws:
region: "us-east-1"
kms_key_id: "arn:aws:kms:us-east-1:123456789012:key/..."
wallet:
encrypted_private_key: "" # Auto-generated on first run
nats:
url: "nats://localhost:4222"
object_store:
bucket_name: "airdrop-data"
max_object_size: 104857600 # 100MBhttp://localhost:3000/api/v1
Currently, the API doesn't require authentication. In production, implement proper authentication mechanisms.
GET /healthResponse:
{
"status": "healthy",
"timestamp": "2024-01-15T10:30:00Z",
"service": "airdrop-backend"
}Example:
curl -X GET http://localhost:3000/healthPOST /api/v1/upload-csv
Content-Type: multipart/form-dataParameters:
round_id(form field): Round ID (integer)csv_file(file): CSV file with columns:address,amount
CSV Format:
address,amount
0x742C4d97C86bCF0176776C16e073b8c6f9Db4021,1000000000000000000
0x8ba1f109551bD432803012645Hac136c5a2B1A,500000000000000000Response:
{
"success": true,
"message": "CSV data processed for round 1",
"round_id": 1,
"data_size_bytes": 1024
}Example:
curl -X POST http://localhost:3000/api/v1/upload-csv \
-F "round_id=1" \
-F "csv_file=@eligibility_round_1.csv"GET /api/v1/download-csv/{round_id}Parameters:
round_id(path): Round ID
Response: CSV file download with proper headers
Example:
curl -X GET http://localhost:3000/api/v1/download-csv/1 \
-o round_1_eligibility.csvPOST /api/v1/upload-json-eligibility/{round_id}
Content-Type: application/jsonParameters:
round_id(path): Round ID
Request Body:
{
"eligibility": {
"0x742C4d97C86bCF0176776C16e073b8c6f9Db4021": "1000000000000000000",
"0x8ba1f109551bD432803012645Hac136c5a2B1A": "500000000000000000"
}
}Response:
{
"success": true,
"message": "JSON eligibility data processed for round 1",
"round_id": 1
}Example:
curl -X POST http://localhost:3000/api/v1/upload-json-eligibility/1 \
-H "Content-Type: application/json" \
-d '{
"eligibility": {
"0x742C4d97C86bCF0176776C16e073b8c6f9Db4021": "1000000000000000000",
"0x8ba1f109551bD432803012645Hac136c5a2B1A": "500000000000000000"
}
}'GET /api/v1/download-json-eligibility/{round_id}Parameters:
round_id(path): Round ID
Response:
{
"eligibility": {
"0x742C4d97C86bCF0176776C16e073b8c6f9Db4021": "1000000000000000000",
"0x8ba1f109551bD432803012645Hac136c5a2B1A": "500000000000000000"
}
}Example:
curl -X GET http://localhost:3000/api/v1/download-json-eligibility/1GET /api/v1/download-trie-data/{round_id}?format={json|hex|base64}Parameters:
round_id(path): Round IDformat(query, optional): Output format (json,hex,base64). Default:json
Response:
{
"round_id": 1,
"root_hash": "0x1234567890abcdef...",
"trie_data": "serialized_data_in_requested_format",
"format": "json",
"merkle_proofs": {
"0x742C4d97C86bCF0176776C16e073b8c6f9Db4021": [
"0xabcdef1234567890...",
"0x1234567890abcdef..."
]
}
}Examples:
# JSON format (default)
curl -X GET http://localhost:3000/api/v1/download-trie-data/1
# Hex format
curl -X GET http://localhost:3000/api/v1/download-trie-data/1?format=hex
# Base64 format
curl -X GET http://localhost:3000/api/v1/download-trie-data/1?format=base64POST /api/v1/upload-compare-trie/{round_id}
Content-Type: application/jsonParameters:
round_id(path): Round ID
Request Body:
{
"round_id": 1,
"root_hash": "0x1234567890abcdef...",
"trie_data": "serialized_data",
"format": "hex"
}Response:
{
"matches": true,
"local_root_hash": "0x1234567890abcdef...",
"external_root_hash": "0x1234567890abcdef...",
"differences": []
}Example:
curl -X POST http://localhost:3000/api/v1/upload-compare-trie/1 \
-H "Content-Type: application/json" \
-d '{
"round_id": 1,
"root_hash": "0x1234567890abcdef",
"trie_data": "0xabcdef123456789",
"format": "hex"
}'POST /api/v1/fetch-external-data/{round_id}
Content-Type: application/jsonParameters:
round_id(path): Round ID
Request Body:
{
"external_url": "https://external-backend.com/api/eligibility/round/1"
}Response:
{
"success": true,
"message": "Successfully updated round 1 with external data",
"round_id": 1,
"external_url": "https://external-backend.com/api/eligibility/round/1"
}Example:
curl -X POST http://localhost:3000/api/v1/fetch-external-data/1 \
-H "Content-Type: application/json" \
-d '{
"external_url": "https://external-backend.com/api/eligibility/round/1"
}'POST /api/v1/compare-external-trie/{round_id}
Content-Type: application/jsonParameters:
round_id(path): Round ID
Request Body:
{
"external_url": "https://external-backend.com/api/trie/round/1"
}Response:
{
"success": true,
"comparison_result": true,
"message": "Trie data matches",
"round_id": 1,
"external_url": "https://external-backend.com/api/trie/round/1"
}Example:
curl -X POST http://localhost:3000/api/v1/compare-external-trie/1 \
-H "Content-Type: application/json" \
-d '{
"external_url": "https://external-backend.com/api/trie/round/1"
}'POST /api/v1/update-trie/{round_id}Parameters:
round_id(path): Round ID
Response:
{
"success": true,
"message": "Trie for round 1 is up to date",
"round_id": 1,
"root_hash": "0x1234567890abcdef...",
"entry_count": 1000,
"last_updated": "2024-01-15T10:30:00Z"
}Example:
curl -X POST http://localhost:3000/api/v1/update-trie/1POST /api/v1/submit-trie/{round_id}Parameters:
round_id(path): Round ID
Response:
{
"success": true,
"message": "Trie update submitted for round 1",
"round_id": 1,
"transaction_hash": "0xabcdef1234567890..."
}Example:
curl -X POST http://localhost:3000/api/v1/submit-trie/1GET /api/v1/trie-info/{round_id}Parameters:
round_id(path): Round ID
Response:
{
"round_id": 1,
"root_hash": "0x1234567890abcdef...",
"entry_count": 1000,
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}Example:
curl -X GET http://localhost:3000/api/v1/trie-info/1POST /api/v1/verify-eligibility
Content-Type: application/jsonRequest Body:
{
"round_id": 1,
"address": "0x742C4d97C86bCF0176776C16e073b8c6f9Db4021",
"amount": "1000000000000000000"
}Response:
{
"is_eligible": true,
"round_id": 1,
"address": "0x742C4d97C86bCF0176776C16e073b8c6f9Db4021",
"amount": "1000000000000000000"
}Example:
curl -X POST http://localhost:3000/api/v1/verify-eligibility \
-H "Content-Type: application/json" \
-d '{
"round_id": 1,
"address": "0x742C4d97C86bCF0176776C16e073b8c6f9Db4021",
"amount": "1000000000000000000"
}'GET /api/v1/get-eligibility/{round_id}/{address}Parameters:
round_id(path): Round IDaddress(path): Ethereum address
Response:
{
"eligible": true,
"round_id": 1,
"address": "0x742C4d97C86bCF0176776C16e073b8c6f9Db4021",
"amount": "1000000000000000000"
}Example:
curl -X GET http://localhost:3000/api/v1/get-eligibility/1/0x742C4d97C86bCF0176776C16e073b8c6f9Db4021GET /api/v1/contract/infoResponse:
{
"contract_address": "0x1234567890123456789012345678901234567890",
"contract_version": "1.0.0",
"round_count": "5",
"interface_type": "json_abi"
}Example:
curl -X GET http://localhost:3000/api/v1/contract/infoGET /api/v1/rounds/{round_id}/activeParameters:
round_id(path): Round ID
Response:
{
"round_id": 1,
"is_active": true
}Example:
curl -X GET http://localhost:3000/api/v1/rounds/1/activeGET /api/v1/rounds/{round_id}/metadataParameters:
round_id(path): Round ID
Response:
{
"round_id": "1",
"root_hash": "0x1234567890abcdef...",
"total_eligible": "1000",
"total_amount": "1000000000000000000000",
"start_time": "1641024000",
"end_time": "1641110400",
"is_active": true,
"metadata_uri": "ipfs://QmExample..."
}Example:
curl -X GET http://localhost:3000/api/v1/rounds/1/metadataGET /api/v1/rounds/{round_id}/validate-consistencyParameters:
round_id(path): Round ID
Response:
{
"round_id": 1,
"is_consistent": true,
"message": "Local trie root matches on-chain root"
}Example:
curl -X GET http://localhost:3000/api/v1/rounds/1/validate-consistencyGET /api/v1/rounds/statisticsResponse:
[
{
"round_id": 1,
"entry_count": 1000,
"last_updated": "2024-01-15T10:30:00Z"
},
{
"round_id": 2,
"entry_count": 1500,
"last_updated": "2024-01-16T10:30:00Z"
}
]Example:
curl -X GET http://localhost:3000/api/v1/rounds/statisticsGET /api/v1/processing-logs?round_id={round_id}Parameters:
round_id(query, optional): Filter by round ID
Response:
[
{
"id": 1,
"round_id": 1,
"operation": "csv_processing",
"status": "completed",
"message": "Processed 1000 records with root hash: 0x123...",
"transaction_hash": null,
"created_at": "2024-01-15T10:30:00Z"
}
]Examples:
# All logs
curl -X GET http://localhost:3000/api/v1/processing-logs
# Logs for specific round
curl -X GET http://localhost:3000/api/v1/processing-logs?round_id=1GET /api/v1/processing-logs/{round_id}Parameters:
round_id(path): Round ID
Response: Same as processing logs but filtered by round
Example:
curl -X GET http://localhost:3000/api/v1/processing-logs/1DELETE /api/v1/rounds/{round_id}Parameters:
round_id(path): Round ID
Response:
{
"success": true,
"message": "Round 1 deleted successfully",
"round_id": 1
}Example:
curl -X DELETE http://localhost:3000/api/v1/rounds/1All endpoints return standard HTTP status codes and JSON error responses:
400 Bad Request:
{
"error": "Invalid input: address format is incorrect"
}404 Not Found:
{
"error": "No trie data found for round 1"
}500 Internal Server Error:
{
"error": "Internal server error"
}- JSON: Standard JSON format for structured data
- CSV: Comma-separated values with headers:
address,amount - Hex: Hexadecimal encoding (with or without
0xprefix) - Base64: Base64 encoding for binary data
All Ethereum addresses must be in hexadecimal format with 0x prefix:
0x742C4d97C86bCF0176776C16e073b8c6f9Db4021
All amounts are in Wei (smallest unit of Ether) as decimal strings:
"1000000000000000000" // 1 ETH in Wei
Eligibility Data Endpoint (GET):
{
"eligibility": {
"0x742C4d97C86bCF0176776C16e073b8c6f9Db4021": "1000000000000000000",
"0x8ba1f109551bD432803012645Hac136c5a2B1A": "500000000000000000"
}
}Trie Data Endpoint (GET):
{
"round_id": 1,
"root_hash": "0x1234567890abcdef",
"trie_data": "0xabcdef123456789",
"format": "hex"
}# Run all tests
cargo test
# Run integration tests
cargo test --test integration
# Run with coverage
cargo tarpaulin --out Htmldocker build -t airdrop-backend:latest .# Run migrations
make migrate
# Reset database
make dev-db-setupexport CONFIG_PATH=/app/config/production.yaml
export RUST_LOG=info
export RUST_BACKTRACE=1# Production deployment
docker-compose up -d
# With monitoring
docker-compose -f docker-compose.yml -f docker-compose.monitoring.yml up -dThe service includes built-in health checks accessible at /health endpoint.
- Private Key Management: Uses AWS KMS envelope encryption
- Input Validation: All inputs are validated and sanitized
- Rate Limiting: Implement rate limiting in production
- HTTPS: Use HTTPS in production environments
- Authentication: Implement proper authentication mechanisms
- Audit Logs: All operations are logged for audit purposes
For issues and questions:
- Check the GitHub Issues
- Review the logs:
docker-compose logs airdrop-backend - Check service health:
curl http://localhost:3000/health
This project is licensed under the Apache 2.0 License - see the LICENSE file for details.