DubheSuiContractsStorage Fees

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):

PoolDescription
free_creditGranted by the Dubhe framework operator — no SUI payment required. Has an expiry timestamp; expired free credit is not counted. Used up first.
credit_poolPurchased 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:

  1. The framework computes the charge using the DApp’s per-DApp fee rates.
  2. It deducts from free_credit first, then from credit_pool.
  3. 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_pool when 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_record and delete_global_field do not charge — only write operations are metered.

Lazy settlement

Unlike immediate per-write charging, Dubhe uses lazy settlement:

  1. Each write increments unsettled_count in the user’s UserStorage — no credit deducted.
  2. settle_writes is 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_bytes

unsettled_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):

ParameterDefault valueDescription
base_fee80,000 MISTFixed cost per write operation
bytes_fee500 MIST/byteVariable cost per byte written
free_credit25 SUIInitial 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.