This guide covers deploying the Accord contract to Stellar testnet and wiring the frontend to it.
- Stellar CLI installed and
accord-deployeridentity created (seedocs/SETUP.md) - Funded testnet account:
stellar keys fund accord-deployer --network testnet
From the repository root:
stellar contract buildThe compiled WASM will be at:
target/wasm32v1-none/release/accord.wasm
bash scripts/deploy.shThe script:
- Uploads the WASM to the network
- Deploys a new contract instance
- Prints the Contract ID — save this value
Alternatively, run manually:
stellar network use testnet
# Upload WASM
stellar contract upload \
--network testnet \
--source-account accord-deployer \
--wasm target/wasm32v1-none/release/accord.wasm
# Deploy contract
stellar contract deploy \
--network testnet \
--source-account accord-deployer \
--wasm target/wasm32v1-none/release/accord.wasmReplace CONTRACT_ID, OWNER_1, OWNER_2, OWNER_3, and THRESHOLD with your values:
stellar contract invoke \
--network testnet \
--source-account accord-deployer \
--id CONTRACT_ID \
-- initialize \
--owners '["OWNER_1","OWNER_2","OWNER_3"]' \
--threshold 2Important: All owner addresses must sign this transaction (each must authorize). In the CLI, use --source-account for each owner or handle via multi-auth in the frontend.
Send tokens to the contract address so it can pay out approved proposals:
# Example: send 100 XLM (using the native XLM SAC on testnet)
stellar contract invoke \
--network testnet \
--source-account accord-deployer \
--id NATIVE_TOKEN_CONTRACT_ID \
-- transfer \
--from accord-deployer \
--to CONTRACT_ID \
--amount 1000000000The native XLM SAC contract ID on testnet:
CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC
Add the contract address to frontend/.env.local:
VITE_CONTRACT_ADDRESS=YOUR_CONTRACT_ID
VITE_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
VITE_NETWORK_PASSPHRASE=Test SDF Network ; September 2015Then restart the dev server:
cd frontend && npm run devCheck the contract on the testnet explorer:
https://stellar.expert/explorer/testnet/contract/YOUR_CONTRACT_ID
Or use Stellar Lab:
https://lab.stellar.org/smart-contracts/contract-explorer?contractId=YOUR_CONTRACT_ID&network=testnet
| Field | Value |
|---|---|
| Contract ID | (fill in after your first deploy) |
| Network | Testnet |
| WASM hash | (printed by deploy script) |
| Deploy tx | (printed by deploy script) |
| Explorer | https://stellar.expert/explorer/testnet |
On every successful contract build pushed to main, the contract CI workflow writes a wasm-manifest.json file and uploads it in the same accord-contract-wasm artifact as the compiled WASM.
Example shape:
{
"wasm_file": "accord.wasm",
"wasm_hash": "0123abcd…",
"commit_sha": "deadbeef…",
"build_timestamp": "2026-07-23T20:00:00Z",
"soroban_sdk_version": "25.3.1"
}| Field | Meaning |
|---|---|
wasm_file |
Basename of the built contract artifact |
wasm_hash |
SHA-256 of the WASM bytes (hex) |
commit_sha |
Git commit that produced the build (GITHUB_SHA) |
build_timestamp |
UTC time the CI step generated the manifest |
soroban_sdk_version |
Pinned soroban-sdk version from the workspace Cargo.toml |
How to verify a deployed contract against source
- Download the
accord-contract-wasmartifact from the CI run for the commit you intend to verify. - Confirm
commit_shainwasm-manifest.jsonmatches that commit. - Recompute
sha256sum(orshasum -a 256) of the artifactaccord.wasmand confirm it equalswasm_hash. - On Stellar Expert (or via
stellar contract info hash --id CONTRACT_ID), confirm the live contract executable hash matcheswasm_hash.
If the live hash does not match the manifest for the claimed commit, do not treat that deployment as corresponding to that source revision.
The contract supports in-place upgrades through a two-step WASM upload and upgrade flow. Only an existing owner may call the upgrade function. All on-chain storage (proposals, owners, threshold) is preserved after a successful upgrade.
Before approving or executing an upgrade, follow docs/UPGRADE_SAFETY.md for WASM hash verification, owner coordination, post-upgrade validation, and red flags.
Step 1 — Upload the new WASM and obtain the WASM hash:
stellar contract upload \
--network testnet \
--source-account accord-deployer \
--wasm target/wasm32v1-none/release/accord.wasmThe command prints the WASM hash — save it for the next step.
Step 2 — Invoke upgrade on the live contract:
stellar contract invoke \
--network testnet \
--source-account accord-deployer \
--id CONTRACT_ID \
-- upgrade \
--caller accord-deployer \
--new_wasm_hash WASM_HASHReplace CONTRACT_ID with the live contract address and WASM_HASH with the hash from Step 1. The contract ID and all on-chain storage are preserved after a successful upgrade.
migrate_to_weighted_governance is a one-time, consequential action for a multisig that was deployed before per-owner voting weights existed (a flat M-of-N approval count). It assigns every current owner a weight of one and sets the total weight equal to the owner count — mathematically identical to the prior flat-count model, so quorum evaluation for existing proposals is unaffected. A contract that was already initialized with real per-owner weights from the start never needs this and will reject the call.
Who calls it, and what to collect beforehand: Any owner may submit the transaction, but the call requires approvers — a list of distinct registered owners who must each authorize (co-sign) the transaction. Because the contract has not yet been migrated when this runs, the required number of co-signers is the contract's existing threshold value interpreted as a flat approval count (the same meaning it had before migration), not a weighted sum. Collect authorized signatures from at least threshold distinct owners before submitting, the same way you would for upgrade or set_guardian.
Order of operations relative to a code upgrade: Migration only makes sense against a contract already running WASM that supports weighted governance. Always upgrade first (see Upgrading the Contract above), confirm the upgrade succeeded, and only then call migrate_to_weighted_governance. Calling it before upgrading isn't possible — the function doesn't exist in pre-weighted-governance WASM — but confirm the upgrade transaction has landed before attempting migration in the same operational window.
Before running the migration, verify:
- The contract has not already been migrated: call
is_governance_migratedand confirm it returnsfalse. If it returnstrue, do not proceed — see the note on repeat calls below. - The contract has already been upgraded to WASM that supports weighted governance (e.g. confirm the WASM hash on-chain matches a build that includes
migrate_to_weighted_governance). - You have collected authorization from at least
thresholddistinct current owners.
stellar contract invoke \
--network testnet \
--source-account accord-deployer \
--id CONTRACT_ID \
-- migrate_to_weighted_governance \
--approvers '["OWNER_1","OWNER_2"]'Immediately after running the migration, verify:
is_governance_migratednow returnstrue.get_total_weightequals the current owner count.get_owner_weightreturns1for every current owner.- Any proposals that were already
PendingorReadystill report the same status (get_proposal), since their snapshottedquorum_weightand cumulativeapprovalsare unaffected by the migration.
What to expect if migration is attempted a second time, or against a contract that never needed it: The call fails with the AlreadyMigrated error and makes no changes to any stored weight data — this is expected, guarded behavior, not a sign of a broken deployment. It means the contract's owners already carry real per-owner weights, whether from a prior successful migration or from having been initialized directly through the weighted initialize.
Pull requests that touch contracts/accord/**, the workspace manifests, or the
simulation script run the Testnet Upgrade Simulation job in
.github/workflows/contract.yml. The job
is also runnable locally:
bash scripts/testnet-upgrade-simulation.shWhat it does
- Builds a legacy-shaped contract with the
legacy-fixturecargo feature. That feature makesinitializeomit the governance-migration flag and the storage-schema marker, simulating a deployment that predates those migrations. It must never be enabled for a production build. - Creates an ephemeral identity, funds it with Friendbot, and deploys the legacy fixture to testnet.
- Initializes owner A, adds owners B and C through governance proposals, and creates a partially-unapproved transfer proposal (an in-flight proposal).
- Builds the current source, uploads its WASM, and calls
upgradeon the same contract ID. The contract address and all storage must survive. - Calls
migrate_to_weighted_governanceand verifies that owners, weights, total weight, threshold, storage-schema version, proposal count, and the in-flight proposal are all correct. - Approves and revokes the pre-upgrade proposal to prove post-migration behaviour still works on pre-existing state.
Isolation and cleanup
- The deployer and owner identities are generated per run. The script removes
them in an exit trap; set
KEEP_IDENTITIES=1to keep them for debugging. - No repository secrets are required. Every account is disposable and funded through Friendbot.
- Soroban has no contract-delete operation, so the testnet contract is left isolated under a unique per-run contract ID. It holds no funds and expires naturally when its storage TTL lapses; it is never used by the frontend or by any other deployment.
- The report is written to
target/testnet-upgrade-simulation/report.md, and CI uploads that directory as thetestnet-upgrade-simulationartifact.
Requirements
- Stellar CLI with
contract deploy,contract upload, andcontract invokesupport (the workflow installsstellar/stellar-cli@v28.1.0). - Rust
wasm32v1-nonetarget. python3for JSON assertions.
Auth errors (Error(Contract, #3) — Unauthorized)
The caller is not a registered owner, or insufficient XLM was available to cover the Soroban authorization fee. Verify the --source-account is one of the contract's owners and that the account holds enough XLM (a few stroops above the base reserve is usually sufficient).
Fee errors (transaction rejected for insufficient balance)
The submitting account does not have enough XLM to cover the transaction fee. Fund the account with at least 10 XLM before retrying:
stellar keys fund accord-deployer --network testnetRPC timeout errors
The Soroban RPC node did not respond within the CLI timeout. Check the RPC node health at https://soroban-testnet.stellar.org/ and retry the command. If the node is healthy, increase the --timeout flag (e.g. --timeout 120) or switch to a different RPC endpoint.
Run these checks after every deploy or upgrade to confirm the contract is live and healthy:
- Contract responds — call
get_thresholdand confirm it returns the expected integer:stellar contract invoke --network testnet --id CONTRACT_ID -- get_threshold
- WASM hash matches — open the contract page on Stellar Expert and verify the WASM hash matches the one printed during
stellar contract upload:https://stellar.expert/explorer/testnet/contract/CONTRACT_ID - Frontend connects — start the frontend (
npm run dev), open the app in a browser, and confirm proposals load without errors. If the page shows a connection error, double-checkVITE_CONTRACT_ADDRESSandVITE_SOROBAN_RPC_URLinfrontend/.env.local.
For production operations, incident containment, rollback procedures, and key rotations, refer to the operational runbooks:
- Incident Response Runbook — severity levels, detection, immediate containment (
freeze,enter_recovery), escalation, and recovery. - Upgrade Rollback Runbook — diagnosing flawed deployments, in-place WASM rollback via
upgrade, bytecode TTL verification, and post-rollback state checks. - Key Rotation Runbook — step-by-step procedures for rotating multisig owner keys and replacing the guardian address.
- Runbooks Index — directory overview, operational prerequisites, and secrets policy.