Skip to content

feat: mainnet deploy orchestrator script#70

Merged
aguilar1x merged 7 commits into
devfrom
feat/deploy-mainnet-script
Jun 30, 2026
Merged

feat: mainnet deploy orchestrator script#70
aguilar1x merged 7 commits into
devfrom
feat/deploy-mainnet-script

Conversation

@aguilar1x

@aguilar1x aguilar1x commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

One-shot script to deploy the three contracts to Stellar mainnet and configure the factory fee, with safety gates.

What scripts/deploy-mainnet.sh does

  • Bootstraps the mainnet network with a working RPC (the CLI's built-in default ships a placeholder RPC).
  • Loads DEPLOYER_SECRET + DEPLOYER_ADDRESS (and optional overrides) from a gitignored .envnothing identifying is hardcoded.
  • Imports the secret into a temporary identity, verifies it resolves to DEPLOYER_ADDRESS before spending, and removes it on exit (trap). The secret never appears in argv.
  • Sequence: build → deploy did-stellar-registry → upload vc-vault template → deploy vc-vault-factoryset_fee_config (USDC) → set_fee_enabled.
  • Safety: --dry-run, DEPLOY MAINNET confirmation gate, funding preflight, USDC-trustline check (offer to defer fees), and a real --inclusion-fee bid so txs don't expire unincluded.
  • Resumable: pre-set DID_ID / VAULT_HASH / FACTORY_ID (e.g. in .env) to skip already-completed steps after a partial run.
  • derive-key subcommand: extract a single-account S… 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

    • Added a new mainnet deployment helper with dry-run support, guided confirmations, and a key-derivation option.
    • Improved deployment safety by checking wallet readiness and fee configuration before making on-chain changes.
  • Documentation

    • Updated deployment docs to use a neutral placeholder for the deployer wallet instead of a specific address.
  • Chores

    • Expanded ignore rules to keep environment files and local tooling files out of version control.

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.
@almanax-ai

almanax-ai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Plan expired

Your subscription has expired. Please renew your subscription to continue using CI/CD integration and other features.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds scripts/deploy-mainnet.sh, a shell script that deploys DID, vault template, and vault factory contracts to Stellar mainnet with dry-run mode, HD key derivation, identity verification, USDC trustline checks, and fee configuration. Also adds .env/.claude/ gitignore rules and updates a testnet docs placeholder.

Changes

Mainnet Deployment Script

Layer / File(s) Summary
.gitignore and testnet docs
.gitignore, docs/deployments/testnet.md
Adds .env, .env.*, .claude/ ignore rules; changes testnet fee dest field from a hardcoded address to a deployer wallet placeholder.
CLI parsing and helpers
scripts/deploy-mainnet.sh
Defines argument parsing for derive-key and --dry-run flags, logging helpers (say, step), run/capture wrappers that switch behavior in dry-run, resolve_identity, and print_params.
Bootstrap, confirmation gate, identity import
scripts/deploy-mainnet.sh
Implements bootstrap_network, the DEPLOY MAINNET typed confirmation gate (skipped in dry-run), cleanup trap, import_and_verify_identity (aborts on public key mismatch), and check_funding/build_all preflight checks.
Contract deployment with resume support
scripts/deploy-mainnet.sh
Adds deploy_did, upload_vault, and deploy_factory functions that skip steps when contract IDs/hashes are already set, enabling resume after partial runs.
Trustline check, fee config, and summary
scripts/deploy-mainnet.sh
Implements check_trustline with an interactive prompt when the USDC trustline is missing, configure_fees setting token/dest/standard/enabled state, and print_summary with dry-run placeholders.
derive-key subcommand and main orchestration
scripts/deploy-mainnet.sh
Adds derive_key which scans HD paths 0–30 to match DEPLOYER_ADDRESS and prints the secret; wires the cleanup trap and main() sequence, dispatching to derive-key or full deployment.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop hop, to mainnet we go,
With dry-run checks and keys in a row,
The trustline gleams, the factory's set,
No hardcoded secrets the repo will get,
A .gitignore patch seals the den —
This bunny deploys with confidence again! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a mainnet deployment orchestrator script.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/deploy-mainnet-script

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aguilar1x aguilar1x self-assigned this Jun 30, 2026
@aguilar1x aguilar1x merged commit 10f8d0e into dev Jun 30, 2026
3 of 4 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4f540 and 1fbc3c4.

📒 Files selected for processing (3)
  • .gitignore
  • docs/deployments/testnet.md
  • scripts/deploy-mainnet.sh

Comment thread scripts/deploy-mainnet.sh
Comment on lines +16 to +17
TMP_IDENTITY="_acta_deployer"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Comment thread scripts/deploy-mainnet.sh
Comment on lines +55 to +62
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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
+fi

Also 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.

Comment thread scripts/deploy-mainnet.sh
Comment on lines +109 to +115
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"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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 --help

Repository: 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/**' || true

Repository: 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/**' || true

Repository: 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:


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.

Comment thread scripts/deploy-mainnet.sh
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")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Comment thread scripts/deploy-mainnet.sh
Comment on lines +221 to +223
body="$(curl -s "$HORIZON_URL/accounts/$FEE_DEST")"
if printf '%s' "$body" | grep -q "$USDC_ISSUER"; then
say " USDC trustline present on $FEE_DEST"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

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.

1 participant