Repository navigation
fix: close money-movement gaps in subprocess env, withdraw, and trade plans - #205
Merged
Merged
Conversation
added 3 commits
October 7, 2026 22:52
… plans - Model-driven subprocesses (Bash, Detach, tool/spend hooks, MCP stdio, git/rg helpers) no longer inherit BLOCKRUN_WALLET_KEY, BASE_CHAIN_WALLET_KEY, SOLANA_WALLET_KEY or BLOCKRUN_API_KEY. A model-written script could otherwise sign transfers no Franklin gate sees. Explicit env in a user's own MCP/hook config still applies. - PolymarketBet withdraw only pays the agent wallet; other recipients are refused before any network call or signature. - The withdrawal double-send guard is persisted before submission on both the relayer and EOA paths, closing the accepted-but-unacknowledged window. EOA retries re-broadcast the same signed bytes; the guard is released only on a receipt, a definite RPC rejection of unknown bytes, or a nonce consumed by another transaction. - Trade-plan coverage binds to the execution fields (no free-text mentions), enforces action/direction and per-line amounts in integer micro-dollars, and debits the plan that authorized the call even if it expired or a newer plan was approved meanwhile.
A timeout, 5xx or lost response after a payment is signed and sent means the outcome is unknown, not failed. The agent must reconcile by tx hash, nonce and balances first, may only re-broadcast the same signed bytes, and must report every attempt and settlement. Applies to built-in tools, Bash scripts and MCP tools alike.
…mission is disproven Addresses four review findings on the money-movement hardening: - A withdraw retry that resolves the earlier attempt (receipt, relayer terminal state, or expired deadline) now reports the outcome and stops. It previously cleared the guard and signed a second withdrawal in the same call. - An advanced nonce plus an RPC that does not know our hash is no longer read as "dropped" (and "nonce too low" no longer counts as a definite broadcast rejection). Only a receipt resolves an EOA withdrawal; until then the same signed bytes are re-broadcast. - Plans saved before per-line accounting that were already drawn on are refused instead of treating every line as unused, and decideTradePlan re-reads the stored plan so a stale object cannot reset consumption. - The trade-plan gate reserves budget before execution. The reservation is released only when the tool reports notSubmitted (validation failure, user cancel, definite venue rejection) or the call is denied before running. A Polymarket submit that fails without a 4xx keeps both the plan reservation and the session cap and is reported as outcome UNKNOWN, so an accepted-but-unacknowledged order cannot be placed twice.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
BLOCKRUN_WALLET_KEY,BASE_CHAIN_WALLET_KEY,SOLANA_WALLET_KEYorBLOCKRUN_API_KEY(sharedsanitizeSubprocessEnv). Before this, a model-written script could load the wallet key from env and sign transfers that no trade-plan, PreSpend or budget gate saw. Env written explicitly into a user's own MCP server or hook config still passes through.to_addressis refused before any network call or signature.pendingWithdrawis persisted before submission on both paths, closing the accepted-but-unacknowledged window. A relayer marker with no transactionID blocks until deadline + grace. On the EOA path, the tx is signed locally, its hash is persisted, then it is broadcast. A retry re-broadcasts the same signed bytes and never signs anew. The guard releases only on a receipt, on a definite RPC rejection of bytes the RPC doesn't know, or when the nonce was consumed by a different transaction.decideTradePlanre-reads the stored plan.notSubmitted(or a pre-execution denial).Notes
src/tools/polymarket/*changes are markedfranklin-local:and need upstreaming.BLOCKRUN_WALLET_KEYmust now set it in its own configenv.~/.blockrunare still readable by the same OS user from a shell; there is no cross-process lock on plan/state writes (TODO left in the code); the Polymarketfundpath has no retry record.Test plan
npm test: 847/847 passtest/subprocess-env.local.mjs: realprintenvthrough Bash, both Detach stages, hooks, MCPtest/withdraw.local.mjs: external recipient refused pre-network, marker written before submit on both paths, lost acknowledgement blocks a second withdrawal, EOA re-broadcast / release rules, persistence failure prevents submissiontest/trade-plan.local.mjs: memo mention denied, outcome-label hole closed, action/direction, per-line cap, authorizing-plan drawdowntest/orders.local.mjs: unknown submit keeps reservation and is not notSubmitted; 4xx / success:false release and are notSubmitted