Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
366
docs/specs/api/api-gateway.md
Normal file
366
docs/specs/api/api-gateway.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# API Gateway Architecture Specification
|
||||
|
||||
## Overview
|
||||
|
||||
This document specifies the API Gateway architecture that provides unified edge API access, authentication, authorization, rate limiting, and request routing for all explorer platform services.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Clients
|
||||
Web[Web Clients]
|
||||
Mobile[Mobile Apps]
|
||||
API_Client[API Clients]
|
||||
end
|
||||
|
||||
subgraph Gateway[API Gateway]
|
||||
Router[Request Router]
|
||||
Auth[Authentication]
|
||||
RateLimit[Rate Limiter]
|
||||
Transform[Request Transformer]
|
||||
Cache[Response Cache]
|
||||
LB[Load Balancer]
|
||||
end
|
||||
|
||||
subgraph Services[Backend Services]
|
||||
Explorer[Explorer API]
|
||||
Search[Search Service]
|
||||
Analytics[Analytics Service]
|
||||
Actions[Action Service]
|
||||
Banking[Banking API]
|
||||
end
|
||||
|
||||
Clients --> Router
|
||||
Router --> Auth
|
||||
Auth --> RateLimit
|
||||
RateLimit --> Transform
|
||||
Transform --> Cache
|
||||
Cache --> LB
|
||||
LB --> Explorer
|
||||
LB --> Search
|
||||
LB --> Analytics
|
||||
LB --> Actions
|
||||
LB --> Banking
|
||||
```
|
||||
|
||||
## Gateway Components
|
||||
|
||||
### Request Router
|
||||
|
||||
**Purpose**: Route requests to appropriate backend services based on path, method, and headers.
|
||||
|
||||
**Routing Rules**:
|
||||
- `/api/v1/blocks/*` → Explorer API
|
||||
- `/api/v1/transactions/*` → Explorer API
|
||||
- `/api/v1/addresses/*` → Explorer API
|
||||
- `/api/v1/search/*` → Search Service
|
||||
- `/api/v1/analytics/*` → Analytics Service
|
||||
- `/api/v1/swap/*` → Action Service
|
||||
- `/api/v1/bridge/*` → Action Service
|
||||
- `/api/v1/banking/*` → Banking API
|
||||
- `/graphql` → Explorer API (GraphQL endpoint)
|
||||
|
||||
**Implementation**: NGINX, Kong, AWS API Gateway, or custom router
|
||||
|
||||
### Authentication
|
||||
|
||||
**Purpose**: Authenticate requests and extract user/API key information.
|
||||
|
||||
**Methods**:
|
||||
1. **API Key Authentication**:
|
||||
- Header: `X-API-Key: <key>`
|
||||
- Query parameter: `?api_key=<key>` (less secure)
|
||||
- Validate key hash against database
|
||||
- Extract user_id and tier from key
|
||||
|
||||
2. **OAuth 2.0**:
|
||||
- Bearer token: `Authorization: Bearer <token>`
|
||||
- Validate JWT token
|
||||
- Extract user claims
|
||||
|
||||
3. **Session Authentication**:
|
||||
- Cookie-based for web clients
|
||||
- Validate session token
|
||||
|
||||
**Anonymous Access**: Allow unauthenticated requests with lower rate limits
|
||||
|
||||
### Authorization
|
||||
|
||||
**Purpose**: Authorize requests based on user permissions and API key tier.
|
||||
|
||||
**Authorization Checks**:
|
||||
- API key tier (free, pro, enterprise)
|
||||
- User roles and permissions
|
||||
- Resource-level permissions (user's own data)
|
||||
- IP whitelist restrictions
|
||||
|
||||
**Enforcement**:
|
||||
- Block unauthorized requests (403 Forbidden)
|
||||
- Filter responses based on permissions
|
||||
- Log authorization failures
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
**Purpose**: Prevent abuse and ensure fair usage.
|
||||
|
||||
**Rate Limiting Strategy**:
|
||||
|
||||
**Tiers**:
|
||||
1. **Anonymous**: 10 req/s, 100 req/min
|
||||
2. **Free API Key**: 100 req/s, 1000 req/min
|
||||
3. **Pro API Key**: 500 req/s, 5000 req/min
|
||||
4. **Enterprise API Key**: 1000 req/s, unlimited
|
||||
|
||||
**Per-Endpoint Limits**:
|
||||
- Simple queries (GET): Higher limits
|
||||
- Complex queries (search, analytics): Lower limits
|
||||
- Write operations (POST): Strict limits
|
||||
|
||||
**Implementation**:
|
||||
- Token bucket algorithm
|
||||
- Redis for distributed rate limiting
|
||||
- Sliding window for smooth rate limiting
|
||||
|
||||
**Rate Limit Headers**:
|
||||
```
|
||||
X-RateLimit-Limit: 100
|
||||
X-RateLimit-Remaining: 95
|
||||
X-RateLimit-Reset: 1640995200
|
||||
```
|
||||
|
||||
**Response on Limit Exceeded**:
|
||||
```json
|
||||
{
|
||||
"error": "rate_limit_exceeded",
|
||||
"message": "Rate limit exceeded. Please try again later.",
|
||||
"retry_after": 60
|
||||
}
|
||||
```
|
||||
HTTP Status: 429 Too Many Requests
|
||||
|
||||
### Request Transformation
|
||||
|
||||
**Purpose**: Transform requests before forwarding to backend services.
|
||||
|
||||
**Transformations**:
|
||||
- Add user context headers (user_id, tier)
|
||||
- Normalize request format
|
||||
- Add tracing headers (request_id, trace_id)
|
||||
- Validate and sanitize input
|
||||
- Add default parameters
|
||||
|
||||
**Example Headers Added**:
|
||||
```
|
||||
X-User-ID: <uuid>
|
||||
X-API-Tier: pro
|
||||
X-Request-ID: <uuid>
|
||||
X-Trace-ID: <uuid>
|
||||
```
|
||||
|
||||
### Response Caching
|
||||
|
||||
**Purpose**: Cache responses to reduce backend load and improve latency.
|
||||
|
||||
**Cacheable Endpoints**:
|
||||
- Block data (cache for 10 seconds)
|
||||
- Token metadata (cache for 1 hour)
|
||||
- Contract ABIs (cache for 1 hour)
|
||||
- Analytics aggregates (cache for 5 minutes)
|
||||
|
||||
**Cache Keys**:
|
||||
- Include path, query parameters, API key tier
|
||||
- Exclude user-specific parameters
|
||||
|
||||
**Cache Headers**:
|
||||
- `Cache-Control: public, max-age=60`
|
||||
- `ETag` for conditional requests
|
||||
- `Vary: X-API-Key` to vary by tier
|
||||
|
||||
**Cache Invalidation**:
|
||||
- Time-based expiration
|
||||
- Manual invalidation on data updates
|
||||
- Event-driven invalidation
|
||||
|
||||
### Load Balancing
|
||||
|
||||
**Purpose**: Distribute requests across backend service instances.
|
||||
|
||||
**Strategies**:
|
||||
- Round-robin: Even distribution
|
||||
- Least connections: Send to least loaded instance
|
||||
- Health-aware: Skip unhealthy instances
|
||||
|
||||
**Health Checks**:
|
||||
- HTTP health check endpoint: `/health`
|
||||
- Check interval: 10 seconds
|
||||
- Unhealthy threshold: 3 consecutive failures
|
||||
- Recovery threshold: 2 successful checks
|
||||
|
||||
## Request/Response Flow
|
||||
|
||||
### Request Flow
|
||||
|
||||
1. **Client Request** → Gateway
|
||||
2. **Routing** → Determine target service
|
||||
3. **Authentication** → Validate credentials
|
||||
4. **Authorization** → Check permissions
|
||||
5. **Rate Limiting** → Check limits
|
||||
6. **Cache Check** → Return cached if available
|
||||
7. **Request Transformation** → Add headers, normalize
|
||||
8. **Load Balancing** → Select backend instance
|
||||
9. **Forward Request** → Send to backend service
|
||||
10. **Response Handling** → Transform, cache, return
|
||||
|
||||
### Response Flow
|
||||
|
||||
1. **Backend Response** → Gateway
|
||||
2. **Response Transformation** → Add headers, format
|
||||
3. **Cache Storage** → Store if cacheable
|
||||
4. **Error Handling** → Format errors consistently
|
||||
5. **Response to Client** → Return response
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Error Format
|
||||
|
||||
**Standard Error Response**:
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "rate_limit_exceeded",
|
||||
"message": "Rate limit exceeded. Please try again later.",
|
||||
"details": {},
|
||||
"request_id": "uuid",
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Codes
|
||||
|
||||
- `400 Bad Request`: Invalid request
|
||||
- `401 Unauthorized`: Authentication required
|
||||
- `403 Forbidden`: Authorization failed
|
||||
- `404 Not Found`: Resource not found
|
||||
- `429 Too Many Requests`: Rate limit exceeded
|
||||
- `500 Internal Server Error`: Server error
|
||||
- `502 Bad Gateway`: Backend service unavailable
|
||||
- `503 Service Unavailable`: Service temporarily unavailable
|
||||
- `504 Gateway Timeout`: Backend timeout
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Request Validation
|
||||
|
||||
- Validate request size (max 10MB)
|
||||
- Validate content-type
|
||||
- Sanitize input to prevent injection
|
||||
- Validate API key format
|
||||
|
||||
### DDoS Protection
|
||||
|
||||
- Rate limiting per IP
|
||||
- Connection limits
|
||||
- Request size limits
|
||||
- WAF integration (Cloudflare)
|
||||
|
||||
### TLS/SSL
|
||||
|
||||
- Require HTTPS for all requests
|
||||
- TLS 1.2+ only
|
||||
- Strong cipher suites
|
||||
- Certificate management
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
### Metrics
|
||||
|
||||
**Key Metrics**:
|
||||
- Request rate (requests/second)
|
||||
- Response time (p50, p95, p99)
|
||||
- Error rate (by error type)
|
||||
- Cache hit rate
|
||||
- Rate limit hits
|
||||
- Backend service health
|
||||
|
||||
### Logging
|
||||
|
||||
**Log Fields**:
|
||||
- Request ID
|
||||
- User ID / API Key
|
||||
- Method, path, query parameters
|
||||
- Response status, latency
|
||||
- Backend service called
|
||||
- Error messages
|
||||
|
||||
**Log Level**: INFO for normal requests, ERROR for failures
|
||||
|
||||
### Tracing
|
||||
|
||||
- Add trace ID to requests
|
||||
- Propagate trace ID to backend services
|
||||
- Correlate requests across services
|
||||
|
||||
## Configuration
|
||||
|
||||
### Gateway Configuration
|
||||
|
||||
```yaml
|
||||
gateway:
|
||||
port: 443
|
||||
health_check_path: /health
|
||||
|
||||
rate_limiting:
|
||||
redis_url: "redis://..."
|
||||
default_limit: 100
|
||||
default_period: 60
|
||||
|
||||
cache:
|
||||
redis_url: "redis://..."
|
||||
default_ttl: 60
|
||||
|
||||
services:
|
||||
explorer_api:
|
||||
base_url: "http://explorer-api:8080"
|
||||
health_check: "/health"
|
||||
|
||||
search_service:
|
||||
base_url: "http://search-service:8080"
|
||||
health_check: "/health"
|
||||
```
|
||||
|
||||
## Implementation Options
|
||||
|
||||
### Option 1: NGINX + Lua
|
||||
|
||||
- High performance
|
||||
- Custom logic with Lua
|
||||
- Good caching support
|
||||
|
||||
### Option 2: Kong
|
||||
|
||||
- API Gateway features out of the box
|
||||
- Plugin ecosystem
|
||||
- Good rate limiting
|
||||
|
||||
### Option 3: AWS API Gateway
|
||||
|
||||
- Managed service
|
||||
- Good integration with AWS services
|
||||
- Cost considerations
|
||||
|
||||
### Option 4: Custom Gateway (Go/Node.js)
|
||||
|
||||
- Full control
|
||||
- Custom features
|
||||
- More maintenance
|
||||
|
||||
**Recommendation**: Start with Kong or NGINX, migrate to custom if needed.
|
||||
|
||||
## References
|
||||
|
||||
- REST API: See `rest-api.md`
|
||||
- Authentication: See `../security/auth-spec.md`
|
||||
- Rate Limiting: See `../security/ddos-protection.md`
|
||||
|
||||
322
docs/specs/api/etherscan-compatible-api.md
Normal file
322
docs/specs/api/etherscan-compatible-api.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# Etherscan-Compatible API Specification
|
||||
|
||||
## Overview
|
||||
|
||||
This document specifies the Etherscan-compatible API layer that provides an API surface matching Blockscout/Etherscan APIs for tool compatibility and easier migration.
|
||||
|
||||
**Base URL**: `https://api.explorer.d-bis.org/api`
|
||||
|
||||
**Note**: This is a compatibility layer that translates Etherscan API calls to our internal API.
|
||||
|
||||
## API Surface Mapping
|
||||
|
||||
### Account Endpoints
|
||||
|
||||
#### Get Account Balance
|
||||
|
||||
**Etherscan**: `GET /api?module=account&action=balance&address={address}&tag=latest`
|
||||
|
||||
**Our Implementation**:
|
||||
- Maps to: `GET /v1/addresses/{chain_id}/{address}` (extract balance)
|
||||
|
||||
**Response Format** (matches Etherscan):
|
||||
```json
|
||||
{
|
||||
"status": "1",
|
||||
"message": "OK",
|
||||
"result": "1000000000000000000"
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Token Balance
|
||||
|
||||
**Etherscan**: `GET /api?module=account&action=tokenbalance&contractaddress={token}&address={address}&tag=latest`
|
||||
|
||||
**Our Implementation**:
|
||||
- Maps to: `GET /v1/addresses/{chain_id}/{address}/tokens` (filter by token address)
|
||||
|
||||
**Response Format**:
|
||||
```json
|
||||
{
|
||||
"status": "1",
|
||||
"message": "OK",
|
||||
"result": "1000000000000000000"
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Transaction List
|
||||
|
||||
**Etherscan**: `GET /api?module=account&action=txlist&address={address}&startblock=0&endblock=99999999&page=1&offset=10&sort=desc`
|
||||
|
||||
**Our Implementation**:
|
||||
- Maps to: `GET /v1/addresses/{chain_id}/{address}/transactions` (with pagination)
|
||||
|
||||
**Response Format**:
|
||||
```json
|
||||
{
|
||||
"status": "1",
|
||||
"message": "OK",
|
||||
"result": [
|
||||
{
|
||||
"blockNumber": "12345",
|
||||
"timeStamp": "1640995200",
|
||||
"hash": "0x...",
|
||||
"from": "0x...",
|
||||
"to": "0x...",
|
||||
"value": "1000000000000000000",
|
||||
"gas": "21000",
|
||||
"gasPrice": "20000000000",
|
||||
"gasUsed": "21000",
|
||||
"isError": "0",
|
||||
"txreceipt_status": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Transaction Endpoints
|
||||
|
||||
#### Get Transaction Status
|
||||
|
||||
**Etherscan**: `GET /api?module=transaction&action=getstatus&txhash={hash}`
|
||||
|
||||
**Our Implementation**:
|
||||
- Maps to: `GET /v1/transactions/{chain_id}/{hash}` (extract status)
|
||||
|
||||
**Response Format**:
|
||||
```json
|
||||
{
|
||||
"status": "1",
|
||||
"message": "OK",
|
||||
"result": {
|
||||
"isError": "0",
|
||||
"errDescription": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Transaction Receipt Status
|
||||
|
||||
**Etherscan**: `GET /api?module=transaction&action=gettxreceiptstatus&txhash={hash}`
|
||||
|
||||
**Response Format**:
|
||||
```json
|
||||
{
|
||||
"status": "1",
|
||||
"message": "OK",
|
||||
"result": {
|
||||
"status": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Block Endpoints
|
||||
|
||||
#### Get Block by Number
|
||||
|
||||
**Etherscan**: `GET /api?module=proxy&action=eth_getBlockByNumber&tag={number}&boolean=true`
|
||||
|
||||
**Our Implementation**:
|
||||
- Maps to: `GET /v1/blocks/{chain_id}/{number}` (transform format)
|
||||
|
||||
**Response Format** (Ethereum JSON-RPC format):
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"number": "0x3039",
|
||||
"hash": "0x...",
|
||||
"transactions": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Contract Endpoints
|
||||
|
||||
#### Get Contract ABI
|
||||
|
||||
**Etherscan**: `GET /api?module=contract&action=getabi&address={address}`
|
||||
|
||||
**Our Implementation**:
|
||||
- Maps to: `GET /v1/contracts/{chain_id}/{address}/abi`
|
||||
|
||||
**Response Format**:
|
||||
```json
|
||||
{
|
||||
"status": "1",
|
||||
"message": "OK",
|
||||
"result": "[{...}]"
|
||||
}
|
||||
```
|
||||
|
||||
#### Verify Contract
|
||||
|
||||
**Etherscan**: `POST /api?module=contract&action=verifysourcecode`
|
||||
|
||||
**Our Implementation**:
|
||||
- Maps to: `POST /v1/contracts/{chain_id}/{address}/verify`
|
||||
|
||||
**Parameter Mapping**:
|
||||
- `sourceCode` → `source_code`
|
||||
- `codeformat` → `verification_method`
|
||||
- `contractaddress` → `address`
|
||||
- `compilerversion` → `compiler_version`
|
||||
|
||||
### Token Endpoints
|
||||
|
||||
#### Get Token Total Supply
|
||||
|
||||
**Etherscan**: `GET /api?module=stats&action=tokensupply&contractaddress={address}`
|
||||
|
||||
**Our Implementation**:
|
||||
- Maps to: `GET /v1/tokens/{chain_id}/{address}` (extract total_supply)
|
||||
|
||||
### Event Logs
|
||||
|
||||
#### Get Event Logs
|
||||
|
||||
**Etherscan**: `GET /api?module=logs&action=getLogs&address={address}&topic0={topic0}&fromBlock={fromBlock}&toBlock={toBlock}`
|
||||
|
||||
**Our Implementation**:
|
||||
- Maps to: `GET /v1/logs` (with filters)
|
||||
|
||||
**Response Format**:
|
||||
```json
|
||||
{
|
||||
"status": "1",
|
||||
"message": "OK",
|
||||
"result": [
|
||||
{
|
||||
"address": "0x...",
|
||||
"topics": ["0x..."],
|
||||
"data": "0x...",
|
||||
"blockNumber": "0x3039",
|
||||
"transactionHash": "0x...",
|
||||
"logIndex": "0x0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Parameter Translation
|
||||
|
||||
### Common Parameters
|
||||
|
||||
| Etherscan | Our API | Notes |
|
||||
|-----------|---------|-------|
|
||||
| `module` | N/A | Determines endpoint category |
|
||||
| `action` | N/A | Determines specific action |
|
||||
| `address` | `address` | Same |
|
||||
| `tag` | `block_number` | Convert "latest" to current block |
|
||||
| `startblock` | `from_block` | Same |
|
||||
| `endblock` | `to_block` | Same |
|
||||
| `page` | `page` | Same |
|
||||
| `offset` | `page_size` | Same |
|
||||
| `sort` | `order` | Same (asc/desc) |
|
||||
|
||||
### Chain ID Handling
|
||||
|
||||
**Etherscan**: No chain_id parameter (single chain)
|
||||
|
||||
**Our API**: Require `chain_id` parameter or use default from context
|
||||
|
||||
**Mapping**: Add `chain_id` parameter to all requests
|
||||
|
||||
## Response Format Compatibility
|
||||
|
||||
### Status Codes
|
||||
|
||||
**Etherscan Format**:
|
||||
- `status: "1"` = Success
|
||||
- `status: "0"` = Error
|
||||
|
||||
**Our Format**:
|
||||
- HTTP 200 = Success (map to status "1")
|
||||
- HTTP 4xx/5xx = Error (map to status "0")
|
||||
|
||||
### Error Messages
|
||||
|
||||
**Etherscan Format**:
|
||||
```json
|
||||
{
|
||||
"status": "0",
|
||||
"message": "NOTOK",
|
||||
"result": "Error message here"
|
||||
}
|
||||
```
|
||||
|
||||
**Our Mapping**:
|
||||
- Map our error codes to Etherscan error messages
|
||||
- Preserve error details in result field
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### For Tools Using Etherscan API
|
||||
|
||||
**Step 1**: Update base URL
|
||||
- Old: `https://api.etherscan.io/api`
|
||||
- New: `https://api.explorer.d-bis.org/api`
|
||||
|
||||
**Step 2**: Add chain_id parameter (if needed)
|
||||
- Add `&chainid=138` to requests
|
||||
- Or use default chain from connection
|
||||
|
||||
**Step 3**: Test compatibility
|
||||
- Verify response formats match
|
||||
- Check error handling
|
||||
- Validate data accuracy
|
||||
|
||||
### For New Integrations
|
||||
|
||||
**Recommendation**: Use native REST API instead of Etherscan-compatible API
|
||||
- More features
|
||||
- Better performance
|
||||
- Clearer API design
|
||||
|
||||
## Limitations
|
||||
|
||||
### Not All Endpoints Supported
|
||||
|
||||
**Supported**:
|
||||
- Account operations
|
||||
- Transaction queries
|
||||
- Block queries
|
||||
- Contract verification
|
||||
- Token operations
|
||||
- Event logs
|
||||
|
||||
**Not Supported** (Etherscan-specific):
|
||||
- Gas tracker (use our analytics API)
|
||||
- Historical stats (use our analytics API)
|
||||
- Token list (use our token API)
|
||||
|
||||
### Response Format Differences
|
||||
|
||||
**Some differences may exist**:
|
||||
- Field naming (camelCase vs snake_case)
|
||||
- Data precision
|
||||
- Additional fields in our responses
|
||||
|
||||
## Implementation
|
||||
|
||||
### Translation Layer
|
||||
|
||||
**Architecture**:
|
||||
- Request interceptor: Parse Etherscan-style requests
|
||||
- Parameter mapper: Convert to our API format
|
||||
- Response transformer: Convert our responses to Etherscan format
|
||||
|
||||
**Code Structure**:
|
||||
```
|
||||
compatibility/
|
||||
├── etherscan/
|
||||
│ ├── request_parser.py # Parse Etherscan requests
|
||||
│ ├── parameter_mapper.py # Map parameters
|
||||
│ └── response_transformer.py # Transform responses
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- REST API: See `rest-api.md`
|
||||
- API Gateway: See `api-gateway.md`
|
||||
|
||||
351
docs/specs/api/graphql-api.md
Normal file
351
docs/specs/api/graphql-api.md
Normal file
@@ -0,0 +1,351 @@
|
||||
# GraphQL API Specification
|
||||
|
||||
## Overview
|
||||
|
||||
This document specifies the GraphQL API for the explorer platform, providing flexible queries, mutations for user data, and subscriptions for real-time updates.
|
||||
|
||||
**Endpoint**: `https://api.explorer.d-bis.org/graphql`
|
||||
|
||||
## Schema Overview
|
||||
|
||||
### Core Types
|
||||
|
||||
#### Block
|
||||
|
||||
```graphql
|
||||
type Block {
|
||||
chainId: Int!
|
||||
number: BigInt!
|
||||
hash: String!
|
||||
parentHash: String!
|
||||
timestamp: DateTime!
|
||||
miner: Address
|
||||
transactionCount: Int!
|
||||
gasUsed: BigInt!
|
||||
gasLimit: BigInt!
|
||||
transactions: [Transaction!]!
|
||||
}
|
||||
```
|
||||
|
||||
#### Transaction
|
||||
|
||||
```graphql
|
||||
type Transaction {
|
||||
chainId: Int!
|
||||
hash: String!
|
||||
block: Block
|
||||
blockNumber: BigInt
|
||||
from: Address!
|
||||
to: Address
|
||||
value: BigInt!
|
||||
gasPrice: BigInt
|
||||
gasUsed: BigInt
|
||||
status: TransactionStatus!
|
||||
logs: [Log!]!
|
||||
traces: [Trace!]!
|
||||
}
|
||||
```
|
||||
|
||||
#### Address
|
||||
|
||||
```graphql
|
||||
type Address {
|
||||
address: String!
|
||||
chainId: Int!
|
||||
balance: BigInt!
|
||||
balanceUSD: Float
|
||||
transactionCount: Int!
|
||||
transactions: TransactionConnection!
|
||||
tokens: [TokenBalance!]!
|
||||
nfts: [NFT!]!
|
||||
label: String
|
||||
tags: [String!]!
|
||||
isContract: Boolean!
|
||||
contract: Contract
|
||||
}
|
||||
```
|
||||
|
||||
#### Token
|
||||
|
||||
```graphql
|
||||
type Token {
|
||||
address: String!
|
||||
chainId: Int!
|
||||
name: String
|
||||
symbol: String
|
||||
type: TokenType!
|
||||
decimals: Int
|
||||
totalSupply: BigInt
|
||||
holderCount: Int!
|
||||
transfers: TokenTransferConnection!
|
||||
holders: HolderConnection!
|
||||
}
|
||||
```
|
||||
|
||||
#### Contract
|
||||
|
||||
```graphql
|
||||
type Contract {
|
||||
address: String!
|
||||
chainId: Int!
|
||||
name: String
|
||||
verificationStatus: VerificationStatus!
|
||||
sourceCode: String
|
||||
abi: JSON
|
||||
compilerVersion: String
|
||||
createdAt: DateTime
|
||||
}
|
||||
```
|
||||
|
||||
## Queries
|
||||
|
||||
### Block Queries
|
||||
|
||||
```graphql
|
||||
query GetBlock($chainId: Int!, $number: BigInt!) {
|
||||
block(chainId: $chainId, number: $number) {
|
||||
number
|
||||
hash
|
||||
timestamp
|
||||
transactionCount
|
||||
transactions {
|
||||
hash
|
||||
from
|
||||
to
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Transaction Queries
|
||||
|
||||
```graphql
|
||||
query GetTransaction($chainId: Int!, $hash: String!) {
|
||||
transaction(chainId: $chainId, hash: $hash) {
|
||||
hash
|
||||
from
|
||||
to
|
||||
value
|
||||
status
|
||||
logs {
|
||||
address
|
||||
topics
|
||||
data
|
||||
}
|
||||
traces {
|
||||
type
|
||||
from
|
||||
to
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Address Queries
|
||||
|
||||
```graphql
|
||||
query GetAddress($chainId: Int!, $address: String!) {
|
||||
address(chainId: $chainId, address: $address) {
|
||||
address
|
||||
balance
|
||||
balanceUSD
|
||||
transactionCount
|
||||
transactions(first: 10) {
|
||||
edges {
|
||||
node {
|
||||
hash
|
||||
blockNumber
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens {
|
||||
token {
|
||||
symbol
|
||||
name
|
||||
}
|
||||
balance
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Search Query
|
||||
|
||||
```graphql
|
||||
query Search($query: String!, $chainId: Int) {
|
||||
search(query: $query, chainId: $chainId) {
|
||||
... on Block {
|
||||
number
|
||||
hash
|
||||
}
|
||||
... on Transaction {
|
||||
hash
|
||||
from
|
||||
to
|
||||
}
|
||||
... on Address {
|
||||
address
|
||||
label
|
||||
}
|
||||
... on Token {
|
||||
address
|
||||
symbol
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mutations
|
||||
|
||||
### User Preferences
|
||||
|
||||
```graphql
|
||||
mutation UpdateWatchlist($chainId: Int!, $address: String!, $label: String) {
|
||||
addToWatchlist(chainId: $chainId, address: $address, label: $label) {
|
||||
success
|
||||
watchlistItem {
|
||||
address
|
||||
label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation RemoveFromWatchlist($chainId: Int!, $address: String!) {
|
||||
removeFromWatchlist(chainId: $chainId, address: $address) {
|
||||
success
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Address Labels
|
||||
|
||||
```graphql
|
||||
mutation AddAddressLabel($chainId: Int!, $address: String!, $label: String!) {
|
||||
addAddressLabel(chainId: $chainId, address: $address, label: $label) {
|
||||
success
|
||||
label {
|
||||
address
|
||||
label
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Subscriptions
|
||||
|
||||
### New Blocks
|
||||
|
||||
```graphql
|
||||
subscription NewBlocks($chainId: Int!) {
|
||||
newBlock(chainId: $chainId) {
|
||||
number
|
||||
hash
|
||||
timestamp
|
||||
transactionCount
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Address Transactions
|
||||
|
||||
```graphql
|
||||
subscription AddressTransactions($chainId: Int!, $address: String!) {
|
||||
addressTransaction(chainId: $chainId, address: $address) {
|
||||
hash
|
||||
from
|
||||
to
|
||||
value
|
||||
status
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pending Transactions
|
||||
|
||||
```graphql
|
||||
subscription PendingTransactions($chainId: Int!) {
|
||||
pendingTransaction(chainId: $chainId) {
|
||||
hash
|
||||
from
|
||||
to
|
||||
value
|
||||
gasPrice
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resolver Architecture
|
||||
|
||||
### DataLoader Pattern
|
||||
|
||||
**Purpose**: Prevent N+1 query problems.
|
||||
|
||||
**Implementation**:
|
||||
- Batch load related entities
|
||||
- Cache within request context
|
||||
- Example: Batch load all blocks for transactions in a query
|
||||
|
||||
### Field Resolvers
|
||||
|
||||
**Strategy**: Lazy loading of related fields.
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
Block: {
|
||||
transactions: (block, args, context) => {
|
||||
return context.loaders.transactions.load({
|
||||
chainId: block.chainId,
|
||||
blockNumber: block.number
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Query Complexity
|
||||
|
||||
**Limits**:
|
||||
- Max depth: 10 levels
|
||||
- Max complexity score: 1000
|
||||
- Rate limit based on complexity
|
||||
|
||||
### Caching
|
||||
|
||||
**Strategy**:
|
||||
- Cache frequently accessed fields (blocks, tokens)
|
||||
- Cache duration: 1-60 seconds depending on data freshness needs
|
||||
- Invalidate on updates
|
||||
|
||||
## Authentication
|
||||
|
||||
Same as REST API: API Key via header or OAuth Bearer token.
|
||||
|
||||
## Error Handling
|
||||
|
||||
**GraphQL Error Format**:
|
||||
```json
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "Resource not found",
|
||||
"extensions": {
|
||||
"code": "NOT_FOUND",
|
||||
"requestId": "uuid"
|
||||
},
|
||||
"path": ["block"]
|
||||
}
|
||||
],
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- REST API: See `rest-api.md`
|
||||
- API Gateway: See `api-gateway.md`
|
||||
|
||||
399
docs/specs/api/rest-api.md
Normal file
399
docs/specs/api/rest-api.md
Normal file
@@ -0,0 +1,399 @@
|
||||
# REST API Specification
|
||||
|
||||
## Overview
|
||||
|
||||
This document specifies the REST API for the explorer platform. The API follows RESTful principles and provides comprehensive access to blockchain data, search, and analytics.
|
||||
|
||||
**Base URL**: `https://api.explorer.d-bis.org/v1`
|
||||
|
||||
**Authentication**: API Key via `X-API-Key` header (optional for read-only endpoints with rate limits)
|
||||
|
||||
## API Design Principles
|
||||
|
||||
1. **RESTful**: Use HTTP methods appropriately (GET, POST, etc.)
|
||||
2. **Consistent**: Uniform response format
|
||||
3. **Versioned**: `/v1` in path, version in response headers
|
||||
4. **Paginated**: All list endpoints support pagination
|
||||
5. **Filtered**: Support filtering and sorting
|
||||
6. **Documented**: OpenAPI 3.0 specification
|
||||
|
||||
## Response Format
|
||||
|
||||
### Success Response
|
||||
|
||||
```json
|
||||
{
|
||||
"data": { ... },
|
||||
"meta": {
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"total": 100,
|
||||
"total_pages": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "not_found",
|
||||
"message": "Resource not found",
|
||||
"details": {},
|
||||
"request_id": "uuid"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Blocks
|
||||
|
||||
#### List Blocks
|
||||
|
||||
`GET /blocks`
|
||||
|
||||
**Query Parameters**:
|
||||
- `chain_id` (integer, required): Chain ID
|
||||
- `page` (integer, default: 1): Page number
|
||||
- `page_size` (integer, default: 20, max: 100): Items per page
|
||||
- `min_block` (integer): Minimum block number
|
||||
- `max_block` (integer): Maximum block number
|
||||
- `miner` (string): Filter by miner address
|
||||
- `sort` (string, default: "number"): Sort field (number, timestamp, gas_used)
|
||||
- `order` (string, default: "desc"): Sort order (asc, desc)
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"chain_id": 138,
|
||||
"number": 12345,
|
||||
"hash": "0x...",
|
||||
"timestamp": "2024-01-01T00:00:00Z",
|
||||
"miner": "0x...",
|
||||
"transaction_count": 100,
|
||||
"gas_used": 15000000,
|
||||
"gas_limit": 20000000
|
||||
}
|
||||
],
|
||||
"meta": { "pagination": {...} }
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Block by Number
|
||||
|
||||
`GET /blocks/{chain_id}/{number}`
|
||||
|
||||
**Response**: Single block object
|
||||
|
||||
#### Get Block by Hash
|
||||
|
||||
`GET /blocks/{chain_id}/hash/{hash}`
|
||||
|
||||
**Response**: Single block object
|
||||
|
||||
### Transactions
|
||||
|
||||
#### List Transactions
|
||||
|
||||
`GET /transactions`
|
||||
|
||||
**Query Parameters**:
|
||||
- `chain_id` (integer, required)
|
||||
- `block_number` (integer): Filter by block
|
||||
- `from_address` (string): Filter by sender
|
||||
- `to_address` (string): Filter by recipient
|
||||
- `status` (string): Filter by status (success, failed)
|
||||
- `min_value` (string): Minimum value
|
||||
- `page`, `page_size`, `sort`, `order`
|
||||
|
||||
**Response**: Array of transaction objects
|
||||
|
||||
#### Get Transaction by Hash
|
||||
|
||||
`GET /transactions/{chain_id}/{hash}`
|
||||
|
||||
**Response**: Single transaction object with full details
|
||||
|
||||
**Includes**:
|
||||
- Transaction data
|
||||
- Receipt data
|
||||
- Logs
|
||||
- Traces (if available)
|
||||
|
||||
#### Get Transaction Receipt
|
||||
|
||||
`GET /transactions/{chain_id}/{hash}/receipt`
|
||||
|
||||
**Response**: Transaction receipt object
|
||||
|
||||
### Addresses
|
||||
|
||||
#### Get Address Info
|
||||
|
||||
`GET /addresses/{chain_id}/{address}`
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"address": "0x...",
|
||||
"chain_id": 138,
|
||||
"balance": "1000000000000000000",
|
||||
"balance_usd": "3000.00",
|
||||
"transaction_count": 500,
|
||||
"token_count": 10,
|
||||
"label": "My Wallet",
|
||||
"tags": ["wallet"],
|
||||
"is_contract": false
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Address Transactions
|
||||
|
||||
`GET /addresses/{chain_id}/{address}/transactions`
|
||||
|
||||
**Query Parameters**: Standard pagination and filtering
|
||||
|
||||
**Response**: Array of transactions
|
||||
|
||||
#### Get Address Token Holdings
|
||||
|
||||
`GET /addresses/{chain_id}/{address}/tokens`
|
||||
|
||||
**Query Parameters**:
|
||||
- `type` (string): Filter by token type (ERC20, ERC721, ERC1155)
|
||||
- `page`, `page_size`
|
||||
|
||||
**Response**: Array of token holdings with balances
|
||||
|
||||
#### Get Address NFTs
|
||||
|
||||
`GET /addresses/{chain_id}/{address}/nfts`
|
||||
|
||||
**Response**: Array of NFT holdings (ERC-721/1155)
|
||||
|
||||
### Tokens
|
||||
|
||||
#### List Tokens
|
||||
|
||||
`GET /tokens`
|
||||
|
||||
**Query Parameters**:
|
||||
- `chain_id` (integer, required)
|
||||
- `type` (string): Filter by type
|
||||
- `verified` (boolean): Filter verified tokens
|
||||
- `search` (string): Search by name or symbol
|
||||
- `page`, `page_size`, `sort`, `order`
|
||||
|
||||
**Response**: Array of token objects
|
||||
|
||||
#### Get Token Info
|
||||
|
||||
`GET /tokens/{chain_id}/{address}`
|
||||
|
||||
**Response**: Token details including metadata
|
||||
|
||||
#### Get Token Holders
|
||||
|
||||
`GET /tokens/{chain_id}/{address}/holders`
|
||||
|
||||
**Query Parameters**: Pagination
|
||||
|
||||
**Response**: Array of holder addresses with balances
|
||||
|
||||
#### Get Token Transfers
|
||||
|
||||
`GET /tokens/{chain_id}/{address}/transfers`
|
||||
|
||||
**Query Parameters**: Pagination, `from_address`, `to_address`
|
||||
|
||||
**Response**: Array of token transfers
|
||||
|
||||
### Contracts
|
||||
|
||||
#### Get Contract Info
|
||||
|
||||
`GET /contracts/{chain_id}/{address}`
|
||||
|
||||
**Response**: Contract details including verification status, source code, ABI
|
||||
|
||||
#### Verify Contract
|
||||
|
||||
`POST /contracts/{chain_id}/{address}/verify`
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"compiler_version": "0.8.19",
|
||||
"optimization_enabled": true,
|
||||
"optimization_runs": 200,
|
||||
"source_code": "...",
|
||||
"constructor_arguments": "0x...",
|
||||
"verification_method": "standard_json"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**: Verification status
|
||||
|
||||
#### Get Contract Source Code
|
||||
|
||||
`GET /contracts/{chain_id}/{address}/source`
|
||||
|
||||
**Response**: Source code and metadata
|
||||
|
||||
#### Get Contract ABI
|
||||
|
||||
`GET /contracts/{chain_id}/{address}/abi`
|
||||
|
||||
**Response**: Contract ABI JSON
|
||||
|
||||
### Logs
|
||||
|
||||
#### Get Logs
|
||||
|
||||
`GET /logs`
|
||||
|
||||
**Query Parameters**:
|
||||
- `chain_id` (integer, required)
|
||||
- `address` (string): Filter by contract address
|
||||
- `topic0` (string): Filter by event signature
|
||||
- `from_block` (integer): Start block
|
||||
- `to_block` (integer): End block
|
||||
- `page`, `page_size`
|
||||
|
||||
**Response**: Array of log objects
|
||||
|
||||
### Traces
|
||||
|
||||
#### Get Transaction Traces
|
||||
|
||||
`GET /transactions/{chain_id}/{hash}/traces`
|
||||
|
||||
**Response**: Array of trace objects
|
||||
|
||||
#### Get Block Traces
|
||||
|
||||
`GET /blocks/{chain_id}/{number}/traces`
|
||||
|
||||
**Response**: Array of trace objects for all transactions in block
|
||||
|
||||
### Search
|
||||
|
||||
#### Unified Search
|
||||
|
||||
`GET /search`
|
||||
|
||||
**Query Parameters**:
|
||||
- `chain_id` (integer, optional): Filter by chain
|
||||
- `q` (string, required): Search query
|
||||
- `type` (string): Filter by type (block, transaction, address, token, contract)
|
||||
- `page`, `page_size`
|
||||
|
||||
**Response**: Mixed array of results with type indicators
|
||||
|
||||
### Analytics
|
||||
|
||||
#### Network Stats
|
||||
|
||||
`GET /analytics/{chain_id}/network/stats`
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"current_block": 12345,
|
||||
"tps": 15.5,
|
||||
"gps": 30000000,
|
||||
"avg_gas_price": 20000000000,
|
||||
"pending_transactions": 50
|
||||
}
|
||||
```
|
||||
|
||||
#### Gas Price Statistics
|
||||
|
||||
`GET /analytics/{chain_id}/gas/price`
|
||||
|
||||
**Query Parameters**:
|
||||
- `period` (string): Time period (1h, 24h, 7d, 30d)
|
||||
|
||||
**Response**: Gas price statistics (min, max, avg, percentiles)
|
||||
|
||||
#### Top Contracts
|
||||
|
||||
`GET /analytics/{chain_id}/contracts/top`
|
||||
|
||||
**Query Parameters**:
|
||||
- `by` (string): Sort by (transactions, gas_used, value)
|
||||
- `limit` (integer, default: 100)
|
||||
|
||||
**Response**: Array of top contracts
|
||||
|
||||
## Pagination
|
||||
|
||||
All list endpoints support pagination:
|
||||
|
||||
**Query Parameters**:
|
||||
- `page` (integer, default: 1): Page number (1-indexed)
|
||||
- `page_size` (integer, default: 20, max: 100): Items per page
|
||||
|
||||
**Response Metadata**:
|
||||
```json
|
||||
{
|
||||
"meta": {
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"total": 500,
|
||||
"total_pages": 25,
|
||||
"has_next": true,
|
||||
"has_prev": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Filtering and Sorting
|
||||
|
||||
**Common Filter Parameters**:
|
||||
- Date ranges: `from_date`, `to_date`
|
||||
- Block ranges: `from_block`, `to_block`
|
||||
- Value ranges: `min_value`, `max_value`
|
||||
- Address filters: `from_address`, `to_address`
|
||||
|
||||
**Sorting**:
|
||||
- `sort` (string): Field to sort by
|
||||
- `order` (string): `asc` or `desc`
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
See API Gateway specification for rate limiting details.
|
||||
|
||||
**Rate Limit Headers**:
|
||||
- `X-RateLimit-Limit`: Request limit
|
||||
- `X-RateLimit-Remaining`: Remaining requests
|
||||
- `X-RateLimit-Reset`: Reset timestamp
|
||||
|
||||
## OpenAPI Specification
|
||||
|
||||
Full OpenAPI 3.0 specification available at:
|
||||
- `/api-docs/openapi.json`
|
||||
- Interactive docs: `/api-docs` (Swagger UI)
|
||||
|
||||
## Versioning
|
||||
|
||||
**Current Version**: v1
|
||||
|
||||
**Version Header**: `API-Version: 1`
|
||||
|
||||
**Deprecation**: Deprecated endpoints return `Deprecation` header with deprecation date
|
||||
|
||||
## References
|
||||
|
||||
- API Gateway: See `api-gateway.md`
|
||||
- GraphQL API: See `graphql-api.md`
|
||||
- Etherscan-Compatible API: See `etherscan-compatible-api.md`
|
||||
|
||||
337
docs/specs/api/websocket-api.md
Normal file
337
docs/specs/api/websocket-api.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# WebSocket API Specification
|
||||
|
||||
## Overview
|
||||
|
||||
This document specifies the WebSocket API for real-time blockchain data subscriptions, including new blocks, pending transactions, and address activity.
|
||||
|
||||
**Endpoint**: `wss://api.explorer.d-bis.org/ws`
|
||||
|
||||
## Connection Lifecycle
|
||||
|
||||
### Connection
|
||||
|
||||
**URL**: `wss://api.explorer.d-bis.org/ws?chain_id=138`
|
||||
|
||||
**Query Parameters**:
|
||||
- `chain_id` (optional): Default chain ID for subscriptions
|
||||
- `api_key` (optional): API key for authenticated connections
|
||||
|
||||
**Headers**:
|
||||
- `Authorization: Bearer <token>` (optional)
|
||||
|
||||
### Authentication
|
||||
|
||||
**Methods**:
|
||||
1. Query parameter: `?api_key=<key>`
|
||||
2. Header: `Authorization: Bearer <token>`
|
||||
3. Message: Send auth message after connection
|
||||
|
||||
**Auth Message**:
|
||||
```json
|
||||
{
|
||||
"type": "auth",
|
||||
"api_key": "your-api-key"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"type": "auth_success",
|
||||
"user_id": "uuid",
|
||||
"tier": "pro"
|
||||
}
|
||||
```
|
||||
|
||||
### Heartbeat
|
||||
|
||||
**Purpose**: Keep connection alive and detect disconnections.
|
||||
|
||||
**Client Ping**: Send every 30 seconds
|
||||
```json
|
||||
{
|
||||
"type": "ping"
|
||||
}
|
||||
```
|
||||
|
||||
**Server Pong**: Response within 1 second
|
||||
```json
|
||||
{
|
||||
"type": "pong",
|
||||
"timestamp": 1640995200
|
||||
}
|
||||
```
|
||||
|
||||
**Timeout**: Connection closed if no pong received within 60 seconds
|
||||
|
||||
## Subscription Model
|
||||
|
||||
### Subscribe to New Blocks
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"type": "subscribe",
|
||||
"channel": "blocks",
|
||||
"chain_id": 138,
|
||||
"params": {
|
||||
"include_transactions": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"type": "subscribed",
|
||||
"subscription_id": "sub_123",
|
||||
"channel": "blocks"
|
||||
}
|
||||
```
|
||||
|
||||
**Updates**:
|
||||
```json
|
||||
{
|
||||
"type": "update",
|
||||
"subscription_id": "sub_123",
|
||||
"data": {
|
||||
"number": 12345,
|
||||
"hash": "0x...",
|
||||
"timestamp": "2024-01-01T00:00:00Z",
|
||||
"transaction_count": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Subscribe to Pending Transactions
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"type": "subscribe",
|
||||
"channel": "pending_transactions",
|
||||
"chain_id": 138,
|
||||
"params": {
|
||||
"from_address": "0x...", // Optional filter
|
||||
"min_value": "1000000000000000000" // Optional filter
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Updates**: Transaction objects as they enter mempool
|
||||
|
||||
### Subscribe to Address Activity
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"type": "subscribe",
|
||||
"channel": "address",
|
||||
"chain_id": 138,
|
||||
"params": {
|
||||
"address": "0x..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Updates**: Transactions involving the address
|
||||
|
||||
### Subscribe to Logs/Events
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"type": "subscribe",
|
||||
"channel": "logs",
|
||||
"chain_id": 138,
|
||||
"params": {
|
||||
"address": "0x...",
|
||||
"topics": ["0xddf252..."] // Event signatures
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Updates**: Matching log entries
|
||||
|
||||
### Unsubscribe
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"type": "unsubscribe",
|
||||
"subscription_id": "sub_123"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"type": "unsubscribed",
|
||||
"subscription_id": "sub_123"
|
||||
}
|
||||
```
|
||||
|
||||
## Message Formats
|
||||
|
||||
### Client Messages
|
||||
|
||||
**Subscribe**:
|
||||
```json
|
||||
{
|
||||
"type": "subscribe",
|
||||
"channel": "<channel_name>",
|
||||
"chain_id": 138,
|
||||
"params": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**Unsubscribe**:
|
||||
```json
|
||||
{
|
||||
"type": "unsubscribe",
|
||||
"subscription_id": "<id>"
|
||||
}
|
||||
```
|
||||
|
||||
**Ping**:
|
||||
```json
|
||||
{
|
||||
"type": "ping"
|
||||
}
|
||||
```
|
||||
|
||||
### Server Messages
|
||||
|
||||
**Update**:
|
||||
```json
|
||||
{
|
||||
"type": "update",
|
||||
"subscription_id": "<id>",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**Error**:
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"code": "invalid_subscription",
|
||||
"message": "Invalid subscription parameters",
|
||||
"subscription_id": "<id>"
|
||||
}
|
||||
```
|
||||
|
||||
**Notification**:
|
||||
```json
|
||||
{
|
||||
"type": "notification",
|
||||
"level": "info", // info, warning, error
|
||||
"message": "Subscription limit reached"
|
||||
}
|
||||
```
|
||||
|
||||
## Subscription Limits
|
||||
|
||||
**Per Connection**:
|
||||
- Anonymous: 5 subscriptions
|
||||
- Free API Key: 20 subscriptions
|
||||
- Pro API Key: 100 subscriptions
|
||||
- Enterprise: Unlimited
|
||||
|
||||
**Per Chain**:
|
||||
- Limit subscriptions per chain to prevent abuse
|
||||
|
||||
## Reconnection Strategy
|
||||
|
||||
### Automatic Reconnection
|
||||
|
||||
**Strategy**: Exponential backoff
|
||||
|
||||
**Backoff Schedule**:
|
||||
- Initial: 1 second
|
||||
- Max: 60 seconds
|
||||
- Multiplier: 2x
|
||||
|
||||
**Behavior**:
|
||||
- Reconnect automatically
|
||||
- Resubscribe to all active subscriptions
|
||||
- Resume from last received update
|
||||
|
||||
### Manual Reconnection
|
||||
|
||||
**Steps**:
|
||||
1. Close connection gracefully
|
||||
2. Reconnect with same parameters
|
||||
3. Resubscribe to desired channels
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
**Limits**:
|
||||
- Message rate: 10 messages/second per connection
|
||||
- Subscription rate: 5 subscriptions/minute per connection
|
||||
- Update rate: Throttled per subscription type
|
||||
|
||||
**Throttling**: Server may batch or skip updates if client cannot keep up
|
||||
|
||||
## Error Codes
|
||||
|
||||
- `invalid_message`: Malformed message
|
||||
- `invalid_subscription`: Invalid subscription parameters
|
||||
- `subscription_limit`: Too many subscriptions
|
||||
- `rate_limit`: Rate limit exceeded
|
||||
- `unauthorized`: Authentication required
|
||||
- `chain_not_supported`: Chain ID not supported
|
||||
- `internal_error`: Server error
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Connection Security
|
||||
|
||||
- WSS (WebSocket Secure) required
|
||||
- TLS 1.2+ only
|
||||
- Certificate validation
|
||||
|
||||
### Authentication
|
||||
|
||||
- API key validation
|
||||
- User permission checks
|
||||
- Subscription authorization
|
||||
|
||||
### Message Validation
|
||||
|
||||
- Validate all incoming messages
|
||||
- Sanitize parameters
|
||||
- Reject invalid requests
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Message Batching
|
||||
|
||||
**Strategy**: Batch multiple updates into single message when possible
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"type": "batch_update",
|
||||
"updates": [
|
||||
{ "subscription_id": "sub_1", "data": {...} },
|
||||
{ "subscription_id": "sub_2", "data": {...} }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Backpressure
|
||||
|
||||
**Strategy**: Drop updates if client cannot process them
|
||||
|
||||
**Indicators**:
|
||||
- Client buffer full
|
||||
- No ping/pong responses
|
||||
- Slow message processing
|
||||
|
||||
## References
|
||||
|
||||
- REST API: See `rest-api.md`
|
||||
- GraphQL API: See `graphql-api.md`
|
||||
- Mempool Service: See `../mempool/mempool-service.md`
|
||||
|
||||
Reference in New Issue
Block a user