> For the complete documentation index, see [llms.txt](/llms.txt)

# Core API

The transport-agnostic client contract that SDKs and libraries build against



The Core API is the contract every Sui client implements. It exists so that libraries do not have to
care which transport their caller chose: an SDK written against the Core API works with
`SuiGrpcClient`, `SuiGraphQLClient`, and the deprecated `SuiJsonRpcClient` alike.

<Callout type="info">
  This page is for SDK and library authors. If you are writing application code, create a client and
  call its [top-level methods](/sui/clients/querying) directly. You do not need `client.core`.
</Callout>

For packaging, extension, and code-generation patterns that go with this contract, see
[Building SDKs](/sui/sdk-building).

## `ClientWithCoreApi` [#clientwithcoreapi]

`ClientWithCoreApi` is the type to accept in library code. It says "any Sui client", and gives you
`client.core` to work through:

```typescript
import type { ClientWithCoreApi } from '@mysten/sui/client';

export class MySDK {
	#client: ClientWithCoreApi;

	constructor(client: ClientWithCoreApi) {
		this.#client = client;
	}

	async getResource(objectId: string) {
		const { object } = await this.#client.core.getObject({
			objectId,
			include: { content: true },
		});

		return object;
	}
}
```

Take the client as a parameter rather than constructing one: the caller has already configured a
network, endpoint, and transport, and a second client would bypass all of it along with the request
cache the caller's client holds.

### Why go through `client.core` [#why-go-through-clientcore]

Top-level methods on `SuiGrpcClient` and `SuiGraphQLClient` are a superset of the Core API: each
transport can add fields where it has extra native data, such as `include: { protoJson: true }` on
gRPC. Those additions are not portable. `client.core` is the subset that every transport guarantees,
so library code that stays inside it keeps working whichever client the caller supplies.

## The contract [#the-contract]

Every client implements these methods on `client.core`. [Querying data](/sui/clients/querying)
documents them in full, with options, include flags, and response shapes.

| Category       | Methods                                                                                    |
| -------------- | ------------------------------------------------------------------------------------------ |
| Objects        | `getObject`, `getObjects`, `listOwnedObjects`                                              |
| Coins          | `getBalance`, `listBalances`, `listCoins`, `getCoinMetadata`                               |
| Dynamic fields | `listDynamicFields`, `getDynamicField`, `getDynamicObjectField`                            |
| Transactions   | `getTransaction`, `executeTransaction`, `signAndExecuteTransaction`, `waitForTransaction`  |
| Simulation     | `simulateTransaction`                                                                      |
| History        | `listTransactions`, `listEvents`                                                           |
| Move           | `getMoveFunction`                                                                          |
| Names          | `resolveNameServiceAddress`, `defaultNameServiceName`, `mvr`                               |
| Network        | `getReferenceGasPrice`, `getCurrentSystemState`, `getProtocolConfig`, `getChainIdentifier` |
| Verification   | `verifyZkLoginSignature`                                                                   |
| Building       | `resolveTransactionPlugin`                                                                 |

`SuiGrpcClient` and `SuiGraphQLClient` also expose every one of these as a top-level method, so the
choice between `client.core.getObject()` and `client.getObject()` is about portability rather than
reach. The deprecated `SuiJsonRpcClient` is the exception: it implements the contract on
`client.core`, but its own top-level methods keep their legacy JSON-RPC names and shapes.

## Typing library code [#typing-library-code]

`SuiClientTypes` holds every option and response type in the contract. Import it to type your own
signatures rather than re-deriving shapes:

```typescript
import type { ClientWithCoreApi, SuiClientTypes } from '@mysten/sui/client';

import { MyStruct } from './generated/my-module';

// Include generics flow through, so `object.content` is typed as present here
function parseResource(object: SuiClientTypes.Object<{ content: true }>) {
	return MyStruct.parse(object.content);
}

async function fetchBalance(
	client: ClientWithCoreApi,
	owner: string,
): Promise<SuiClientTypes.Balance> {
	const { balance } = await client.core.getBalance({ owner });
	return balance;
}
```

The `Include` generic is what makes optional data type-safe: a field that was not requested is typed
`undefined`, so forgetting to ask for `content` is a compile error rather than a runtime one.

### Discriminated unions [#discriminated-unions]

Polymorphic types in the contract use a `$kind` discriminant rather than optional fields. Narrow on
`$kind` and the matching property is typed for you:

```typescript
function describeOwner(owner: SuiClientTypes.ObjectOwner) {
	switch (owner.$kind) {
		case 'AddressOwner':
			return `owned by ${owner.AddressOwner}`;
		case 'ObjectOwner':
			return `owned by object ${owner.ObjectOwner}`;
		case 'Shared':
			return `shared since ${owner.Shared.initialSharedVersion}`;
		case 'ConsensusAddressOwner':
			return `consensus-owned by ${owner.ConsensusAddressOwner.owner}`;
		case 'Immutable':
			return 'immutable';
		default:
			return 'unknown';
	}
}
```

`TransactionResult`, `SimulateTransactionResult`, `DatatypeResponse`, `ExecutionError`, and
`OpenSignatureBody` follow the same pattern.

### Common types [#common-types]

| Type                   | Description                                                                              |
| ---------------------- | ---------------------------------------------------------------------------------------- |
| `Object<Include>`      | The unified object shape; `Include` controls which optional fields are present and typed |
| `Coin`                 | Coin object with balance                                                                 |
| `Balance`              | Balance summary for a coin type                                                          |
| `CoinMetadata`         | Metadata for a coin type                                                                 |
| `Transaction<Include>` | An executed transaction; `Include` controls which optional fields are present            |
| `TransactionResult`    | Success or failure result from execution                                                 |
| `TransactionEffects`   | Detailed effects from transaction execution                                              |
| `Event`                | Emitted event from a transaction                                                         |
| `EventEntry`           | Queried event with its ledger position                                                   |
| `TransactionFilter`    | Filter for transaction queries                                                           |
| `EventFilter`          | Filter for event queries                                                                 |
| `ObjectOwner`          | Union of all owner types                                                                 |
| `ExecutionStatus`      | Success/failure status with error details                                                |
| `DynamicFieldName`     | Name identifier for dynamic fields                                                       |
| `FunctionResponse`     | Move function metadata                                                                   |
| `Network`              | Network identifier type                                                                  |

Option types follow a predictable naming scheme (`GetObjectOptions`, `ListCoinsOptions`,
`SimulateTransactionOptions`, and so on), along with the include shapes `ObjectInclude`,
`TransactionInclude`, and `SimulateTransactionInclude`.

## Cross-transport differences [#cross-transport-differences]

All transports produce the same shapes, but they read from different backends, and a few differences
are visible to callers. Library code that runs against any client should account for them.

| Behavior                    | What differs                                                                                                                            |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `include: { json: true }`   | Field shapes vary between implementations. Use `content` and parse BCS when the result must be stable                                   |
| `include: { events: true }` | GraphQL returns at most the first 50 events of a transaction; gRPC and JSON-RPC return all of them                                      |
| JSON-RPC query gaps         | `EventEntry.checkpoint` is `null`, and a bare generic `eventType` filter matches nothing where gRPC and GraphQL match any instantiation |
| Page-size caps              | Over-large `limit` values are silently truncated on gRPC and rejected with an error on GraphQL and JSON-RPC                             |
| Short pages                 | gRPC bounds how much ledger a filtered query scans, so a page can be short or empty with `hasNextPage: true`                            |
| Read-after-write            | GraphQL reads an index that trails execution. Use `waitForTransaction` before reading your own writes                                   |

Two constraints are enforced by the client itself, so they fail the same way everywhere: a query
takes at most one of `after` or `before`, and paginating transactions filtered by `function`
requires a fully qualified `package::module::function`.

## Client services [#client-services]

Beyond the methods, `ClientWithCoreApi` carries a few properties worth knowing about when writing
library code:

| Property          | Purpose                                                                      |
| ----------------- | ---------------------------------------------------------------------------- |
| `client.network`  | The network the client was created for (`'mainnet'`, `'testnet'`, and so on) |
| `client.cache`    | A shared cache; call `cache.scope('my-sdk')` for a namespace of your own     |
| `client.core.mvr` | Move Registry resolution, with its own cache                                 |

Every Core API method also accepts a `signal`, so a library method can pass its caller's
`AbortSignal` straight through:

```typescript
async function getResource(
	client: ClientWithCoreApi,
	objectId: string,
	options?: { signal?: AbortSignal },
) {
	return client.core.getObject({
		objectId,
		include: { content: true },
		signal: options?.signal,
	});
}
```

SDKs attach themselves to a client with `$extend`, so several SDKs can compose on one client
instance. See [Building SDKs](/sui/sdk-building) for how to author an extension, choose peer
dependencies, and structure your SDK's methods.
