From c0b10ff019d4a1263e251bf7766c6814a02fddd0 Mon Sep 17 00:00:00 2001 From: GabrielMartinezRodriguez Date: Tue, 6 May 2025 16:02:52 +0200 Subject: [PATCH 1/3] feat(faucet): limt one tiem per window, increase quantity, and send pending time in case of rate limit error --- apps/faucet/package.json | 9 +++++---- apps/faucet/src/env.ts | 1 - apps/faucet/src/errors.ts | 10 ++++++++-- apps/faucet/src/faucet-usage.repository.ts | 7 ++++++- apps/faucet/src/services/faucet.ts | 7 +++++-- bun.lock | 1 + 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/faucet/package.json b/apps/faucet/package.json index 095f045722..8cd7edd00c 100644 --- a/apps/faucet/package.json +++ b/apps/faucet/package.json @@ -14,13 +14,14 @@ "@happy.tech/txm": "workspace:0.1.0", "@hono/node-server": "^1.13.8", "@scalar/hono-api-reference": "^0.5.175", + "better-sqlite3": "^11.7.0", "hono": "^4.7.2", "hono-openapi": "^0.4.4", - "neverthrow": "^8.2.0", - "zod": "^3.23.8", - "better-sqlite3": "^11.7.0", "kysely": "^0.27.5", - "viem": "^2.21.53" + "ms": "^2.1.3", + "neverthrow": "^8.2.0", + "viem": "^2.21.53", + "zod": "^3.23.8" }, "devDependencies": { "@happy.tech/happybuild": "workspace:0.1.1", diff --git a/apps/faucet/src/env.ts b/apps/faucet/src/env.ts index 1871d80308..1971bfeb63 100644 --- a/apps/faucet/src/env.ts +++ b/apps/faucet/src/env.ts @@ -24,7 +24,6 @@ const envSchema = z.object({ TOKEN_AMOUNT: z.string().transform((s) => BigInt(s)), FAUCET_DB_PATH: z.string().trim(), FAUCET_RATE_LIMIT_WINDOW_SECONDS: z.string().transform((s) => Number(s)), - FAUCET_RATE_LIMIT_MAX_REQUESTS: z.string().transform((s) => Number(s)), }) const parsedEnv = envSchema.safeParse(process.env) diff --git a/apps/faucet/src/errors.ts b/apps/faucet/src/errors.ts index 9a0892f262..07a0002cb6 100644 --- a/apps/faucet/src/errors.ts +++ b/apps/faucet/src/errors.ts @@ -1,4 +1,5 @@ import type { ContentfulStatusCode } from "hono/utils/http-status" +import ms from "ms" export abstract class HappyFaucetError extends Error { public readonly statusCode: ContentfulStatusCode @@ -23,8 +24,13 @@ export class FaucetFetchError extends HappyFaucetError { } export class FaucetRateLimitError extends HappyFaucetError { - constructor(message?: string, options?: ErrorOptions) { - super(429, message || "Rate limit exceeded", options) + constructor(timeToWait: number, message?: string, options?: ErrorOptions) { + super( + 429, + message || + "Rate limit exceeded, please wait " + ms(timeToWait, { long: true }) + " before requesting again", + options, + ) } } diff --git a/apps/faucet/src/faucet-usage.repository.ts b/apps/faucet/src/faucet-usage.repository.ts index 20a09ae6e0..e5ca2fed94 100644 --- a/apps/faucet/src/faucet-usage.repository.ts +++ b/apps/faucet/src/faucet-usage.repository.ts @@ -28,7 +28,12 @@ export class FaucetUsageRepository { async findAllByAddress(address: Address): Promise> { const result = await ResultAsync.fromPromise( - db.selectFrom("faucetUsage").selectAll().where("address", "=", address).execute(), + db + .selectFrom("faucetUsage") + .selectAll() + .where("address", "=", address) + .orderBy("occurredAt", "desc") + .execute(), unknownToError, ) diff --git a/apps/faucet/src/services/faucet.ts b/apps/faucet/src/services/faucet.ts index b445056406..7642d8c3d0 100644 --- a/apps/faucet/src/services/faucet.ts +++ b/apps/faucet/src/services/faucet.ts @@ -29,8 +29,11 @@ export class FaucetService { if (faucetUsageResult.isErr()) { return err(faucetUsageResult.error) } - if (faucetUsageResult.value.length >= env.FAUCET_RATE_LIMIT_MAX_REQUESTS) { - return err(new FaucetRateLimitError()) + if (faucetUsageResult.value.length >= 1) { + const lastRequest = faucetUsageResult.value[0] + const timeToWait = + lastRequest.occurredAt.getTime() + env.FAUCET_RATE_LIMIT_WINDOW_SECONDS * 1000 - Date.now() + return err(new FaucetRateLimitError(timeToWait)) } const faucetUsage = FaucetUsage.create(address) diff --git a/bun.lock b/bun.lock index aa2566b864..b5fb28c0b3 100644 --- a/bun.lock +++ b/bun.lock @@ -59,6 +59,7 @@ "hono": "^4.7.2", "hono-openapi": "^0.4.4", "kysely": "^0.27.5", + "ms": "^2.1.3", "neverthrow": "^8.2.0", "viem": "^2.21.53", "zod": "^3.23.8", From ff980be89e930b911e4ca24b44e165eeb7c1d820 Mon Sep 17 00:00:00 2001 From: GabrielMartinezRodriguez Date: Mon, 12 May 2025 12:41:11 +0200 Subject: [PATCH 2/3] chore(faucet): remove ms dependecy and create our own formatMs function --- apps/faucet/package.json | 1 - apps/faucet/src/errors.ts | 5 ++--- bun.lock | 1 - support/common/lib/index.ts | 1 + support/common/lib/utils/time.ts | 18 ++++++++++++++++++ 5 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 support/common/lib/utils/time.ts diff --git a/apps/faucet/package.json b/apps/faucet/package.json index 8cd7edd00c..19662a5811 100644 --- a/apps/faucet/package.json +++ b/apps/faucet/package.json @@ -18,7 +18,6 @@ "hono": "^4.7.2", "hono-openapi": "^0.4.4", "kysely": "^0.27.5", - "ms": "^2.1.3", "neverthrow": "^8.2.0", "viem": "^2.21.53", "zod": "^3.23.8" diff --git a/apps/faucet/src/errors.ts b/apps/faucet/src/errors.ts index 07a0002cb6..2a62cc270a 100644 --- a/apps/faucet/src/errors.ts +++ b/apps/faucet/src/errors.ts @@ -1,5 +1,5 @@ +import { formatMs } from "@happy.tech/common" import type { ContentfulStatusCode } from "hono/utils/http-status" -import ms from "ms" export abstract class HappyFaucetError extends Error { public readonly statusCode: ContentfulStatusCode @@ -27,8 +27,7 @@ export class FaucetRateLimitError extends HappyFaucetError { constructor(timeToWait: number, message?: string, options?: ErrorOptions) { super( 429, - message || - "Rate limit exceeded, please wait " + ms(timeToWait, { long: true }) + " before requesting again", + message || "Rate limit exceeded, please wait " + formatMs(timeToWait, true) + " before requesting again", options, ) } diff --git a/bun.lock b/bun.lock index b5fb28c0b3..aa2566b864 100644 --- a/bun.lock +++ b/bun.lock @@ -59,7 +59,6 @@ "hono": "^4.7.2", "hono-openapi": "^0.4.4", "kysely": "^0.27.5", - "ms": "^2.1.3", "neverthrow": "^8.2.0", "viem": "^2.21.53", "zod": "^3.23.8", diff --git a/support/common/lib/index.ts b/support/common/lib/index.ts index b180ba948e..576efd2b60 100644 --- a/support/common/lib/index.ts +++ b/support/common/lib/index.ts @@ -93,6 +93,7 @@ export { stringify } from "./utils/string" export { throttle } from "./utils/throttle" export { getUrlProtocol } from "./utils/urlProtocol" export { type UUID, createUUID } from "./utils/uuid" +export { formatMs } from "./utils/time" // === DATA ======================================================================================== diff --git a/support/common/lib/utils/time.ts b/support/common/lib/utils/time.ts new file mode 100644 index 0000000000..65c42e347c --- /dev/null +++ b/support/common/lib/utils/time.ts @@ -0,0 +1,18 @@ +const unit = { + ms: 1, + s: 1e3, + m: 60e3, + h: 3_600e3, + d: 86_400e3, + } as const; + +export function formatMs(ms: number, long = false): string { + const abs = Math.abs(ms); + for (const [abbr, size] of Object.entries(unit).reverse()) { + if (abs >= size) { + const val = Math.round(ms / size); + return long ? `${val} ${abbr}${Math.abs(val) !== 1 ? 's' : ''}` : `${val}${abbr}`; + } + } + return `${ms}ms`; +} \ No newline at end of file From eea769ed5d5090f1cab8f8fd18416e5e67d801c5 Mon Sep 17 00:00:00 2001 From: GabrielMartinezRodriguez Date: Wed, 21 May 2025 10:56:37 +0200 Subject: [PATCH 3/3] chore(facuet): added if to check that time to wait in greather than 0 --- apps/faucet/src/services/faucet.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/faucet/src/services/faucet.ts b/apps/faucet/src/services/faucet.ts index 7642d8c3d0..160ee2fede 100644 --- a/apps/faucet/src/services/faucet.ts +++ b/apps/faucet/src/services/faucet.ts @@ -33,7 +33,10 @@ export class FaucetService { const lastRequest = faucetUsageResult.value[0] const timeToWait = lastRequest.occurredAt.getTime() + env.FAUCET_RATE_LIMIT_WINDOW_SECONDS * 1000 - Date.now() - return err(new FaucetRateLimitError(timeToWait)) + + if (timeToWait > 0) { + return err(new FaucetRateLimitError(timeToWait)) + } } const faucetUsage = FaucetUsage.create(address)