feat: add Safe proposer (delegate) script#1472
Draft
mrice32 wants to merge 2 commits into
Draft
Conversation
Add script/safe-multisig/addProposer.ts to register a proposer (Safe Transaction Service "delegate") across a list of Safes on multiple chains, authorizing each with a Ledger signature. A proposer can only propose transactions to the off-chain tx-service on an owner's behalf; it is not a signer and cannot approve, execute, or move funds. Adding one is off-chain (EIP-712 signature POSTed to api.safe.global) — no on-chain tx, no gas. Details: - Resolves chainId and tx-service URL per chain from Safe's config service at runtime, so the path segment is correct even where it differs from the app short-name (matic -> pol, hyper-evm -> hyper). - Signs Delegate(address delegateAddress, uint256 totp) via `cast wallet sign --ledger`; the EIP-712 payload is byte-identical to ethers/safe-eth-py, verified before use. - Verifies the recovered signer equals the delegator before POSTing, so a wrong derivation path aborts instead of authorizing a bad key. - Verifies delegator ownership and skips already-registered proposers (idempotent); supports --dry-run, --force, --yes and per-field flags. - proposers.config.json seeds the dev wallet proposer, signer, and the 11 target Safes. README documents the flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Eb92y57KqXi4oJvdB94N68
Print the decoded fields (delegateAddress, totp, chainId) plus the Domain hash, Message hash, and final digest before each signature, so the values can be cross-checked against the Ledger's blind-signing screen. Digest = keccak256(0x1901 || domainHash || messageHash). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Eb92y57KqXi4oJvdB94N68
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
Adds
script/safe-multisig/addProposer.tsto register a proposer (the Safe Transaction Service calls it a delegate) on a list of Safes across multiple chains, authorizing each with a connected Ledger.A proposer may only propose transactions to the off-chain tx-service on an owner's behalf — it is not a signer and cannot approve, execute, or move funds. Adding one is an off-chain operation (an EIP-712 signature POSTed to
api.safe.global): no on-chain transaction, no gas.This follows the existing
script/safe-multisig/*.tspattern.forgecan't make authenticated HTTPS POSTs to Safe's REST API, so signing is done withcast wallet sign --ledger(a Foundry tool) and TS handles only the loop + HTTP.What it does, per Safe
chainIdand the tx-service URL from Safe's config service at runtime, so the path segment is correct even where it differs from the app short-name (matic→.../pol,hyper-evm→.../hyper).delegatoris an owner of the Safe; skips with a warning otherwise (--forceto override).Delegate(address delegateAddress, uint256 totp)viacast wallet sign --ledgerand verifies the recovered address equalsdelegatorbefore POSTing, so a wrong derivation path aborts instead of authorizing the wrong key.Files
addProposer.ts— the script (flags:--config,--delegate,--delegator,--label,--hd-path,--safes,--dry-run,--force,--yes; optionalSAFE_API_KEY).proposers.config.json— seeded with the dev-wallet proposer, the authorizing owner, and the 11 target Safes.README.md— documents the flow.Validation
cast's EIP-712 output for this payload is byte-identical to ethers' / safe-eth-py's, and the recovered signer matches.--dry-runagainst Safe's live API resolved all 11 chains, confirmed the signer is an owner on every Safe, and detected that Monad already has this proposer (correctly skipped).Notes
chainId).--no-verifyfor that reason (TS/JSON/MD only, no Rust touched).🤖 Generated with Claude Code