DubheSuiClient

Dubhe Client SDK for Sui

Before getting started with Dubhe Client SDK, please install the required dependencies:

pnpm install @0xobelisk/sui-client @0xobelisk/sui-common

Note: @0xobelisk/sui-common contains essential configuration type definitions like DubheConfig, which are necessary for contract development.

Dubhe is a client-agnostic SDK that supports various platforms including browsers, Node.js, and the COCOS game engine. It provides a simple interface to interact with your Sui Move contracts.

Getting Started

Prerequisites

Before using the SDK, make sure you have:

  1. Created and deployed your contract using the Dubhe CLI
  2. Obtained the packageId after deployment

Data Model Setup

First, define your contract’s configuration using DubheConfig:

import { DubheConfig } from "@0xobelisk/sui-common";
 
export const dubheConfig = {
  name: "counter",
  description: "counter",
  systems: ["counter"],
  schemas: {
    counter: {
      structure: {
        value: "StorageValue<u32>",
      },
    },
  },
} as DubheConfig;

Generate the contract code using CLI:

pnpm dubhe schemagen

Initializing the Client

There are two ways to initialize the Dubhe client:

  1. Using dynamic metadata loading:
import { loadMetadata, Dubhe, NetworkType } from "@0xobelisk/sui-client";
 
const network = "testnet" as NetworkType;
const packageId = "YOUR_PACKAGE_ID";
 
const metadata = await loadMetadata(network, packageId);
const dubhe = new Dubhe({
  networkType: network,
  packageId: packageId,
  metadata: metadata,
  secretKey: privkey,
});
  1. Using pre-saved metadata (recommended for better performance):
import metadata from "./metadata.json";
 
const dubhe = new Dubhe({
  networkType: network,
  packageId: packageId,
  metadata: metadata,
  secretKey: privkey,
});

Configuration Parameters

The Dubhe constructor accepts the following parameters:

interface DubheParams {
  // Authentication (one of these is required)
  mnemonics?: string; // 12 or 24 mnemonic words, separated by space
  secretKey?: string; // Base64 or hex string or bech32 format private key
  // Note: if mnemonics is provided, secretKey will be ignored
 
  // Network Configuration
  networkType?: NetworkType; // 'mainnet' | 'testnet' | 'devnet' | 'localnet'
  // Default: 'mainnet'
  fullnodeUrls?: string[]; // Array of fullnode URLs
  // Default: Network-specific default URL
  faucetUrl?: string; // Custom faucet URL for testnet/devnet/localnet
 
  // Contract Configuration
  packageId?: string; // Your contract's package ID
  metadata?: SuiMoveNormalizedModules; // Contract metadata
 
  // Indexer Configuration
  indexerUrl?: string; // Custom indexer API URL
  indexerWsUrl?: string; // Custom indexer WebSocket URL
  // Default: Derived from indexerUrl
 
  // HTTP Configuration
  customFetch?: typeof fetch; // Custom fetch implementation
  defaultOptions?: FetchOptions; // Default fetch options for all requests
}

Examples

  1. Basic initialization with private key:
const dubhe = new Dubhe({
  networkType: "testnet",
  packageId: "0x123...",
  metadata: metadata,
  secretKey: "YOUR_PRIVATE_KEY",
});
  1. Using mnemonics with custom network configuration:
const dubhe = new Dubhe({
  mnemonics: "word1 word2 ... word24",
  networkType: "testnet",
  fullnodeUrls: ["https://custom-fullnode.example.com"],
  packageId: "0x123...",
  metadata: metadata,
});
  1. With custom indexer configuration:
const dubhe = new Dubhe({
  secretKey: "YOUR_PRIVATE_KEY",
  networkType: "mainnet",
  packageId: "0x123...",
  metadata: metadata,
  indexerUrl: "https://custom-indexer.example.com",
  indexerWsUrl: "wss://custom-indexer-ws.example.com",
});

Executing Transactions

To call contract methods:

import { Transaction } from "@0xobelisk/sui-client";
 
// Create transaction
const tx = new Transaction();
 
// Execute transaction with callbacks
const response = await dubhe.tx.counter_system.inc({
  tx,
  params: [
    /* your parameters */
  ],
  typeArguments: ["0x2::coin::Coin<0x2::sui::SUI>"], // optional
  isRaw: false, // optional, defaults to false
  onSuccess: async (result) => {
    // Handle successful transaction
    console.log("Transaction succeeded:", result.digest);
    await dubhe.waitForTransaction(result.digest);
  },
  onError: (error) => {
    // Handle transaction error
    console.error("Transaction failed:", error);
  },
});
 
// For wallet integration
const walletTx = await dubhe.tx.counter_system.inc({
  tx,
  params: [
    /* your parameters */
  ],
  typeArguments: [],
  isRaw: true,
});
const response = await dubhe.signAndSendTxn({
  tx: walletTx,
  onSuccess: async (result) => {
    // Handle successful transaction
    console.log("Transaction succeeded:", result.digest);
    await dubhe.waitForTransaction(result.digest);
  },
  onError: (error) => {
    // Handle transaction error
    console.error("Transaction failed:", error);
  },
});

Transaction Parameters

Both query and transaction methods accept a parameter structure with the following fields:

{
  tx: Transaction;           // Required: Transaction instance
  params?: TransactionArg[]; // Optional: Array of transaction arguments
  typeArguments?: string[];  // Optional: Generic type arguments
  isRaw?: boolean;          // Optional: Return raw transaction instead of executing
  onSuccess?: (result: SuiTransactionBlockResponse) => void | Promise<void>; // Optional: Success callback
  onError?: (error: Error) => void | Promise<void>;  // Optional: Error callback
}

Example Usage with Callbacks

Here’s a practical example showing how to use callbacks:

const tx = new Transaction();
 
try {
  await dubhe.tx.map_system.register({
    tx,
    params: [
      /* your parameters */
    ],
    onSuccess: async (result) => {
      // Add delay if needed
      setTimeout(async () => {
        // Show success notification
        toast("Register Successful", {
          description: new Date().toUTCString(),
          action: {
            label: "Check in Explorer",
            onClick: () =>
              window.open(dubhe.getTxExplorerUrl(result.digest), "_blank"),
          },
        });
      }, 2000);
 
      // Wait for transaction to be confirmed
      await dubhe.waitForTransaction(result.digest);
    },
    onError: (error) => {
      console.error("Transaction failed:", error);
      toast.error("Transaction failed. Please try again.");
    },
  });
} catch (error) {
  // Handle any unexpected errors
  console.error("Unexpected error:", error);
}

Querying Data

To query contract state:

// Create transaction
const tx = new Transaction();
 
// Query with struct parameters
const result = await dubhe.query.counter_system.get({
  tx,
  params: [
    /* your parameters */
  ],
  typeArguments: [], // optional
  isRaw: false, // optional
});
 
// For BCS encoded results
const decodedData = dubhe.view(result);

BCS Data Decoding

The SDK provides a view() method to decode BCS-encoded return values from contract queries.

For detailed examples and advanced usage of querying data, please refer to our Query With Client Guide.

Supported Types

  • Basic types (u8, u16, u32, u64, u128, u256)
  • Boolean
  • String
  • Vector
  • Struct
  • Option
  • Custom objects

Example with Complex Types

// Example contract return type
struct GameState {
    score: u64,
    player_name: String,
    is_active: bool,
    items: vector<Item>
}
 
// Query and decode
const tx = new Transaction();
const result = await dubhe.query.game_system.get_state(tx, params);
const decodedState = dubhe.view(result);

Known Limitations

⚠️ Important Note:

Some complex nested structures might require additional handling.

Querying Schema State

To query the state of schema fields defined in your Dubhe config, you can use either parseState() or state() method:

Using parseState

const result = await dubhe.parseState({
  schema: "counter", // Schema name from your Dubhe config
  objectId: "0x123...", // Object ID of the schema instance
  storageType: "StorageValue<u64>", // Storage type of the field
  params: [], // Parameters for StorageMap/StorageDoubleMap keys
});

Using state

const tx = new Transaction();
const result = await dubhe.state({
  tx, // Transaction instance
  schema: "counter", // Schema name from your Dubhe config
  params: [tx.object("0x123...")], // Parameters including object ID
});

The storage types support three formats:

  1. StorageValue<V> - For single value storage
// Query a simple storage value
const value = await dubhe.parseState({
  schema: "player",
  objectId: "0x123...",
  storageType: "StorageValue<u64>",
  params: [], // No params needed for StorageValue
});
  1. StorageMap<K,V> - For key-value map storage
// Query a value from storage map
const mapValue = await dubhe.parseState({
  schema: "inventory",
  objectId: "0x123...",
  storageType: "StorageMap<address,u64>",
  params: ["0x456..."], // Key to look up in the map
});
  1. StorageDoubleMap<K1,K2,V> - For double key-value map storage
// Query a value from double map
const doubleMapValue = await dubhe.parseState({
  schema: "game",
  objectId: "0x123...",
  storageType: "StorageDoubleMap<address,u64,u64>",
  params: ["0x456...", 42], // Two keys needed for double map
});

Supported Key Types

The following key types are supported for StorageMap and StorageDoubleMap:

  • Basic types: u8, u16, u32, u64, u128, u256, bool
  • address
  • Custom object types (format: package::module::type)

Error Handling

The method will throw an error if:

  • Invalid storage type format is provided
  • Wrong number of parameters for the storage type
  • Unsupported key type is used
  • Schema doesn’t exist
  • Object ID is invalid

Parameter Validation

  • StorageValue: No additional parameters required
  • StorageMap: Exactly one key parameter required
  • StorageDoubleMap: Exactly two key parameters required

Best Practices

  1. Use pre-saved metadata for better performance in production

    • Generate and save metadata during deployment
    • Load metadata from file instead of fetching it every time
  2. Implement proper error handling for BCS decoding

    • Handle potential decoding errors for complex types
    • Validate data types before decoding
    • Use try-catch blocks for parsing operations
  3. Consider the limitations of enum type handling when designing your contract return types

    • Be aware of Sui metadata limitations for enums
    • Design simpler enum structures when possible
    • Test enum handling thoroughly

Support

For more information or support, please visit our GitHub repository or join our community channels.