Migrating from JSON-RPC to gRPC
A practical guide to migrating your Sui application from JSON-RPC to gRPC.
Why Migrate?
Sui has deprecated JSON-RPC in favor of gRPC, with full deprecation planned for April 2026. Here's why the migration is worth it:
- Performance: gRPC's binary Protocol Buffers are 3-10x faster than JSON
- Streaming: Subscribe to real-time updates instead of polling
- Type safety: Catch errors at compile time with generated types
- New features: Some Sui capabilities are only available via gRPC
📌 Inodra Gives You Breathing Room
You'll eventually need to migrate, but it's not urgent if you're using Inodra. We'll continue supporting JSON-RPC on a best-effort basis even after Sui removes it by translating requests behind the scenes. Plan your migration when it makes sense for your team.
Migration Checklist
- [ ] Get an Inodra API key (same key works for both protocols)
- [ ] Install gRPC dependencies
- [ ] Decide: use server reflection for quick testing, or compile protos for production
- [ ] Map your JSON-RPC calls to gRPC equivalents (see table below)
- [ ] Update authentication (same
x-api-key, different header format) - [ ] Test with grpcurl before changing code
- [ ] Migrate incrementally - start with read operations
Method Mapping
Here's how common JSON-RPC methods map to gRPC services and methods:
Checkpoint & Transaction Queries
| JSON-RPC Method | gRPC Service.Method |
|---|---|
sui_getLatestCheckpointSequenceNumber | LedgerService/GetLatestCheckpoint |
sui_getCheckpoint | LedgerService/GetCheckpoint |
sui_getCheckpoints | LedgerService/GetCheckpoint (batch) |
sui_getTransactionBlock | LedgerService/GetTransaction |
sui_multiGetTransactionBlocks | LedgerService/BatchGetTransactions |
Object Queries
| JSON-RPC Method | gRPC Service.Method |
|---|---|
sui_getObject | LedgerService/GetObject |
sui_multiGetObjects | LedgerService/BatchGetObjects |
sui_tryGetPastObject | LedgerService/GetObject (with version) |
Balance & Coin Queries
| JSON-RPC Method | gRPC Service.Method |
|---|---|
sui_getBalance | StateService/GetBalance |
sui_getAllBalances | StateService/ListBalances |
sui_getCoins | StateService/ListCoins |
sui_getAllCoins | StateService/ListCoins |
suix_getCoinMetadata | StateService/GetCoinInfo |
Transaction Execution
| JSON-RPC Method | gRPC Service.Method |
|---|---|
sui_executeTransactionBlock | TransactionExecutionService/ExecuteTransaction |
sui_dryRunTransactionBlock | TransactionExecutionService/SimulateTransaction |
Move & Package Queries
| JSON-RPC Method | gRPC Service.Method |
|---|---|
sui_getNormalizedMoveModule | MovePackageService/GetPackage |
sui_getNormalizedMoveFunction | MovePackageService/GetFunction |
sui_getNormalizedMoveStruct | MovePackageService/GetDatatype |
Name Service
| JSON-RPC Method | gRPC Service.Method |
|---|---|
suix_resolveNameServiceAddress | NameService/LookupName |
suix_resolveNameServiceNames | NameService/ReverseLookupName |
Before & After: Code Examples
The Sui SDK 2.0 includes SuiGrpcClient with full type safety, making migration straightforward.
Getting the Latest Checkpoint
Before (JSON-RPC with Sui SDK 2.0):
import { SuiJsonRpcClient, JsonRpcHTTPTransport } from '@mysten/sui/jsonRpc'
const transport = new JsonRpcHTTPTransport({
url: 'https://mainnet-api.inodra.com/v1/jsonrpc',
headers: { 'x-api-key': process.env.INODRA_API_KEY! }
})
const client = new SuiJsonRpcClient({
transport,
network: 'mainnet'
})
const checkpoint = await client.getLatestCheckpointSequenceNumber()
console.log('Latest checkpoint:', checkpoint)After (gRPC with Sui SDK 2.0):
import { SuiGrpcClient } from '@mysten/sui/grpc'
import { GrpcTransport } from '@protobuf-ts/grpc-transport'
import { ChannelCredentials } from '@grpc/grpc-js'
const transport = new GrpcTransport({
host: 'mainnet-grpc.inodra.com:443',
channelCredentials: ChannelCredentials.createSsl(),
meta: { 'x-api-key': process.env.INODRA_API_KEY! }
})
const client = new SuiGrpcClient({ network: 'mainnet', transport })
// Service methods return { response }
const { response } = await client.ledgerService.getEpoch({})
console.log('Current epoch:', response.epoch)What changed:
- Same SDK (
@mysten/sui), different subpath imports - Transport switched from
JsonRpcHTTPTransporttoGrpcTransport - Client changed from
SuiJsonRpcClienttoSuiGrpcClient - SDK 2.0 provides high-level methods (e.g.,
client.getBalance()) and low-level service methods
Note: This example uses native gRPC which requires Node.js. For browser applications, use gRPC-Web with
GrpcWebFetchTransportinstead. See the gRPC Gateway docs.
Getting an Object
Before (JSON-RPC):
const object = await client.getObject({
id: '0x123...',
options: { showContent: true, showOwner: true }
})
console.log('Object:', object.data)After (gRPC):
// High-level method (recommended)
const object = await client.getObject({ id: '0x123...' })
console.log('Object:', object)Getting Account Balance
Before (JSON-RPC):
const balance = await client.getBalance({
owner: '0xabc...',
coinType: '0x2::sui::SUI'
})
console.log('Balance:', balance.totalBalance)After (gRPC):
// High-level method (recommended)
const balance = await client.getBalance({ owner: '0xabc...', coinType: '0x2::sui::SUI' })
console.log('Balance:', balance.balance)Subscribing to Updates (New in gRPC!)
This is where gRPC really shines. No more polling - get real-time updates as they happen.
Before (JSON-RPC - requires polling):
// Had to poll for new checkpoints
setInterval(async () => {
const latest = await client.getLatestCheckpointSequenceNumber()
console.log('Polled checkpoint:', latest)
}, 1000)After (gRPC - real-time streaming):
// Subscribe to new checkpoints in real-time
const call = client.subscriptionService.subscribeCheckpoints({ startCheckpointSequenceNumber: 0n })
for await (const message of call.responses) {
console.log('New checkpoint:', message.checkpoint?.sequenceNumber)
}Why streaming matters:
- Lower latency: Data arrives as soon as it's available
- Less server load: One connection vs repeated polling
- Cost efficient: Each streamed message costs only 2 CU
Common Gotchas
1. Response Format Differences
gRPC responses use Protocol Buffers, which have slightly different structures:
// JSON-RPC response
{
"result": {
"data": { ... },
"error": null
}
}
// gRPC response (direct object)
{
checkpoint: { sequenceNumber: "123", ... }
}2. Error Handling
JSON-RPC returns errors in the response body. gRPC throws exceptions with status codes:
// JSON-RPC error handling
if (response.error) {
console.error(response.error.message)
}
// gRPC error handling with SuiGrpcClient
import { RpcError } from '@protobuf-ts/runtime-rpc'
try {
const object = await client.getObject({ id: '0x999...' })
} catch (err) {
if (err instanceof RpcError) {
switch (err.code) {
case 'NOT_FOUND':
console.error('Object not found')
break
case 'UNAUTHENTICATED':
console.error('Invalid API key')
break
default:
console.error('Error:', err.message)
}
}
}3. Field Masks for Performance
gRPC supports field masks to request only specific fields - use them to reduce response size:
const request = {
objectId: '0x123...',
fieldMask: {
paths: ['content', 'owner'] // Only fetch these fields
}
}4. Numbers are Strings
Protocol Buffers handle large numbers as strings to avoid precision loss:
// JSON-RPC might return
{
balance: 1000000000
}
// gRPC returns
{
balance: '1000000000'
}
// Always parse explicitly
const balance = BigInt(response.balance)Testing Your Migration
Step 1: Test with grpcurl First
Before changing code, verify calls work with grpcurl:
# Test the endpoint works
grpcurl -H "x-api-key: YOUR_API_KEY" \
mainnet-grpc.inodra.com:443 \
sui.rpc.v2.LedgerService/GetLatestCheckpoint
# Compare with your existing JSON-RPC call
curl -X POST https://mainnet-api.inodra.com/v1/jsonrpc \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"sui_getLatestCheckpointSequenceNumber","params":[]}'Tip: Server reflection is enabled, so you can explore all available services with:
bashgrpcurl -H "x-api-key: YOUR_API_KEY" mainnet-grpc.inodra.com:443 list
Step 2: Run Both in Parallel
During migration, run both implementations and compare results:
async function compareBalance() {
const owner = '0xabc...'
const jsonRpcResult = await jsonRpcClient.getBalance({ owner, coinType: '0x2::sui::SUI' })
const grpcResult = await grpcClient.getBalance({ owner, coinType: '0x2::sui::SUI' })
console.log('JSON-RPC:', jsonRpcResult.totalBalance)
console.log('gRPC:', grpcResult.balance)
}Step 3: Migrate Incrementally
- Start with read-only operations (checkpoints, objects, balances)
- Move to transaction simulation
- Finally migrate transaction execution
- Keep JSON-RPC as fallback until confident
Need Help?
- gRPC Gateway documentation for detailed setup
- Sui gRPC API reference for method details
- Discord community for migration support
🚀 Ready to start?
Get your API key - the same key works for both JSON-RPC and gRPC.