Skip to content

Usage log: the next write to the same day file skips the read - #64

Merged
Fizzl13 merged 39 commits into
mainfrom
claude/x402-agents-solana-payments-nceg9b
Oct 14, 2026
Merged

Fizzl13 merged 39 commits into
mainfrom
claude/x402-agents-solana-payments-nceg9b

Conversation

@Fizzl13

@Fizzl13 Fizzl13 commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Render billed 68.5 GB of outbound bandwidth by 14 Oct against 5 GB included ($0.15/GB beyond that). The usage log is most of it.

Measured from the usage-log commits of the last 24 hours, each write downloads the whole day file and uploads it again. That came to about 0.35 GB a day for Doctor, 0.42 GB for presign-guard and 0.27 GB for Ichimoku. PlainText was 1.95 GB a day until SmartContractExplainer#63.

Change. Only a service itself writes its own day files. The writer now keeps what it last wrote and that content's git blob sha. The next append to the same file skips the read, which removes the download half of each write: roughly 43% of a write's traffic, since the upload is base64.

If the file did change in between, for example when a deploy's old and new instance both write, GitHub refuses the stale sha with a 409. The writer then drops what it kept and reads the file again, as before. A new day's file (a different path) is read normally.

Same code as x402-doctor's lib/usage-log.js. New test in test/usage.test.js: one read then writes only, and a file changed elsewhere is read again without losing a line (fails on the old code). npm test: 34/34 pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48


Generated by Claude Code

claude added 30 commits October 4, 2026 16:57
mpp-pay from x402-doctor (#139): the agent sends USDC.e on Tempo with the MPP
memo and answers with the transaction hash, checked on a Tempo RPC; the 402
then carries an evm and a tempo challenge and /openapi.json lists both offers.
Off until MPP_TEMPO_RECIPIENT is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
A 402 with a payment that was refused now logs network (from the x402
payment header, or the MPP credential's method: evm = base, tempo = Tempo),
so the usage log can show which network traffic comes from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
On mainnet the paid routes also accept the same dollar price in RLUSD on
the XRP Ledger, to XRPL_PAY_TO (default Frits's account with the RLUSD
trust line; XRPL_PAY_TO=off turns it off), verified and submitted by an
in-process facilitator from @x402/xrpl, as in the other Fizzl services.
The usage log counts RLUSD in dollars.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
Agents built on t54's x402-xrpl / Xrpl.X402 SDK (most of the XRPL AI Hub)
sign with the x402 SourceTag 804681468, bind the invoice in Memos and send
{ signedTxBlob, invoiceId }. @x402/xrpl refuses Memos, so those payments now
go to t54's hosted facilitator, which verified and settled such a payment
on testnet; standard @x402/xrpl payments stay in-process (also re-tested on
testnet with the SourceTag in the requirements). Every XRPL accept now
carries extra.sourceTag. XRPL_T54_URL=off keeps everything local.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
Google crawled the homepage but has not indexed it yet; these give it
a clear signal which URL to index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
A refused MPP credential is logged as payment_failed with protocol mpp;
it now also carries reason (the problem detail sent back, max 160
characters), so a client that can't pay us shows what went wrong.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
With ALGORAND_PAY_TO set (an Algorand account with the USDC opt-in), both
paid routes also offer the same dollar price in USDC (ASA 31566704) on
Algorand mainnet, settled by GoPlausible's public facilitator, which pays
the Algorand fee. It sits before the URL facilitator and offers only
Algorand, so PayAI never claims it. Off without the variable or with "off".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
GoPlausible's x402 catalog reads the x402-merchant extension (name,
website, logo, categories) when it settles a payment and shows it on the
merchant, one per pay-to address. Both paid routes now carry it, the same
on every fizzl.eu service.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
check_wallet_approvals and explain_approval declare an outputSchema
(verdict, explanation) and answer with structuredContent next to the
same "VERDICT: explanation" text (which the usage log reads); feedback
does too. Extra fields are allowed, so new ones never break a call.
MCP directories such as Smithery score output schemas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
The XRPL AI Hub (xrpl-ai.org) ingests /.well-known/x402 and lists each
resource by its name and description; without them it shows the long
402 text. The manifest moves to well-known.js, with resources as
{ url, method, name, description } objects (the endpoints list stays)
and a description that names the payment options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
HSTS (one year), nosniff, a Referrer-Policy and X-Frame-Options, and no
x-powered-by header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
Dependabot alerts are off here, so a new advisory like this week's MCP SDK one
went unnoticed. Every Monday npm audit now checks PlainText's production
dependencies. A high or critical finding the server can reach opens one issue
labelled dependency-audit (GitHub emails the owner) or adds the new list to
the open one, and the next clean run closes it.
Checked against the lockfile from before the MCP SDK update: it reports
exactly that advisory, and nothing on the current lockfile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
A refused MPP credential was logged with its reason, a refused x402 payment
only as payment_failed. The 402 that answers it already says why:
PAYMENT-REQUIRED carries error when verification failed, PAYMENT-RESPONSE
carries errorReason when settlement failed. The log now keeps that as
reason (first line only, URLs blanked so an RPC error can never put a keyed
URL in the log). All 32 tests pass, including a new one that fails without
this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
…ay file raw

PlainText wrote the usage log once per call: 2,700 commits a day, each
downloading and re-uploading the whole day file. That is about 1.9 GB a day
of Render bandwidth, billed above the 5 GB the plan includes. It now batches
like the other services: one write per 10 minutes (USAGE_LOG_FLUSH_MS), the
queue written first on SIGTERM (a deploy or the free plan going to sleep).

The day file is also read raw, as in the other services: the JSON form has
no content for files over 1 MB, which would have overwritten the day with
only the new lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
Every write downloaded the whole day file before uploading it again. Only
this service writes its own files, so it now keeps what it last wrote and
its sha, and the next append to the same file skips the read: about half the
Render bandwidth of each write. If the file did change (a deploy's old and new
instance both writing), GitHub refuses the stale sha with a 409 and the file
is read again, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsD5haPKxeWjYmErHzvU48
@Fizzl13
Fizzl13 merged commit 38ea586 into main Oct 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants