Learn how to use gRPC with the Sui blockchain — from setup to streaming to production middleware.
# 1. Install dependencies
npm install
# 2. Get the official Sui proto files
npm run setup:protos
# 3. Run any example
npm run example:01grpc_class/
├── LESSON_NOTES.md # Full 2-hour lesson notes
├── src/
│ └── client.ts # gRPC client wrapping all 7 services (22 methods)
├── examples/
│ ├── 01-get-checkpoint.ts # Get a checkpoint (unary call)
│ ├── 02-get-transaction.ts # Get transaction details + batch
│ ├── 03-get-balance.ts # Balances, coin info, owned objects
│ ├── 04-get-object.ts # Get object + batch get objects
│ ├── 05-stream-checkpoints.ts # Stream live checkpoints (THE key demo)
│ ├── 06-inspect-package.ts # Inspect smart contract structure
│ ├── 07-other-services.ts # ServiceInfo, Epoch, NameService
│ └── 08-filter-contract-events.ts # Stream + filter for contract events
├── middleware/
│ ├── server.ts # gRPC-to-WebSocket bridge for frontends
│ └── frontend-example.html # Browser page that connects to middleware
└── protos/ # Sui proto files (run setup:protos to populate)
| # | Command | What It Demos |
|---|---|---|
| 01 | npm run example:01 |
Get checkpoint — basic unary call |
| 02 | npm run example:02 |
Get transaction + batch transactions |
| 03 | npm run example:03 |
Get balance, list balances, coin info, owned objects |
| 04 | npm run example:04 |
Get object + batch get objects |
| 05 | npm run example:05 |
Stream live checkpoints (30s) |
| 06 | npm run example:06 |
Inspect a Move smart contract |
| 07 | npm run example:07 |
Node info, epoch, SuiNS name lookup |
| 08 | npm run example:08 |
Stream + filter for contract events (2min) |
The middleware bridges gRPC to the browser via WebSocket:
# Start the middleware server
npm run middleware
# Then open middleware/frontend-example.html in your browserOptional environment variables:
GRPC_ENDPOINT=fullnode.testnet.sui.io:443
WS_PORT=8080
WATCH_PACKAGE_ID=0x... # Filter for a specific contract
WATCH_ADDRESS=0x... # Filter for a specific walletIf you're building your own project from zero, these are the 3 required steps:
mkdir my-sui-grpc && cd my-sui-grpc
npm init -y
npm install @grpc/grpc-js @grpc/proto-loader
npm install -D typescript ts-node @types/nodeProto files are the contract between your code and the Sui node. Without them, the client doesn't know what methods exist or what data to send/receive.
git clone https://github.com/MystenLabs/sui-apis.git --depth=1
mkdir -p protos
cp -r sui-apis/proto/* protos/
rm -rf sui-apisThis gives you 32 proto files defining all 7 services and 22 methods.
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import * as path from 'path';
// 1. Load the proto file
const packageDefinition = protoLoader.loadSync(
path.join(__dirname, 'protos/sui/rpc/v2/ledger_service.proto'),
{
keepCase: true, // Keep snake_case field names
longs: String, // Big numbers as strings
enums: String, // Enum names as strings
defaults: true, // Include default values
oneofs: true, // Support oneof fields
includeDirs: [path.join(__dirname, 'protos')],
}
);
// 2. Get the service constructor
const proto = grpc.loadPackageDefinition(packageDefinition) as any;
const LedgerService = proto.sui.rpc.v2.LedgerService;
// 3. Create the client (connects to Sui testnet, free, no auth)
const client = new LedgerService(
'fullnode.testnet.sui.io:443',
grpc.credentials.createSsl()
);
// 4. Make a call
client.GetCheckpoint(
{ read_mask: { paths: ['sequence_number', 'digest'] } },
new grpc.Metadata(),
(err: any, response: any) => {
if (err) console.error(err);
else console.log(response);
}
);Repeat steps 1-3 for each service you want to use (StateService, SubscriptionService, etc.), loading the corresponding proto file.
| Service | Methods | Purpose |
|---|---|---|
| LedgerService | 7 | Read historical data (checkpoints, transactions, objects) |
| StateService | 5 | Query current state (balances, owned objects, dynamic fields) |
| SubscriptionService | 1 | Real-time checkpoint streaming |
| TransactionExecutionService | 2 | Execute & simulate transactions |
| MovePackageService | 4 | Inspect smart contracts |
| NameService | 2 | SuiNS name resolution |
| SignatureVerificationService | 1 | Signature validation |
The server filters for you using method parameters and field masks:
// Filter owned objects by type (only SUI coins)
await client.listOwnedObjects('0xWallet...', 50, undefined,
{ paths: ['object_id', 'object_type'] },
'0x2::coin::Coin<0x2::sui::SUI>'
);
// Filter balance by coin type
await client.getBalance('0xWallet...', '0x2::sui::SUI');
// Field masks — control WHAT data comes back (smaller response)
await client.getCheckpoint(undefined, undefined, {
paths: ['sequence_number', 'digest'] // only these fields
});The stream sends every checkpoint. You filter on your side:
const stream = client.subscribeCheckpoints({
paths: [
'sequence_number',
'transactions.digest',
'transactions.events.events.event_type',
'transactions.events.events.json',
'transactions.effects.status',
]
});
stream.on('data', (data) => {
const checkpoint = data.checkpoint;
if (!checkpoint?.transactions) return;
checkpoint.transactions.forEach(tx => {
const txStr = JSON.stringify(tx);
// Filter by contract package ID
if (txStr.includes('0xYourPackageId')) {
console.log('Contract event:', tx.digest);
}
// Filter by wallet address
if (txStr.includes('0xYourWalletAddress')) {
console.log('Your transaction:', tx.digest);
}
// Filter by event type
tx.events?.events?.forEach(event => {
if (event.event_type?.includes('GameStarted')) {
console.log('Game started!');
}
});
// Filter by tx status (failed transactions)
if (tx.effects?.status?.status === 'FAILURE') {
console.log('Failed tx:', tx.digest);
}
});
});| Unary | Streaming | |
|---|---|---|
| Who filters | Server (parameters) | You (in your code) |
| How | Pass coin_type, owner, object_type, field masks | Check fields or JSON.stringify + .includes() |
| Field masks | Yes — controls response size | Yes — controls what data the stream sends |
Field masks matter even more for streaming — without them, every checkpoint sends full transaction data. Keep them tight.
| Endpoint | Network |
|---|---|
fullnode.testnet.sui.io:443 |
Testnet |
fullnode.mainnet.sui.io:443 |
Mainnet |