Docs Técnicas
atlas-airdrop
atlas-airdrop is a standalone HTTP faucet service that funds a destination wallet by submitting a small batch of signed transactions to the node API. It owns public request ingress, config validation, rate limiting, upstream node interaction, and Prometheus telemetry.
Summary
atlas-airdrop is a standalone HTTP faucet service that funds a destination wallet by submitting a small batch of signed transactions to the node API. It owns public request ingress, config validation, rate limiting, upstream node interaction, and Prometheus telemetry.
The Rust backend is the important part of this crate for the current pass. There is also a colocated frontend under `frontend/`, but that UI is out of scope for the Rust-only documentation pass.
Why This Crate Exists
- expose a simple public faucet endpoint for demo and testnet flows
- protect the faucet with strict startup validation, CORS policy, and rate limits
- load the signing keys needed to fund wallets and issue demo bank assets
- coordinate nonce reads and transaction submission against
atlas-node - publish metrics for operational visibility
Current Role In The Workspace
atlas-airdrop currently owns five related concerns:
- secure startup and environment validation
- public HTTP ingress for airdrop requests
- in-memory IP and wallet rate limiting
- transaction funding logic against the node REST API
- a Prometheus metrics endpoint for the faucet service
This crate is clearly a standalone service. It is not embedded by atlas-node and does not act as a library for the rest of the application layer.
Public Surface
Package Targets
- binary target
atlas-airdrop - no library target
Internal Modules
fundersecuritytelemetry
HTTP Surface
POST /api/airdropGET /health
Feature Flags
unsafe-demodemo-keys
The crate also contains a compile-time guard: release builds that enable demo-keys must also enable unsafe-demo.
Key Modules
- `main.rs`: app state, router construction, startup flow, rate limiting, and the
request_airdrop(...)handler - `funder.rs`: signing-key loading, nonce reads, signed transfer construction, transaction submission, and confirmation polling
- `security.rs`: environment parsing, secure defaults, URL/origin validation, demo-key policy, and client-IP extraction
- `telemetry.rs`: metric names and request/upstream recording helpers
Inputs And Outputs
Inputs
- environment variables for bind address, upstream node URL, CORS, body limits, rate limits, amounts, and signing keys
- a JSON request body containing a destination wallet address
x-real-ipheaders when trusted ingress is enabled- upstream node API responses from
/api/balanceand/api/transaction
Outputs
- a public JSON API that reports success or rejection
- three signed funding transactions per accepted request
- Prometheus metrics served from the configured metrics bind address
- structured warning/error logs with explicit security codes
Internal Dependencies
Workspace Dependencies
atlas-common- optional
atlas-airdrop-demobehind thedemo-keysfeature
atlas-airdrop-demo is not part of the core service logic. It only supplies deterministic demo seeds when the service is intentionally built for unsafe demo flows.
External Dependencies That Shape The Design
axumfor the public HTTP APIreqwestfor upstream node callsed25519-dalekfor transaction signingtower-httpfor CORS handlingmetricsplusmetrics-exporter-prometheusfor observabilitydotenvyfor local env-file loading
Used By
No Rust crate depends directly on atlas-airdrop as a library.
Operationally, it is used as a standalone service in the demo and container stack:
- `start_demo_dev.sh`: builds and starts the faucet with
demo-keys,unsafe-demo - `start_demo_prod.sh`: same pattern for the production-style demo flow
- `docker/Dockerfile`: builds the faucet binary and the colocated frontend
- `docker/supervisord.conf`: runs
atlas-airdropas its own process - `observability/prometheus.yml`: scrapes the faucet metrics endpoint
- `observability/alert-rules.yml`: defines service and upstream-error alerts for the faucet
Request And Routing Model
build_router(...) in `main.rs` is intentionally small:
POST /api/airdropfor faucet requestsGET /healthfor a simple liveness check
The router also applies:
- a strict body-size limit
- explicit CORS origins
- no extra public routes by default
request_airdrop(...) performs the full request workflow:
- record request telemetry
- derive the client IP
- validate the destination address
- enforce per-IP and per-wallet rate limits
- acquire a nonce lock to avoid concurrent nonce collisions
- execute the funding sequence
- update in-memory limit trackers on success
Funding Model
The funding logic in `funder.rs` is tailored to the current demo/testnet economics.
For each accepted request, execute_airdrop(...) submits three transactions:
- ATLAS gas funding from the admin wallet
- USD issuance from the SeedBank issuance vault
- USD issuance from the MetaBank issuance vault
To do that, the service:
- derives the admin wallet address from the admin signing key
- fetches current nonces from
/api/balance?query=... - builds
SignedTransactionvalues locally - posts them to
/api/transaction - waits for the final vault nonce to reach the submitted value
The service treats the node API as the source of truth for nonce state and transaction acceptance.
Security Model
atlas-airdrop is opinionated about security for such a small service.
Secure Defaults
SecurityConfig::default() in `security.rs` starts from:
- loopback-only bind addresses
- loopback-only default node API over plain HTTP
- explicit localhost CORS origins
- demo keys disabled
- insecure remote HTTP upstreams disabled
- non-zero body, rate-limit, and amount defaults
Environment Validation
SecurityConfig::from_env() and validate() reject insecure or malformed startup states, including:
- non-loopback bind addresses
- empty allowed-origin lists
- zero rate limits or zero airdrop amounts
- demo-key usage without the correct cargo feature
- remote upstreams over plain HTTP unless explicitly overridden
Security Codes
The crate assigns explicit security codes such as:
AD-SEC-001AD-SEC-004AD-SEC-006AD-SEC-007
Those codes show up in config and runtime errors, which makes operational incidents easier to classify.
Rate Limiting And State Model
The faucet keeps two in-memory limit trackers inside AppState:
ip_limits: IP address to recent request timestampswallet_limits: wallet address to last successful request time
These are process-local and protected by tokio::Mutex.
The service also keeps a single nonce_lock mutex, which serializes the full funding flow so concurrent requests do not race on upstream nonce reads.
Telemetry And Observability
telemetry.rs defines counters and histograms for:
- requests received
- requests succeeded
- requests rejected by reason
- end-to-end request latency
- upstream request count and latency
- upstream error count
At startup, main.rs installs a Prometheus exporter bound to metrics_bind_addr.
Testing
The crate has solid unit and router coverage for a standalone service:
- startup/security helper validation
- client-IP extraction
- CORS behavior
- oversized body rejection
- invalid-address rejection
- wallet and IP rate-limit rejection
- signed transaction construction
- upstream submission helpers
This is strong coverage for the service boundary, even though it does not replace end-to-end testing against a live node.
Risks Or Design Tension
Funding Logic Is Hardcoded To The Current Demo Economy
The service is not a generic faucet. It hardcodes:
- one admin wallet
- two issuance vaults
- one bank asset symbol
- one fixed three-transaction funding sequence
That is fine for the current demo stack, but it makes the crate tightly coupled to today's institutional setup.
Rate Limits Are In-Memory Only
IP and wallet limits disappear on restart and are not shared across replicas. The current design assumes a single local faucet process, not a distributed service.
Nonce Coordination Uses A Single Global Lock
The nonce_lock is simple and safe, but it serializes all airdrop execution. That reduces throughput as soon as the faucet receives concurrent traffic.
Address Validation Is Shallow
is_valid_airdrop_address(...) only checks that the string is non-empty and starts with nb. That allows both exposed and hidden Atlas-style prefixes, but it is still much looser than full address parsing.
Success Detection Depends On Response Text
submit_tx(...) considers a transaction accepted only when the HTTP response is successful and the body contains the string "accepted". That is a brittle contract if the node API response shape changes.
Startup Still Shows Some Demo Drift
Two small signals stand out in `main.rs`:
- the service explicitly loads a crate-local
.envfile before falling back to the default env flow - it logs
LLM_PROVIDEReven though the current Rust service has no direct LLM request path
That suggests some operational residue from adjacent experiments or earlier variants of the faucet service.