DubheSuiContractsError Handling

Error Handling

Dubhe generates a typed error module from the errors field in dubhe.config.ts. Each entry becomes a #[error] annotated constant (human-readable abort message) paired with a public assertion helper that your system contracts call.


Defining errors in config

// dubhe.config.ts
import { defineConfig } from '@0xobelisk/sui-common';
 
export const dubheConfig = defineConfig({
  name: 'mygame',
  description: 'My Game',
  errors: {
    player_not_found: 'Player not found',
    not_enough_health: 'Not enough health',
    game_not_started: 'Game has not started yet'
  }
});

Both a plain string and an object { message: string } are accepted and produce identical output:

errors: {
  player_not_found: 'Player not found',           // plain string
  not_enough_health: { message: 'Not enough health' },  // object form — identical output
}

Generated Move code

Running generate produces sources/codegen/error.move:

// Auto-generated — do not edit directly.
module mygame::error {
 
    #[error]
    const EPlayerNotFound: vector<u8> = b"Player not found";
    public fun player_not_found(condition: bool) { assert!(condition, EPlayerNotFound) }
 
    #[error]
    const ENotEnoughHealth: vector<u8> = b"Not enough health";
    public fun not_enough_health(condition: bool) { assert!(condition, ENotEnoughHealth) }
 
    #[error]
    const EGameNotStarted: vector<u8> = b"Game has not started yet";
    public fun game_not_started(condition: bool) { assert!(condition, EGameNotStarted) }
}

Naming conventions:

Config key (snake_case)Move constant (EPascalCase)Helper function
player_not_foundEPlayerNotFoundplayer_not_found(bool)
not_enough_healthENotEnoughHealthnot_enough_health(bool)

Using errors in system contracts

Import the module and call the helper, passing the success conditiontrue means OK, false triggers the abort:

module mygame::game_system;
 
use dubhe::dapp_service::DappHub;
use mygame::error;
use mygame::level;
use mygame::game_state;
 
public entry fun attack(dh: &mut DappHub, ctx: &mut TxContext) {
    let player = ctx.sender();
 
    // Aborts with "Player not found" if player has no level entry
    error::player_not_found(level::has(dh, player));
 
    // Aborts with "Game has not started" if the game flag is false
    error::game_not_started(game_state::get(dh));
 
    // ... game logic ...
}

Convention: Pass the condition that must be true for the function to proceed. error::player_not_found(level::has(dh, player)) reads as: “assert player_not_found guard: level::has must be true”.


How abort messages appear on-chain

The #[error] attribute is a Move 2024 feature. When a transaction aborts, the error constant’s vector<u8> value is surfaced as a human-readable string in Sui explorers and RPC responses — instead of a raw numeric code:

MoveAbort in mygame::error: "Player not found"

This is a compile-time annotation; the message is fixed at build time and cannot include runtime values.


Why a wrapper function instead of a public constant

Move does not support public const. Constants are always module-private. This means a constant defined in error.move cannot be referenced directly from your system contracts.

The generated wrapper function is the standard workaround: it calls assert! with the private constant internally, and your system contract only sees a public fun it can call:

// This is not possible in Move — constants cannot be public:
// assert!(condition, mygame::error::EPlayerNotFound);  ❌
 
// The wrapper exposes it as a public function call:
error::player_not_found(condition);  // ✓

Regenerating errors

After editing the errors field in dubhe.config.ts, re-run generate to update error.move:

node node_modules/@0xobelisk/sui-cli/dist/dubhe.js generate

The sources/codegen/ directory is fully managed by generate. Do not edit error.move by hand — your changes will be overwritten.