DApp Admin
When a DApp is first deployed (genesis::run), the account that executed the transaction becomes the DApp admin. The admin can update metadata, transfer ownership, pause the DApp, and top up storage credits.
Admin functions all operate on DappStorage — the per-DApp shared object created during deployment. Access it via the object ID stored in .history/sui_<network>/latest.json after dubhe publish.
What the admin can do
| Action | Function |
|---|---|
| Update name, description, website, cover | set_metadata<DappKey> |
| Transfer admin role | propose_ownership<DappKey> + accept_ownership<DappKey> |
| Emergency pause / resume | set_paused<DappKey> |
| Top up storage credits | recharge_credit<DappKey> |
| Register a new upgrade | upgrade_dapp<DappKey> |
Updating metadata
use dubhe::dapp_system;
use dubhe::dapp_service::DappStorage;
use mygame::dapp_key::DappKey;
use std::ascii::string;
public entry fun update_metadata(dapp_storage: &mut DappStorage, ctx: &mut TxContext) {
dapp_system::set_metadata<DappKey>(
dapp_storage,
string(b"My Game v2"),
string(b"An on-chain strategy game"),
string(b"https://mygame.xyz"),
vector[string(b"https://mygame.xyz/cover.png")], // cover_url (list)
vector[string(b"PartnerProtocol")], // partners (list)
ctx
);
}Only the current admin can call set_metadata. Attempting to call it from any other account aborts with NO_PERMISSION.
Two-step ownership transfer
Dubhe uses the Ownable2Step pattern to prevent accidental loss of admin control. A single typo in the target address cannot permanently lock you out — the nominee must actively accept the role.
Step 1 — Propose
The current admin nominates a new owner:
// Called by the current admin
dapp_system::propose_ownership<DappKey>(
&mut dapp_storage,
@0xNEW_ADMIN,
ctx
);At this point the existing admin retains full control. The pending_admin field in DappStorage is set to @0xNEW_ADMIN.
To cancel a pending transfer, call propose_ownership again with @0x0:
// Cancel — clears the pending nominee
dapp_system::propose_ownership<DappKey>(&mut dapp_storage, @0x0, ctx);Step 2 — Accept
The nominated account must confirm by calling accept_ownership from their own wallet:
// Called by the NEW admin (@0xNEW_ADMIN)
dapp_system::accept_ownership<DappKey>(
&mut dapp_storage,
ctx
);On success:
- The
adminfield inDappStorageis set to the caller - The
pending_adminfield is cleared to@0x0
If there is no pending transfer (or the caller is not the nominee), the transaction aborts.
Emergency pause
Pause the DApp to halt all writes during an incident:
// Admin pauses — all entry functions guarded by ensure_not_paused will abort
dapp_system::set_paused<DappKey>(
&mut dapp_storage,
true,
ctx
);
// Resume when ready
dapp_system::set_paused<DappKey>(
&mut dapp_storage,
false,
ctx
);For the pause flag to take effect, your entry functions must include the guard:
dapp_system::ensure_not_paused<DappKey>(&dapp_storage);See Access Control for the recommended guard order.
Topping up storage credits
Anyone can top up a DApp’s storage credits — the admin, a sponsor, or a community member:
use sui::coin;
use dubhe::dapp_service::{DappHub, DappStorage};
use dubhe::dapp_system;
// Recharge with 0.1 SUI (100_000_000 MIST)
let payment = coin::split(&mut my_sui_coin, 100_000_000, ctx);
dapp_system::recharge_credit<DappKey>(&dapp_hub, &mut dapp_storage, payment, ctx);- Payment is forwarded to the Dubhe framework treasury
- Credits are added to the DApp’s
credit_poolat a 1:1 rate (1 MIST = 1 credit unit) - No admin restriction — any account can top up any DApp
See Storage Fees for how credits are consumed and how to estimate how much SUI your DApp needs.
Registering an upgrade
After publishing a new package version, the admin must register it:
dapp_system::upgrade_dapp<DappKey>(
&mut dapp_storage,
new_package_id, // address of the newly published package
migrate::on_chain_version(), // must be > current on-chain version
ctx
);See Contract Upgrading for the full upgrade workflow.