Resilience & Reliability
How Etch ensures your token launches succeed even when things go wrong.
Overview
Token launches involve multiple on-chain transactions across different protocols. Etch is designed to handle failures gracefully at every step.
Idempotency
Every launch has a unique launchId. If you accidentally submit the same launch twice (or if our job queue retries), Etch detects this and returns the existing result instead of creating duplicate tokens.
// Second request with same launchId
{
"status": "already_processed",
"mint": "ABC123...",
"pool": "XYZ789..."
}Best Practice: Always generate a unique launchId client-side (e.g., UUID) and store it before calling the API.
Transaction Retries
Solana transactions can fail for transient reasons:
- Blockhash expired
- Network congestion
- RPC node issues
Etch automatically retries failed transactions with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 2 seconds |
| 3 | 4 seconds |
| 4 | 8 seconds |
| 5 | 16 seconds |
Retryable vs Non-Retryable Errors
Retryable:
- Blockhash not found
- Too many requests (429)
- Network timeouts
- RPC 503/504
Non-Retryable:
- Insufficient funds
- Invalid signature
- Account not found
- Program errors
Partial Failure Handling
A token launch involves multiple steps:
- Create SPL token mint
- Mint total supply
- Create Meteora pool
- Upload metadata
- Create vesting (optional)
- Create DAO (optional)
If an optional step fails (metadata, vesting, DAO), the launch continues and completes with the core token and pool. You’ll receive a webhook with partial results.
{
"event": "launch.complete",
"data": {
"mint": "ABC123...",
"pool": "XYZ789...",
"warnings": [
{
"step": "vesting",
"error": "Streamflow service unavailable"
}
]
}
}Connection Health Monitoring
Etch monitors connections to all dependencies:
- Solana RPC — Health checked every 30 seconds
- Database — Connection pool with automatic reconnection
- Redis — Graceful fallback to in-memory cache
If a connection becomes unhealthy, Etch automatically reconnects on the next request.
Dead Letter Queue
When a launch fails after all retries, it’s saved to a dead letter queue (DLQ) for manual review. The DLQ contains:
- Original launch configuration
- Error message and stack trace
- Number of retry attempts
- Timestamp
Contact support to:
- View DLQ items
- Retry failed launches
- Get refunds for unrecoverable failures
Timeouts
All operations have strict timeouts to prevent hung requests:
| Operation | Timeout |
|---|---|
| Transaction confirmation | 60 seconds |
| Webhook delivery | 10 seconds |
| RPC requests | 30 seconds |
| Database queries | 10 seconds |
Rate Limiting
Rate limits prevent abuse and ensure fair access:
| Endpoint | Limit |
|---|---|
| General API | 60/min |
| Auth endpoints | 10/min |
| Launch endpoint | 5/hour |
| Webhooks | 10/hour |
Rate limiting uses a sliding window algorithm. When limits are exceeded:
HTTP 429 Too Many Requests
Retry-After: 60Note: Rate limits apply per wallet AND IP address to prevent bypass attacks.
Best Practices
1. Implement Webhook Handlers Properly
// ✅ Good: Acknowledge immediately, process async
app.post("/webhook", (req, res) => {
res.status(200).send("OK");
processWebhookAsync(req.body);
});
// ❌ Bad: Blocking processing
app.post("/webhook", async (req, res) => {
await processWebhook(req.body); // May timeout
res.status(200).send("OK");
});2. Store Launch IDs Before Calling API
// ✅ Good: Store first, then call
const launchId = crypto.randomUUID();
await db.launches.create({ id: launchId, status: "pending" });
const result = await etch.launch({ launchId, ... });
await db.launches.update({ id: launchId, ...result });3. Handle Partial Success
if (result.warnings?.length > 0) {
// Core launch succeeded but optional features failed
log.warn("Partial success", result.warnings);
// Maybe retry failed steps manually
}4. Monitor the Health Endpoint
const health = await fetch("https://etch.film.fun/api/health");
if (health.status === "unhealthy") {
// Delay launches until recovered
await delay(60000);
}