Use code JSON50 for 50% off your first month of any plan Code JSON50 - 50% off your first month
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. 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 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
suix_getBalanceStateService/GetBalance
suix_getAllBalancesStateService/ListBalances
suix_getCoinsStateService/ListCoins
suix_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',
  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):

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 1 Credit

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

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, not 0x2::sui::SUI).
  • Chain identifiers are base58.
  • Balance responses: balance is a human-readable number when coin metadata is available and falls back to the raw base-unit string (with decimals: 0) when it is not.
  • Package responses: modules is 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?

🚀 Ready to start?

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

Released under the MIT License.