Skip to content

feat: Safe infrastructure and CrossChainRelay deployment#64

Merged
smartprogrammer93 merged 4 commits intoshasta-pocfrom
feat/safe-wallet-deployment
Mar 30, 2026
Merged

feat: Safe infrastructure and CrossChainRelay deployment#64
smartprogrammer93 merged 4 commits intoshasta-pocfrom
feat/safe-wallet-deployment

Conversation

@smartprogrammer93
Copy link
Copy Markdown

@smartprogrammer93 smartprogrammer93 commented Mar 29, 2026

Summary

Adds CrossChainRelay deployment to the Surge deployment pipeline. Safe v1.4.1 contracts use canonical addresses already in L2 genesis — no custom deployment needed.

Changes

  • `deploy-cross-chain-relay.sh` — deploys CrossChainRelay on L2 (forwards bridge messages to arbitrary targets)
  • Docker Compose profile `relay-deployer` added
  • `deploy-surge-full.sh` — calls relay deployment after protocol contracts
  • `.env.devnet` — canonical Safe v1.4.1 addresses, `VITE_L2_RELAY` var

Safe v1.4.1 (canonical — no deployment needed)

Already present on both Gnosis L1 and Surge L2 genesis at standard addresses:

  • SafeProxyFactory: `0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67`
  • SafeL2: `0x29fcB43b46531BcA003ddC8FCB67FFE91900C762`
  • MultiSend: `0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526`
  • FallbackHandler: `0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99`

Related PRs

🤖 Generated with Claude Code

smartprogrammer93 and others added 2 commits March 29, 2026 12:29
- Add deploy-safe-infra.sh: deploys SafeL2, SafeProxyFactory, MultiSend,
  MultiSendCallOnly, FallbackHandler on BOTH L1 and L2 with matched nonces
- Add deploy-cross-chain-relay.sh: deploys CrossChainRelay on L2
- Add docker-compose profiles: safe-deployer, relay-deployer
- Update deploy-surge-full.sh: call Safe and relay deployments after
  UserOpsSubmitter (backwards compatible — both exist)
- Update .env.devnet: add SAFE_DEPLOYER_PRIVATE_KEY and VITE_SAFE_* vars

Requires SAFE_DEPLOYER_PRIVATE_KEY to be a fresh EOA with nonce 0 on
both chains for identical contract addresses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Safe v1.4.1 contracts are already deployed at canonical addresses on
both Gnosis L1 and Surge L2 (in genesis). No custom deployment needed.

Removed:
- deployer/deploy-safe-infra.sh
- Docker compose safe-deployer profile
- deploy_safe_contracts() function
- SAFE_DEPLOYER_PRIVATE_KEY env var

Kept:
- CrossChainRelay deployer (still needed on L2)
- Canonical Safe addresses in .env.devnet

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@smartprogrammer93
Copy link
Copy Markdown
Author

@claude review

@claude
Copy link
Copy Markdown

claude bot commented Mar 29, 2026

Claude finished @smartprogrammer93's task in 1m 33s —— View job


Code Review

  • Gather context and read changed files
  • Review .env.devnet
  • Review deploy-cross-chain-relay.sh
  • Review deploy-surge-full.sh
  • Review docker-compose-protocol.yml
  • Post review feedback

Overall this is a clean, well-structured addition that follows the existing deployer pattern. A few issues worth fixing:


deployer/deploy-cross-chain-relay.sh

LOG_LEVEL is undefined. It's passed to forge script but never exported by this script and not injected via docker-compose environment. If it's unset it expands to empty string (fine), but it should either be removed or explicitly passed — it's invisible to the caller.

$LOG_LEVEL \     # ← never set; other deployers don't pass this either

Fix this →

FOUNDRY_PROFILE is set inline instead of via docker-compose. Other deployers (surge-userops-submitter-deployer) use FOUNDRY_PROFILE: layer1o in the docker-compose environment block. Here it's baked into the script as FOUNDRY_PROFILE=shared. This is an inconsistency — the profile can't be overridden without editing the script.

Fix this →

The success banner will never print on failure — that's correct since set -e exits on forge failure — but the banner at the bottom is misleading: it says ✅ CrossChainRelay deployed which could be printed even if broadcast was skipped (BROADCAST=false). A minor cosmetic note.


docker-compose-protocol.yml

VERIFY_ARG is redundant. It's passed as an environment variable into the container, but the script recomputes it from VERIFY anyway (overwriting whatever was injected). It won't cause bugs — the script's local assignment wins — but it's dead config. Same pattern exists in other deployers, so I understand it's inherited. Worth a cleanup pass.

No depends_on for the L2 node. FORK_URL is hardcoded to http://surge-l2-execution:8547, but there's no depends_on: surge-l2-execution. If the relay deployer starts before the L2 node is ready, it'll fail immediately. This is consistent with other deployers, so it's not a regression — but worth flagging as a systemic issue since the relay specifically targets L2 (unlike L1 deployers which may be external).


deploy-surge-full.sh

Missing "run with debug mode" hint in the error path. deploy_userops_submitter_contract() (line ~1549) has:

if [[ "$mode" == "silence" ]]; then
    log_error "Run with debug mode for more details: --mode debug"
fi

deploy_relay_contract() omits this, making failure harder to diagnose in silent mode.

Fix this →


.env.devnet

VITE_L2_RELAY= is left blank, which makes sense since the address is only known post-deployment. Worth adding a comment like # populated after relay deployment so it's clear this is intentional and not an oversight.

The canonical Safe v1.4.1 addresses look correct.


Summary

Severity Issue
Minor LOG_LEVEL unset but referenced in forge call
Minor FOUNDRY_PROFILE hardcoded in script vs. docker-compose env
Minor Missing --mode debug hint in deploy_relay_contract error path
Nit VERIFY_ARG injected redundantly (inherited pattern)
Nit No depends_on for L2 node (inherited pattern)
Nit VITE_L2_RELAY= could use a clarifying comment

No blocking issues. The core logic is sound and consistent with the existing deployment pattern.

- Remove undefined LOG_LEVEL from forge script call
- Move FOUNDRY_PROFILE to docker-compose env (matching other deployers)
- Remove redundant VERIFY_ARG from docker-compose env
- Add missing --mode debug hint in deploy_relay_contract error path
- Add clarifying comment for blank VITE_L2_RELAY

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@smartprogrammer93
Copy link
Copy Markdown
Author

@claude review

@claude
Copy link
Copy Markdown

claude bot commented Mar 29, 2026

Claude finished @smartprogrammer93's task in 1m 51s —— View job


Code Review

  • Gather context and read changed files
  • Review .env.devnet
  • Review deploy-cross-chain-relay.sh
  • Review deploy-surge-full.sh
  • Review docker-compose-protocol.yml
  • Post review feedback

Overall this is a clean addition that follows the existing deployer pattern well. The latest commit (0bfde22) has already addressed the FOUNDRY_PROFILE and silence-mode hint issues from prior feedback. A few remaining things worth flagging:


deployer/deploy-cross-chain-relay.sh

No deployment artifact is captured. deploy-userops-submitter.sh copies its output to /deployment/composability_userops_submitter.json so the address is preserved. The relay script has no equivalent — meaning VITE_L2_RELAY= in .env.devnet stays blank forever unless someone updates it manually. If there's a downstream consumer of that address (the PR description references Catalyst UserOp routing), this gap will matter.

Fix this →

--evm-version paris is unexplained. No other deployer in this repo specifies --evm-version. Paris predates the PUSH0 opcode (Shanghai), so this is significant if the L2 EVM differs from the L1. A one-line comment explaining why would help future maintainers.

$LOG_LEVEL is correctly omitted here (unlike deploy-userops-submitter.sh which passes it but never defines it — that's an existing bug in the other script, and the relay script is better for not inheriting it).


docker-compose-protocol.yml

FORK_URL is hardcoded to http://surge-l2-execution:8547 instead of using ${L2_ENDPOINT_HTTP} or a similar env var. All other deployer services use env var references for URLs (e.g. FORK_URL: ${L1_ENDPOINT_HTTP}). If the L2 host or port changes, this service requires a direct docker-compose edit rather than an env file update.

Fix this →

VERIFY_ARG is not passed — this is correct and an improvement over surge-userops-submitter-deployer, which redundantly injects VERIFY_ARG: ${VERIFY_ARG} into the container (the script recomputes it from $VERIFY anyway). No action needed here; just noting the inconsistency favours the relay approach.

No depends_on for surge-l2-execution. Consistent with other deployers, so not a regression, but the relay is specifically L2-only (unlike L1 deployers that may use an external endpoint), making it more fragile if the L2 node starts slowly.


deploy-surge-full.sh

deploy_relay_contract correctly includes the silence-mode debug hint and follows the same structure as deploy_userops_submitter_contract. No issues here.


.env.devnet

The VITE_L2_RELAY= comment (# populated after relay deployment) is clear and appropriate. The Safe v1.4.1 canonical addresses match the known deterministic deployment addresses.


Summary

Severity Issue
Moderate No deployment artifact copy — VITE_L2_RELAY has no automated path to being populated
Minor FORK_URL hardcoded in docker-compose instead of env var reference
Nit --evm-version paris unexplained (no comment)
Nit No depends_on for L2 node (inherited pattern)

The moderate issue only matters if something downstream actually reads VITE_L2_RELAY. If the address is being set manually post-deploy, the current state is fine — but worth confirming.

- Copy relay.json deployment artifact to /deployment/deployment_relay.json
- Replace hardcoded L2 URL with ${L2_ENDPOINT_HTTP} env var
- Add comment explaining --evm-version paris requirement

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@smartprogrammer93
Copy link
Copy Markdown
Author

@claude review

@claude
Copy link
Copy Markdown

claude bot commented Mar 29, 2026

Claude finished @smartprogrammer93's task in 1m 45s —— View job


Code Review

  • Gather context
  • Review .env.devnet
  • Review deployer/deploy-cross-chain-relay.sh
  • Review deploy-surge-full.sh
  • Review docker-compose-protocol.yml
  • Post review feedback

All issues raised in the previous two reviews have been resolved in the latest commits. This is a clean, ready-to-merge addition. One new item worth confirming before merge:


deployer/deploy-cross-chain-relay.sh

Artifact path assumption needs verification. The script copies ./deployments/relay.json after forge runs:

cp ./deployments/relay.json /deployment/deployment_relay.json

This path is derived from the shared Foundry profile config and whatever filename DeployCrossChainRelay.s.sol produces. If the actual output path differs (e.g. ./deployments/shared/DeployCrossChainRelay.json), the container will fail with a cryptic cp error after a successful deployment — confusing to debug. Worth confirming this path matches the forge script's actual output before merging.

Verify this →

PRIVATE_KEY is unquoted in the forge call. This is a shell quoting nit (consistent with deploy-userops-submitter.sh), unlikely to matter in practice but "$PRIVATE_KEY" is more correct.


deploy-surge-full.sh

temp_output is never cleaned up. The temp file /tmp/relay_deploy_output_$$ is created in both debug and non-debug paths but never removed — same pattern as deploy_userops_submitter_contract. A minor resource leak.


All previously flagged issues — resolved ✓

Issue Status
LOG_LEVEL unset in forge call ✅ Not present in relay script (userops submitter still has it — pre-existing)
FOUNDRY_PROFILE hardcoded in script ✅ Moved to docker-compose environment block
Missing silence-mode debug hint ✅ Added at line 1590–1592
No deployment artifact copy cp ./deployments/relay.json /deployment/deployment_relay.json added
FORK_URL hardcoded ✅ Now uses ${L2_ENDPOINT_HTTP}
--evm-version paris unexplained ✅ Comment added explaining PUSH0/MCOPY absence on L2
VITE_L2_RELAY= needs clarifying comment ✅ Comment added

Summary

Severity Issue
Minor Artifact path ./deployments/relay.json — verify it matches forge output before merging
Nit $PRIVATE_KEY unquoted (inherited pattern)
Nit temp_output not cleaned up on success (inherited pattern)

No blocking issues. The core logic is sound and all prior feedback has been addressed.

@smartprogrammer93 smartprogrammer93 merged commit 8413b63 into shasta-poc Mar 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants