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. Public JSON-RPC endpoints are shut down in stages during July 2026, with full deactivation on July 31, 2026 - see the Sui JSON-RPC shutdown survival guide for the full timeline and what breaks first. 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 want to migrate, but it's not urgent if you're using Inodra. Our JSON-RPC gateway keeps working after Sui's shutdown by translating requests behind the scenes, and any future change to its availability will be announced well in advance. 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 |
|---|---|
suix_getBalance | StateService/GetBalance |
suix_getAllBalances | StateService/ListBalances |
suix_getCoins | StateService/ListCoins |
suix_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',
rpc: { 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 1 Credit
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
Breaking Changes in Inodra's /v1 Convenience Endpoints
As part of Inodra's own migration off upstream JSON-RPC, the /v1 REST convenience endpoints (accounts, objects, transactions) now return @mysten/sui v2 canonical payloads instead of the legacy JSON-RPC shapes. If you consume these endpoints directly, note:
- Object payloads are v2 gRPC object structs: stable top-level fields are
objectId,version,digest,type,owner; nested layouts differ from the legacy JSON-RPC object. - Transaction payloads are v2 GraphQL nodes (
digest,transactionJson,effects). - Move type strings use full 64-hex addresses (e.g.
0x000...002::sui::SUI, not0x2::sui::SUI). - Chain identifiers are base58.
- Balance responses:
balanceis a human-readable number when coin metadata is available and falls back to the raw base-unit string (withdecimals: 0) when it is not. - Package responses:
modulesis now an array of{ name }objects instead of an array of module-name strings.
The gateway protocols themselves (gRPC, GraphQL, JSON-RPC passthrough) are unaffected - this only concerns the REST convenience layer, which remains a convenience surface rather than a stability contract. The OpenAPI spec at /docs reflects the new shapes.
Need Help?
- gRPC Gateway documentation for detailed setup
- Sui gRPC API reference for method details
- Discord community for migration support
- Inodra plans and pricing - hands-on migration support is included on every paid plan
🚀 Ready to start?
Get your API key - the same key works for both JSON-RPC and gRPC.