Dubhe Client SDK
Dubhe provides a layered set of client packages for interacting with on-chain contracts, the indexer, and the ECS world from TypeScript/JavaScript.
Package Overview
| Package | Purpose |
|---|---|
@0xobelisk/client/sui | Recommended entry point — bundles all clients in one call |
@0xobelisk/react | React Provider + hooks for frontend apps |
@0xobelisk/sui-client | Low-level Sui contract client (Dubhe class) |
@0xobelisk/graphql-client | Apollo-based indexer GraphQL client |
@0xobelisk/ecs | Entity-Component-System query / subscription layer |
@0xobelisk/client/sui — Recommended Entry Point
createClient initialises all clients at once and returns a DubheClientBundle.
Use this for any project that is not using React (Node.js scripts, tests, non-React frontends).
Install
pnpm add @0xobelisk/clientUsage
import { createClient } from '@0xobelisk/client/sui';
import metadata from './metadata.json';
import { Network, PackageId, DappHubId, DappStorageId, FrameworkPackageId } from './deployment';
const client = createClient({
network: Network, // 'mainnet' | 'testnet' | 'devnet' | 'localnet'
packageId: PackageId,
metadata, // SuiMoveNormalizedModules from loadMetadata()
dappHubId: DappHubId, // DappHub shared object ID
dappStorageId: DappStorageId, // DappStorage shared object ID
frameworkPackageId: FrameworkPackageId, // only needed for localnet / devnet
credentials: {
secretKey: process.env.PRIVATE_KEY // base64 or hex private key
// mnemonics: '...' // alternative: 12/24-word mnemonic
},
endpoints: {
graphql: 'http://localhost:8080/graphql', // dubhe-indexer proxy (default port 8080)
websocket: 'ws://localhost:8080/graphql',
grpc: 'http://localhost:8085' // dubhe-indexer gRPC backend (default port 8085)
// fullnodeUrls: ['https://...'], // optional: override RPC endpoint
// channelUrl: 'http://...', // optional: DubheChannel server
}
// options: { // optional performance tuning
// enableBatchOptimization: true,
// cacheTimeout: 5000,
// debounceMs: 100,
// }
});
// client.contract — Dubhe instance (call Move functions)
// client.graphqlClient — DubheGraphqlClient (query indexer via GraphQL)
// client.grpcClient — DubheGrpcClient (query indexer via gRPC)
// client.ecsWorld — DubheECSWorld (entity/component queries)
// client.address — user wallet address stringCalling Move functions
import { Transaction } from '@mysten/sui/transactions';
// Send a transaction
const tx = new Transaction();
await client.contract.tx.player_system.level_up({ tx });
// Read-only query (devInspect)
const result = await client.contract.query.player_system.get_level({
tx: new Transaction()
});@0xobelisk/react — React Provider + Hooks
For React / Next.js apps. Wraps createClient in a Context Provider with useRef-based
single-initialisation (no re-renders on config re-evaluation).
Install
pnpm add @0xobelisk/reactSetup: Wrap your app with DubheProvider
import { DubheProvider } from '@0xobelisk/react';
import metadata from './metadata.json';
import { Network, PackageId, DappHubId, DappStorageId, FrameworkPackageId } from './deployment';
function App() {
return (
<DubheProvider
config={{
network: Network,
packageId: PackageId,
metadata,
dappHubId: DappHubId,
dappStorageId: DappStorageId,
frameworkPackageId: FrameworkPackageId,
credentials: { secretKey: process.env.NEXT_PUBLIC_PRIVATE_KEY },
endpoints: {
graphql: 'http://localhost:8080/graphql',
websocket: 'ws://localhost:8080/graphql',
grpc: 'http://localhost:8085'
}
}}
>
<MyApp />
</DubheProvider>
);
}Hooks
All hooks must be called inside a component tree wrapped by DubheProvider.
useDubhe — full bundle
import { useDubhe } from '@0xobelisk/react';
import { Transaction } from '@mysten/sui/transactions';
function PlayerCard() {
const { contract, graphqlClient, grpcClient, ecsWorld, address } = useDubhe();
const handleLevelUp = async () => {
const tx = new Transaction();
await contract.tx.player_system.level_up({ tx });
};
return <button onClick={handleLevelUp}>Level Up ({address})</button>;
}useDubheContract — contract only
const contract = useDubheContract();
await contract.tx.my_system.my_action({ tx });useDubheGraphQL — GraphQL client only
const graphqlClient = useDubheGraphQL();
const data = await graphqlClient.getAllTables('player', { first: 10 });The GraphQL endpoint is the dubhe-indexer proxy at port 8080 (default:
http://localhost:8080/graphql). Set it viaendpoints.graphqlinDubheProvider.
useDubheECS — ECS world only
const ecsWorld = useDubheECS();
const players = await ecsWorld.queryWith('player');grpcClient from useDubhe
The full bundle returned by useDubhe() also contains grpcClient — a DubheGrpcClient instance connected to the dubhe-indexer gRPC backend (default port 8085):
const { grpcClient } = useDubhe();
const result = await grpcClient.dubheGrpcClient.queryTable({
tableId: 'player',
filters: [],
pagination: { page: 0, pageSize: 20 }
});@0xobelisk/sui-client — Low-Level Contract Client
Use Dubhe directly when you need fine-grained control, or are not using the bundle factory.
Install
pnpm add @0xobelisk/sui-clientUsage
import { Dubhe, loadMetadata } from '@0xobelisk/sui-client';
import { Transaction } from '@mysten/sui/transactions';
import { Network, PackageId, DappStorageId, FrameworkPackageId } from './deployment';
const metadata = await loadMetadata(Network, PackageId);
const dubhe = new Dubhe({
networkType: Network,
packageId: PackageId,
metadata,
secretKey: process.env.PRIVATE_KEY,
// mnemonics: '...', // alternative: 12/24-word mnemonic
// fullnodeUrls: ['https://...'], // override default RPC endpoint
frameworkPackageId: FrameworkPackageId, // dubhe framework package ID (localnet/devnet only; testnet/mainnet use SDK defaults)
dappStorageId: DappStorageId
// channelUrl: 'http://...', // DubheChannel endpoint (if using Channel)
});
const address = dubhe.getAddress();
// Send a transaction
const tx = new Transaction();
await dubhe.tx.player_system.level_up({
tx,
onSuccess: async (result) => {
console.log('tx:', result.digest);
await dubhe.waitForTransaction(result.digest);
},
onError: (err) => console.error(err)
});
// Read-only query
const result = await dubhe.query.player_system.get_level({ tx: new Transaction() });
const level = dubhe.view(result); // decode BCS return valueTransaction with raw PTB (wallet integration)
// Build the transaction without executing it
const rawTx = await dubhe.tx.player_system.level_up({
tx,
isRaw: true // returns the Transaction object instead of executing
});
// Sign and send via an external wallet adapter
const response = await dubhe.signAndSendTxn({
tx: rawTx,
onSuccess: (result) => console.log('success:', result.digest)
});DubheParams Reference
type DubheParams = {
// Authentication — all optional.
// If neither secretKey nor mnemonics is provided, a random 24-word mnemonic is generated.
secretKey?: string; // base64, hex, or bech32 private key
mnemonics?: string; // 12 or 24 words, space-separated
// Network
networkType?: 'mainnet' | 'testnet' | 'devnet' | 'localnet'; // default: 'mainnet'
fullnodeUrls?: string[]; // override default RPC endpoints
faucetUrl?: string;
// Contract
packageId?: string; // your contract's package ID
metadata?: SuiMoveNormalizedModules; // from loadMetadata()
// Dubhe Framework (required for session / settlement features)
frameworkPackageId?: string; // dubhe framework package ID — from deployment.ts FrameworkPackageId
// needed for localnet and devnet; testnet/mainnet use SDK defaults
dappStorageId?: string; // DappStorage object ID — from deployment.ts DappStorageId
// DubheChannel (optional)
channelUrl?: string; // channel server URL
};Loading Metadata
metadata is required by all contract clients. Load it at startup:
import { loadMetadata } from '@0xobelisk/sui-client';
// Fetch from the network at runtime (one network call)
const metadata = await loadMetadata('testnet', '0x<packageId>');
// Or import a pre-generated JSON file (faster — no network call)
import metadata from './metadata.json';To generate metadata.json locally after deployment:
dubhe load-metadata --network testnet --package-id 0x<packageId>Deployment Artifacts
After dubhe publish, run dubhe store-config to generate a typed TypeScript file with all deployment IDs:
dubhe store-config --network testnet --output-ts-path ./src/deployment.ts// src/deployment.ts — generated by dubhe store-config
export const Network: NetworkType = 'testnet';
export const PackageId = '0x...';
export const DappHubId = '0x...'; // DappHub shared object ID
export const DappStorageId = '0x...'; // DappStorage shared object ID
export const FrameworkPackageId = undefined; // set automatically for localnetImport these values to initialise the client — do not import .history/…/latest.json directly.
UserStorage and Session Management
The Dubhe class exposes helper methods for the UserStorage lifecycle and session key management. All methods require frameworkPackageId and packageId to be set in the constructor.
initUserStorage
Create a UserStorage shared object for the signer. Each address may only call this once per DApp.
await dubhe.initUserStorage({
dappHubId: DappHubId,
dappStorageId: DappStorageId
});getUserStorageId
Resolve the UserStorage object ID for a given address.
const userStorageId = await dubhe.getUserStorageId(userAddress);
if (!userStorageId) {
// User has not initialised their storage yet
await dubhe.initUserStorage({ dappHubId, dappStorageId });
}getUserStorageFields
Read all fields from a UserStorage object.
const fields = await dubhe.getUserStorageFields(userStorageId);
// fields.canonical_owner, fields.write_count, fields.session_key, ...getDappStorageFields
Read all metadata and fee-state fields from a DappStorage object.
const fields = await dubhe.getDappStorageFields(dappStorageId);
// fields.version, fields.admin, fields.credit_pool, fields.paused, ...activateSession
Grant a session wallet permission to write to UserStorage on behalf of the canonical owner.
await dubhe.activateSession({
userStorageId,
sessionWallet: ephemeralAddress,
durationMs: 3_600_000 // 1 hour (range: 60_000 – 604_800_000)
});deactivateSession
Revoke the active session key. Can be called by the canonical owner, the session key itself, or anyone after expiry.
await dubhe.deactivateSession({ userStorageId });settleWrites
Settle accumulated write debt for a user. Deducts from the DApp’s credit pool.
await dubhe.settleWrites({
dappHubId,
dappStorageId,
userStorageId
});DubheChannel
DubheChannel provides a server-side transaction relay and real-time data layer.
Set channelUrl in the constructor to enable Channel features.
submitToChannel
Submit a PTB to the channel server for execution. The channel server signs and broadcasts the transaction, removing the user’s need to sign each action.
const nonce = await dubhe.latestNonce();
const tx = new Transaction();
// ... build tx ...
await dubhe.submitToChannel({ tx, nonce });latestNonce
Fetch the latest nonce for an account from the channel server.
const nonce = await dubhe.latestNonce(); // signer address
const nonce = await dubhe.latestNonce({ account: '0x...' }); // specific addressqueryChannelTable
Read a resource table entry from the channel server’s state.
const data = await dubhe.queryChannelTable({
table: 'player',
key: [] // composite key fields (empty for no-key resources)
// account: '0x...', // defaults to signer address
});subscribeChannelTable
Subscribe to real-time updates for a resource table via Server-Sent Events.
const unsubscribe = await dubhe.subscribeChannelTable(
{ table: 'player' },
{ onMessage: (data) => console.log('update:', data) }
);
// Stop listening:
unsubscribe();Utility Methods
// Get the signer's wallet address
dubhe.getAddress(): string
// Decode BCS return values from a query
dubhe.view(devInspectResult): any
// Get the Sui explorer URL for a transaction
dubhe.getTxExplorerUrl(txDigest: string): string
// Wait for a transaction to be confirmed
await dubhe.waitForTransaction(txDigest: string)