Storage Fees
Every write operation in a Dubhe-built DApp consumes storage credits. This page explains how the fee system works, how credits are settled lazily, and how to top up a DApp’s credit balance.
Credit pools
Each registered DApp maintains two credit pools, both denominated in MIST (the smallest unit of SUI, where 1 SUI = 1,000,000,000 MIST):
| Pool | Description |
|---|---|
free_credit | Granted by the Dubhe framework operator — no SUI payment required. Has an expiry timestamp; expired free credit is not counted. Used up first. |
credit_pool | Purchased by topping up via recharge_credit. Used only after free_credit is exhausted. Does not expire. |
When a settlement is processed:
effective_free = free_credit if not expired, else 0
total_available = effective_free + credit_pool
if total_available >= total_cost:
deduct from effective_free first, then credit_pool
else if total_available > 0:
partial settlement (proportional)
else:
settlement skipped silently (emit SettlementSkipped event)Settlement never aborts a transaction — if credit runs out, the DApp simply records a debt and emits an event. No user transaction is rolled back due to insufficient credit.
This behaviour applies to user writes only. Global resource writes follow a different path — see below.
Global resource write fees
Writes to global resources (global: true in dubhe.config.ts) are charged immediately and synchronously — not through lazy settlement.
When set_global_record or set_global_field is called:
- The framework computes the charge using the DApp’s per-DApp fee rates.
- It deducts from
free_creditfirst, then fromcredit_pool. - If combined credit is insufficient, the transaction aborts with
insufficient_credit_error.
// Writing to a global resource — charged immediately.
// Aborts if DappStorage has insufficient credit.
dapp_system::set_global_record<DappKey>(
&mut dapp_storage,
key,
field_names,
values,
offchain,
ctx,
);Consequences for DApp design:
- Keep adequate credit in the DApp’s
credit_poolwhen using global resources. A depleted pool causes every global write to abort until recharged. - For high-frequency write paths, prefer per-user resources (lazy-settled) over global resources (immediately charged).
delete_global_recordanddelete_global_fielddo not charge — only write operations are metered.
Lazy settlement
Unlike immediate per-write charging, Dubhe uses lazy settlement:
- Each write increments
unsettled_countin the user’sUserStorage— no credit deducted. settle_writesis called (typically at the start of a subsequent transaction) to compute and deduct the actual fee.
// Settle pending writes for a user — call at the start of a transaction
dapp_system::settle_writes<DappKey>(
&dapp_hub,
&mut dapp_storage,
&mut user_storage,
ctx,
);Benefits:
- User transactions never abort due to insufficient credit.
- Multiple writes in one transaction are charged in a single settlement batch.
- The DApp admin can choose when to settle (e.g., once per session).
Fee formula
total_cost = base_fee × unsettled_writes + bytes_fee × unsettled_bytesunsettled_writes is the number of write operations since the last settlement. unsettled_bytes is the cumulative byte size of all records written.
Default parameters (set during framework deployment and configurable by the framework operator):
| Parameter | Default value | Description |
|---|---|---|
base_fee | 80,000 MIST | Fixed cost per write operation |
bytes_fee | 500 MIST/byte | Variable cost per byte written |
free_credit | 25 SUI | Initial free allocation per new DApp |
Recharging a DApp’s credit balance
Any account — the DApp owner, a sponsor, or a community member — can top up a DApp’s credit_pool by sending a Coin<SUI> payment. The SUI is forwarded to the framework treasury; credits are added at a 1:1 rate (1 MIST = 1 credit unit).
use dubhe::dapp_service::{DappHub, DappStorage};
// Anyone can top up credits for a registered DApp.
dapp_system::recharge_credit<DappKey>(
&dapp_hub,
&mut dapp_storage,
payment, // Coin<SUI>
ctx,
);There is no admin restriction on recharge_credit — any address may call it.
Framework operator: managing free credits
The Dubhe framework operator can grant, revoke, or extend free credits for any DApp:
// Grant free credits (replaces any existing free credit allocation)
dapp_system::grant_free_credit<DappKey>(
&dapp_hub, // immutable reference
&mut dapp_storage,
5_000_000_000, // amount in MIST (≈ 5 SUI)
expires_at_ms, // Clock ms timestamp when free credit expires
ctx,
);
// Revoke all free credits
dapp_system::revoke_free_credit<DappKey>(&dapp_hub, &mut dapp_storage, ctx);
// Extend expiry without changing the amount
dapp_system::extend_free_credit<DappKey>(
&dapp_hub, // immutable reference
&mut dapp_storage,
new_expires_at_ms,
ctx,
);Only the framework operator can call these functions. Any other caller aborts with NO_PERMISSION.
Write debt limit
To prevent abuse, there is a maximum number of unsettled writes per UserStorage (MAX_UNSETTLED_WRITES). If a user exceeds this limit, further writes abort with USER_DEBT_LIMIT_EXCEEDED. This encourages DApps to call settle_writes regularly.
Common questions
Q: What happens if a DApp runs out of credit during settlement?
Settlement is partial — the Framework deducts what credit is available and records the remainder as unsettled. No data is lost. The DApp continues to function; future writes accumulate further debt until the DApp is topped up.
Q: Who receives the SUI paid during recharge?
The Coin<SUI> is transferred directly to the framework treasury address. The framework operator is responsible for maintaining on-chain infrastructure funded by these payments.
Q: Is there a fee for read operations?
No. get_field, get_record, and has_record are pure reads and incur no credit deduction or unsettled count increment.
Q: Is there a fee for delete_record?
No. Deletion does not increment unsettled_count. Only set_record and set_field (write operations) count toward the fee.
Q: Can I change the fee parameters after deployment?
Yes — the framework operator can update per-DApp fee rates via dapp_system::set_dapp_fee. Changes take effect from the next settle_writes call onwards.