diff --git a/packages/app/server/src/env.ts b/packages/app/server/src/env.ts index cc993ae07..d09a4d9d2 100644 --- a/packages/app/server/src/env.ts +++ b/packages/app/server/src/env.ts @@ -12,7 +12,7 @@ export const env = createEnv({ // Server PORT: z.coerce.number().default(3069), NODE_ENV: z - .enum(['development', 'production', 'staging', "test"]) + .enum(['development', 'production', 'staging', 'test']) .default('development'), // Network & Blockchain @@ -50,8 +50,12 @@ export const env = createEnv({ GOOGLE_SERVICE_ACCOUNT_KEY_ENCODED: z.string().optional(), // API Keys - Echo - API_KEY_HASH_SECRET: z.string().default('change-this-in-production-very-secret-key'), - API_ECHO_ACCESS_JWT_SECRET: z.string().default('api-jwt-secret-change-in-production'), + API_KEY_HASH_SECRET: z + .string() + .default('change-this-in-production-very-secret-key'), + API_ECHO_ACCESS_JWT_SECRET: z + .string() + .default('api-jwt-secret-change-in-production'), // Echo Configuration ECHO_MARKUP: z.string().default('1.25'), diff --git a/packages/app/server/src/handlers/settle.ts b/packages/app/server/src/handlers/settle.ts index 935797c3a..bb262b259 100644 --- a/packages/app/server/src/handlers/settle.ts +++ b/packages/app/server/src/handlers/settle.ts @@ -42,6 +42,12 @@ export async function settle( try { xPaymentData = validateXPaymentHeader(headers, req); } catch (error) { + logger.error('Payment header validation failed in settle', { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + url: req.url, + method: req.method, + }); buildX402Response(req, res, maxCost); return undefined; } @@ -92,6 +98,19 @@ export async function settle( const settleResult = await facilitatorClient.settle(settleRequest); if (!settleResult.success || !settleResult.transaction) { + logger.error('Payment settlement failed', { + errorReason: settleResult.errorReason, + payer: settleResult.payer, + paymentAmount: paymentAmount, + nonce: payload.authorization.nonce, + signature: payload.signature.substring(0, 20) + '...', // Log first 20 chars for debugging + from: payload.authorization.from, + to: payload.authorization.to, + validAfter: payload.authorization.validAfter, + validBefore: payload.authorization.validBefore, + url: req.url, + method: req.method, + }); buildX402Response(req, res, maxCost); return undefined; } diff --git a/packages/app/server/src/services/facilitator/facilitatorProxy.ts b/packages/app/server/src/services/facilitator/facilitatorProxy.ts index 5ac3e1a72..cc4e805ac 100644 --- a/packages/app/server/src/services/facilitator/facilitatorProxy.ts +++ b/packages/app/server/src/services/facilitator/facilitatorProxy.ts @@ -4,6 +4,7 @@ import { PaymentRequirements, SettleResponse, VerifyResponse, + ExactEvmPayload, } from './x402-types'; import logger, { logMetric } from '../../logger'; import dotenv from 'dotenv'; @@ -64,12 +65,10 @@ export async function facilitatorProxy< payload: PaymentPayload, paymentRequirements: PaymentRequirements ): Promise { - if (!PROXY_FACILITATOR_URL) { throw new Error('PROXY_FACILITATOR_URL is not set'); } - logMetric('facilitator_proxy_attempt', 1, { method, }); @@ -96,6 +95,59 @@ export async function facilitatorProxy< ); if (res.status !== 200) { + let errorBody: string | undefined; + try { + errorBody = await res.text(); + } catch (e) { + // Ignore errors reading response body + } + + // Extract payment details for logging + const paymentDetails: Record = { + scheme: payload.scheme, + network: payload.network, + x402Version: payload.x402Version, + }; + + // Extract signature and nonce if it's an ExactEvmPayload + if ( + 'payload' in payload && + payload.payload && + typeof payload.payload === 'object' + ) { + const evmPayload = payload.payload as ExactEvmPayload; + if ( + 'signature' in evmPayload && + typeof evmPayload.signature === 'string' + ) { + paymentDetails.signature = + evmPayload.signature.substring(0, 20) + '...'; + } + if ( + 'authorization' in evmPayload && + evmPayload.authorization && + typeof evmPayload.authorization === 'object' + ) { + if ('nonce' in evmPayload.authorization) { + paymentDetails.nonce = evmPayload.authorization.nonce; + } + if ('from' in evmPayload.authorization) { + paymentDetails.from = evmPayload.authorization.from; + } + if ('to' in evmPayload.authorization) { + paymentDetails.to = evmPayload.authorization.to; + } + } + } + + logger.error('Facilitator proxy returned non-200 status', { + method, + status: res.status, + statusText: res.statusText, + errorBody: errorBody?.substring(0, 1000), // Limit to first 1000 chars + paymentPayload: paymentDetails, + }); + logMetric('facilitator_proxy_failure', 1, { method, status: res.status, diff --git a/packages/app/server/src/types.ts b/packages/app/server/src/types.ts index e6febc9d0..b42182411 100644 --- a/packages/app/server/src/types.ts +++ b/packages/app/server/src/types.ts @@ -163,7 +163,7 @@ type TokenAmount = string; type Url = string; type Nonce = string; -interface ExactEvmPayloadAuthorization { +export interface ExactEvmPayloadAuthorization { from: Address; to: Address; value: TokenAmount;