Migrating from JSON-RPC
Migrate from JSON-RPC to the new Core API using SuiGrpcClient or SuiGraphQLClient.
This guide covers migrating from SuiJsonRpcClient to the new client APIs. The JSON-RPC API is
being deprecated in favor of SuiGrpcClient and SuiGraphQLClient.
We recommend using SuiGrpcClient for most operations, including filtering transactions and
events with the core query API. SuiGraphQLClient remains useful for complex queries that aren't
covered by the core API.
Choosing a client
| Client | Best For |
|---|---|
SuiGrpcClient | Most operations, SDK integrations, queries, real-time data |
SuiGraphQLClient | Complex queries not covered by the core API (e.g. historical objects) |
Quick migration to gRPC
For most use cases, migrate to SuiGrpcClient:
- import { SuiJsonRpcClient, getJsonRpcFullnodeUrl } from '@mysten/sui/jsonRpc';
+ import { SuiGrpcClient } from '@mysten/sui/grpc';
- const client = new SuiJsonRpcClient({
- url: getJsonRpcFullnodeUrl('mainnet'),
- network: 'mainnet',
- });
+ const client = new SuiGrpcClient({
+ baseUrl: 'https://fullnode.mainnet.sui.io:443',
+ network: 'mainnet',
+ });Both clients use the same full node URLs, so you can use the same endpoint when migrating.
Core API methods
The gRPC client should work with almost all mysten SDKs as a drop in replacement for the JSON-RPC client. When using the client directly, the methods and data returned will not be exactly the same as what was available in JSON-RPC.
Methods replaced by core API
These JSON-RPC methods have direct replacements in the core API:
| JSON-RPC Method | Core API Replacement |
|---|---|
getCoins | listCoins |
getAllCoins | listOwnedObjects with type: '0x2::coin::Coin' |
getAllBalances | listBalances |
getOwnedObjects | listOwnedObjects |
multiGetObjects | getObjects |
getDynamicFields | listDynamicFields |
getDynamicFieldObject | getDynamicField |
devInspectTransactionBlock | simulateTransaction with checksEnabled: false |
dryRunTransactionBlock | simulateTransaction |
getNormalizedMoveFunction | getMoveFunction |
getMoveFunctionArgTypes | getMoveFunction |
queryTransactionBlocks | listTransactions |
queryEvents | listEvents |
getCheckpoints | listCheckpoints |
The query methods (listTransactions, listEvents, and listCheckpoints) behave identically on
every client, including SuiJsonRpcClient. On gRPC they require a full node running sui v1.76 or
later, and additionally accept extended filters
(combined, negated, and additional predicates). See Core API query
methods for details.
Example: Migrating queryTransactionBlocks
- const result = await jsonRpcClient.queryTransactionBlocks({
- filter: { FromAddress: '0xabc...' },
- options: { showEffects: true },
- limit: 10,
- });
- const digests = result.data.map((tx) => tx.digest);
+ const result = await client.core.listTransactions({
+ filter: { sender: '0xabc...' },
+ include: { effects: true },
+ limit: 10,
+ });
+ const digests = result.transactions.map(
+ (tx) => (tx.Transaction ?? tx.FailedTransaction).digest,
+ );The core API filters map to the JSON-RPC TransactionFilter variants:
| JSON-RPC filter | Core API filter predicate |
|---|---|
FromAddress | sender |
MoveFunction | function |
ToAddress / FromOrToAddress / ChangedObject / AffectedObject | No core equivalent — use the gRPC extended filters |
Example: Migrating queryEvents
- const result = await jsonRpcClient.queryEvents({
- query: { MoveEventType: '0x2::coin::CoinCreated' },
- limit: 10,
- order: 'descending',
- });
+ const result = await client.core.listEvents({
+ filter: { eventType: '0x2::coin::CoinCreated' },
+ limit: 10,
+ order: 'descending',
+ });The JSON-RPC EventFilter variants map to sender, emitModule (replaces MoveModule /
MoveEventModule), and eventType (replaces MoveEventType). Each returned event includes its
ledger position (checkpoint, transactionDigest, and eventIndex).
Example: Migrating getCheckpoints
- const result = await jsonRpcClient.getCheckpoints({
- descendingOrder: true,
- limit: 10,
- });
- const checkpoints = result.data;
+ const result = await client.core.listCheckpoints({
+ order: 'descending',
+ limit: 10,
+ });
+ const checkpoints = result.checkpoints;Example: Migrating devInspectTransactionBlock
- const result = await jsonRpcClient.devInspectTransactionBlock({
- sender: '0xabc...',
- transactionBlock: tx,
- });
- const returnValues = result.results?.[0]?.returnValues;
+ const result = await client.core.simulateTransaction({
+ transaction: tx,
+ checksEnabled: false,
+ include: { commandResults: true },
+ });
+ const returnValues = result.commandResults?.[0]?.returnValues;Example: Migrating getOwnedObjects
- const { data } = await jsonRpcClient.getOwnedObjects({
- owner: '0xabc...',
- options: { showContent: true },
- });
+ const { objects } = await grpcClient.listOwnedObjects({
+ owner: '0xabc...',
+ include: { content: true },
+ });Methods replaced by gRPC services
These JSON-RPC methods can be replaced by calling gRPC service clients directly:
| JSON-RPC Method | gRPC Service Replacement |
|---|---|
getCheckpoint | ledgerService.getCheckpoint |
getLatestCheckpointSequenceNumber | ledgerService.getServiceInfo (read checkpointHeight) |
getCurrentEpoch | ledgerService.getEpoch (omit epoch for the current) |
getLatestSuiSystemState | ledgerService.getEpoch with system_state in the read mask |
getCommitteeInfo | ledgerService.getEpoch with committee in the read mask |
getProtocolConfig | ledgerService.getEpoch with protocol_config in the read mask |
getCoinMetadata | stateService.getCoinInfo (response includes metadata) |
getTotalSupply | stateService.getCoinInfo (response includes treasury) |
getNormalizedMoveModule | movePackageService.getPackage (response includes all modules) |
getNormalizedMoveModulesByPackage | movePackageService.getPackage |
getNormalizedMoveStruct | movePackageService.getDatatype |
resolveNameServiceAddress | nameService.lookupName |
resolveNameServiceNames | nameService.reverseLookupName |
getEpochs (paginated listing) has no gRPC equivalent — use the epochs GraphQL query instead.
getValidatorsApy also has no replacement: there is no canonical definition of validator APY, so
it must be computed client-side from validator exchange rates (see Validator APY
below).
Example: using gRPC service clients
import { SuiGrpcClient } from '@mysten/sui/grpc';
const client = new SuiGrpcClient({
baseUrl: 'https://fullnode.mainnet.sui.io:443',
network: 'mainnet',
});
// Get the latest checkpoint sequence number (replaces getLatestCheckpointSequenceNumber)
const { response: info } = await client.ledgerService.getServiceInfo({});
const latestCheckpoint = info.checkpointHeight;
// Get a specific checkpoint by sequence number. `checkpointId` is required (a oneof);
// the read mask defaults to `sequence_number,digest`, so request more fields as needed.
const { response } = await client.ledgerService.getCheckpoint({
checkpointId: { oneofKind: 'sequenceNumber', sequenceNumber: latestCheckpoint },
});
// Get the current epoch (omit `epoch` for the current one). The system state,
// committee, and protocol config are NOT returned by default — you must request
// them via the read mask (the default mask is just `epoch`).
const { response: epoch } = await client.ledgerService.getEpoch({
readMask: { paths: ['epoch', 'system_state', 'committee', 'protocol_config'] },
});
const systemState = epoch.epoch?.systemState;
const committee = epoch.epoch?.committee;
const protocolConfig = epoch.epoch?.protocolConfig;
// Get coin metadata and total supply in one call
const { response: coinInfo } = await client.stateService.getCoinInfo({
coinType: '0x2::sui::SUI',
});
const metadata = coinInfo.metadata;
const totalSupply = coinInfo.treasury?.totalSupply;
// Get Move package information (includes all modules)
const { response: pkg } = await client.movePackageService.getPackage({
packageId: '0x2',
});
// Get a specific Move datatype (struct or enum)
const { response: datatype } = await client.movePackageService.getDatatype({
packageId: '0x2',
moduleName: 'coin',
name: 'Coin',
});
// Resolve SuiNS name
const { response: address } = await client.nameService.lookupName({
name: 'example.sui',
});Methods replaced by gRPC subscriptions
The deprecated JSON-RPC websocket subscriptions can be replaced with the gRPC subscriptionService,
which provides filtered, real-time streams (requires a full node running sui v1.76 or later for
transaction and event subscriptions):
| JSON-RPC Method | gRPC Service Replacement |
|---|---|
subscribeTransaction | subscriptionService.subscribeTransactions |
subscribeEvent | subscriptionService.subscribeEvents |
const stream = client.subscriptionService.subscribeEvents({
filter: {
terms: [
{
literals: [
{
negated: false,
predicate: {
oneofKind: 'eventType',
eventType: { eventType: '0x2::coin::CoinCreated' },
},
},
],
},
],
},
readMask: { paths: ['event_type', 'contents', 'json', 'checkpoint', 'transaction_digest'] },
});
for await (const frame of stream.responses) {
if (frame.event) {
console.log(frame.event.eventType, frame.event.json);
}
}Subscriptions always begin at the current tip of the chain. To recover events missed between
subscriptions, replay the gap with client.core.listEvents using the same filter.
Methods requiring GraphQL
Some JSON-RPC methods don't have gRPC equivalents and require using SuiGraphQLClient instead:
| JSON-RPC Method | GraphQL Alternative |
|---|---|
multiGetTransactionBlocks | multiGetTransactionEffects query |
getEpochs | epochs query |
getCoinMetadata | coinMetadata query (or gRPC stateService.getCoinInfo) |
getTotalSupply | coinMetadata query (or gRPC stateService.getCoinInfo) |
getStakes | address.stakedSuis query |
getStakesByIds | multiGetObjects query |
tryGetPastObject | Historical object queries |
getNetworkMetrics | Use indexer |
getAddressMetrics | Use indexer |
getMoveCallMetrics | Use indexer |
Setting up GraphQL client
import { SuiGraphQLClient } from '@mysten/sui/graphql';
const graphqlClient = new SuiGraphQLClient({
url: 'https://sui-mainnet.mystenlabs.com/graphql',
network: 'mainnet',
});Fetching multiple transactions
Replace multiGetTransactionBlocks with a GraphQL query:
const result = await graphqlClient.query({
query: `
query MultiGetTransactions($digests: [String!]!) {
multiGetTransactionEffects(keys: $digests) {
transaction {
digest
transactionBcs
}
status
epoch { epochId }
}
}
`,
variables: {
digests: ['digest1', 'digest2', 'digest3'],
},
});Querying historical objects
Replace tryGetPastObject with a GraphQL query specifying a version:
const result = await graphqlClient.query({
query: `
query GetObjectAtVersion($id: SuiAddress!, $version: UInt53!) {
object(address: $id, version: $version) {
address
version
digest
asMoveObject {
contents {
type { repr }
bcs
}
}
}
}
`,
variables: {
id: '0x123...',
version: 42,
},
});Querying coin metadata
Replace getCoinMetadata and getTotalSupply with a GraphQL query:
const result = await graphqlClient.query({
query: `
query GetCoinMetadata($coinType: String!) {
coinMetadata(coinType: $coinType) {
name
symbol
description
decimals
iconUrl
supply
}
}
`,
variables: {
coinType: '0x2::sui::SUI',
},
});Querying staked SUI
Replace getStakes with a GraphQL query:
const result = await graphqlClient.query({
query: `
query GetStakes($owner: SuiAddress!) {
address(address: $owner) {
stakedSuis {
nodes {
principal
stakeActivationEpoch
estimatedReward
contents {
bcs
}
}
}
}
}
`,
variables: {
owner: '0xabc...',
},
});Validator APY
There is no replacement for getValidatorsApy, and one will not be added. There is no canonical
definition of validator APY — no other chain's core/general-purpose RPCs offer this endpoint — so it
must be computed client-side from validator staking-pool exchange rates.
The general approach: read each validator's exchangeRates (a table of pool_token_amount /
sui_amount per epoch) and estimate the annualized rate from the change in exchange rate over the
trailing epochs. Two concrete reference implementations:
- The
jsonrpc-altimplementation insui-indexer-alt-jsonrpc, which is compact and readable. - This GraphQL approach, which fetches the precursor exchange-rate information in a single query.
Treat whichever formula you adopt as a definition of validator APY, not the definition.
Response format differences
The gRPC client uses the core API response format, which differs from JSON-RPC responses. See the
@mysten/sui migration guide
for details on the new response format.
Key differences:
// Transaction result access
- const status = result.effects?.status?.status;
+ const tx = result.Transaction ?? result.FailedTransaction;
+ const status = tx.effects.status.success;
// Include options
- { showEffects: true, showEvents: true }
+ { effects: true, events: true }Client extensions
Client extensions work the same way with both clients:
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { deepbook } from '@mysten/deepbook-v3';
import { suins } from '@mysten/suins';
const client = new SuiGrpcClient({
baseUrl: 'https://fullnode.mainnet.sui.io:443',
network: 'mainnet',
}).$extend(deepbook({ address: myAddress }), suins());
// Use extended functionality
await client.deepbook.checkManagerBalance(manager, asset);
await client.suins.getName('0xabc...');See also
- SuiGrpcClient Documentation - Full gRPC client documentation
- SuiGraphQLClient Documentation - GraphQL client documentation
- Core API - Transport-agnostic API methods
- gRPC Overview - Sui gRPC API documentation