Store the CDP auth key as a crypto.Signer interface so ECDSA and Ed25519 keys
share one signing path, and detect raw base64 Ed25519 keys inline instead of a
separate helper.
persistSettlement now returns the write error instead of swallowing it, and
both the HTTP and MCP settlement hooks log failures instead of silently
dropping them. Also remove the dead function_runtime_ms webhook field and a
stale log value.
Simplify the payable-tool registration API by merging
AddPayableToolWithResolver into AddPayableTool, and thread a configurable
MaxTimeoutSeconds plus Extra through the payment options. getStyleProfile
keeps its 600s advertise timeout.
Extract ActivityPub outbox walk into internal/activitypub/outbox.go with
webfinger/actor resolution, Create/Note filtering and IRI dereferencing.
Fix searchStatus returning wrong post, nil-account panic in SearchActors,
@local handle resolution, empty Author via noteToInternalPost, protocol-
relative URL handling, pagination loops, limit clamping (200) and cacheErr
masking. Add per-host token-bucket throttle (1/s, cross-host concurrency)
in throttle.go and background GTS warming via search?resolve for outbox-
fetched posts without following remotes. Align AGENTS.md.
Co-Authored-By: internal-model
read/write race on xrpc.Client.Auth: Do() reads client.Auth unlocked while
refreshSession mutated AccessJwt/RefreshJwt in place (only refreshMu serialized
refreshes, not concurrent request readers).
- add authMu RWMutex; every request now uses a clientSnapshot() copy that pins
the current AuthInfo under the read lock, so requests never touch Auth directly
- refreshSession replaces client.Auth with a fresh immutable AuthInfo under the
exclusive lock instead of mutating the old one in place
- remove the now-obsolete 'benign race' comment; lock order refreshMu -> authMu
- replace http.DefaultClient with a 60s-timeout client and send a
User-Agent header
- retry 5xx and transport errors with the same ctx-aware backoff used
for 429s; drop the unreachable trailing return and defer-in-loop body
- resolveUser returns the username; posts carry the handle (was numeric
ID), error on 200-with-empty-data, and skip empty-ID tweets
- remove the lower limit floor so small limits pass through
- AGENTS.md: refresh the stale twitter clamp/backoff gotcha
- embed posts in a bounded worker pool while preserving index order,
with per-post error context and transient (429/5xx) retry backoff
- SummarizeStyle errors when the stream ends before a done chunk or the
model truncates at its context limit (done_reason length) instead of
returning a partial summary for a payable tool
- New() drops the unused ctx param and names its model params; callers
in main.go updated
- budget relay queries across a shared 20s deadline (5s per relay) and
dedupe events by ID across relays
- surface 'could not connect to any relay' when no query succeeded and
abort early on relay-connect and stream-cancel errors
- guard zero/negative limits across all fetchers
- GetActor falls through relays and rejects empty profile metadata;
SearchActors propagates relay errors instead of silently returning none
- NewNostrClient validates relay ws://wss:// schemes, trims, and dedupes
- TTL-cache DID/actor lookups (24h) under a dedicated mutex, with a
separate refresh lock so auth refreshes never fight the cache
- clamp zero/excessive limits (maxBskyFeedLimit, maxAuthorFeedPages)
and stop timeline pagination early once a bound or applimit is hit
- restrict feed URIs to http(s) and validate AT-URIs in GetPost
- derive the profile URL from the handle instead of the raw URI
- paginate GetLatestNPosts up to the requested limit (40/page, the
Mastodon API cap) instead of relying on silent server clamping
- cap GetPostsInTimeframe at maxTimeframePages to stop runaway paging
- bound response bodies with a 1 MB LimitReader
- short-circuit on a canceled ctx between pages and guard limit <= 0
Drop the redundant Post.Text/internalPost.Text fields and complete the
rename in server converters and test fixtures so the tree builds again.
- types.go: remove Text from Post and internalPost; toPost only writes
Author when the account acct is present
- posts_convert.go: apPostsPtrToItems / apPostsToAnalyticsItems read Content
- server tests: drop duplicate Text literal fields (values identical to Content)
- Add Server.Shutdown() method delegating to X402Server
- Wire main-server graceful shutdown with 15s bounded timeout
- Derive long-lived clients (bluesky, ollama) from backgroundCtx instead of
signal context to prevent poisoned sessions on SIGTERM during startup
- Remove empty errgroup goroutine that only logged on cancellation
- Bind readyz sidecar to configurable READYZ_ADDRESS (default 127.0.0.1)
instead of 0.0.0.0
- Validate port strings (ReadyzPort, MetricsPort, ListenPort) at config load
- Fix ready-check Ollama probe: use defer for body drain, guard nil resp
- nostr GetPost: track whether any relay was reached so an unreachable
relay set surfaces 'could not connect to any relay' (consistent with
GetLatestNPosts/GetPostsInTimeframe) instead of a misleading
'post not found'.
- ollama SummarizeStyle: surface ctx cancellation after Generate when
the Ollama stream reader closes cleanly on a canceled context,
instead of returning a truncated summary.
Extend x402 actual-cost settlement beyond getStyleProfile to every
Twitter-capable tool (findSimilarUsers, getEngagementMetrics,
getPostingPatterns, getTopicModeling, getSentimentTrends,
getEngagementDrivers). Each carries a twitterPostCount (or source
platform + post count) in its response so settlement passes on the
realized Twitter API lookup and per-post costs.
- tools: payableDef uses UsesTwitter + TwitterPostCap instead of a
fixed Scheme; registerTools advertises upto at the cap-derived
maximum when Twitter is enabled, otherwise exact at the flat base
price.
- x402: export pricing constants and MaxTwitterCostMicro /
FormatPriceMicro / MicroFromPriceString helpers; add
AddPayableToolWithResolver and a per-tool settleResolvers map used
by both the MCP wrapper and REST override; buildSchemeConfigs
registers only the schemes declared in a tool's requirements.
- analytics: default aggregates include Twitter only when enabled;
result count recorded before caching and returned as twitterPostCount.
- server/twitterEnabled gates the upto switch (config bearer token).
The MCP PaymentWrapper verifies against the low-level x402 resource server,
whose facilitatorClients map is only populated by Initialize(), which was
never called - paid MCP calls failed with no_facilitator_for_network once
the amount fix let clients reach server-side verification. INITIALIZE the
resource server at construction (best-effort, warn-only) and retry on every
inbound request via receiving middleware until it succeeds once. 10s bounded
timeout per attempt; after first success the map persists for the process
lifetime so later facilitator outages self-heal without a restart.
MCP clients failed to create payments with invalid_exact_evm_client_amount:
0.01 because the PaymentWrapper advertised raw dollar prices as amounts,
while REST re-runs prices through BuildPaymentRequirementsFromOptions.
Mirror that conversion for the MCP Accepts at registration time; keep the
REST PaymentOption with the raw price to avoid double conversion.
- Register only upto scheme for getStyleProfile (variable pricing)
- Add debug logging to settlement handler (scheme, override amount)
- Add debug logging to error handler (settlement overrides header)
- Add debug logging to verifyErrorCapture (payment signature, overrides)
- Add debug logging to handleRESTCall (actual cost override)
This will help trace why the facilitator returns 'invalid_exact_evm_payload_authorization_value'
when upto scheme is configured for getStyleProfile
- Added Scheme field to payableDef struct in tools.go
- getStyleProfile now uses 'upto' scheme (variable pricing: base + per-post)
- Modified buildRequirements in settlement.go to accept scheme parameter
- Default scheme is 'exact' for backwards compatibility
- getStyleProfile uses 'upto' scheme to match its variable pricing model
(base /bin/bash.10 + /bin/bash.01 user lookup + /bin/bash.005 per post)
- This fixes 'invalid_exact_evm_payload_authorization_value' error from facilitator
which expected upto scheme for variable pricing
- Changed 'actor' to 'author' in schemaExtra input BodyFields for:
- getPost
- getLatestPosts
- getPostsInTime
- getStyleProfile
- findSimilarUsers
- These now match the actual request struct field names (Author)
- Output BodyFields remain unchanged (still use 'actor' for response)
- internal/x402/x402.go: Added 120s timeout to nethttp.Config for payment middleware
(was defaulting to 30s, which was too short for Ollama embeddings)
- cmd/server/main.go: Created custom HTTP client with 120s timeout for Ollama client
- Both changes ensure slow tools (getStyleProfile, findSimilarUsers) complete without timeout
- Proper key type detection: Ed25519 (base64, 64 bytes) vs EC (PEM)
- Fixed PKCS8 fallback - removed incorrect PKCS8->EdDSA path for EC keys
- Added 'iat' claim to match official SDK
- Added base64 import for Ed25519 key decoding
- Return proper errors instead of ignoring them in GetAuthHeaders
- Added unit tests for EC/Ed25519 keys, token generation, and auth headers
- x402: wrap payment middleware to log verify failures (decoded PAYMENT-REQUIRED with scheme/network/asset/amount/payTo per accept) and settlement errors
- x402: add verifyErrorCapture and decodePaymentRequiredHeader for structured error logging
- start-test-server.sh: build binary first (go build -o /tmp/mcpserver), run with output redirected to log file, wait 2s on shutdown for log flush
- workflow: Cleanup uses kill -TERM + sleep 2 to let server flush logs
- main.go: defer log.Close() to flush slog handlers on shutdown
- All tests pass, gofmt/go vet clean
- x402: add detailed settlement success/error logging per tool (network, tx, payer, amount) and ErrorHandler for settlement failures that currently return a new 402
- server_init: log persisting HTTP and MCP settlements with transaction details
- workflow: dump /tmp/mcpserver-test.log on failure and tail 500 lines on always for debugging Base Sepolia exact/upto flows
The HTTP middleware in AddPayableTool was hardcoded to only register
the exact scheme for eip155:8453 (Base mainnet), while the MCP handler
registered both exact and upto schemes for all networks from ACTIVE_NETWORKS.
This mismatch caused 'no scheme server for exact on eip155:84532' errors
when testing with Sepolia.
Changes:
- Added networks field to X402Server struct to store active networks
- Store networks in NewX402Server constructor
- In AddPayableTool, dynamically register both exact and upto EVM
schemes for all configured networks instead of hardcoding eip155:8453
- Updated tests to pass correct network configuration
- x402.NewX402Server now accepts networks []string parameter to register
exact/upto EVM schemes for each network
- server_init.go passes resolved active networks (from resolveActiveNetworks)
to x402 server - includes eip155:84532 (sepolia), eip155:8453 (base),
and any other configured networks
- Registers both exact and upto schemes per network
- Updates x402_test.go calls to pass []string{}
- Fixes: 'no scheme server for exact on eip155:84532' error
Server was passing only ListenAddress (127.0.0.1) without port to x402.Start,
causing 'listen tcp: address 127.0.0.1: missing port in address'.
Now combines address:port for TCP when no port already present and
not a unix socket. Keeps unix socket paths unchanged.
- config: MATRIX_WEBHOOK_URL / MATRIX_SERVER_ID via os.Getenv (optional),
keep hostname fallback for MatrixServerID, avoid Fatal when webhook
not configured (test-endpoints omits it, webhook.go nil-safe)
- auth: NewCDPAuth converts literal \n to real newlines before
pem.Decode, so Forgejo single-line secret
-----BEGIN EC PRIVATE KEY-----\n... works while malformed
still returns error and CI fails via log.Fatal (as required)