feat: mainnet deploy orchestrator script#70
Conversation
One-shot deploy of did-stellar-registry, vc-vault template and vc-vault-factory to mainnet, then USDC fee config. Identity + all params come from a gitignored .env (nothing identifying hardcoded). Dry-run, confirmation gate, identity verification, funding/trustline preflight, and a derive-key helper.
…included on mainnet)
… skip completed work
Plan expiredYour subscription has expired. Please renew your subscription to continue using CI/CD integration and other features. |
📝 WalkthroughWalkthroughAdds ChangesMainnet Deployment Script
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/deploy-mainnet.sh`:
- Around line 16-17: The temporary identity handling in deploy-mainnet.sh is
unsafe: the fixed TMP_IDENTITY alias and the _acta_derive flow can overwrite or
leave behind existing keys, and dry-run still imports/removes a signing
identity. Update the identity lifecycle around the secret-handling logic to use
per-run aliases, track which identities were actually created, and only
import/remove keys when not in dry-run. Make the changes in the
TMP_IDENTITY-related setup and the _acta_derive / cleanup paths so early exits
still leave no stray identities and pre-existing local keys are never deleted.
- Around line 55-62: The argument parsing in the deploy-mainnet.sh loop
currently lets derive-key proceed even when --dry-run is set, which can still
trigger recovery phrase prompts and secret output. Update the SUBCOMMAND/DRY_RUN
handling in the top-level argument parser and the derive-key path so that
derive-key explicitly rejects --dry-run (for example by erroring out before any
key derivation flow starts), keeping the safeguard in the derive-key command
logic rather than silently ignoring it.
- Line 157: The Horizon account lookup curl call can hang indefinitely and
transport failures currently bypass the script’s own abort handling. Update the
curl invocation that assigns code using HORIZON_URL and DEPLOYER_ADDRESS to use
explicit timeout options and to fail in a way the script can detect, then handle
that failure in the surrounding deploy flow so it emits the script’s abort
message before exiting. Apply the same fix to the other matching curl usage at
the referenced second location in deploy-mainnet.sh.
- Around line 109-115: The bootstrap_network helper currently calls stellar
network add unconditionally, which can fail on a resumed deploy if the network
was already configured. Update bootstrap_network to be resume-safe by checking
whether the network is already present before calling stellar network add, or by
removing any existing entry first and then re-adding it. Use the existing
bootstrap_network function and NETWORK_NAME/RPC_URL/PASSPHRASE values to keep
the behavior consistent while avoiding an abort before resumable steps run.
- Around line 221-223: The trustline check in the deploy-mainnet script is too
broad because it greps the account JSON for only the issuer string, which can
match unrelated fields or other assets. Update the logic around the
curl/printf/grep block in the deploy flow to inspect the same balances[] entry
for both asset_code equal to USDC and asset_issuer equal to USDC_ISSUER before
allowing fees to be enabled. Use the existing symbols HORIZON_URL, FEE_DEST, and
USDC_ISSUER to locate the check and make the match specific to the USDC
trustline.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 74621dd5-3a81-4546-a93c-818476717bcf
📒 Files selected for processing (3)
.gitignoredocs/deployments/testnet.mdscripts/deploy-mainnet.sh
| TMP_IDENTITY="_acta_deployer" | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Fix temporary identity lifecycle before handling secrets.
--dry-run still imports/removes a signing identity, fixed aliases can delete a pre-existing local key, and _acta_derive can remain if derivation exits early. Use per-run aliases, track created identities, and skip key import in dry-run.
Suggested fix
-TMP_IDENTITY="_acta_deployer"
+TMP_IDENTITY="_acta_deployer_$$"
+DEPLOY_IDENTITY_CREATED=0
+DERIVE_IDENTITY=""
cleanup() {
- stellar keys rm "$TMP_IDENTITY" --force >/dev/null 2>&1 || true
+ if [ "${DEPLOY_IDENTITY_CREATED:-0}" -eq 1 ]; then
+ stellar keys rm "$TMP_IDENTITY" --force >/dev/null 2>&1 || true
+ fi
+ if [ -n "${DERIVE_IDENTITY:-}" ]; then
+ stellar keys rm "$DERIVE_IDENTITY" --force >/dev/null 2>&1 || true
+ fi
}
import_and_verify_identity() {
step "Importing + verifying deployer identity"
+ if [ "$DRY_RUN" -eq 1 ]; then
+ say " [dry-run] would import and verify deployer identity"
+ return 0
+ fi
: "${DEPLOYER_SECRET:?DEPLOYER_SECRET is not set in .env}"
- stellar keys rm "$TMP_IDENTITY" --force >/dev/null 2>&1 || true
printf '%s\n' "$DEPLOYER_SECRET" | stellar keys add "$TMP_IDENTITY" --secret-key >/dev/null
+ DEPLOY_IDENTITY_CREATED=1
derived="$(stellar keys public-key "$TMP_IDENTITY")"
derive_key() {
: "${DEPLOYER_ADDRESS:?set DEPLOYER_ADDRESS to the deployer G… address in .env}"
- tmp="_acta_derive"
- stellar keys rm "$tmp" --force >/dev/null 2>&1 || true
+ tmp="_acta_derive_$$"
+ DERIVE_IDENTITY="$tmp"
say "Paste your Freighter 12/24-word recovery phrase when prompted."
stellar keys add "$tmp" --seed-phrase >/dev/null
@@
- stellar keys rm "$tmp" --force >/dev/null 2>&1 || true
+ stellar keys rm "$tmp" --force >/dev/null 2>&1 || true
+ DERIVE_IDENTITY=""
exit 1
@@
stellar keys secret "$tmp" --hd-path "$found"
- stellar keys rm "$tmp" --force >/dev/null 2>&1 || true
+ stellar keys rm "$tmp" --force >/dev/null 2>&1 || true
+ DERIVE_IDENTITY=""Also applies to: 133-143, 261-285, 291-298
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/deploy-mainnet.sh` around lines 16 - 17, The temporary identity
handling in deploy-mainnet.sh is unsafe: the fixed TMP_IDENTITY alias and the
_acta_derive flow can overwrite or leave behind existing keys, and dry-run still
imports/removes a signing identity. Update the identity lifecycle around the
secret-handling logic to use per-run aliases, track which identities were
actually created, and only import/remove keys when not in dry-run. Make the
changes in the TMP_IDENTITY-related setup and the _acta_derive / cleanup paths
so early exits still leave no stray identities and pre-existing local keys are
never deleted.
| for arg in "$@"; do | ||
| case "$arg" in | ||
| derive-key) SUBCOMMAND="derive-key" ;; | ||
| --dry-run) DRY_RUN=1 ;; | ||
| -h|--help) usage; exit 0 ;; | ||
| *) echo "Unknown argument: $arg" >&2; usage; exit 1 ;; | ||
| esac | ||
| done |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Reject derive-key --dry-run explicitly.
--dry-run is silently ignored for derive-key, so a user can still be prompted for a recovery phrase and print a secret after requesting a dry run.
Suggested fix
for arg in "$@"; do
case "$arg" in
derive-key) SUBCOMMAND="derive-key" ;;
--dry-run) DRY_RUN=1 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown argument: $arg" >&2; usage; exit 1 ;;
esac
done
+
+if [ "$SUBCOMMAND" = "derive-key" ] && [ "$DRY_RUN" -eq 1 ]; then
+ echo "--dry-run is only supported for full deploy, not derive-key" >&2
+ usage
+ exit 1
+fiAlso applies to: 310-311
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/deploy-mainnet.sh` around lines 55 - 62, The argument parsing in the
deploy-mainnet.sh loop currently lets derive-key proceed even when --dry-run is
set, which can still trigger recovery phrase prompts and secret output. Update
the SUBCOMMAND/DRY_RUN handling in the top-level argument parser and the
derive-key path so that derive-key explicitly rejects --dry-run (for example by
erroring out before any key derivation flow starts), keeping the safeguard in
the derive-key command logic rather than silently ignoring it.
| bootstrap_network() { | ||
| step "Configuring '$NETWORK_NAME' network ($RPC_URL)" | ||
| run stellar network add "$NETWORK_NAME" \ | ||
| --rpc-url "$RPC_URL" \ | ||
| --network-passphrase "$PASSPHRASE" | ||
| say " network '$NETWORK_NAME' -> $RPC_URL" | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify Stellar CLI behavior before relying on resume flow.
command -v stellar >/dev/null || {
echo "stellar CLI is not installed in this environment"
exit 0
}
stellar network add --helpRepository: ACTA-Team/contracts-acta
Length of output: 211
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== deploy script excerpt ==\n'
sed -n '90,135p' scripts/deploy-mainnet.sh
printf '\n== repo references for stellar network add / resume / idempot ==\n'
rg -n "stellar network add|network remove|network update|resume|idempot" scripts . -g '!**/.git/**' || true
printf '\n== files mentioning deploy-mainnet ==\n'
rg -n "deploy-mainnet\.sh|bootstrap_network" . -g '!**/.git/**' || trueRepository: ACTA-Team/contracts-acta
Length of output: 3655
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== deploy script flow around bootstrap ==\n'
sed -n '240,330p' scripts/deploy-mainnet.sh
printf '\n== resume references ==\n'
rg -n "resume|resumable|rerun|re-run|recovery|idempotent|already configured|skip if already present" scripts docs -g '!**/.git/**' || true
printf '\n== stellar network commands in repo ==\n'
rg -n "stellar network (add|rm|remove|set|list|ls)" . -g '!**/.git/**' || trueRepository: ACTA-Team/contracts-acta
Length of output: 4104
🌐 Web query:
Stellar CLI "network add" existing network already exists overwrite update idempotent
💡 Result:
The Stellar CLI network add command does not natively support an "overwrite" or "update" flag to modify an existing network configuration [1][2]. When attempting to add a network with a name that already exists in your configuration, the command will typically fail because the entry already exists [1][2]. To update or modify an existing network, you must first remove the current configuration and then add it again with the new parameters [2][3]: 1. Remove the existing network: stellar network rm 2. Add the network again with the desired settings: stellar network add --rpc-url <RPC_URL> --network-passphrase <NETWORK_PASSPHRASE> Unlike some other Stellar CLI commands (such as those for contract aliases or identity management) that feature explicit --overwrite options [4][5], the network add command is not designed to be idempotent in this manner [1][2]. If you need to manage network configurations programmatically, it is recommended to ensure your workflow performs these steps sequentially or manually verifies the existence of the network before attempting the addition.
Citations:
- 1: https://github.com/stellar/soroban-examples/blob/main/FULL_HELP_DOCS.md
- 2: https://github.com/stellar/soroban-examples/blob/b46f4e0c9dccc9e51980b559915f52d8b94e9236/FULL_HELP_DOCS.md
- 3: [Bug] --network with a non-existent network should return better error stellar/stellar-cli#1297
- 4: https://github.com/stellar/stellar-cli/blob/main/FULL_HELP_DOCS.md
- 5: https://developers.stellar.org/docs/tools/cli/stellar-cli
Make stellar network add resume-safe stellar network add fails when the network already exists, so a resumed deploy can abort before any resumable steps run. Remove the existing entry first or skip the add when it’s already configured.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/deploy-mainnet.sh` around lines 109 - 115, The bootstrap_network
helper currently calls stellar network add unconditionally, which can fail on a
resumed deploy if the network was already configured. Update bootstrap_network
to be resume-safe by checking whether the network is already present before
calling stellar network add, or by removing any existing entry first and then
re-adding it. Use the existing bootstrap_network function and
NETWORK_NAME/RPC_URL/PASSPHRASE values to keep the behavior consistent while
avoiding an abort before resumable steps run.
| say " [dry-run] would GET $HORIZON_URL/accounts/$DEPLOYER_ADDRESS" | ||
| return 0 | ||
| fi | ||
| code="$(curl -s -o /dev/null -w '%{http_code}' "$HORIZON_URL/accounts/$DEPLOYER_ADDRESS")" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Add timeouts and explicit curl failure handling.
A stalled Horizon call can hang the deploy indefinitely, and transport failures currently exit via set -e without the script’s abort message.
Suggested fix
- code="$(curl -s -o /dev/null -w '%{http_code}' "$HORIZON_URL/accounts/$DEPLOYER_ADDRESS")"
+ code="$(curl -sS --connect-timeout 10 --max-time 30 \
+ -o /dev/null -w '%{http_code}' "$HORIZON_URL/accounts/$DEPLOYER_ADDRESS")" || {
+ say " ABORT: unable to reach Horizon for $DEPLOYER_ADDRESS"
+ exit 1
+ }
@@
- body="$(curl -s "$HORIZON_URL/accounts/$FEE_DEST")"
+ body="$(curl -fsS --connect-timeout 10 --max-time 30 "$HORIZON_URL/accounts/$FEE_DEST")" || {
+ say " ABORT: unable to fetch Horizon account for fee dest $FEE_DEST"
+ exit 1
+ }Also applies to: 221-221
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/deploy-mainnet.sh` at line 157, The Horizon account lookup curl call
can hang indefinitely and transport failures currently bypass the script’s own
abort handling. Update the curl invocation that assigns code using HORIZON_URL
and DEPLOYER_ADDRESS to use explicit timeout options and to fail in a way the
script can detect, then handle that failure in the surrounding deploy flow so it
emits the script’s abort message before exiting. Apply the same fix to the other
matching curl usage at the referenced second location in deploy-mainnet.sh.
| body="$(curl -s "$HORIZON_URL/accounts/$FEE_DEST")" | ||
| if printf '%s' "$body" | grep -q "$USDC_ISSUER"; then | ||
| say " USDC trustline present on $FEE_DEST" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Match the trustline by both asset code and issuer
Grepping the account JSON for $USDC_ISSUER can match unrelated fields or other assets from the same issuer, so fees may be enabled without a USDC trustline. Check asset_code == "USDC" and asset_issuer == $USDC_ISSUER on the same balances[] entry.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/deploy-mainnet.sh` around lines 221 - 223, The trustline check in the
deploy-mainnet script is too broad because it greps the account JSON for only
the issuer string, which can match unrelated fields or other assets. Update the
logic around the curl/printf/grep block in the deploy flow to inspect the same
balances[] entry for both asset_code equal to USDC and asset_issuer equal to
USDC_ISSUER before allowing fees to be enabled. Use the existing symbols
HORIZON_URL, FEE_DEST, and USDC_ISSUER to locate the check and make the match
specific to the USDC trustline.
One-shot script to deploy the three contracts to Stellar mainnet and configure the factory fee, with safety gates.
What
scripts/deploy-mainnet.shdoesmainnetnetwork with a working RPC (the CLI's built-in default ships a placeholder RPC).DEPLOYER_SECRET+DEPLOYER_ADDRESS(and optional overrides) from a gitignored.env— nothing identifying is hardcoded.DEPLOYER_ADDRESSbefore spending, and removes it on exit (trap). The secret never appears in argv.did-stellar-registry→ uploadvc-vaulttemplate → deployvc-vault-factory→set_fee_config(USDC) →set_fee_enabled.--dry-run,DEPLOY MAINNETconfirmation gate, funding preflight, USDC-trustline check (offer to defer fees), and a real--inclusion-feebid so txs don't expire unincluded.DID_ID/VAULT_HASH/FACTORY_ID(e.g. in.env) to skip already-completed steps after a partial run.derive-keysubcommand: extract a single-accountS…from a Freighter recovery phrase.Other changes
.gitignore: ignore.env/.claude/to protect deploy secrets.docs/deployments/testnet.md: redact the deployer wallet from the fee record.Verified offline (dry-run end-to-end, resume, identity mismatch abort, derive-key) and proven live: deployed all three contracts to mainnet.
Summary by CodeRabbit
New Features
Documentation
Chores