Covers the full deploy and upgrade sequence for the three Soroban contracts in this repo.
All commands target Stellar Testnet (--network testnet). Swap testnet for futurenet or a local node as needed.
# Rust + wasm target
rustup target add wasm32-unknown-unknown
# Stellar CLI (must be ≥ 21.x to match soroban-sdk 21.7.6)
cargo install --locked stellar-cli --features opt
# Confirm versions
stellar --version
rustc --versionSet these shell variables once per session (or export them from .env.local):
export ADMIN_SECRET="S..." # Secret key of the deployer/admin account
export ADMIN_PUBLIC="G..." # Matching public key
export NETWORK="testnet"
export RPC_URL="https://soroban-testnet.stellar.org"
export NETWORK_PASSPHRASE="Test SDF Network ; September 2015"Fund the account on testnet if needed:
stellar keys fund $ADMIN_PUBLIC --network $NETWORKvinculo_lending depends on vinculo_sbt at the Rust crate level, so deploy in this order:
vinculo_sbtstaking_pool(independent, can be parallel with step 1)vinculo_lending
cd backend/contracts/vinculo_sbt
cargo build --release --target wasm32-unknown-unknownWasm output: target/wasm32-unknown-unknown/release/vinculo_sbt.wasm
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/vinculo_sbt.wasm \
--source $ADMIN_SECRET \
--network $NETWORKSave the printed contract ID:
export SBT_CONTRACT_ID="C..."stellar contract invoke \
--id $SBT_CONTRACT_ID \
--source $ADMIN_SECRET \
--network $NETWORK \
-- init \
--admin $ADMIN_PUBLICinit panics if called twice — it is safe to re-run only on a freshly deployed contract.
# 1. Build the new wasm
cargo build --release --target wasm32-unknown-unknown
# 2. Upload the new wasm blob and capture the hash
WASM_HASH=$(stellar contract upload \
--wasm target/wasm32-unknown-unknown/release/vinculo_sbt.wasm \
--source $ADMIN_SECRET \
--network $NETWORK)
# 3. Upgrade the live contract in-place
stellar contract invoke \
--id $SBT_CONTRACT_ID \
--source $ADMIN_SECRET \
--network $NETWORK \
-- upgrade \
--new_wasm_hash $WASM_HASHNote:
upgradeis a built-in Soroban host function. The contract code is replaced but all storage (tiers, admin) is preserved.
cd backend/contracts/staking_pool
cargo build --release --target wasm32-unknown-unknownWasm output: target/wasm32-unknown-unknown/release/staking_pool.wasm
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/staking_pool.wasm \
--source $ADMIN_SECRET \
--network $NETWORKexport STAKING_CONTRACT_ID="C..."Requires a Stellar Asset Contract (SAC) address for the token. Use the XLM SAC on testnet or deploy your own:
stellar contract invoke \
--id $STAKING_CONTRACT_ID \
--source $ADMIN_SECRET \
--network $NETWORK \
-- init \
--token <TOKEN_SAC_ADDRESS>cd backend/contracts/staking_pool
cargo build --release --target wasm32-unknown-unknown
WASM_HASH=$(stellar contract upload \
--wasm target/wasm32-unknown-unknown/release/staking_pool.wasm \
--source $ADMIN_SECRET \
--network $NETWORK)
stellar contract invoke \
--id $STAKING_CONTRACT_ID \
--source $ADMIN_SECRET \
--network $NETWORK \
-- upgrade \
--new_wasm_hash $WASM_HASHAll balances and stake records in persistent storage survive the upgrade.
Depends on vinculo_sbt — $SBT_CONTRACT_ID must be set before deploying.
cd backend/contracts/vinculo_lending
cargo build --release --target wasm32-unknown-unknownWasm output: target/wasm32-unknown-unknown/release/vinculo_lending.wasm
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/vinculo_lending.wasm \
--source $ADMIN_SECRET \
--network $NETWORKexport LENDING_CONTRACT_ID="C..."stellar contract invoke \
--id $LENDING_CONTRACT_ID \
--source $ADMIN_SECRET \
--network $NETWORK \
-- init_lending \
--token <TOKEN_SAC_ADDRESS> \
--sbt $SBT_CONTRACT_IDstellar contract invoke \
--id $LENDING_CONTRACT_ID \
--source $ADMIN_SECRET \
--network $NETWORK \
-- fund_pool \
--from $ADMIN_PUBLIC \
--amount 10000000000cd backend/contracts/vinculo_lending
cargo build --release --target wasm32-unknown-unknown
WASM_HASH=$(stellar contract upload \
--wasm target/wasm32-unknown-unknown/release/vinculo_lending.wasm \
--source $ADMIN_SECRET \
--network $NETWORK)
stellar contract invoke \
--id $LENDING_CONTRACT_ID \
--source $ADMIN_SECRET \
--network $NETWORK \
-- upgrade \
--new_wasm_hash $WASM_HASHActive loans (
Loanentries in persistent storage) are preserved across upgrades.
Update .env.local with the new contract IDs:
NFT_CONTRACT_ID=<new SBT_CONTRACT_ID>
VITE_LENDING_CONTRACT_ID=<new LENDING_CONTRACT_ID>
Restart the backend and frontend so they pick up the new values.
Soroban does not support reverting a upgrade call directly. Recovery options:
| Scenario | Action |
|---|---|
| Bad upgrade (logic bug) | Re-upload the previous wasm blob and call upgrade again with the old hash. Keep the old .wasm file or its hash in version control. |
| Bad deploy (wrong init args) | Deploy a fresh contract instance, re-initialize with correct args, update .env.local. |
| Corrupted storage | There is no on-chain rollback. Redeploy and migrate user state off-chain if needed. |
| Lost contract ID | Query stellar contract id or check the deployment transaction in the Stellar explorer. |
Best practice: before any upgrade, record the current wasm hash:
stellar contract info --id $CONTRACT_ID --network $NETWORKStore that hash so you can roll back by re-uploading the old wasm.
See scripts/deploy_contracts.sh for a single script that runs the full deploy + init sequence from a clean clone.