Skip to content

Commit bcc6636

Browse files
skip rate limit coupons (#179)
* skip rate-limiters based on coupon * remove unused chains
1 parent 78ffe92 commit bcc6636

4 files changed

Lines changed: 45 additions & 172 deletions

File tree

CouponService/couponService.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ import { DynamoDBDocumentClient, UpdateCommand, ScanCommand } from "@aws-sdk/lib
33
import { Mutex, MutexInterface } from 'async-mutex'
44
import { CouponValidity } from "../types"
55

6-
type Coupon = {
6+
export type Coupon = {
77
id: string,
88
faucetConfigId: string,
99
maxLimitAmount: number,
1010
consumedAmount: number,
1111
expiry: number,
1212
amountPerCoupon: number,
1313
reset: boolean,
14+
skipIpRateLimit?: boolean,
15+
skipWalletRateLimit?: boolean,
1416
}
1517

1618
type CouponConfig = {
@@ -169,4 +171,8 @@ export class CouponService {
169171
release()
170172
}
171173
}
174+
175+
getCoupon(id: string): Coupon | undefined {
176+
return this.coupons.get(id)
177+
}
172178
}

config.json

Lines changed: 2 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"ID": "GLOBAL",
44
"RATELIMIT": {
55
"REVERSE_PROXIES": 2,
6-
"MAX_LIMIT": 40,
6+
"MAX_LIMIT": 200,
77
"WINDOW_SIZE": 1,
88
"PATH": "/",
99
"SKIP_FAILED_REQUESTS": false
@@ -28,7 +28,7 @@
2828
"IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/43113/chain-logo.png",
2929
"MAX_PRIORITY_FEE": "10000000000",
3030
"MAX_FEE": "100000000000",
31-
"DRIP_AMOUNT": 2,
31+
"DRIP_AMOUNT": 0.01,
3232
"DECIMALS": 18,
3333
"RECALIBRATE": 30,
3434
"COUPON_REQUIRED": true,
@@ -38,22 +38,6 @@
3838
"WINDOW_SIZE": 1440
3939
}
4040
},
41-
{
42-
"ID": "WAGMI",
43-
"NAME": "WAGMI Testnet",
44-
"TOKEN": "WGM",
45-
"RPC": "https://subnets.avax.network/wagmi/wagmi-chain-testnet/rpc",
46-
"CHAINID": 11111,
47-
"EXPLORER": "https://subnets.avax.network/wagmi/wagmi-chain-testnet/explorer",
48-
"IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/11111/chain-logo.png",
49-
"MAX_PRIORITY_FEE": "2000000000",
50-
"MAX_FEE": "100000000000",
51-
"DRIP_AMOUNT": 2,
52-
"RATELIMIT": {
53-
"MAX_LIMIT": 1,
54-
"WINDOW_SIZE": 1440
55-
}
56-
},
5741
{
5842
"ID": "DFK",
5943
"NAME": "DeFi Kingdoms Testnet",
@@ -70,22 +54,6 @@
7054
"WINDOW_SIZE": 1440
7155
}
7256
},
73-
{
74-
"ID": "NMAC",
75-
"NAME": "Rise of the Warbots Testnet",
76-
"TOKEN": "NMAC",
77-
"RPC": "https://testnet1.rotw.games",
78-
"CHAINID": 7777,
79-
"EXPLORER": "https://testnet.avascan.info/blockchain/nmac",
80-
"IMAGE": "https://rotw.games/assets/icons/icon_nmac_fill.png",
81-
"MAX_PRIORITY_FEE": "2000000000",
82-
"MAX_FEE": "130000000000",
83-
"DRIP_AMOUNT": 2,
84-
"RATELIMIT": {
85-
"MAX_LIMIT": 1,
86-
"WINDOW_SIZE": 1440
87-
}
88-
},
8957
{
9058
"ID": "NUM",
9159
"NAME": "Numbers Testnet",
@@ -151,57 +119,6 @@
151119
"WINDOW_SIZE": 1440
152120
}
153121
},
154-
{
155-
"ID": "MASA",
156-
"NAME": "Masa Testnet",
157-
"TOKEN": "MASA",
158-
"RPC": "https://subnets.avax.network/masatestne/testnet/rpc",
159-
"CHAINID": 103454,
160-
"EXPLORER": "https://subnets-test.avax.network/masatestnet",
161-
"IMAGE": "https://safe.masa.finance/images/networks/masa.png",
162-
"MAX_PRIORITY_FEE": "300000000000",
163-
"MAX_FEE": "10000000000000",
164-
"DRIP_AMOUNT": 2,
165-
"DECIMALS": 18,
166-
"RATELIMIT": {
167-
"MAX_LIMIT": 1,
168-
"WINDOW_SIZE": 1440
169-
}
170-
},
171-
{
172-
"ID": "LT0",
173-
"NAME": "LT0 Testnet",
174-
"TOKEN": "LT",
175-
"RPC": "https://subnets.avax.network/lt0/testnet/rpc",
176-
"CHAINID": 31330,
177-
"EXPLORER": "https://subnets-test.avax.network/lt0",
178-
"IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/43113/chain-logo.png",
179-
"MAX_PRIORITY_FEE": "3000000000",
180-
"MAX_FEE": "100000000000",
181-
"DRIP_AMOUNT": 4,
182-
"DECIMALS": 18,
183-
"RATELIMIT": {
184-
"MAX_LIMIT": 2,
185-
"WINDOW_SIZE": 1440
186-
}
187-
},
188-
{
189-
"ID": "FERDYNET",
190-
"NAME": "FerdyFlip Testnet",
191-
"TOKEN": "FAVAX",
192-
"RPC": "https://testnet-rpc.ferdyflip.xyz/rpc",
193-
"CHAINID": 668577,
194-
"EXPLORER": "https://devnet.routescan.io",
195-
"IMAGE": "https://www.ferdyflip.xyz/images/heads.png",
196-
"MAX_PRIORITY_FEE": "10000000000",
197-
"MAX_FEE": "100000000000",
198-
"DRIP_AMOUNT": 20,
199-
"DECIMALS": 18,
200-
"RATELIMIT": {
201-
"MAX_LIMIT": 2,
202-
"WINDOW_SIZE": 1440
203-
}
204-
},
205122
{
206123
"ID": "MXSGAMES",
207124
"NAME": "MXS Games Testnet",
@@ -269,56 +186,6 @@
269186
"WINDOW_SIZE": 1440
270187
}
271188
},
272-
{
273-
"ID": "INTERSECT",
274-
"NAME": "Intersect Testnet",
275-
"TOKEN": "PEARL",
276-
"RPC": "https://subnets.avax.network/pearl/testnet/rpc",
277-
"CHAINID": 1612,
278-
"EXPLORER": "https://subnets-test.avax.network/intersect",
279-
"IMAGE": "https://raw.githubusercontent.com/UzairHannure/Subnet-Brand-Assets/main/ddd.webp",
280-
"MAX_PRIORITY_FEE": "2000000000",
281-
"MAX_FEE": "50000000000",
282-
"DRIP_AMOUNT": 5,
283-
"RATELIMIT": {
284-
"MAX_LIMIT": 3,
285-
"WINDOW_SIZE": 1440
286-
}
287-
},
288-
{
289-
"ID": "PTNEWLO",
290-
"NAME": "PTNEWLO Testnet",
291-
"TOKEN": "PTNL",
292-
"RPC": "https://subnets.avax.network/ptnewlo/testnet/rpc",
293-
"CHAINID": 57487,
294-
"EXPLORER": "https://subnets-test.avax.network/ptnewlo",
295-
"IMAGE": "https://images.ctfassets.net/gcj8jwzm6086/4wnyhip4JrhrRueC21kmUP/bfaa12a82cb2894f8316fba2827c9d58/AvaCloud_subnet_PTNEWLO.png",
296-
"MAX_PRIORITY_FEE": "100000000000",
297-
"MAX_FEE": "100000000000",
298-
"DRIP_AMOUNT": 5,
299-
"DECIMALS": 18,
300-
"RATELIMIT": {
301-
"MAX_LIMIT": 1,
302-
"WINDOW_SIZE": 1440
303-
}
304-
},
305-
{
306-
"ID": "CDEVNET",
307-
"NAME": "Devnet (C-Chain)",
308-
"TOKEN": "AVAX",
309-
"RPC": "https://etna.avax-dev.network/ext/bc/C/rpc",
310-
"CHAINID": 43117,
311-
"EXPLORER": "https://feat-etna-devnet.subnets.pages.dev/devnet-c-chain",
312-
"IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/43113/chain-logo.png",
313-
"MAX_PRIORITY_FEE": "10000000000",
314-
"MAX_FEE": "100000000000",
315-
"DRIP_AMOUNT": 5,
316-
"DECIMALS": 18,
317-
"RATELIMIT": {
318-
"MAX_LIMIT": 1,
319-
"WINDOW_SIZE": 1440
320-
}
321-
},
322189
{
323190
"ID": "ONIGIRI",
324191
"NAME": "ONIGIRI Testnet",
@@ -475,35 +342,6 @@
475342
"WINDOW_SIZE": 1440
476343
}
477344
},
478-
{
479-
"ID": "WAVAXWAGMI",
480-
"HOSTID": "WAGMI",
481-
"NAME": "Wrapped AVAX",
482-
"TOKEN": "WAVAX",
483-
"IMAGE": "https://glacier-api.avax.network/proxy/chain-assets/main/chains/43113/chain-logo.png",
484-
"CONTRACTADDRESS": "0x21cf0eB2E3Ab483a67C900b27dA8F34185991982",
485-
"DRIP_AMOUNT": 2000000000,
486-
"GASLIMIT": "150000",
487-
"DECIMALS": 18,
488-
"RATELIMIT": {
489-
"MAX_LIMIT": 1,
490-
"WINDOW_SIZE": 1440
491-
}
492-
},
493-
{
494-
"ID": "WWGM",
495-
"HOSTID": "WAGMI",
496-
"NAME": "Wrapped WAGMI",
497-
"TOKEN": "WWGM",
498-
"CONTRACTADDRESS": "0x3Ee7094DADda15810F191DD6AcF7E4FFa37571e4",
499-
"DRIP_AMOUNT": 2,
500-
"GASLIMIT": "150000",
501-
"DECIMALS": 18,
502-
"RATELIMIT": {
503-
"MAX_LIMIT": 1,
504-
"WINDOW_SIZE": 1440
505-
}
506-
},
507345
{
508346
"ID": "EUROEC",
509347
"HOSTID": "C",

middlewares/rateLimiter.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import rateLimit, { RateLimitRequestHandler } from 'express-rate-limit'
22
import { searchIP } from 'range_check'
33
import { RateLimiterConfig } from '../types'
4+
import { CouponService } from '../CouponService/couponService'
45

56
export class RateLimiter {
67
PATH: string
8+
type: 'ip' | 'wallet' | 'global'
79

8-
constructor(app: any, configs: RateLimiterConfig[], keyGenerator?: any) {
10+
constructor(
11+
app: any,
12+
configs: RateLimiterConfig[],
13+
type: 'ip' | 'wallet' | 'global',
14+
couponService: CouponService,
15+
keyGenerator?: any,
16+
) {
917
this.PATH = configs[0].RATELIMIT.PATH || '/api/sendToken'
18+
this.type = type
1019

1120
let rateLimiters: any = new Map()
1221
configs.forEach((config: any) => {
@@ -26,6 +35,17 @@ export class RateLimiter {
2635
}
2736

2837
app.use(this.PATH, (req: any, res: any, next: any) => {
38+
// skip rate limit based on coupon - coupon limit verification is done in the request handler
39+
const couponId = req.body?.couponId
40+
if (couponId) {
41+
const coupon = couponService.getCoupon(couponId)
42+
if (this.type === 'ip' && coupon && coupon.skipIpRateLimit) {
43+
return next()
44+
} else if (this.type === 'wallet' && coupon && coupon.skipWalletRateLimit) {
45+
return next()
46+
}
47+
}
48+
2949
if(this.PATH == '/api/sendToken' && req.body.chain) {
3050
return rateLimiters.get(req.body.erc20 ? req.body.erc20 : req.body.chain)(req, res, next)
3151
} else {

server.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,28 @@ if (NATIVE_CLIENT) {
4545
app.use(express.static(path.join(__dirname, "client")))
4646
}
4747

48-
new RateLimiter(app, [GLOBAL_RL])
48+
const couponService = new CouponService(couponConfig)
49+
const mainnetCheckService = new MainnetCheckService(MAINNET_BALANCE_CHECK_RPC)
50+
51+
new RateLimiter(app, [GLOBAL_RL], 'global', couponService)
4952

5053
new RateLimiter(app, [
5154
...evmchains,
5255
...erc20tokens
53-
])
56+
], 'ip', couponService)
5457

5558
// address rate limiter
5659
new RateLimiter(app, [
5760
...evmchains,
5861
...erc20tokens
59-
], (req: any, res: any) => {
62+
], 'wallet', couponService, (req: any, res: any) => {
6063
const addr = req.body?.address
6164

6265
if(typeof addr == "string" && addr) {
6366
return addr.toUpperCase()
6467
}
6568
})
6669

67-
const couponService = new CouponService(couponConfig)
68-
const mainnetCheckService = new MainnetCheckService(MAINNET_BALANCE_CHECK_RPC)
69-
7070
const captcha: VerifyCaptcha = new VerifyCaptcha(app, process.env.CAPTCHA_SECRET!, process.env.V2_CAPTCHA_SECRET!)
7171

7272
let evms = new Map<string, EVMInstanceAndConfig>()
@@ -165,6 +165,13 @@ router.post('/sendToken', captcha.middleware, async (req: any, res: any) => {
165165
return
166166
}
167167

168+
let skippedIpRateLimit, skippedWalletRateLimit;
169+
if (coupon) {
170+
const couponItem = couponService.getCoupon(coupon)
171+
skippedIpRateLimit = couponItem?.skipIpRateLimit
172+
skippedWalletRateLimit = couponItem?.skipWalletRateLimit
173+
}
174+
168175
// logging requests (if enabled)
169176
DEBUG && console.log(JSON.stringify({
170177
date: new Date(),
@@ -176,6 +183,8 @@ router.post('/sendToken', captcha.middleware, async (req: any, res: any) => {
176183
checkPassedType: pipelineValidity.checkPassedType,
177184
dripAmount: pipelineValidity.dripAmount,
178185
mainnetBalance: pipelineValidity.mainnetBalance,
186+
skippedIpRateLimit,
187+
skippedWalletRateLimit,
179188
ip: req.headers["cf-connecting-ip"] || req.ip
180189
}))
181190

0 commit comments

Comments
 (0)