tools: safe-tx-verify skill — independent Safe multisig tx verifier#51
Conversation
A uv script (PEP 723 inline deps) to verify a Safe transaction before signing on a Ledger: recomputes the EIP-712 domainHash / messageHash / safeTxHash from the raw parameters (never trusting the backend), decodes the calldata via this repo's compiled ABIs, and labels addresses against DEPLOYMENT_ADDRESSES.md. Supports the unified Safe Transaction Service endpoint, multi-chain prefixes, --onchain nonce/version checks, and --json. Run: uv run tools/safe-tx-verify/verify_safe_tx.py "oeth:0x..." --nonce N EIP-712 hashing validated against real Optimism Safe txs (recomputed safeTxHash matched the backend for every sampled tx, incl. delegatecall multiSend batches). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @rplusq's task in 3m 47s —— View job Code Review
Found 3 issue(s)Issue 1: External domain URLs in hardcoded RPC listID: verify-safe-tx-external-rpcs-a3f1 🔒 External Domain URLs (Non-blocking) Context:
Recommendation: Document in README that users should prefer Issue 2: Multiple proposals at same nonce — warning only goes to stderr, first tx silently usedID: verify-safe-tx-multi-proposal-silent-4b2c Context:
Recommendation: Add an if len(results) > 1:
print(c(f"WARNING: {len(results)} proposals at nonce {args.nonce}; pass --index N to select.", "33"), file=sys.stderr)
tx = results[args.index] # args.index defaults to 0Issue 3:
|
A thin Claude Code skill (.claude/skills/safe-tx-verify) so "verify this safe tx" runs the committed tools/safe-tx-verify script — no duplicated hashing logic, the script stays the single trust anchor. Also gitignore .claude/ runtime artifacts while tracking shared skills. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fy baseGas default
Address auto-review: (1) when multiple proposals share a nonce, refuse to silently
verify results[0] — list every candidate's safeTxHash and exit non-zero until the
signer picks with --index (prevents verifying a different tx than is being signed,
esp. under --json/redirected stdout). (2) tx.setdefault("baseGas", 0) — the inner
.get() was a no-op.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sion, stale-nonce), drop thin reimpl Replace the minimal tools/ reimplementation with the complete original skill at .claude/skills/safe-tx-verify/ (committed to the repo, and usable as a Claude Code skill). The original is far more capable: propose mode (verify a not-yet-queued tx you're about to create, next-free-nonce aware), recursion into multiSend batches + Timelock execute/schedule/*Batch + ProxyAdmin upgradeAndCall/upgradeTo, automatic stale-nonce detection via on-chain nonce(), MultiSend singleton (canonical vs eip155) labeling, repo-ABI selector index with 4byte fallback, and version-aware EIP-712 typehashes. Dependency-free (cast for keccak/decode); wrapped with a PEP-723 header so it runs via 'uv run' (or plain python3). Validated end-to-end: OP Safe nonce 48 -> message hash matches, safeTxHash matches the Safe API cross-check, forceWithdrawAll decoded + address labeled from DEPLOYMENT_ADDRESSES.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@claude review |
What
Full
safe-tx-verifyskill at.claude/skills/safe-tx-verify/— committed to the repo and usable as a Claude Code skill. It independently verifies a Safe (Gnosis Safe) multisig transaction before signing on a Ledger: it recomputes the EIP-712domainHash/messageHash/safeTxHashfrom the raw params (never trusting the backend's hash), decodes the calldata via this repo's compiled ABIs, and labels every address againstDEPLOYMENT_ADDRESSES.md.Capabilities
safeTxHashagainst the Safe API (exit 3 + "DO NOT SIGN" on mismatch).--to/--data/…) — verify a tx you're about to create (not yet queued); defaults to the Safe's next free nonce (on-chainnonce()reconciled against the queue).multiSendbatches, Timelockexecute/schedule/*Batch, and ProxyAdminupgradeAndCall/upgradeTo— surfaces nested actions (e.g. a proxy upgrade hidden in a timelock batch).nonce(), excludes/labels superseded nonces, auto-selects the single live one, lists candidates (exit 2) when several are live.Runtime
Dependency-free: uses
cast(foundry) for keccak/decoding. Wrapped with a PEP-723 header so it runs viauv run(or plainpython3):uv run .claude/skills/safe-tx-verify/scripts/verify_safe_tx.py "oeth:0x398A2749487B2a91f2f543C01F7afD19AEE4b6b0" --nonce 48Validation
End-to-end on OP Safe
0x398A…b6b0nonce 48: recomputed message hash andsafeTxHashmatch, the Safe-API cross-check passes, andforceWithdrawAll(address)decodes onStakeWeight [Optimism]with the argument address labeled fromDEPLOYMENT_ADDRESSES.md.Notes
.gitignoretracks.claude/skills/while ignoring local Claude Code runtime artifacts (worktrees, locks).tools/reimplementation (removed in this PR). The prior auto-review notes are addressed by the restored design: competing proposals are listed (exit 2) with stale ones flagged rather than silently picking one, and there's nobaseGasno-op.🤖 Generated with Claude Code