This repository is an engineering exploration into translating traditional Web2 infrastructure into non-EVM decentralized environments. It demonstrates how a backend rate limiter can be rebuilt as a high-performance on-chain Solana program using the Anchor framework.
Instead of relying on centralized in-memory datastores (like Redis), this implementation forces the limiter state into a Solana account, requiring precise management of Program Derived Addresses (PDAs), state serialization, and compute unit constraints.
Technical Focus:
Translating Web2 logic to Web3 execution environments.
Anchor framework account management and state mutation.
Handling Solana-specific resource and compute constraints.
API rate limiting exists in Web2 systems to protect services from abuse, accidental overload, bot traffic, and unfair usage by a single client.
Typical reasons for rate limiting:
- prevent spam and denial-of-service patterns
- protect shared backend infrastructure
- enforce product quotas
- keep service quality stable under load
- limit how many requests a client can make in a time window
In a normal backend, the server checks whether a client has already used too many requests and either allows the request or rejects it.
A standard Web2 rate limiter often looks like this:
Client -> API -> Redis counter
Flow:
- A client sends a request to an API server.
- The backend identifies the caller by API key, user id, IP, wallet, or session.
- The API checks a counter in Redis.
- If the counter is below the configured limit, the request is allowed and the counter is incremented.
- If the counter is at the limit, the request is rejected, usually with HTTP 429.
Redis is commonly used because it is centralized, fast, and cheap to update on every request.
This project maps the same idea to Solana:
Client -> Solana program -> RateLimitAccount
Flow:
- A client sends a transaction to the Solana program.
- The program derives the owner's rate limiter PDA.
- The program reads the current counter state from
RateLimitAccount. - If the active time window has expired, the counter is reset.
- If the request count is still below the limit, the counter is incremented.
- If the request count has already reached the limit, the transaction returns a custom on-chain error.
This makes the rate-limiter state transparent and verifiable on chain.
The main account is RateLimitAccount.
Fields:
owner: Pubkeyrequest_count: u64window_start: i64limit: u64window_duration: i64
Meaning:
owner: the wallet that owns this limiter staterequest_count: number of accepted requests in the current windowwindow_start: unix timestamp for the start of the current windowlimit: max allowed requests in one windowwindow_duration: window size in seconds
PDA seeds:
[b"rate_limit", owner.as_ref()]
This gives one limiter account per owner.
Account size:
- 8 bytes discriminator
- 32 bytes
Pubkey - 8 bytes
u64 - 8 bytes
i64 - 8 bytes
u64 - 8 bytes
i64 - total:
72bytes
Creates the PDA-backed RateLimitAccount and stores the limiter configuration.
Inputs:
limitwindow_duration
Behavior:
- creates the limiter PDA
- sets the owner
- sets
request_count = 0 - sets
window_start = 0 - stores the configured limit and window duration
Checks whether a request is allowed.
Behavior:
- gets the current unix timestamp from Solana
Clock - if
current_time - window_start > window_duration, resets the window - if
request_count >= limit, returnsRateLimitExceeded - otherwise increments
request_count
Errors:
RateLimitExceededUnauthorizedInvalidLimiter
Submitted Devnet program:
88JwScoavVyiG2KCQt1tewbM7yrBJ8LN7cuKjebDmyLN
Explorer: (https://explorer.solana.com/address/88JwScoavVyiG2KCQt1tewbM7yrBJ8LN7cuKjebDmyLN?cluster=devnet)
Deployment:
CLI test transactions:
- Initialize limiter:- https://explorer.solana.com/tx/5dMeHeycJkUXxSK4C3Rdnsu724V5ZJyhcntjJ6jvaw2aEjetdibBYUZewdzTLicLNjHvbhMM2L8TgzAfYa9ivyoY?cluster=devnet
- check request #1:- https://explorer.solana.com/tx/3ASLhSaDcShocbbEzLMJGFeZPpf7AFwB2bFUU7WK3obUHhNFfF8zcDkP3o1npgNmXf8ZSxkdmWKaetMssjVeXmbf?cluster=devnet
- check request #2:- https://explorer.solana.com/tx/48CWG8kWDKKdboBX5DSAimDVAWmFFNPqRjGRAVukcBp7tHEKA38c2VTz9KK6a7KyVkkkY2XbcADw2WwDGJ6hwRps?cluster=devnet
Observed result:
- the first two
check-requestcalls succeeded - the third
check-requestcall failed withRateLimitExceeded - this confirmed the on-chain fixed-window limiter behavior
Compared to a traditional backend rate limiter, the on-chain model has several differences:
Advantages:
- state is transparent and verifiable
- rate limiting rules cannot be modified by a centralized backend
- other programs can compose with the limiter
Tradeoffs:
- transactions are slower than in-memory counters
- each request requires a blockchain transaction
- account storage costs rent
Install:
- Rust
- Solana CLI
- Anchor CLI
- Node.js
- npm
anchor buildanchor testFor local validator:
solana config set --url localhostFor Devnet:
solana config set --url devnetanchor deployIf needed, direct deploy with Solana CLI:
solana program deploy target/deploy/rate_limiter.so --program-id target/deploy/rate_limiter-keypair.jsonInitialize a limiter:
ts-node client/cli.ts initialize-limiter --limit 2 --window-duration 60Check a request:
ts-node client/cli.ts check-requestprograms/rate-limiter/src/lib.rsprograms/rate-limiter/src/state.rsprograms/rate-limiter/src/errors.rsprograms/rate-limiter/src/instructions/initialize_limiter.rsprograms/rate-limiter/src/instructions/check_request.rstests/rate-limiter.tsclient/cli.ts
This project demonstrates how a common Web2 backend pattern—API rate limiting—can be implemented as a Solana program.
Instead of storing counters in Redis and enforcing quotas in API middleware, the rate limiting state is stored in a program-owned account and enforced directly by on-chain logic.
This shows how traditional backend infrastructure patterns can be translated into Solana’s distributed state machine model.