Skip to content

gRPC Gateway

Sui gRPC with full chain history - live node and archival storage behind a single endpoint, with server reflection enabled.

What is gRPC?

gRPC is a high-performance RPC framework that uses Protocol Buffers for serialization and HTTP/2 for transport. Unlike JSON-RPC which sends human-readable text, gRPC sends compact binary data - making it faster and more bandwidth-efficient.

FeaturegRPCJSON-RPC
Data formatBinary (Protocol Buffers)JSON text
TransportHTTP/2HTTP/1.1
StreamingNative supportNot supported
Type safetyCompile-timeRuntime
Code generationBuilt-inManual
Typical latencyLowerHigher

For a deeper dive, see the official gRPC documentation.

Why Sui is Moving to gRPC

📌 Important: JSON-RPC Deprecation

Sui has deprecated JSON-RPC in favor of gRPC and GraphQL. If you're currently using JSON-RPC, see our migration guide.

Sui chose gRPC for:

  • Performance: Binary serialization is faster than JSON
  • Streaming: Subscribe to real-time checkpoint updates
  • Type safety: Catch errors at compile time, not runtime
  • Modern tooling: Better developer experience with generated clients

Inodra makes gRPC easy - just add your API key and start making calls.

One Endpoint: Live Node + Archival Storage

Endpoint: mainnet-grpc.inodra.com:443 (TLS required)

Upstream, Sui splits gRPC access across separate endpoints: a full node for recent state (older data is pruned) and a dedicated archival service that only implements a subset of the API. With Inodra, both live behind one endpoint:

  1. Live node - current state, transaction execution, simulation, and real-time checkpoint streaming.
  2. Archival storage - every checkpoint, transaction, and object version back to genesis.

The gateway routes each request to the right backend automatically. Same endpoint, same API key, same services - whether you're asking for the latest checkpoint or checkpoint 1,000 from 2023:

bash
# Latest checkpoint and full-history checkpoint - identical call, same endpoint
grpcurl -H "x-api-key: YOUR_API_KEY" -d '{}' \
  mainnet-grpc.inodra.com:443 sui.rpc.v2.LedgerService/GetServiceInfo

grpcurl -H "x-api-key: YOUR_API_KEY" -d '{"sequence_number": 1000, "read_mask": {"paths": ["summary", "contents"]}}' \
  mainnet-grpc.inodra.com:443 sui.rpc.v2.LedgerService/GetCheckpoint

Key Features:

  • Full Sui gRPC compatibility, all services on one endpoint
  • Complete chain history (no separate archival endpoint or pruning horizon)
  • Server reflection enabled (explore without proto files)
  • HTTP/2 for optimal performance
  • TLS encryption

Explicit Routing: x-inodra-route

By default the gateway routes automatically: requests go to the live node first, and fall back to archival storage when the node no longer has the data (or is briefly unavailable). Checkpoint numbers below the node's retention floor skip straight to archival, and explicit epoch lookups are served archival-first so historical epochs return complete protocol configs.

If you want to pin a request to one backend, set the x-inodra-route header:

ValueBehavior
nodeLive node only - pruned history returns NOT_FOUND
archivalArchival storage only - lags the live tip by a few seconds
(unset)Automatic routing with fallback (recommended)
bash
# Force the archival backend
grpcurl -H "x-api-key: YOUR_API_KEY" -H "x-inodra-route: archival" \
  -d '{"sequence_number": 1000, "read_mask": {"paths": ["summary"]}}' \
  mainnet-grpc.inodra.com:443 sui.rpc.v2.LedgerService/GetCheckpoint

# Force the live node (recent data only)
grpcurl -H "x-api-key: YOUR_API_KEY" -H "x-inodra-route: node" \
  -d '{"digest": "TRANSACTION_DIGEST_HERE"}' \
  mainnet-grpc.inodra.com:443 sui.rpc.v2.LedgerService/GetTransaction

What to know before pinning:

  • Explicit routing disables fallback. The gateway calls exactly the backend you named and returns its answer as-is - including NOT_FOUND for data the other backend has.
  • The header applies to LedgerService methods only. The other services (state, execution, streaming, packages, names, signature verification) are live-only and always served by the node; GetServiceInfo always reflects the node.
  • Batch methods (BatchGetTransactions, BatchGetObjects) normally split per item across both backends; with an explicit route the whole batch goes to one backend.

gRPC vs gRPC-Web

Inodra supports both native gRPC and gRPC-Web on the same endpoint:

ProtocolTransportUse CaseStreaming
gRPC-WebGrpcWebFetchTransportBrowsers, universalServer-side only
Native gRPCGrpcTransportNode.js, servers, CLIFull (bidirectional)

Why two protocols?

  • Browsers can't make native gRPC calls (HTTP/2 limitations)
  • gRPC-Web wraps gRPC in a browser-compatible format
  • Inodra's infrastructure automatically handles both

Which should you use?

  • Browser/Frontend: gRPC-Web (GrpcWebFetchTransport)
  • Node.js/Backend: Either works; native gRPC has full streaming support
  • Universal code: gRPC-Web (works everywhere)

Note: gRPC-Web uses the standard binary protocol (application/grpc-web+proto), which is what all gRPC-Web client libraries send. JSON payloads over gRPC-Web are not part of the Sui protocol stack and are not supported - use the GraphQL API or JSON-RPC gateway if you want JSON over plain HTTP.

Server Reflection: Explore Without Proto Files

One of the biggest barriers to getting started with gRPC is downloading and compiling proto files. With Inodra, you don't need to - server reflection lets you discover services, methods, and message types directly from the API.

Think of it as a self-documenting API. You can:

  • List all available services
  • See what methods each service offers
  • Inspect request/response message formats
  • Make calls without any local proto files

This is perfect for prototyping, debugging, or learning the API before setting up a full development environment.

Quick Start with grpcurl

grpcurl is the easiest way to explore gRPC APIs:

bash
# macOS
brew install grpcurl

# Linux
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

# Windows (via Scoop)
scoop install grpcurl

Discover Available Services

bash
# List all services (no proto files needed!)
grpcurl -H "x-api-key: YOUR_API_KEY" \
  mainnet-grpc.inodra.com:443 list

# Example output:
# grpc.reflection.v1.ServerReflection
# sui.rpc.v2.LedgerService
# sui.rpc.v2.MovePackageService
# sui.rpc.v2.NameService
# sui.rpc.v2.SignatureVerificationService
# sui.rpc.v2.StateService
# sui.rpc.v2.SubscriptionService
# sui.rpc.v2.TransactionExecutionService

Explore a Service

bash
# See all methods in LedgerService
grpcurl -H "x-api-key: YOUR_API_KEY" \
  mainnet-grpc.inodra.com:443 \
  describe sui.rpc.v2.LedgerService

# See the request format for a specific method
grpcurl -H "x-api-key: YOUR_API_KEY" \
  mainnet-grpc.inodra.com:443 \
  describe sui.rpc.v2.GetCheckpointRequest

Make API Calls

bash
# Get current epoch info (includes gas price, protocol version)
grpcurl -H "x-api-key: YOUR_API_KEY" \
  -d '{}' \
  mainnet-grpc.inodra.com:443 \
  sui.rpc.v2.LedgerService/GetEpoch

# Get specific checkpoint - works for any checkpoint since genesis
grpcurl -H "x-api-key: YOUR_API_KEY" \
  -d '{"sequence_number": 12345678}' \
  mainnet-grpc.inodra.com:443 \
  sui.rpc.v2.LedgerService/GetCheckpoint

# Get transaction by digest - full history, no pruning
grpcurl -H "x-api-key: YOUR_API_KEY" \
  -d '{"digest": "TRANSACTION_DIGEST_HERE"}' \
  mainnet-grpc.inodra.com:443 \
  sui.rpc.v2.LedgerService/GetTransaction

🚀 Ready to try it?

Get your free API key and start exploring with grpcurl in under 2 minutes.

Available Services

Sui's gRPC API is organized into specialized services. All of them are available on the one endpoint, with full-history coverage:

ServicePurposeCommon Use Cases
LedgerServiceQuery blockchain data (full history)Indexers, block explorers, archives
StateServiceQuery current on-chain stateWallets, DeFi apps, balance checks
TransactionExecutionServiceSubmit and simulate transactionsAny dApp that writes to Sui
SubscriptionServiceStream real-time checkpoint dataLive dashboards, monitoring
MovePackageServiceAccess Move package metadataDeveloper tools, contract analysis
SignatureVerificationServiceValidate signaturesAuthentication, zkLogin
NameServiceResolve SuiNS domain namesName resolution, reverse lookups

For detailed method documentation, see Sui's gRPC API reference.

Field Masks

Most read methods accept a read_mask to select exactly the fields you need. This reduces response size, latency, and streaming costs:

bash
# Only the sequence number, digest, and summary of a checkpoint
grpcurl -H "x-api-key: YOUR_API_KEY" \
  -d '{"sequence_number": 12345678, "read_mask": {"paths": ["sequence_number", "digest", "summary"]}}' \
  mainnet-grpc.inodra.com:443 \
  sui.rpc.v2.LedgerService/GetCheckpoint

Note: with grpcurl, write the field mask as a JSON object ({"paths": [...]}). The compact string form ("read_mask": "summary,contents") is rejected by current grpcurl versions. Generated clients (TypeScript, Go, etc.) take a FieldMask with paths either way.

One archival caveat: for historical checkpoints, GetCheckpoint serves summary, contents, signature, and BCS masks - but the per-transaction transactions.* masks are only populated for recent checkpoints (the same is true of Sui's own archival service). To read full transaction data at any age, take the digests from contents and call GetTransaction / BatchGetTransactions, which return every field across the entire history.

TypeScript Quick Start

For server-side applications with full bidirectional streaming support. This is the recommended approach for backends:

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': 'YOUR_API_KEY' }
})
const client = new SuiGrpcClient({ network: 'mainnet', transport })

// Get current epoch info (includes gas price, protocol version)
const { response } = await client.ledgerService.getEpoch({})
console.log('Epoch:', response.epoch?.epoch, 'Gas price:', response.epoch?.referenceGasPrice)

// Get account balance (high-level method)
const balance = await client.getBalance({
  owner: '0xabc...',
  coinType: '0x2::sui::SUI'
})
console.log('Balance:', balance.balance?.balance)

// Get an object
const object = await client.getObject({ objectId: '0x123...' })

Required packages:

bash
npm install @mysten/sui @protobuf-ts/grpc-transport @grpc/grpc-js

Note: SDK 2.0 provides high-level methods (e.g., client.getBalance()) and low-level service methods (e.g., client.ledgerService.getEpoch()). High-level methods return processed data; service methods return { response, status, headers }.

Option 2: gRPC-Web (Browsers)

For browser applications where native gRPC isn't available:

typescript
import { SuiGrpcClient } from '@mysten/sui/grpc'
import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport'

const transport = new GrpcWebFetchTransport({
  baseUrl: 'https://mainnet-grpc.inodra.com',
  meta: { 'x-api-key': 'YOUR_API_KEY' }
})
const client = new SuiGrpcClient({ network: 'mainnet', transport })

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

Required packages:

bash
npm install @mysten/sui @protobuf-ts/grpcweb-transport

Option 3: Dynamic Loading (No Proto Compilation)

For more control or when the SDK doesn't cover your use case:

typescript
import * as grpc from '@grpc/grpc-js'
import * as protoLoader from '@grpc/proto-loader'

// Download proto files first:
// git clone https://github.com/MystenLabs/sui-apis.git
const packageDefinition = protoLoader.loadSync('sui/rpc/v2/ledger_service.proto', {
  includeDirs: ['./sui-apis/proto'],
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true
})
const loaded = grpc.loadPackageDefinition(packageDefinition) as any

// Create client with TLS (package path is sui.rpc.v2)
const client = new loaded.sui.rpc.v2.LedgerService(
  'mainnet-grpc.inodra.com:443',
  grpc.credentials.createSsl()
)

// Add API key to metadata
const metadata = new grpc.Metadata()
metadata.add('x-api-key', 'YOUR_API_KEY')

// Get current epoch info (keepCase preserves snake_case field names)
client.GetEpoch({}, metadata, (err: Error | null, response: any) => {
  if (err) {
    console.error('Error:', err.message)
    return
  }
  console.log('Epoch:', response.epoch?.epoch, 'Gas price:', response.epoch?.reference_gas_price)
})

Protocol Buffer Definitions

For code generation and type definitions, Sui provides official .proto files:

Since Inodra serves Sui's standard gRPC API, you can use Sui's official proto files directly for client generation.

Using Proto Files

bash
# Clone Sui's proto definitions
git clone https://github.com/MystenLabs/sui-apis.git
cd sui-apis/proto

# Node.js uses dynamic loading via @grpc/proto-loader, no code generation needed

In Node.js, load the cloned protos directly with @grpc/proto-loader - see Option 3 above for a complete working example. For other languages (Go, Python, Rust, etc.), generate clients from the same proto files with protoc or buf and point them at mainnet-grpc.inodra.com:443 with your API key in the request metadata.

Streaming

gRPC's real power is streaming - subscribe to real-time updates without polling:

typescript
// Subscribe to live checkpoint updates (optionally pass a read_mask to limit fields)
const call = client.subscriptionService.subscribeCheckpoints({})
for await (const message of call.responses) {
  console.log('New checkpoint:', message.checkpoint?.sequenceNumber)
  // Process each checkpoint as it arrives - no polling delay
}

Note: the stream starts at the live tip - SubscribeCheckpointsRequest has no start-cursor field. To backfill history before going live, page through LedgerService.GetCheckpoint up to the first streamed cursor, then continue from the stream.

Why streaming matters:

  • Lower latency: Data arrives as soon as it's available
  • Less server load: One connection vs repeated polling
  • Cost efficient: Standard gRPC calls cost 1 Credit each

Streaming billing (SubscribeCheckpoints):

Checkpoint streaming is billed per data volume to reflect the bandwidth used:

MetricRate
Per MB streamed20 Credits
Minimum per message1 Credit

Using field masks to request only the data you need (e.g., events only) significantly reduces costs. A full checkpoint averages ~950 KB (~19 Credits), while a masked request for events only is much smaller.

Error Handling

gRPC uses standard status codes for errors. With SuiGrpcClient, errors are thrown as exceptions:

typescript
import { RpcError } from '@protobuf-ts/runtime-rpc'

try {
  const object = await client.getObject({ objectId: '0x123...' })
} catch (err) {
  if (err instanceof RpcError) {
    switch (err.code) {
      case 'UNAUTHENTICATED':
        console.error('Invalid API key')
        break
      case 'NOT_FOUND':
        console.error('Object not found')
        break
      case 'UNAVAILABLE':
        console.error('Service unavailable, retrying...')
        break
      case 'RESOURCE_EXHAUSTED':
        console.error('Rate limited')
        break
      default:
        console.error('Error:', err.message)
    }
  }
}

Note: status codes are canonical and safe to branch on. Error message strings are intentionally generic (e.g., Resource not found) - build retry and fallback logic on the status code, not the message text.

When to Use gRPC

Perfect for:

  • High-frequency trading applications
  • Indexers and analytics over the full chain history
  • Real-time monitoring dashboards
  • Microservices that need type safety
  • Mobile apps (bandwidth-sensitive)
  • Applications requiring sub-100ms latency
  • Any use case needing streaming

Consider alternatives if:

  • You want flexible JSON queries over HTTP - see the GraphQL API
  • Your team isn't familiar with Protocol Buffers

Next Steps

Released under the MIT License.