Developer SDK
The @mysten/sui TypeScript SDK and the Rust sui-sdk: clients, transaction blocks, keypairs, and object queries.
Third-party documentation. This is independently authored analysis of the public Sui codebase — not the official docs, and not reviewed or endorsed by the Sui team.
Developer SDK
The official developer surfaces are the @mysten/sui TypeScript SDK and the sui-sdk Rust crate, plus the sui CLI. This module focuses on the TypeScript SDK, which is what most application builders use.
The four pillars of the SDK
- Clients —
SuiClienttalks to any full node's JSON-RPC: queries (getObject,getCoins,queryTransactionBlocks) and submission (signAndExecuteTransaction). - Transaction blocks —
Transactionis a declarative builder for a set of commands:moveCall,splitCoins,transferObjects,mergeCoins,transfer, plus explicit gas configuration. Commands can reference each other's outputs. - Keypairs & signers —
Ed25519Keypair,Secp256k1Keypair, and wallet signer adapters. Signing happens locally; the SDK sends the signed bytes to the RPC. - GraphQL client (newer addition) — an alternative, strongly-typed query surface for more complex data shapes, alongside the REST-style JSON-RPC.
Reading state
client.getObject()— the canonical read; options control how much content/owner/version is returned.client.getCoins()/getBalance— coin enumeration with pagination; balances are computed from coin objects.client.getDynamicFieldObject— read a named field from a parent object.client.queryTransactionBlocks— filter history by sender, package, object, or digest.
Writing state
- Build a
Transactionblock. - Set a gas budget (required — there's no automatic gas estimation).
signAndExecuteTransactionand readeffects.status,effects.objectChanges, anddigest.
Every command returns a reference you can feed into the next command, which is how multi-step flows (split → transfer) are composed in one transaction.
The CLI
sui client manages keys, addresses, gas, and RPC endpoints; sui move build/publish/upgrade compiles and publishes packages. The CLI is the fastest way to bootstrap: create an address, request test SUI on the faucet, publish a package, then move to the SDK for app logic.
Edge cases
- Gas budget is mandatory and unforgiving: too low →
InsufficientGasand a failed status. Estimate withdevInspectTransactionfirst, or set a comfortable buffer. - Command references are by index/output, not by string handles — passing the wrong object reference produces confusing version errors at submit time.
- Coin operations need a coin object, not a balance; the SDK's helpers (e.g.
tx.gas) exist precisely so you don't have to pick a coin manually for common flows.
Examples
Query an object
import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";const client = new SuiClient({ url: getFullnodeUrl("mainnet") });const obj = await client.getObject({id: "0x<object_id>",options: { showContent: true, showOwner: true },});if (obj.data?.content) {console.log(JSON.stringify(obj.data.content, null, 2));} else {console.log("Object not found or wrapped");}
Every piece of Sui state is an object you can query by ID. Dynamic fields and child objects are discoverable through the same client.
Send SUI to another address
import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";import { Transaction } from "@mysten/sui/transactions";import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";import { fromB64 } from "@mysten/sui/utils";const client = new SuiClient({ url: getFullnodeUrl("testnet") });const kp = Ed25519Keypair.fromSecretKey(fromB64(PRIVATE_KEY_B64));const tx = new Transaction();const [coin] = tx.splitCoins(tx.gas, [tx.pure.u64(1_000_000_000n)]); // 1 SUIconst recipient = "0x<recipient>";tx.transferObjects([coin], tx.pure.address(recipient));tx.setGasBudget(10_000_000);const res = await client.signAndExecuteTransaction({signer: kp,transaction: tx,options: { showEffects: true, showEvents: true },});console.log(res.digest);
Transfers are object transfers: split the gas coin, then move the child coin. Note the explicit gas budget — Sui does not do dynamic gas estimation the way EVM chains do.
Query all coins for an address
import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";const client = new SuiClient({ url: getFullnodeUrl("mainnet") });const coins = await client.getCoins({owner: "0x<address>",});console.log("total coins:", coins.data.length);// Paginate with cursor if you have many coins:while (coins.hasNextPage) {const next = await client.getCoins({owner: "0x<address>",cursor: coins.nextCursor!,});coins.data.push(...next.data);coins.hasNextPage = next.hasNextPage;coins.nextCursor = next.nextCursor;}
Balances on Sui are a set of coin objects, not a single number — paginate with the cursor API for wallets with many coins.
Edge Cases
- Every Sui transaction must declare a gas budget — if it's too low the tx fails with InsufficientGas and no state changes, so quote with headroom.
- Owned-object transactions are not ordered by consensus; two concurrent txs moving the same object may both fail (version/sequence conflict) and must be retried — do not assume retry-free idempotency.
- Dynamic fields are not typed by the framework — a wrongly-typed read can panic the module; validate with the SDK's parse helpers before trusting returned content.
Interface
import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";import { Transaction } from "@mysten/sui/transactions";import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";import { fromB64 } from "@mysten/sui/utils";// A client talks to any Sui RPC (full node):const client = new SuiClient({ url: getFullnodeUrl("mainnet") });// Keypairs sign transactions; a keypair wraps a secret key + pubkey.const kp = Ed25519Keypair.fromSecretKey(fromB64(PRIVATE_KEY_B64));// Transaction blocks are declarative: add commands, then send.const tx = new Transaction();tx.moveCall({target: `0x<package>::counter::increment`,arguments: [tx.object(COUNTER_ID)],});const res = await client.signAndExecuteTransaction({signer: kp,transaction: tx,options: { showEffects: true },});console.log(res.effects?.status, res.digest);
