diff --git a/src/config/index.ts b/src/config/index.ts index 1e10db8..30b8bce 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -21,6 +21,21 @@ export function loadConfig(override = true): void { loadConfig(false); +export const RPC_429_USER_MESSAGE = + "Your RPC endpoint is returning too many requests (HTTP 429). Please configure your own private RPC URL via RPC_URL and try again."; + +/** + * Returns true when Satellite is using the default (public) RPC URL, + * i.e. when the user has NOT provided a custom RPC_URL override. + */ +export function isUsingPublicRpc(): boolean { + return !process.env.RPC_URL || process.env.RPC_URL.trim().length === 0; +} + +export function isRpc429Error(err: unknown): boolean { + return (err as any)?.response?.status === 429; +} + export function getRuntimeConfig() { return { get API_KEY() { diff --git a/src/domain/portal/publish.ts b/src/domain/portal/publish.ts index 4ef9bca..b1a3ede 100644 --- a/src/domain/portal/publish.ts +++ b/src/domain/portal/publish.ts @@ -9,7 +9,7 @@ import { generateKeyPairFromSeed } from "@stablelib/ed25519"; import * as ucans from "@ucans/ucans"; import { AgentClient } from "../../sdk/smart-agent"; import { FileManager } from "../../sdk/file-manager"; -import { getRuntimeConfig } from "../../config"; +import { getRuntimeConfig, isUsingPublicRpc, isRpc429Error, RPC_429_USER_MESSAGE } from "../../config"; import type { PublishResult } from "../../types"; @@ -104,6 +104,12 @@ export const handleExistingFileOp = async (fileId: string, operation: "update" | return executeOperation(fileManager, file, operation); } catch (error: any) { + if (isUsingPublicRpc() && isRpc429Error(error)) { + // For public RPCs, map HTTP 429 into a clear user-facing message. + logger.error(`Failed to publish file ${fileId}: ${RPC_429_USER_MESSAGE}`); + throw new Error(RPC_429_USER_MESSAGE); + } + logger.error(`Failed to publish file ${fileId}:`, error); throw error; } diff --git a/src/infra/worker/eventProcessor.ts b/src/infra/worker/eventProcessor.ts index 65b6c12..5bee74f 100644 --- a/src/infra/worker/eventProcessor.ts +++ b/src/infra/worker/eventProcessor.ts @@ -1,4 +1,4 @@ -import { getRuntimeConfig } from "../../config"; +import { getRuntimeConfig, isUsingPublicRpc, isRpc429Error, RPC_429_USER_MESSAGE } from "../../config"; import { handleNewFileOp, getProxyAuthParams, handleExistingFileOp } from "../../domain/portal"; import { FilesModel, EventsModel } from "../database/models"; import type { Event, ProcessResult, UpdateFilePayload } from "../../types"; @@ -31,8 +31,11 @@ export const processEvent = async (event: Event): Promise => { } catch (error) { const normalized = normalizeRateLimitError(error); if (normalized instanceof RateLimitError) throw normalized; - const errorMsg = error instanceof Error ? error.message : String(error); - logger.error(`Error processing ${type} event for file ${fileId}:`, errorMsg); + let errorMsg = error instanceof Error ? error.message : String(error); + if (isUsingPublicRpc() && isRpc429Error(error)) { + errorMsg = RPC_429_USER_MESSAGE; + } + logger.error(`Error processing ${type} event for file ${fileId}: ${errorMsg}`); return { success: false, error: errorMsg }; } }; diff --git a/src/sdk/smart-agent.ts b/src/sdk/smart-agent.ts index e13d2b9..f58824a 100644 --- a/src/sdk/smart-agent.ts +++ b/src/sdk/smart-agent.ts @@ -81,19 +81,15 @@ export class AgentClient { request: IExecuteUserOperationRequest | IExecuteUserOperationRequest[], customGasLimit?: number, ) { - try { - const smartAccountAgent = this.getSmartAccountAgent(); - - const callData = await this.getCallData(request); - - return await smartAccountAgent.sendUserOperation({ - callData, - callGasLimit: BigInt(customGasLimit || this.MAX_CALL_GAS_LIMIT), - nonce: getNonce(), - }); - } catch (error) { - throw error; - } + const smartAccountAgent = this.getSmartAccountAgent(); + + const callData = await this.getCallData(request); + + return await smartAccountAgent.sendUserOperation({ + callData, + callGasLimit: BigInt(customGasLimit || this.MAX_CALL_GAS_LIMIT), + nonce: getNonce(), + }); } async executeUserOperationRequest(