Skip to content

fix(validation): return 422, add missing schemas and PATCH metadata e… #136

fix(validation): return 422, add missing schemas and PATCH metadata e…

fix(validation): return 422, add missing schemas and PATCH metadata e… #136

name: Contract Deployment
on:
push:
branches:
- main
pull_request:
types:
- opened
- reopened
- synchronize
branches:
- main
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
type: choice
options:
- testnet
- staging
- mainnet
env:
RUST_BACKTRACE: 1
jobs:
# Testnet deployment - automatic on main branch merge
deploy-testnet:
name: Deploy to Testnet
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment:
name: testnet
url: https://stellar.expert/explorer/testnet
outputs:
contract_id: ${{ steps.deploy.outputs.contract_id }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Cache Cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
contracts/target
key: ${{ runner.os }}-cargo-${{ hashFiles('contracts/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install soroban-cli
run: |
cargo install soroban-cli --locked
- name: Build contract
working-directory: contracts
run: |
cargo build --target wasm32-unknown-unknown --release --no-default-features
- name: Deploy to testnet
id: deploy
working-directory: contracts
env:
SECRET_KEY: ${{ secrets.TESTNET_SECRET_KEY }}
NETWORK_PASSPHRASE: Test SDF Network ; September 2015
RPC_URL: https://soroban-testnet.stellar.org:443
run: |
if [ -z "$SECRET_KEY" ]; then
echo "Error: TESTNET_SECRET_KEY secret not configured"
exit 1
fi
echo "Deploying to testnet..."
DEPLOY_OUTPUT=$(soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/stellar_goal_vault.wasm \
--source-account "$SECRET_KEY" \
--network testnet \
--network-passphrase "$NETWORK_PASSPHRASE" \
--rpc-url "$RPC_URL" \
2>&1)
DEPLOY_EXIT_CODE=$?
if [ $DEPLOY_EXIT_CODE -ne 0 ]; then
echo "Deployment failed: $DEPLOY_OUTPUT"
exit 1
fi
CONTRACT_ID=$(echo "$DEPLOY_OUTPUT" | grep -oE '[A-Z0-9]{56}' | head -n 1)
if [ -z "$CONTRACT_ID" ]; then
CONTRACT_ID=$(echo "$DEPLOY_OUTPUT" | tr -d '[:space:]')
fi
if [ ${#CONTRACT_ID} -ne 56 ]; then
echo "Invalid contract ID format"
exit 1
fi
echo "contract_id=$CONTRACT_ID" >> $GITHUB_OUTPUT
echo "Contract ID: $CONTRACT_ID"
echo "TESTNET_CONTRACT_ID=$CONTRACT_ID" >> $GITHUB_ENV
- name: Update testnet contract ID in repository
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const contractId = '${{ steps.deploy.outputs.contract_id }}';
// Update .env.example with new contract ID
try {
let envExample = fs.readFileSync('.env.example', 'utf8');
const updated = envExample.replace(
/TESTNET_CONTRACT_ID=.*/,
`TESTNET_CONTRACT_ID=${contractId}`
);
fs.writeFileSync('.env.example', updated);
} catch (e) {
console.log('Could not update .env.example:', e.message);
}
- name: Commit and push contract ID update
if: steps.deploy.outputs.contract_id != ''
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add .env.example
git diff --staged --quiet || git commit -m "chore: update testnet contract ID [skip ci]"
git push
- name: Create deployment summary
run: |
echo "### Testnet Deployment Successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Contract ID:** \`${{ steps.deploy.outputs.contract_id }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Explorer:** [View on Stellar Expert](https://stellar.expert/explorer/testnet/contract/${{ steps.deploy.outputs.contract_id }})" >> $GITHUB_STEP_SUMMARY
# Staging deployment - automatic on release PR
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
if: |
github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'release') &&
github.event.pull_request.draft == false
environment:
name: staging
url: https://stellar.expert/explorer/testnet
outputs:
contract_id: ${{ steps.deploy.outputs.contract_id }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Cache Cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
contracts/target
key: ${{ runner.os }}-cargo-${{ hashFiles('contracts/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install soroban-cli
run: |
cargo install soroban-cli --locked
- name: Build contract
working-directory: contracts
run: |
cargo build --target wasm32-unknown-unknown --release --no-default-features
- name: Deploy to staging (testnet)
id: deploy
working-directory: contracts
env:
SECRET_KEY: ${{ secrets.STAGING_SECRET_KEY }}
NETWORK_PASSPHRASE: Test SDF Network ; September 2015
RPC_URL: https://soroban-testnet.stellar.org:443
run: |
if [ -z "$SECRET_KEY" ]; then
echo "Error: STAGING_SECRET_KEY secret not configured"
exit 1
fi
echo "Deploying to staging (testnet)..."
DEPLOY_OUTPUT=$(soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/stellar_goal_vault.wasm \
--source-account "$SECRET_KEY" \
--network testnet \
--network-passphrase "$NETWORK_PASSPHRASE" \
--rpc-url "$RPC_URL" \
2>&1)
DEPLOY_EXIT_CODE=$?
if [ $DEPLOY_EXIT_CODE -ne 0 ]; then
echo "Deployment failed: $DEPLOY_OUTPUT"
exit 1
fi
CONTRACT_ID=$(echo "$DEPLOY_OUTPUT" | grep -oE '[A-Z0-9]{56}' | head -n 1)
if [ -z "$CONTRACT_ID" ]; then
CONTRACT_ID=$(echo "$DEPLOY_OUTPUT" | tr -d '[:space:]')
fi
if [ ${#CONTRACT_ID} -ne 56 ]; then
echo "Invalid contract ID format"
exit 1
fi
echo "contract_id=$CONTRACT_ID" >> $GITHUB_OUTPUT
echo "Contract ID: $CONTRACT_ID"
echo "STAGING_CONTRACT_ID=$CONTRACT_ID" >> $GITHUB_ENV
- name: Comment on PR with contract ID
uses: actions/github-script@v7
if: steps.deploy.outputs.contract_id != ''
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## 🚀 Staging Deployment Successful\n\n**Contract ID:** \`${{ steps.deploy.outputs.contract_id }}\`\n\n**Explorer:** [View on Stellar Expert](https://stellar.expert/explorer/testnet/contract/${{ steps.deploy.outputs.contract_id }})\n\nThis deployment is on testnet for staging purposes.`
})
- name: Create deployment summary
run: |
echo "### Staging Deployment Successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Contract ID:** \`${{ steps.deploy.outputs.contract_id }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Explorer:** [View on Stellar Expert](https://stellar.expert/explorer/testnet/contract/${{ steps.deploy.outputs.contract_id }})" >> $GITHUB_STEP_SUMMARY
# Mainnet deployment - manual approval required
deploy-mainnet:
name: Deploy to Mainnet
runs-on: ubuntu-latest
if: |
(github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'mainnet') ||
(github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, '[deploy-mainnet]'))
environment:
name: mainnet
url: https://stellar.expert/explorer/public
outputs:
contract_id: ${{ steps.deploy.outputs.contract_id }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Cache Cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
contracts/target
key: ${{ runner.os }}-cargo-${{ hashFiles('contracts/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install soroban-cli
run: |
cargo install soroban-cli --locked
- name: Build contract
working-directory: contracts
run: |
cargo build --target wasm32-unknown-unknown --release --no-default-features
- name: Deploy to mainnet
id: deploy
working-directory: contracts
env:
SECRET_KEY: ${{ secrets.MAINNET_SECRET_KEY }}
NETWORK_PASSPHRASE: Public Global Stellar Network ; September 2015
RPC_URL: https://soroban-rpc.publicnode.stellar.org:443
run: |
if [ -z "$SECRET_KEY" ]; then
echo "Error: MAINNET_SECRET_KEY secret not configured"
exit 1
fi
echo "⚠️ DEPLOYING TO MAINNET - THIS IS IRREVERSIBLE ⚠️"
echo "Contract ID: $CONTRACT_ID"
sleep 10
echo "Deploying to mainnet..."
DEPLOY_OUTPUT=$(soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/stellar_goal_vault.wasm \
--source-account "$SECRET_KEY" \
--network public \
--network-passphrase "$NETWORK_PASSPHRASE" \
--rpc-url "$RPC_URL" \
2>&1)
DEPLOY_EXIT_CODE=$?
if [ $DEPLOY_EXIT_CODE -ne 0 ]; then
echo "Deployment failed: $DEPLOY_OUTPUT"
exit 1
fi
CONTRACT_ID=$(echo "$DEPLOY_OUTPUT" | grep -oE '[A-Z0-9]{56}' | head -n 1)
if [ -z "$CONTRACT_ID" ]; then
CONTRACT_ID=$(echo "$DEPLOY_OUTPUT" | tr -d '[:space:]')
fi
if [ ${#CONTRACT_ID} -ne 56 ]; then
echo "Invalid contract ID format"
exit 1
fi
echo "contract_id=$CONTRACT_ID" >> $GITHUB_OUTPUT
echo "Contract ID: $CONTRACT_ID"
echo "MAINNET_CONTRACT_ID=$CONTRACT_ID" >> $GITHUB_ENV
- name: Update mainnet contract ID in repository
uses: actions/github-script@v7
if: steps.deploy.outputs.contract_id != ''
with:
script: |
const fs = require('fs');
const contractId = '${{ steps.deploy.outputs.contract_id }}';
// Update .env.example with new contract ID
try {
let envExample = fs.readFileSync('.env.example', 'utf8');
const updated = envExample.replace(
/MAINNET_CONTRACT_ID=.*/,
`MAINNET_CONTRACT_ID=${contractId}`
);
fs.writeFileSync('.env.example', updated);
} catch (e) {
console.log('Could not update .env.example:', e.message);
}
- name: Commit and push contract ID update
if: steps.deploy.outputs.contract_id != ''
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add .env.example
git diff --staged --quiet || git commit -m "chore: update mainnet contract ID [skip ci]"
git push
- name: Create GitHub release
if: steps.deploy.outputs.contract_id != ''
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ github.run_number }}
release_name: Mainnet Contract Deployment v${{ github.run_number }}
body: |
## Mainnet Contract Deployment
**Contract ID:** `${{ steps.deploy.outputs.contract_id }}`
**Explorer:** [View on Stellar Expert](https://stellar.expert/explorer/public/contract/${{ steps.deploy.outputs.contract_id }})
This contract has been deployed to the Stellar mainnet and is ready for production use.
draft: false
prerelease: false
- name: Create deployment summary
run: |
echo "### 🎉 Mainnet Deployment Successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Contract ID:** \`${{ steps.deploy.outputs.contract_id }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Explorer:** [View on Stellar Expert](https://stellar.expert/explorer/public/contract/${{ steps.deploy.outputs.contract_id }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "⚠️ **IMPORTANT:** This contract is now live on mainnet. All transactions are irreversible." >> $GITHUB_STEP_SUMMARY