From 17785a62e8376a331cf04659b6a934003fb9efef Mon Sep 17 00:00:00 2001 From: Raj Ranjan Date: Fri, 16 May 2025 14:44:47 +0530 Subject: [PATCH 1/2] skip rate-limiters based on coupon --- CouponService/couponService.ts | 8 +++++++- middlewares/rateLimiter.ts | 22 +++++++++++++++++++++- server.ts | 21 +++++++++++++++------ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/CouponService/couponService.ts b/CouponService/couponService.ts index 8d64f5a..8b35dd6 100644 --- a/CouponService/couponService.ts +++ b/CouponService/couponService.ts @@ -3,7 +3,7 @@ import { DynamoDBDocumentClient, UpdateCommand, ScanCommand } from "@aws-sdk/lib import { Mutex, MutexInterface } from 'async-mutex' import { CouponValidity } from "../types" -type Coupon = { +export type Coupon = { id: string, faucetConfigId: string, maxLimitAmount: number, @@ -11,6 +11,8 @@ type Coupon = { expiry: number, amountPerCoupon: number, reset: boolean, + skipIpRateLimit?: boolean, + skipWalletRateLimit?: boolean, } type CouponConfig = { @@ -169,4 +171,8 @@ export class CouponService { release() } } + + getCoupon(id: string): Coupon | undefined { + return this.coupons.get(id) + } } \ No newline at end of file diff --git a/middlewares/rateLimiter.ts b/middlewares/rateLimiter.ts index edf6461..8a5ec7a 100644 --- a/middlewares/rateLimiter.ts +++ b/middlewares/rateLimiter.ts @@ -1,12 +1,21 @@ import rateLimit, { RateLimitRequestHandler } from 'express-rate-limit' import { searchIP } from 'range_check' import { RateLimiterConfig } from '../types' +import { CouponService } from '../CouponService/couponService' export class RateLimiter { PATH: string + type: 'ip' | 'wallet' | 'global' - constructor(app: any, configs: RateLimiterConfig[], keyGenerator?: any) { + constructor( + app: any, + configs: RateLimiterConfig[], + type: 'ip' | 'wallet' | 'global', + couponService: CouponService, + keyGenerator?: any, + ) { this.PATH = configs[0].RATELIMIT.PATH || '/api/sendToken' + this.type = type let rateLimiters: any = new Map() configs.forEach((config: any) => { @@ -26,6 +35,17 @@ export class RateLimiter { } app.use(this.PATH, (req: any, res: any, next: any) => { + // skip rate limit based on coupon - coupon limit verification is done in the request handler + const couponId = req.body?.couponId + if (couponId) { + const coupon = couponService.getCoupon(couponId) + if (this.type === 'ip' && coupon && coupon.skipIpRateLimit) { + return next() + } else if (this.type === 'wallet' && coupon && coupon.skipWalletRateLimit) { + return next() + } + } + if(this.PATH == '/api/sendToken' && req.body.chain) { return rateLimiters.get(req.body.erc20 ? req.body.erc20 : req.body.chain)(req, res, next) } else { diff --git a/server.ts b/server.ts index a5dd5f0..1b4375a 100644 --- a/server.ts +++ b/server.ts @@ -45,18 +45,21 @@ if (NATIVE_CLIENT) { app.use(express.static(path.join(__dirname, "client"))) } -new RateLimiter(app, [GLOBAL_RL]) +const couponService = new CouponService(couponConfig) +const mainnetCheckService = new MainnetCheckService(MAINNET_BALANCE_CHECK_RPC) + +new RateLimiter(app, [GLOBAL_RL], 'global', couponService) new RateLimiter(app, [ ...evmchains, ...erc20tokens -]) +], 'ip', couponService) // address rate limiter new RateLimiter(app, [ ...evmchains, ...erc20tokens -], (req: any, res: any) => { +], 'wallet', couponService, (req: any, res: any) => { const addr = req.body?.address if(typeof addr == "string" && addr) { @@ -64,9 +67,6 @@ new RateLimiter(app, [ } }) -const couponService = new CouponService(couponConfig) -const mainnetCheckService = new MainnetCheckService(MAINNET_BALANCE_CHECK_RPC) - const captcha: VerifyCaptcha = new VerifyCaptcha(app, process.env.CAPTCHA_SECRET!, process.env.V2_CAPTCHA_SECRET!) let evms = new Map() @@ -165,6 +165,13 @@ router.post('/sendToken', captcha.middleware, async (req: any, res: any) => { return } + let skippedIpRateLimit, skippedWalletRateLimit; + if (coupon) { + const couponItem = couponService.getCoupon(coupon) + skippedIpRateLimit = couponItem?.skipIpRateLimit + skippedWalletRateLimit = couponItem?.skipWalletRateLimit + } + // logging requests (if enabled) DEBUG && console.log(JSON.stringify({ date: new Date(), @@ -176,6 +183,8 @@ router.post('/sendToken', captcha.middleware, async (req: any, res: any) => { checkPassedType: pipelineValidity.checkPassedType, dripAmount: pipelineValidity.dripAmount, mainnetBalance: pipelineValidity.mainnetBalance, + skippedIpRateLimit, + skippedWalletRateLimit, ip: req.headers["cf-connecting-ip"] || req.ip })) From 9b5669fe4f80ad9ffcaf93e64ccb4e4e8a97f47e Mon Sep 17 00:00:00 2001 From: Raj Ranjan Date: Fri, 16 May 2025 14:45:13 +0530 Subject: [PATCH 2/2] remove unused chains --- config.json | 166 +--------------------------------------------------- 1 file changed, 2 insertions(+), 164 deletions(-) diff --git a/config.json b/config.json index 41f6488..bc9cd21 100644 --- a/config.json +++ b/config.json @@ -3,7 +3,7 @@ "ID": "GLOBAL", "RATELIMIT": { "REVERSE_PROXIES": 2, - "MAX_LIMIT": 40, + "MAX_LIMIT": 200, "WINDOW_SIZE": 1, "PATH": "/", "SKIP_FAILED_REQUESTS": false @@ -28,7 +28,7 @@ "IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/43113/chain-logo.png", "MAX_PRIORITY_FEE": "10000000000", "MAX_FEE": "100000000000", - "DRIP_AMOUNT": 2, + "DRIP_AMOUNT": 0.01, "DECIMALS": 18, "RECALIBRATE": 30, "COUPON_REQUIRED": true, @@ -38,22 +38,6 @@ "WINDOW_SIZE": 1440 } }, - { - "ID": "WAGMI", - "NAME": "WAGMI Testnet", - "TOKEN": "WGM", - "RPC": "https://subnets.avax.network/wagmi/wagmi-chain-testnet/rpc", - "CHAINID": 11111, - "EXPLORER": "https://subnets.avax.network/wagmi/wagmi-chain-testnet/explorer", - "IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/11111/chain-logo.png", - "MAX_PRIORITY_FEE": "2000000000", - "MAX_FEE": "100000000000", - "DRIP_AMOUNT": 2, - "RATELIMIT": { - "MAX_LIMIT": 1, - "WINDOW_SIZE": 1440 - } - }, { "ID": "DFK", "NAME": "DeFi Kingdoms Testnet", @@ -70,22 +54,6 @@ "WINDOW_SIZE": 1440 } }, - { - "ID": "NMAC", - "NAME": "Rise of the Warbots Testnet", - "TOKEN": "NMAC", - "RPC": "https://testnet1.rotw.games", - "CHAINID": 7777, - "EXPLORER": "https://testnet.avascan.info/blockchain/nmac", - "IMAGE": "https://rotw.games/assets/icons/icon_nmac_fill.png", - "MAX_PRIORITY_FEE": "2000000000", - "MAX_FEE": "130000000000", - "DRIP_AMOUNT": 2, - "RATELIMIT": { - "MAX_LIMIT": 1, - "WINDOW_SIZE": 1440 - } - }, { "ID": "NUM", "NAME": "Numbers Testnet", @@ -151,57 +119,6 @@ "WINDOW_SIZE": 1440 } }, - { - "ID": "MASA", - "NAME": "Masa Testnet", - "TOKEN": "MASA", - "RPC": "https://subnets.avax.network/masatestne/testnet/rpc", - "CHAINID": 103454, - "EXPLORER": "https://subnets-test.avax.network/masatestnet", - "IMAGE": "https://safe.masa.finance/images/networks/masa.png", - "MAX_PRIORITY_FEE": "300000000000", - "MAX_FEE": "10000000000000", - "DRIP_AMOUNT": 2, - "DECIMALS": 18, - "RATELIMIT": { - "MAX_LIMIT": 1, - "WINDOW_SIZE": 1440 - } - }, - { - "ID": "LT0", - "NAME": "LT0 Testnet", - "TOKEN": "LT", - "RPC": "https://subnets.avax.network/lt0/testnet/rpc", - "CHAINID": 31330, - "EXPLORER": "https://subnets-test.avax.network/lt0", - "IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/43113/chain-logo.png", - "MAX_PRIORITY_FEE": "3000000000", - "MAX_FEE": "100000000000", - "DRIP_AMOUNT": 4, - "DECIMALS": 18, - "RATELIMIT": { - "MAX_LIMIT": 2, - "WINDOW_SIZE": 1440 - } - }, - { - "ID": "FERDYNET", - "NAME": "FerdyFlip Testnet", - "TOKEN": "FAVAX", - "RPC": "https://testnet-rpc.ferdyflip.xyz/rpc", - "CHAINID": 668577, - "EXPLORER": "https://devnet.routescan.io", - "IMAGE": "https://www.ferdyflip.xyz/images/heads.png", - "MAX_PRIORITY_FEE": "10000000000", - "MAX_FEE": "100000000000", - "DRIP_AMOUNT": 20, - "DECIMALS": 18, - "RATELIMIT": { - "MAX_LIMIT": 2, - "WINDOW_SIZE": 1440 - } - }, { "ID": "MXSGAMES", "NAME": "MXS Games Testnet", @@ -269,56 +186,6 @@ "WINDOW_SIZE": 1440 } }, - { - "ID": "INTERSECT", - "NAME": "Intersect Testnet", - "TOKEN": "PEARL", - "RPC": "https://subnets.avax.network/pearl/testnet/rpc", - "CHAINID": 1612, - "EXPLORER": "https://subnets-test.avax.network/intersect", - "IMAGE": "https://raw.githubusercontent.com/UzairHannure/Subnet-Brand-Assets/main/ddd.webp", - "MAX_PRIORITY_FEE": "2000000000", - "MAX_FEE": "50000000000", - "DRIP_AMOUNT": 5, - "RATELIMIT": { - "MAX_LIMIT": 3, - "WINDOW_SIZE": 1440 - } - }, - { - "ID": "PTNEWLO", - "NAME": "PTNEWLO Testnet", - "TOKEN": "PTNL", - "RPC": "https://subnets.avax.network/ptnewlo/testnet/rpc", - "CHAINID": 57487, - "EXPLORER": "https://subnets-test.avax.network/ptnewlo", - "IMAGE": "https://images.ctfassets.net/gcj8jwzm6086/4wnyhip4JrhrRueC21kmUP/bfaa12a82cb2894f8316fba2827c9d58/AvaCloud_subnet_PTNEWLO.png", - "MAX_PRIORITY_FEE": "100000000000", - "MAX_FEE": "100000000000", - "DRIP_AMOUNT": 5, - "DECIMALS": 18, - "RATELIMIT": { - "MAX_LIMIT": 1, - "WINDOW_SIZE": 1440 - } - }, - { - "ID": "CDEVNET", - "NAME": "Devnet (C-Chain)", - "TOKEN": "AVAX", - "RPC": "https://etna.avax-dev.network/ext/bc/C/rpc", - "CHAINID": 43117, - "EXPLORER": "https://feat-etna-devnet.subnets.pages.dev/devnet-c-chain", - "IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/43113/chain-logo.png", - "MAX_PRIORITY_FEE": "10000000000", - "MAX_FEE": "100000000000", - "DRIP_AMOUNT": 5, - "DECIMALS": 18, - "RATELIMIT": { - "MAX_LIMIT": 1, - "WINDOW_SIZE": 1440 - } - }, { "ID": "ONIGIRI", "NAME": "ONIGIRI Testnet", @@ -475,35 +342,6 @@ "WINDOW_SIZE": 1440 } }, - { - "ID": "WAVAXWAGMI", - "HOSTID": "WAGMI", - "NAME": "Wrapped AVAX", - "TOKEN": "WAVAX", - "IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/43113/chain-logo.png", - "CONTRACTADDRESS": "0x21cf0eB2E3Ab483a67C900b27dA8F34185991982", - "DRIP_AMOUNT": 2000000000, - "GASLIMIT": "150000", - "DECIMALS": 18, - "RATELIMIT": { - "MAX_LIMIT": 1, - "WINDOW_SIZE": 1440 - } - }, - { - "ID": "WWGM", - "HOSTID": "WAGMI", - "NAME": "Wrapped WAGMI", - "TOKEN": "WWGM", - "CONTRACTADDRESS": "0x3Ee7094DADda15810F191DD6AcF7E4FFa37571e4", - "DRIP_AMOUNT": 2, - "GASLIMIT": "150000", - "DECIMALS": 18, - "RATELIMIT": { - "MAX_LIMIT": 1, - "WINDOW_SIZE": 1440 - } - }, { "ID": "EUROEC", "HOSTID": "C",