Operational guide for rotating Dongle smart contract administrator keys on Stellar/Soroban deployments.
Dongle stores admins in on-chain storage (AdminList, Admin(address)). Privileged actions require Address::require_auth() from an admin account. Rotation means adding a new admin with the new key, verifying behavior, then removing the old admin — never the reverse.
Goals:
- Maintain uninterrupted moderation and configuration capability
- Avoid a window with zero admins
- Produce an auditable trail via
AdminActionLogand admin events
| Threat | Impact | Mitigation |
|---|---|---|
| Compromised admin key | Unauthorized fee changes, verification abuse, review deletion | Multisig threshold, timelocks, rapid rotation |
| Lost admin key | Contract becomes ungovernable if last admin | Maintain ≥2 admins; secure offline backups |
| Insider collusion | Threshold admins bypass controls | Separate key custody, monitoring, incident runbooks |
| Rotation mistake (remove before add) | Zero admins; irreversible without upgrade | Always add-before-remove checklist |
See also THREAT_MODEL.md for broader contract assumptions.
- Soroban CLI / Stellar CLI configured for target network
- Contract ID and current admin list (
get_admin_list) - New admin key generated on hardware wallet or HSM
- Maintenance window communicated to operators
- Inventory — Record current admins, approval threshold, and pending timelock/proposal actions.
- Generate new key — Create the replacement admin keypair; store seed material offline.
- Add new admin (existing admin signs):
stellar contract invoke \ --id <CONTRACT_ID> \ --source-account <CURRENT_ADMIN> \ -- add_admin \ --caller <CURRENT_ADMIN> \ --new_admin <NEW_ADMIN>
- Verify addition — Confirm
is_admin(new_admin) == trueand admin count increased. - Testnet validation — Run the checklist in Section 6 on testnet before mainnet.
- Operational smoke test — With the new key, perform a non-destructive admin read (
get_admin_list) and a reversible action on testnet (e.g. add/remove a test reserved name). - Remove old admin (new or remaining admin signs):
stellar contract invoke \ --id <CONTRACT_ID> \ --source-account <NEW_ADMIN> \ -- remove_admin \ --caller <NEW_ADMIN> \ --admin_to_remove <OLD_ADMIN>
- Final verification — Old key must fail
is_admin; new key must succeed; at least one admin remains.
When get_admin_approval_threshold() > 1, use the admin proposal flow instead of direct add_admin / remove_admin. Collect threshold approvals before execution.
If the new admin misbehaves or was misconfigured before old admin removal:
- Do not remove the old admin.
- Remove the faulty new admin with a trusted remaining admin.
- Investigate root cause; restart rotation from Step 3.
If the old admin was already removed and the new admin is lost:
- There is no on-chain recovery without a remaining admin or contract upgrade.
- Escalate to incident response (Section 8).
After every rotation:
| Check | Command / method | Expected |
|---|---|---|
| Admin count | get_admin_count |
≥ 1, matches expectation |
| New admin active | is_admin(new_admin) |
true |
| Old admin revoked | is_admin(old_admin) |
false |
| Event log | Index AdminAdded / AdminRemoved events |
Matches rotation timeline |
| Action log | list_admin_actions (if indexed) |
Records admin changes |
Run on testnet before mainnet rotation:
- Deploy or locate testnet contract ID
- Fund current and new admin accounts
-
add_adminsucceeds and emits event - New admin can call a gated read-only admin endpoint
- New admin can execute one reversible admin action
-
remove_adminon old key succeeds -
CannotRemoveLastAdmintriggers when attempting to remove sole admin - Multisig/timelock paths tested if enabled on deployment
- Document transaction hashes and ledger sequence numbers
- Maintain at least two independent admins in production
- Use hardware wallets for admin keys; never commit secrets to CI
- Rotate on a scheduled cadence (e.g. quarterly) and after personnel changes
- Monitor admin events via indexer alerts
- Prefer timelocked fee/config changes on mainnet. Scheduled admin actions
must execute between 1 day and 90 days out (
TIMELOCK_MIN_DELAY…TIMELOCK_MAX_DELAY); anything outside that window is rejected withInvalidInput. SeeTIMELOCK.md. For sub-day emergencies useemergency_pause, not the timelock. - During any rotation or incident-response run, inventory pending timelock
actions (
list_scheduled_actions) andcancel_actionanything unexpected. - Keep this playbook and contract ID in your internal ops wiki
Assume active attacker if a admin private key may be exposed.
- Identify compromised address in
get_admin_list - If another admin is available: add a clean emergency admin from uncompromised key
- If multisig: submit emergency proposal to remove compromised admin
- Notify team; preserve ledger/event evidence
- Remove compromised admin once replacement is active
- Review
AdminActionLogand recent transactions for unauthorized:- Fee changes
- Verification approvals/revocations
- Review moderation (
hide_review,admin_delete_review) - Treasury or timelock executions
- Revoke off-chain API keys tied to the compromised identity
- Re-run Section 6 checklist on testnet with new keys
- Publish internal post-incident summary with timeline and ledger hashes
- Update custody procedures; schedule follow-up rotation
- Pause dependent frontends that rely on admin-curated data
- Coordinate contract upgrade or migration with stakeholders
- Do not attempt ad-hoc mainnet experiments without testnet proof
| Role | Name | Contact | Notes |
|---|---|---|---|
| Primary admin custodian | |||
| Backup admin custodian | |||
| Security lead | |||
| Indexer/on-call |
Related docs: INITIALIZATION_DEPLOYMENT_CHECKLIST.md, THREAT_MODEL.md, CONTRACT_INTERFACE.md