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:
- Created and deployed your contract using the Dubhe CLI
- 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:
- 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,
});
- 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
- Basic initialization with private key:
const dubhe = new Dubhe({
networkType: "testnet",
packageId: "0x123...",
metadata: metadata,
secretKey: "YOUR_PRIVATE_KEY",
});
- 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,
});
- 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:
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
});
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
});
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
Indexer Integration
The Dubhe SDK provides built-in support for querying indexed data through its Indexer Client.
Querying Transactions
// Query transactions with pagination
const transactions = await dubhe.getTransactions({
first: 10, // Number of items per page
after: "cursor", // Cursor for pagination
orderBy: ["ID_ASC"], // Sorting options
});
Querying Events
// Query events with filters
const events = await dubhe.getEvents({
first: 10,
after: "cursor",
name: "event_name", // Filter by event name
checkpoint: "checkpoint_id",
orderBy: ["ID_ASC"],
});
Querying Schema Data
// Query schema data with filters
const schemas = await dubhe.getSchemas({
name: "schema_name",
key1: "key1_value",
key2: "key2_value",
is_removed: false,
first: 10,
after: "cursor",
orderBy: ["ID_ASC"],
});
Storage Queries
Query Multiple Storage Items
// Query storage items with pagination
const storage = await dubhe.getStorage({
name: "storage_name",
key1: "key1_value",
key2: "key2_value",
is_removed: false,
first: 10,
after: "cursor",
orderBy: ["ID_ASC"],
});
// Access the results
console.log(storage.value); // Parsed storage values
console.log(storage.data); // Raw storage data
console.log(storage.pageInfo); // Pagination information
console.log(storage.totalCount); // Total number of items
Query Single Storage Item
// Query a single storage item
const item = await dubhe.getStorageItem({
name: "storage_name",
key1: "key1_value",
key2: "key2_value",
});
if (item) {
console.log(item.value); // Parsed storage value
console.log(item.data); // Raw storage data
}
Real-time Subscriptions
Subscribe to real-time updates for specific schemas or events:
const websocket = await dubhe.subscribe(
["schema_name", "event_name"],
(data) => {
console.log("Received update:", data);
}
);
Response Types
Connection Response
interface ConnectionResponse<T> {
edges: Array<{
cursor: string;
node: T;
}>;
pageInfo: {
hasNextPage: boolean;
endCursor?: string;
};
totalCount: number;
}
Storage Response
interface StorageResponse<T> {
data: T[]; // Raw data
value: any[]; // Parsed values
pageInfo: {
hasNextPage: boolean;
endCursor?: string;
};
totalCount: number;
}
Storage Item Response
interface StorageItemResponse<T> {
data: T; // Raw data
value: any; // Parsed value
}
Pagination Example
Here’s a complete example of paginating through storage items:
const pageSize = 10;
let currentPage = await dubhe.getStorage({
name: "storage_name",
first: pageSize,
orderBy: ["ID_ASC"],
});
// Process first page
console.log("Current Page Data:", currentPage.value);
// Fetch subsequent pages
while (currentPage.pageInfo.hasNextPage) {
currentPage = await dubhe.getStorage({
name: "storage_name",
first: pageSize,
after: currentPage.pageInfo.endCursor,
orderBy: ["ID_ASC"],
});
console.log("Next Page Data:", currentPage.value);
}
Order By Options
The following order fields are supported:
- For Transactions:
ID_ASC
,ID_DESC
,CHECKPOINT_ASC
,CHECKPOINT_DESC
- For Events:
ID_ASC
,ID_DESC
,CHECKPOINT_ASC
,CHECKPOINT_DESC
- For Schemas:
ID_ASC
,ID_DESC
,NAME_ASC
,NAME_DESC
Best Practices
-
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
-
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
-
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
-
Optimize indexer queries
- Use appropriate page sizes for pagination
- Implement cursor-based pagination for large datasets
- Cache frequently accessed data when possible
- Use specific filters to reduce result set size
-
Handle real-time subscriptions efficiently
- Implement proper cleanup for WebSocket connections
- Handle reconnection scenarios
- Process subscription data asynchronously
- Set up error handling for connection issues
Support
For more information or support, please visit our GitHub repository or join our community channels.