A secure upgrade framework for NEAR smart contracts with proposal workflows, custom authorization, and cooldown periods.
near-versionist enables controlled, secure upgrades for NEAR smart contracts through a proposal-based workflow. Instead of immediately deploying new code, changes go through a propose → review → accept cycle with configurable authorization and mandatory cooldown periods.
- Proposal Workflow - New code must be proposed before deployment, allowing review
- Custom Authorization - Define who can propose and who can accept updates via the
UpdatableAuthtrait - Cooldown Periods - Mandatory waiting time before accepting proposals
- Checksum Verification - SHA-256 verification prevents race conditions during acceptance
- Optional Init Calls - Execute initialization functions after upgrade
Add to your Cargo.toml:
[dependencies]
near-versionist = "0.2"use near_sdk::{near, AccountId};
use near_versionist::{updatable, UpdatableAuth};
#[updatable(cooldown = 604800000)] // 7-day cooldown
#[near(contract_state)]
pub struct Contract {
owner_id: AccountId,
maintainer_id: AccountId,
}
impl UpdatableAuth for Contract {
fn is_authorized_to_propose_update(&self, account_id: &AccountId) -> bool {
&self.maintainer_id == account_id
}
fn is_authorized_to_accept_update(&self, account_id: &AccountId) -> bool {
&self.owner_id == account_id
}
}The #[updatable] macro generates these methods on your contract:
| Method | Description |
|---|---|
get_update_proposal() |
View current proposal (checksum, timestamp, cooldown) |
propose_update(code) |
Propose new contract code |
propose_update_with_init_call(code, method, args, gas) |
Propose with post-upgrade initialization |
accept_update(checksum) |
Accept and deploy (after cooldown) |
reject_update() |
Reject current proposal |
# Build your new contract
cargo near build
# Encode the WASM as base64
CODE=$(base64 -i target/near/my_contract.wasm)
# Propose the update
near call my-contract.near propose_update \
--args "{\"code\": \"$CODE\"}" \
--accountId maintainer.near \
--gas 300Tgasnear view my-contract.near get_update_proposalOutput:
{
"checksum": "a1b2c3d4...",
"init_call": null,
"proposed_at": 1706000000000000000,
"cooldown": 604800000
}near call my-contract.near accept_update \
--args '{"checksum": "a1b2c3d4..."}' \
--accountId owner.near \
--gas 300TgasImplement the UpdatableAuth trait to control access:
impl UpdatableAuth for Contract {
// Who can propose new code
fn is_authorized_to_propose_update(&self, account_id: &AccountId) -> bool {
self.maintainers.contains(account_id)
}
// Who can accept/reject proposals
fn is_authorized_to_accept_update(&self, account_id: &AccountId) -> bool {
&self.owner_id == account_id
}
}Default implementation allows all accounts - always override for production use.
near-versionist/
├── near-versionist/ # Runtime library with UpdatableAuth trait
├── near-versionist-macros/ # #[updatable] procedural macro
└── examples/contract/ # Example contract implementation
MIT