Found while playing tact.wtf end-to-end through AgentCash (MCP + CLI) as an autonomous agent. Three related issues, ordered by severity.
1. MPP wallet agents can join paid games but can never act afterward
Repro:
POST /api/v1/games/{gameId}/join on a paid lobby via AgentCash (no Tact-Agent-Token, pure MPP payment). Join succeeds — resolveVerifiedMppWalletPrincipal(payment.payer) creates a wallet-kind principal and the tank spawns.
POST /api/v1/games/{gameId}/commands with any command → 401 authentication_required ("Create a browser session or send Tact-Agent-Token.").
Cause: requireRequestPrincipal (apps/web/src/lib/server/identity.ts) only resolves tact_agent_* tokens and tact_session cookies. There is no wallet→token exchange, and /commands issues no MPP/SIWX challenge. So a wallet-DID principal has no way to authenticate outside of paid routes — the tank is permanently frozen.
The AgentCash origin guidance says "the verified payer wallet becomes the agent identity, so AgentCash can join without an API key" — true for joining, but the resulting player is a zombie. Game 913c235d-7ee1-4931-a3f8-4a51e48691e6 ("AgentCash Production Match") on prod now contains two such zombie tanks ("AgentCash Prod" and "Fable") in a match that can never progress.
Possible fixes: accept an MPP identity credential on /commands (mpp.dev "Identity" flow), or return a tact_agent_* token in the paid-join response for wallet principals, or document that paid joins must include a pre-provisioned Tact-Agent-Token header (the existingPrincipal ?? payer fallback in the join route already supports this — it's just undiscoverable).
2. www.tact.wtf 308 redirect breaks all MPP payments
https://www.tact.wtf/* returns 308 → https://tact.wtf/*. The MPP challenge/credential flow doesn't survive the redirect, so every paid call through the www host fails with mpp_payment_rejected. npx agentcash try https://www.tact.wtf (and the registered origin listing) advertises the www URL, so a fresh agent's first payment always fails until it discovers the apex domain by hand-inspecting the redirect.
Fix: either serve the API on both hosts or register/advertise only the apex origin.
3. Idempotent join replays still settle a new MPP charge
Replaying POST /join with the same commandId/idempotencyKey correctly returns "replayed": true and doesn't double-charge the app-level buy-in (prize pool unchanged) — but each replay settles a fresh MPP payment (distinct tx hashes per attempt: 0x7adeca5a…, 0x9c442a43…). withMppCharge wraps the handler, so payment settles before the idempotency check runs. Retrying agents get charged the entry price every retry.
Happy to provide tx hashes / request logs. Filed by Claude on behalf of @sragss.
Found while playing tact.wtf end-to-end through AgentCash (MCP + CLI) as an autonomous agent. Three related issues, ordered by severity.
1. MPP wallet agents can join paid games but can never act afterward
Repro:
POST /api/v1/games/{gameId}/joinon a paid lobby via AgentCash (noTact-Agent-Token, pure MPP payment). Join succeeds —resolveVerifiedMppWalletPrincipal(payment.payer)creates awallet-kind principal and the tank spawns.POST /api/v1/games/{gameId}/commandswith any command →401 authentication_required("Create a browser session or send Tact-Agent-Token.").Cause:
requireRequestPrincipal(apps/web/src/lib/server/identity.ts) only resolvestact_agent_*tokens andtact_sessioncookies. There is no wallet→token exchange, and/commandsissues no MPP/SIWX challenge. So a wallet-DID principal has no way to authenticate outside of paid routes — the tank is permanently frozen.The AgentCash origin guidance says "the verified payer wallet becomes the agent identity, so AgentCash can join without an API key" — true for joining, but the resulting player is a zombie. Game
913c235d-7ee1-4931-a3f8-4a51e48691e6("AgentCash Production Match") on prod now contains two such zombie tanks ("AgentCash Prod" and "Fable") in a match that can never progress.Possible fixes: accept an MPP identity credential on
/commands(mpp.dev "Identity" flow), or return atact_agent_*token in the paid-join response for wallet principals, or document that paid joins must include a pre-provisionedTact-Agent-Tokenheader (theexistingPrincipal ?? payerfallback in the join route already supports this — it's just undiscoverable).2. www.tact.wtf 308 redirect breaks all MPP payments
https://www.tact.wtf/*returns308→https://tact.wtf/*. The MPP challenge/credential flow doesn't survive the redirect, so every paid call through the www host fails withmpp_payment_rejected.npx agentcash try https://www.tact.wtf(and the registered origin listing) advertises the www URL, so a fresh agent's first payment always fails until it discovers the apex domain by hand-inspecting the redirect.Fix: either serve the API on both hosts or register/advertise only the apex origin.
3. Idempotent join replays still settle a new MPP charge
Replaying
POST /joinwith the samecommandId/idempotencyKeycorrectly returns"replayed": trueand doesn't double-charge the app-level buy-in (prize pool unchanged) — but each replay settles a fresh MPP payment (distinct tx hashes per attempt:0x7adeca5a…,0x9c442a43…).withMppChargewraps the handler, so payment settles before the idempotency check runs. Retrying agents get charged the entry price every retry.Happy to provide tx hashes / request logs. Filed by Claude on behalf of @sragss.