Files
claw-ts-worker/src/services/x402/tracker.ts
T
nirholas c0b205208d Add Dockerfile and Vercel integration for MCP server
 Implement x402 payment handling in WebFetchTool
📝 Refactor count increment logic in X402 payment tracker
 Introduce feature flag management in Bun build
 Create macro for package versioning and issue reporting
 Add preload script for Bun bundler features
2026-03-31 10:45:43 +00:00

82 lines
2.4 KiB
TypeScript

/**
* x402 Payment Tracker
*
* Tracks x402 payments within a session for cost display and safety limits.
* Integrates with the main cost tracking system.
*/
import chalk from 'chalk'
import { logForDebugging } from '../../utils/debug.js'
import { formatNumber } from '../../utils/format.js'
import type { X402PaymentRecord } from './types.js'
/** In-memory payment records for the current session */
let sessionPayments: X402PaymentRecord[] = []
let sessionTotalUSD = 0
/** Add a payment record to the session tracker */
export function addX402Payment(record: X402PaymentRecord): void {
sessionPayments.push(record)
sessionTotalUSD += record.amountUSD
logForDebugging(
`[x402] Session total: $${sessionTotalUSD.toFixed(4)} (${sessionPayments.length} payments)`,
)
}
/** Get total USD spent via x402 in the current session */
export function getX402SessionSpentUSD(): number {
return sessionTotalUSD
}
/** Get all payment records for the current session */
export function getX402SessionPayments(): readonly X402PaymentRecord[] {
return sessionPayments
}
/** Get the count of payments in this session */
export function getX402PaymentCount(): number {
return sessionPayments.length
}
/** Reset session payment tracking (used on session switch) */
export function resetX402SessionPayments(): void {
sessionPayments = []
sessionTotalUSD = 0
}
/** Format x402 payment summary for display */
export function formatX402Cost(): string {
if (sessionPayments.length === 0) {
return ''
}
const lines: string[] = []
lines.push(
`x402 payments: $${sessionTotalUSD.toFixed(4)} (${sessionPayments.length} ${sessionPayments.length === 1 ? 'payment' : 'payments'})`,
)
// Group by resource domain
const byDomain: Record<string, { count: number; totalUSD: number }> = {}
for (const payment of sessionPayments) {
try {
const domain = new URL(payment.resource).hostname
if (!byDomain[domain]) {
byDomain[domain] = { count: 0, totalUSD: 0 }
}
byDomain[domain].count += 1
byDomain[domain].totalUSD += payment.amountUSD
} catch {
// Skip malformed URLs
}
}
for (const [domain, stats] of Object.entries(byDomain)) {
lines.push(
`${domain}:`.padStart(21) +
` ${formatNumber(stats.count)} ${stats.count === 1 ? 'request' : 'requests'} ($${stats.totalUSD.toFixed(4)})`,
)
}
return chalk.dim(lines.join('\n'))
}