Skip to content

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 MethodgRPC Service.Method
sui_getLatestCheckpointSequenceNumberLedgerService/GetLatestCheckpoint
sui_getCheckpointLedgerService/GetCheckpoint
sui_getCheckpointsLedgerService/GetCheckpoint (batch)
sui_getTransactionBlockLedgerService/GetTransaction
sui_multiGetTransactionBlocksLedgerService/BatchGetTransactions

Object Queries

JSON-RPC MethodgRPC Service.Method
sui_getObjectLedgerService/GetObject
sui_multiGetObjectsLedgerService/BatchGetObjects
sui_tryGetPastObjectLedgerService/GetObject (with version)

Balance & Coin Queries

JSON-RPC MethodgRPC Service.Method
sui_getBalanceStateService/GetBalance
sui_getAllBalancesStateService/ListBalances
sui_getCoinsStateService/ListCoins
sui_getAllCoinsStateService/ListCoins
suix_getCoinMetadataStateService/GetCoinInfo

Transaction Execution

JSON-RPC MethodgRPC Service.Method
sui_executeTransactionBlockTransactionExecutionService/ExecuteTransaction
sui_dryRunTransactionBlockTransactionExecutionService/SimulateTransaction

Move & Package Queries

JSON-RPC MethodgRPC Service.Method
sui_getNormalizedMoveModuleMovePackageService/GetPackage
sui_getNormalizedMoveFunctionMovePackageService/GetFunction
sui_getNormalizedMoveStructMovePackageService/GetDatatype

Name Service

JSON-RPC MethodgRPC Service.Method
suix_resolveNameServiceAddressNameService/LookupName
suix_resolveNameServiceNamesNameService/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):

typescript
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):

typescript
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 JsonRpcHTTPTransport to GrpcTransport
  • Client changed from SuiJsonRpcClient to SuiGrpcClient
  • 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 GrpcWebFetchTransport instead. See the gRPC Gateway docs.

Getting an Object

Before (JSON-RPC):

typescript
const object = await client.getObject({
  id: '0x123...',
  options: { showContent: true, showOwner: true }
})
console.log('Object:', object.data)

After (gRPC):

typescript
// High-level method (recommended)
const object = await client.getObject({ id: '0x123...' })
console.log('Object:', object)

Getting Account Balance

Before (JSON-RPC):

typescript
const balance = await client.getBalance({
  owner: '0xabc...',
  coinType: '0x2::sui::SUI'
})
console.log('Balance:', balance.totalBalance)

After (gRPC):

typescript
// 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):

typescript
// Had to poll for new checkpoints
setInterval(async () => {
  const latest = await client.getLatestCheckpointSequenceNumber()
  console.log('Polled checkpoint:', latest)
}, 1000)

After (gRPC - real-time streaming):

typescript
// 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:

typescript
// 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:

typescript
// 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:

typescript
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:

typescript
// 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:

bash
# 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:

bash
grpcurl -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:

typescript
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

  1. Start with read-only operations (checkpoints, objects, balances)
  2. Move to transaction simulation
  3. Finally migrate transaction execution
  4. Keep JSON-RPC as fallback until confident

Need Help?

🚀 Ready to start?

Get your API key - the same key works for both JSON-RPC and gRPC.

Released under the MIT License.