Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Effect.ts includes a built-in RateLimiter that provides token-bucket and fixed-window algorithms. This guide shows how to use it directly and through voltaire-effect’s RateLimiterService.
Effect’s Built-in RateLimiter
Create rate limiters directly using RateLimiter.make:
import { Effect, RateLimiter, Function } from "effect"
const program = Effect.gen(function* () {
// Create a rate limiter: 10 requests per second
const limiter = yield* RateLimiter.make({
limit: 10,
interval: "1 second",
algorithm: "token-bucket" // or "fixed-window"
})
// Apply to any Effect
const result = yield* limiter(
Effect.succeed("rate limited operation")
)
return result
})
Algorithms
| Algorithm | Behavior | Best For |
|---|
token-bucket | Smooth, evenly distributed requests (default) | Sustained traffic |
fixed-window | Allows bursts at start of each interval | Bursty workloads |
Compose Multiple Limits
Chain rate limiters for tiered limits (common with RPC providers):
import { Effect, RateLimiter, Function } from "effect"
const program = Effect.gen(function* () {
// Per-second limit
const perSecond = yield* RateLimiter.make({
limit: 10,
interval: "1 second"
})
// Per-day limit (many providers have daily quotas)
const perDay = yield* RateLimiter.make({
limit: 10_000,
interval: "1 day"
})
// Compose: request must pass BOTH limits
const composed = Function.compose(perSecond, perDay)
// Apply to operations
const result = yield* composed(
Effect.succeed("passes both rate limits")
)
return result
})
Weighted Costs
Some operations cost more than others (e.g., eth_getLogs vs eth_blockNumber):
import { Effect, RateLimiter } from "effect"
const program = Effect.gen(function* () {
const limiter = yield* RateLimiter.make({
limit: 100,
interval: "1 second"
})
// Standard operation: costs 1 token
yield* limiter(simpleCall)
// Expensive operation: costs 5 tokens
yield* limiter(expensiveCall).pipe(
RateLimiter.withCost(5)
)
})
voltaire-effect RateLimiterService
The voltaire-effect package wraps Effect’s RateLimiter with Ethereum-specific features:
import { Effect } from "effect"
import {
RateLimiterService,
DefaultRateLimiter,
HttpTransport,
getBalance
} from "voltaire-effect"
const program = Effect.gen(function* () {
const rateLimiter = yield* RateLimiterService
const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
// Wrap any RPC call with rate limiting
const balance = yield* rateLimiter.withRateLimit(
"eth_getBalance",
getBalance(address)
)
return balance
}).pipe(
Effect.scoped,
Effect.provide(DefaultRateLimiter({
global: { limit: 10, interval: "1 seconds" }
})),
Effect.provide(HttpTransport("https://eth.llamarpc.com"))
)
Global + Per-Method Limits
Configure different limits for different RPC methods:
import { DefaultRateLimiter } from "voltaire-effect"
const RateLimited = DefaultRateLimiter({
// Global limit applies to all methods
global: {
limit: 50,
interval: "1 seconds",
algorithm: "token-bucket"
},
// Per-method overrides (stacks with global)
methods: {
"eth_getLogs": { limit: 5, interval: "1 seconds" },
"eth_call": { limit: 30, interval: "1 seconds" },
"debug_traceTransaction": { limit: 1, interval: "5 seconds" }
}
})
Fail-Fast vs Delay
Control behavior when limits are exceeded:
// Delay (default): Queue requests until capacity available
const DelayLayer = DefaultRateLimiter({
global: { limit: 10, interval: "1 seconds" },
behavior: "delay"
})
// Fail: Immediately fail with RateLimitError
const FailFastLayer = DefaultRateLimiter({
global: { limit: 10, interval: "1 seconds" },
behavior: "fail"
})
Handle failures:
import { Effect } from "effect"
import { RateLimiterService, RateLimitError } from "voltaire-effect"
const program = Effect.gen(function* () {
const rateLimiter = yield* RateLimiterService
return yield* rateLimiter.withRateLimit("eth_call", someEffect)
}).pipe(
Effect.catchTag("RateLimitError", (e) => {
console.log(`Rate limited: ${e.method}`)
return Effect.succeed(fallbackValue)
})
)
Real-World: RPC Provider Limits
Common provider rate limits:
| Provider | Free Tier | Paid Tier |
|---|
| Infura | 10 req/sec | 100+ req/sec |
| Alchemy | 25 CU/sec | 300+ CU/sec |
| QuickNode | Varies | Varies |
| Public RPCs | 1-5 req/sec | N/A |
Configure for your provider:
import { Effect, Layer } from "effect"
import {
Provider,
DefaultRateLimiter,
HttpTransport,
getBlock
} from "voltaire-effect"
// Infura configuration with safety margin
const InfuraRateLimiter = DefaultRateLimiter({
global: { limit: 8, interval: "1 seconds" }, // Under 10/sec limit
methods: {
"eth_getLogs": { limit: 2, interval: "1 seconds" },
"debug_traceTransaction": { limit: 1, interval: "5 seconds" }
},
behavior: "delay"
})
const InfuraProvider = Layer.mergeAll(Provider, InfuraRateLimiter).pipe(
Layer.provide(HttpTransport("https://mainnet.infura.io/v3/YOUR_KEY"))
)
// Batch operations respect limits automatically
const fetchBlocks = Effect.gen(function* () {
const blockNumbers = Array.from({ length: 100 }, (_, i) => i + 1000000)
return yield* Effect.all(
blockNumbers.map((n) =>
getBlock({ blockTag: `0x${n.toString(16)}` })
),
{ concurrency: "unbounded" } // Rate limiter handles throttling
)
}).pipe(
Effect.scoped,
Effect.provide(InfuraProvider)
)
API Reference
RateLimiter.make
declare const make: (options: {
readonly limit: number
readonly interval: DurationInput // "1 second", "500 millis", Duration.seconds(1)
readonly algorithm?: "token-bucket" | "fixed-window"
}) => Effect.Effect<RateLimiter, never, Scope>
RateLimiter.withCost
declare const withCost: (cost: number) => <A, E, R>(
effect: Effect.Effect<A, E, R>
) => Effect.Effect<A, E, R>
DefaultRateLimiter
declare const DefaultRateLimiter: (config: {
readonly global?: {
readonly limit: number
readonly interval: DurationInput
readonly algorithm?: "token-bucket" | "fixed-window"
}
readonly methods?: Record<string, {
readonly limit: number
readonly interval: DurationInput
readonly algorithm?: "token-bucket" | "fixed-window"
}>
readonly behavior?: "delay" | "fail"
}) => Layer.Layer<RateLimiterService>
See Also