# Migrate to 2.0 (/sui/migrations/sui-2.0) This guide covers the breaking changes across the latest release of all the `@mysten/*` packages. The primary goal of this release is to support the gRPC and GraphQL APIs across all Mysten SDKs. These releases also include removals of deprecated APIs, some renaming for better consistency, and significant internal refactoring to improve maintainability. Starting with this release, Mysten packages will now be published as ESM only packages. ## Quick reference [#quick-reference] | Package | Key Changes | | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | [`@mysten/sui`](/sui/migrations/sui-2.0/sui) | Client API stabilization, SuiClient removal, BCS schema alignment, transaction executors | | [`@mysten/dapp-kit`](/sui/migrations/sui-2.0/dapp-kit) | Complete rewrite with framework-agnostic core | | [`@mysten/kiosk`](/sui/migrations/sui-2.0/kiosk) | Client extension pattern, low-level helpers removed, KioskTransaction pattern | | [`@mysten/zksend`](/sui/migrations/sui-2.0/zksend) | Client extension pattern | | [`@mysten/suins`](/sui/migrations/sui-2.0/suins) | Client extension pattern | | [`@mysten/deepbook-v3`](/sui/migrations/sui-2.0/deepbook-v3) | Client extension pattern | | [`@mysten/walrus`](/sui/migrations/sui-2.0/walrus) | Client extension pattern, requires client instead of RPC URL | | [`@mysten/seal`](/sui/migrations/sui-2.0/seal) | Client extension pattern | | [`@mysten/wallet-standard`](/sui/migrations/sui-2.0/wallet-builders) | Removal of reportTransactionEffects, new core API response format | | [Migrating from JSON-RPC](/sui/migrations/sui-2.0/json-rpc-migration) | Migrate from deprecated JSON-RPC to gRPC and GraphQL | ## Common migration patterns [#common-migration-patterns] ### ESM migration [#esm-migration] All `@mysten/*` packages are now ESM only. If your project does not already use ESM, you will need to add `"type": "module"` to your `package.json`: ```json { "type": "module" } ``` If you are using TypeScript with `moduleResolution` `"Node"`, `"Classic"`, or `"Node10"`, you will need to update your `tsconfig.json` to use `"NodeNext"`, `"Node16"`, or `"Bundler"`: ```json { "compilerOptions": { "moduleResolution": "NodeNext", "module": "NodeNext" } } ``` This enables proper resolution of the SDK's subpath exports (for example, `@mysten/sui/client`, `@mysten/sui/transactions`). If you maintain a library that depends on any of the `@mysten/*` packages, you might also need to update your library to be ESM only to ensure it works correctly everywhere. Applications using bundlers and recent Node.js versions (>=22) might still work when using `require` to load ESM packages, but we recommend migrating to ESM. **Why ESM only?** Many packages in the ecosystem (specifically critical cryptography dependencies) are now published as ESM only. Supporting CommonJS has prevented us from using the latest versions of these dependencies, making our SDKs harder to maintain and risking missing critical security updates. ### Client migration [#client-migration] The recommended app migration is to create one `SuiGrpcClient` and use its top-level methods: ```diff - import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'; + import { SuiGrpcClient } from '@mysten/sui/grpc'; - const client = new SuiClient({ url: getFullnodeUrl('mainnet') }); + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.mainnet.sui.io:443', + network: 'mainnet', + }); ``` Then migrate old JSON-RPC method names to the gRPC top-level methods: ```diff - const coins = await client.getCoins({ owner }); + const coins = await client.listCoins({ owner }); - const txs = await client.queryTransactionBlocks({ filter, options }); + const txs = await client.listTransactions({ filter, include }); - const events = await client.queryEvents({ query, order: 'descending' }); + const events = await client.listEvents({ filter, order: 'descending' }); - const transaction = await client.getTransactionBlock({ digest, options }); + const transaction = await client.getTransaction({ digest, include }); ``` The gRPC API runs on full nodes, so in most cases you can use the same full node host when migrating from JSON-RPC to gRPC. Standard transaction and event queries are top-level methods on both `SuiGrpcClient` and `SuiGraphQLClient`. Use custom GraphQL queries for indexed data, historical object versions, or selection sets that are not covered by the shared methods. `SuiJsonRpcClient` still exists under `@mysten/sui/jsonRpc` for legacy code, but JSON-RPC APIs are deprecated in the Sui TypeScript SDK. See [Migrating from JSON-RPC](/sui/migrations/sui-2.0/json-rpc-migration) for detailed replacements. ### Network parameter required [#network-parameter-required] All client constructors now require an explicit `network` parameter: ```ts const grpcClient = new SuiGrpcClient({ baseUrl: 'https://fullnode.mainnet.sui.io:443', network: 'mainnet', // Required }); const graphqlClient = new SuiGraphQLClient({ url: 'https://sui-mainnet.mystenlabs.com/graphql', network: 'mainnet', // Required }); const jsonRpcClient = new SuiJsonRpcClient({ url: 'https://fullnode.mainnet.sui.io:443', network: 'mainnet', // Required }); ``` ### `ClientWithCoreApi` Interface [#clientwithcoreapi-interface] Many SDK methods now accept any client implementing `ClientWithCoreApi`. SDKs use `client.core.()` so they can work across `SuiGrpcClient`, `SuiGraphQLClient`, and the deprecated `SuiJsonRpcClient` while apps keep using the top-level methods on their chosen client: ```ts import type { ClientWithCoreApi } from '@mysten/sui/client'; import { SuiGrpcClient } from '@mysten/sui/grpc'; const client = new SuiGrpcClient({ baseUrl: 'https://fullnode.mainnet.sui.io:443', network: 'mainnet', }); // App code: use top-level methods. const { balance } = await client.getBalance({ owner }); // SDK code: accept ClientWithCoreApi and use client.core. async function readForSdk(client: ClientWithCoreApi, objectId: string) { return client.core.getObject({ objectId }); } ``` ## Package-specific guides [#package-specific-guides] For detailed migration instructions, see the SDK-specific guides: * **[`@mysten/sui`](/sui/migrations/sui-2.0/sui):** Core SDK changes including client API, BCS schemas, transactions, zkLogin, and GraphQL * **[`@mysten/dapp-kit`](/sui/migrations/sui-2.0/dapp-kit):** Complete migration guide for the new dApp kit architecture * **[`@mysten/kiosk`](/sui/migrations/sui-2.0/kiosk):** Kiosk SDK now exports a client extension, low-level helpers removed * **[`@mysten/zksend`](/sui/migrations/sui-2.0/zksend):** zkSend SDK now exports a client extension * **[`@mysten/suins`](/sui/migrations/sui-2.0/suins):** SuiNS now exports a client extension * **[`@mysten/deepbook-v3`](/sui/migrations/sui-2.0/deepbook-v3):** DeepBook DEX now exports a client extension * **[`@mysten/walrus`](/sui/migrations/sui-2.0/walrus):** Walrus storage now exports a client extension * **[`@mysten/seal`](/sui/migrations/sui-2.0/seal):** Seal encryption now exports a client extension ## Transport migration [#transport-migration] * **[Migrating from JSON-RPC](/sui/migrations/sui-2.0/json-rpc-migration):** Migrate from the deprecated JSON-RPC client to gRPC and GraphQL ## Ecosystem migration guides [#ecosystem-migration-guides] For wallet builders and SDK maintainers building on the Sui ecosystem: * **[Wallet builders](/sui/migrations/sui-2.0/wallet-builders):** Guide for wallet implementations adapting to `reportTransactionEffects` removal and new core API response format * **[SDK maintainers](/sui/migrations/sui-2.0/sdk-maintainers):** Guide for SDK authors migrating to `ClientWithCoreApi` and the new transport-agnostic architecture ## Non-existent objects [#non-existent-objects] When migrating from the v1 SDK to the v2 SDK, review any code paths that read objects or dynamic fields that may not exist. In v1, methods such as `core.getObject` and `getDynamicField` return `null` when the requested object or field does not exist. In v2, the same operations throw an exception instead. Applications that previously relied on `null` checks should be updated to handle exceptions appropriately, either through try/catch blocks or by validating object existence before attempting to read it. This behavioral change may require updates to error handling logic to avoid unexpected runtime failures after migration. --- # Agent Migration Prompt (/sui/migrations/sui-2.0/agent-prompt) Copy and paste the following prompt into your AI coding assistant (Claude Code, Cursor, and others) to migrate your codebase to v2. If a planning mode is available we recommend starting there first. ```txt ## Sui TypeScript SDK v2 Migration Migrate this codebase to the latest version of `@mysten/*` packages. ### Step 1: Identify Project Tools Examine the project to identify: - Package manager (npm, pnpm, yarn, bun) - check for lock files - Build tools and scripts in package.json - Type checking, linting, and test commands ### Step 2: Read the Migration Guide Fetch and read the full migration guide from: https://sdk.mystenlabs.com/sui/migrations/sui-2.0/llms.txt This file contains the migration instructions for most affected @mysten package, but please be sure to install the latest version of all @mysten packages even if they do not have an explicit migration guide ### Step 3: Migrate Apply the migration patterns from the guide. Do not make changes without first reading the relevant section of the migration guide. ### Step 4: Validate Run all validation scripts to verify the migration including: * Type check * Lint * Build * Test Fix all errors before considering the migration complete. ``` --- # @mysten/dapp-kit (/sui/migrations/sui-2.0/dapp-kit) This guide helps you migrate from the original `@mysten/dapp-kit` (legacy) to the new `@mysten/dapp-kit-react` package. The legacy `@mysten/dapp-kit` package only supports the deprecated JSON RPC API and will not receive further updates. Migrate to `@mysten/dapp-kit-react` for gRPC support and new features. ## Overview [#overview] The new dApp kit SDK represents a complete rewrite with these key changes: * Framework agnostic: Split into `@mysten/dapp-kit-core` (framework-agnostic) and `@mysten/dapp-kit-react` (React bindings) * No React Query dependency: Direct promise-based API instead of mutation hooks * Web Components: UI components built with Lit Elements for cross-framework compatibility * Smaller bundle size: No React Query dependency, lighter state management with nanostores * Better SSR support: Compatible with SSR frameworks like Next.js * Cross-framework compatibility: Core functionality can be used in vanilla JS, Vue, React, and other frameworks ## Step-by-step migration [#step-by-step-migration] ### Step 1: Update dependencies [#step-1-update-dependencies] Remove the old package and install the new ones: ```bash npm uninstall @mysten/dapp-kit npm i @mysten/dapp-kit-react @mysten/dapp-kit-core @mysten/sui ``` ### Step 2: Create dApp Kit instance [#step-2-create-dapp-kit-instance] Create a new instance of the dApp kit using the `createDAppKit` function and register the global type. ```tsx // dapp-kit.ts import { createDAppKit } from '@mysten/dapp-kit-react'; import { SuiGrpcClient } from '@mysten/sui/grpc'; const GRPC_URLS = { testnet: 'https://fullnode.testnet.sui.io:443', }; export const dAppKit = createDAppKit({ networks: ['testnet'], createClient(network) { return new SuiGrpcClient({ network, baseUrl: GRPC_URLS[network] }); }, }); // global type registration necessary for the hooks to work correctly declare module '@mysten/dapp-kit-react' { interface Register { dAppKit: typeof dAppKit; } } ``` ### Step 3: Register types [#step-3-register-types] The `declare module` block in the previous step registers your dApp kit instance's type globally. This enables all hooks like `useDAppKit()`, `useCurrentNetwork()`, and `useCurrentClient()` to automatically infer the correct types based on your configuration (for example, your specific networks and client type). ```tsx declare module '@mysten/dapp-kit-react' { interface Register { dAppKit: typeof dAppKit; } } ``` Without this registration, hooks return generic types and you lose type safety for things like network names. If you prefer not to use global type registration, you can pass the `dAppKit` instance explicitly to each hook instead: ```tsx const connection = useWalletConnection({ dAppKit }); const network = useCurrentNetwork({ dAppKit }); ``` ### Step 4: Replace provider setup [#step-4-replace-provider-setup] Replace the nested dApp kit providers with a single unified provider. You can keep your existing `QueryClientProvider` for data fetching. ```diff // App.tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; - import { SuiClientProvider, WalletProvider } from '@mysten/dapp-kit'; + import { DAppKitProvider } from '@mysten/dapp-kit-react'; + import { dAppKit } from './dapp-kit.ts'; export function App() { const queryClient = new QueryClient(); - const networkConfig = { - mainnet: { url: 'https://mainnet.sui.io:443' }, - testnet: { url: 'https://testnet.sui.io:443' }, - }; return ( - - - - - + + + ); } ``` ### Step 5: Configuration option changes [#step-5-configuration-option-changes] The `createDAppKit` function has different configuration options than the old `WalletProvider`: | Old (`WalletProvider`) | New (`createDAppKit`) | Notes | | ---------------------- | --------------------- | -------------------------------------------------------------------- | | - | `networks` (required) | List of network identifiers your app supports | | - | `createClient` | Function to create a client for each network | | - | `defaultNetwork` | Network to use by default (defaults to first in `networks`) | | `autoConnect` (false) | `autoConnect` (true) | Default changed from `false` to `true` | | `enableUnsafeBurner` | `enableBurnerWallet` | Renamed | | `slushWallet` | `slushWalletConfig` | Renamed | | `storage` | `storage` | Unchanged | | `storageKey` | `storageKey` | Unchanged | | `preferredWallets` | - | Removed | | `walletFilter` | - | Removed (wallets filtered by network compatibility) | | `theme` | - | Removed (UI components are now web components with built-in styling) | | - | `walletInitializers` | New option for registering custom wallets | ### Step 6: Update hook usage [#step-6-update-hook-usage] The new dApp kit has a dramatically simplified hook API. Most hooks from the original version have been replaced with direct action calls through `useDAppKit()`. **Available hooks in the new version:** * `useDAppKit()`: Access the dAppKit instance for calling actions * `useCurrentClient()`: Get the blockchain client (renamed from `useSuiClient`) * `useCurrentAccount()`: Get the current connected account * `useCurrentWallet()`: Get the current connected wallet * `useWallets()`: Get the list of available wallets * `useWalletConnection()`: Get the current wallet connection status * `useCurrentNetwork()`: Get the current network **Removed hooks:** All wallet action hooks have been replaced with direct action calls through `useDAppKit()`: * `useConnectWallet` -> Use `dAppKit.connectWallet()` * `useDisconnectWallet` -> Use `dAppKit.disconnectWallet()` * `useSignTransaction` -> Use `dAppKit.signTransaction()` * `useSignAndExecuteTransaction` -> Use `dAppKit.signAndExecuteTransaction()` * `useSignPersonalMessage` -> Use `dAppKit.signPersonalMessage()` * `useSwitchAccount` -> Use `dAppKit.switchAccount()` All data fetching hooks have been removed (giving you flexibility to use your preferred solution): * `useSuiClientQuery` -> Use `useCurrentClient()` with your data fetching solution * `useSuiClientMutation` -> Use `useCurrentClient()` with your data fetching solution * `useSuiClientInfiniteQuery` -> Use `useCurrentClient()` with your data fetching solution * `useSuiClientQueries` -> Use `useCurrentClient()` with your data fetching solution * `useResolveSuiNSNames` -> Use `useCurrentClient()` directly Other removed hooks: * `useAutoConnectWallet` -> Auto-connect is enabled by default * `useAccounts` -> Use `useWalletConnection()` to access `connection.wallet.accounts` * `useWalletStore` -> Use specific hooks like `useWalletConnection()` instead ### Step 7: Replace mutation patterns [#step-7-replace-mutation-patterns] The built-in mutation hooks have been removed. Use TanStack Query's `useMutation` with `useDAppKit()` to get similar functionality. **Chain parameter replaced with network:** In the old dApp kit, you could optionally pass a `chain` parameter (for example, `sui:mainnet`) to methods like `signTransaction` and `signAndExecuteTransaction`. In the new dApp kit, use the `network` parameter instead - the chain is automatically derived from the network. It is optional and defaults to the active network, so you only need it when targeting a network other than the active one (for example, an app that operates on both mainnet and testnet). Likewise, the per-account `account` override is preserved as an optional parameter that defaults to the connected account. ```diff - const { mutateAsync: signAndExecute } = useSignAndExecuteTransaction(); - await signAndExecute({ transaction, chain: 'sui:mainnet' }); + const dAppKit = useDAppKit(); + await dAppKit.signAndExecuteTransaction({ transaction, network: 'mainnet' }); ``` **Mutation example:** ```diff - import { useSignAndExecuteTransaction } from '@mysten/dapp-kit'; + import { useMutation } from '@tanstack/react-query'; + import { useDAppKit } from '@mysten/dapp-kit-react'; import type { Transaction } from '@mysten/sui/transactions'; export function ExampleComponent({ transaction }: { transaction: Transaction }) { - const { mutateAsync: signAndExecute } = useSignAndExecuteTransaction(); + const dAppKit = useDAppKit(); + + const { mutateAsync: signAndExecute } = useMutation({ + mutationFn: (tx: Transaction) => dAppKit.signAndExecuteTransaction({ transaction: tx }), + }); const handleClick = async () => { - await signAndExecute( - { transaction }, - { - onSuccess: (result: any) => console.log(result), - onError: (error: any) => console.error(error), - }, - ); + await signAndExecute(transaction, { + onSuccess: (result) => console.log(result), + onError: (error) => console.error(error), + }); }; return ; } ``` **Alternative: Direct promise-based calls** If you don't need React Query's state management, you can call `dAppKit` methods directly: ```tsx import { useDAppKit } from '@mysten/dapp-kit-react'; import type { Transaction } from '@mysten/sui/transactions'; export function ExampleComponent({ transaction }: { transaction: Transaction }) { const dAppKit = useDAppKit(); const handleClick = async () => { try { const result = await dAppKit.signAndExecuteTransaction({ transaction }); console.log(result); } catch (error) { console.error(error); } }; return ; } ``` ### Step 8: Replace data fetching patterns [#step-8-replace-data-fetching-patterns] The built-in data fetching hooks have been removed. Use TanStack Query's `useQuery` with `useCurrentClient()` to get similar functionality: ```diff - import { useSuiClientQuery } from '@mysten/dapp-kit'; + import { useQuery } from '@tanstack/react-query'; + import { useCurrentClient } from '@mysten/dapp-kit-react'; export function ExampleComponent({ objectId }: { objectId: string }) { + const client = useCurrentClient(); + - const { data, isLoading, error } = useSuiClientQuery('getObject', { - id: objectId, - }); + const { data, isLoading, error } = useQuery({ + queryKey: ['object', objectId], + queryFn: () => client.getObject({ objectId }), + }); // ... } ``` **Alternative: Direct data fetching** If you don't need React Query's caching and state management, you can fetch data directly: ```tsx import { useCurrentClient } from '@mysten/dapp-kit-react'; import { useState, useEffect } from 'react'; export function ExampleComponent({ objectId }: { objectId: string }) { const client = useCurrentClient(); const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { client .getObject({ objectId }) .then((result) => setData(result.object ?? null)) .catch((err) => setError(err.message)) .finally(() => setIsLoading(false)); }, [client, objectId]); // ... } ``` ### Step 9: Update the remaining code [#step-9-update-the-remaining-code] The following hooks from the original dApp kit are not available anymore: * `useSuiClientQuery` → Use `useQuery` from `@tanstack/react-query` * `useSuiClientMutation` → Use `useMutation` from `@tanstack/react-query` * `useSuiClientInfiniteQuery` → Use `useInfiniteQuery` from `@tanstack/react-query` * `useResolveSuiNSNames` → Use `useCurrentClient()` with the suins extension The `reportTransactionEffects` feature is planned for deprecation in the [Wallet Standard](https://docs.sui.io/standards/wallet-standard) and so the dApp kit provides no replacement. The following have been removed: * `useReportTransactionEffects` hook * `reportTransactionEffects` callback from `useSignTransaction` * Automatic transaction effects reporting from `useSignAndExecuteTransaction` ## CSS and theming changes [#css-and-theming-changes] The new dApp kit no longer bundles a CSS file. If you were importing the old CSS file, remove the import: ```diff - import '@mysten/dapp-kit/dist/full/index.css'; ``` The new dApp kit uses web components with built-in styling that can be customized using CSS custom properties. See the [Theming documentation](/dapp-kit/theming) for details on customizing the appearance of dApp kit components. **Quick theme setup:** ```css :root { --primary: #4f46e5; --primary-foreground: #ffffff; --background: #ffffff; --foreground: #0f172a; --border: #e2e8f0; --radius: 0.5rem; } ``` ## Removing TanStack Query [#removing-tanstack-query] If you were only using `@tanstack/react-query` for dApp kit and don't need it for other parts of your application, you can now remove it: ```bash npm uninstall @tanstack/react-query ``` --- # @mysten/deepbook-v3 (/sui/migrations/sui-2.0/deepbook-v3) This package now exports a client extension that integrates with Sui clients. ```diff - import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'; - import { DeepBookClient } from '@mysten/deepbook-v3'; + import { SuiGrpcClient } from '@mysten/sui/grpc'; + import { deepbook } from '@mysten/deepbook-v3'; - const suiClient = new SuiClient({ url: getFullnodeUrl('mainnet') }); - const deepBookClient = new DeepBookClient({ - client: suiClient, - address: myAddress, - env: 'mainnet', - balanceManagers: { ... }, - }); + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.mainnet.sui.io:443', + network: 'mainnet', + }).$extend( + deepbook({ + address: myAddress, + // network is auto-detected from the client + balanceManagers: { ... }, + }), + ); - await deepBookClient.checkManagerBalance(manager, asset); + await client.deepbook.checkManagerBalance(manager, asset); ``` --- # Migrating from JSON-RPC (/sui/migrations/sui-2.0/json-rpc-migration) JSON-RPC APIs are deprecated in the Sui TypeScript SDK. For most application code, migrate from `SuiJsonRpcClient` to [`SuiGrpcClient`](/sui/clients/grpc) and call the gRPC client's top-level methods. Use [`SuiGraphQLClient`](/sui/clients/graphql) when the code needs custom indexed queries, historical object versions, or custom GraphQL selection sets. Standard transaction and event queries are available directly on both clients. Create one client for the transport you want to use. Application code should usually call top-level methods like `client.getObject()` and `client.listTransactions()`. SDKs and libraries should accept `ClientWithCoreApi` and call `client.core.()`. ## Choosing a target client [#choosing-a-target-client] | Client | Use For | | ------------------ | ---------------------------------------------------------------------------------- | | `SuiGrpcClient` | Standard reads, writes, simulations, transaction/event queries, and streams | | `SuiGraphQLClient` | The same shared methods plus custom indexed queries and historical object versions | | `SuiJsonRpcClient` | Maintaining legacy JSON-RPC code while migrating | ## Quick migration to gRPC [#quick-migration-to-grpc] Replace `SuiJsonRpcClient` with `SuiGrpcClient`: ```diff - 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', + }); ``` Full node hosts commonly expose both JSON-RPC and gRPC. When migrating, pass the full node endpoint as `baseUrl` instead of `url`, and verify the protocol and port used by your node provider. ## Use top-level methods in apps [#use-top-level-methods-in-apps] The gRPC and GraphQL clients expose top-level methods for the common client API. These methods use the same options and response shapes as `client.core`, with transport-specific additions where the transport can expose more data. This includes transaction and event methods. Use `getTransaction`, `waitForTransaction`, `listTransactions`, and `listEvents` directly instead of dropping down to a raw gRPC service or a custom GraphQL query for standard queries. ```typescript const { object } = await client.getObject({ objectId: '0x...', include: { content: true, display: true }, }); const { balance } = await client.getBalance({ owner: '0x...', }); const result = await client.signAndExecuteTransaction({ transaction, signer, include: { effects: true, balanceChanges: true }, }); ``` For SDKs and shared libraries, accept `ClientWithCoreApi` and use `client.core` so the caller can provide `SuiGrpcClient`, `SuiGraphQLClient`, or a legacy `SuiJsonRpcClient` during migration: ```typescript import type { ClientWithCoreApi } from '@mysten/sui/client'; export async function readObject(client: ClientWithCoreApi, objectId: string) { return client.core.getObject({ objectId, include: { content: true }, }); } ``` ## Method replacements [#method-replacements] Replace legacy JSON-RPC method names with the gRPC top-level method when one exists. In SDK code, use the same replacement under `client.core`. | JSON-RPC Method | App Code Replacement | | ---------------------------- | --------------------------------------------------------------------- | | `getObject` | `getObject` | | `multiGetObjects` | `getObjects` | | `getOwnedObjects` | `listOwnedObjects` for an exact `StructType` filter | | `getCoins` | `listCoins` | | `getAllBalances` | Paginate `listBalances` and map its normalized response | | `getBalance` | `getBalance` | | `getCoinMetadata` | `getCoinMetadata` | | `getDynamicFields` | `listDynamicFields` | | `getDynamicFieldObject` | `getDynamicField` or `getDynamicObjectField`, depending on field kind | | `getTransactionBlock` | `getTransaction` | | `multiGetTransactionBlocks` | Multiple `getTransaction` calls | | `executeTransactionBlock` | `executeTransaction` | | `waitForTransaction` | `waitForTransaction` | | `dryRunTransactionBlock` | `simulateTransaction` | | `devInspectTransactionBlock` | `simulateTransaction` with the sender set and `checksEnabled: false` | | `queryTransactionBlocks` | `listTransactions` | | `queryEvents` | `listEvents` | | `getNormalizedMoveFunction` | `getMoveFunction` | | `getMoveFunctionArgTypes` | No direct equivalent; `getMoveFunction` returns normalized signatures | | `resolveNameServiceAddress` | `resolveNameServiceAddress` (returns `{ address }`) | | `resolveNameServiceNames` | No direct equivalent for listing every name assigned to an address | The JSON-RPC `getDynamicFieldObject` method returned an object for both field kinds. For a regular dynamic field, it returned the `0x2::dynamic_field::Field` object. For a dynamic object field, it derived the wrapper field, extracted its child ID, and returned the referenced child object instead of the wrapper. The replacement APIs expose these operations separately. `getDynamicField` returns a normalized field entry and its BCS-encoded value. `getDynamicObjectField` derives the wrapper, extracts the child ID, and loads the referenced object. If the field kind is not known in advance, call `listDynamicFields` and check whether its `$kind` is `DynamicField` or `DynamicObject`. SDKs can call the same methods through `client.core`. ```typescript const page = await client.listDynamicFields({ parentId }); for (const field of page.dynamicFields) { if (field.$kind === 'DynamicObject') { const { object } = await client.getDynamicObjectField({ parentId, name: field.name, include: { content: true }, }); console.log(object.objectId, object.content); } else { const { dynamicField } = await client.getDynamicField({ parentId, name: field.name, }); console.log(dynamicField.value.type, dynamicField.value.bcs); } } ``` The dynamic object field wrapper remains accessible through `getDynamicField`. Wrap the name type explicitly and pass the original name BCS bytes: ```typescript const { dynamicField } = await client.getDynamicField({ parentId, name: { type: `0x2::dynamic_object_field::Wrapper<${fieldName.type}>`, bcs: fieldName.bcs, }, }); console.log(dynamicField.fieldId, dynamicField.childId); ``` `getMoveFunction` exposes normalized parameter signatures, but it does not reproduce the legacy `Pure`, `Object`, and object-access classifications from `getMoveFunctionArgTypes`. Likewise, `defaultNameServiceName` and raw gRPC `nameService.reverseLookupName` return only the configured default name; they do not replace the paginated list returned by `resolveNameServiceNames`. Keep a legacy endpoint or use an application indexer when those exact results are required. ```typescript const { address } = await client.resolveNameServiceAddress({ name: 'example.sui', }); ``` Some composed helpers are currently exposed through `client.core` rather than as top-level gRPC or GraphQL methods: ```typescript const { protocolConfig } = await client.core.getProtocolConfig(); const { systemState } = await client.core.getCurrentSystemState(); const { chainIdentifier } = await client.core.getChainIdentifier(); ``` ## Object and coin reads [#object-and-coin-reads] ### Migrating `getOwnedObjects` [#migrating-getownedobjects] ```diff - const { data } = await jsonRpcClient.getOwnedObjects({ - owner: '0xabc...', - filter: { StructType: '0x2::coin::Coin<0x2::sui::SUI>' }, - options: { showContent: true }, - }); + const { objects } = await client.listOwnedObjects({ + owner: '0xabc...', + type: '0x2::coin::Coin<0x2::sui::SUI>', + include: { content: true }, + }); ``` Only the legacy `StructType` filter maps directly to `listOwnedObjects.type`. Legacy package, module, owner, object ID, version, and boolean-composition filters do not have top-level Core equivalents. A custom GraphQL `ObjectFilter` can cover package, module, and owner-kind cases. For the remaining filters, use an indexer or paginate and filter the normalized results in application code. ### Migrating `getCoins` [#migrating-getcoins] ```diff - const coins = await jsonRpcClient.getCoins({ - owner: '0xabc...', - coinType: '0x2::sui::SUI', - }); + const coins = await client.listCoins({ + owner: '0xabc...', + coinType: '0x2::sui::SUI', + }); ``` ### Migrating `getAllBalances` [#migrating-getallbalances] Unlike `getAllBalances`, `listBalances` is paginated. It also returns normalized `balance`, `coinBalance`, and `addressBalance` fields instead of the legacy `CoinBalance` shape with `coinObjectCount`, `totalBalance`, and `lockedBalance`. Follow `cursor` while `hasNextPage` is true and map the result explicitly if existing code depends on the legacy shape. ### Migrating object include options [#migrating-object-include-options] ```diff - const object = await jsonRpcClient.getObject({ - id: objectId, - options: { - showBcs: true, - showContent: true, - showDisplay: true, - }, - }); + const { object } = await client.getObject({ + objectId, + include: { + content: true, + json: true, + display: true, + }, + }); ``` Use `include.content` for BCS-encoded Move struct bytes. It is the most stable cross-transport shape for parsing application data. ## Transaction execution and simulation [#transaction-execution-and-simulation] Legacy JSON-RPC methods accept serialized transaction blocks as base64 strings. `executeTransaction` accepts bytes, while `simulateTransaction` accepts bytes or a `Transaction`, so decode existing strings before passing them to the new client: ```typescript import { Transaction } from '@mysten/sui/transactions'; import { fromBase64 } from '@mysten/sui/utils'; ``` ### Migrating `getTransactionBlock` [#migrating-gettransactionblock] ```diff - const result = await jsonRpcClient.getTransactionBlock({ - digest, - options: { - showEffects: true, - showEvents: true, - showInput: true, - }, - }); + const result = await client.getTransaction({ + digest, + include: { + effects: true, + events: true, + transaction: true, + }, + }); ``` Both `SuiGrpcClient` and `SuiGraphQLClient` expose `getTransaction`. SDKs can make the same request through `client.core.getTransaction`. For multiple digests, call the same top-level method for each transaction: ```diff - const results = await jsonRpcClient.multiGetTransactionBlocks({ - digests, - options: { showEffects: true }, - }); + const results = await Promise.all( + digests.map((digest) => + client.getTransaction({ + digest, + include: { effects: true }, + }), + ), + ); ``` The raw gRPC `ledgerService.batchGetTransactions` method remains available when an application specifically needs the transport's batch wire API, but it is not required for ordinary client code. ### Migrating `executeTransactionBlock` [#migrating-executetransactionblock] ```diff - const result = await jsonRpcClient.executeTransactionBlock({ - transactionBlock: bytes, - signature, - options: { - showEffects: true, - showEvents: true, - }, - }); + const result = await client.executeTransaction({ + transaction: fromBase64(bytes), + signatures: [signature], + include: { + effects: true, + events: true, + }, + }); - const status = result.effects?.status.status; + const tx = result.Transaction ?? result.FailedTransaction; + const success = tx.status.success; ``` ### Migrating `signAndExecuteTransaction` [#migrating-signandexecutetransaction] ```diff - const result = await jsonRpcClient.signAndExecuteTransaction({ - transaction, - signer, - options: { showEffects: true }, - }); + const result = await client.signAndExecuteTransaction({ + transaction, + signer, + include: { effects: true }, + }); + if (result.$kind === 'FailedTransaction') { + throw new Error(result.FailedTransaction.status.error?.message ?? 'Transaction failed'); + } ``` ### Migrating `waitForTransaction` [#migrating-waitfortransaction] ```diff - const result = await jsonRpcClient.waitForTransaction({ - digest, - options: { showEffects: true }, - timeout: 60_000, - pollInterval: 2_000, - }); + const result = await client.waitForTransaction({ + digest, + include: { effects: true }, + timeout: 60_000, + pollSchedule: [0, 2_000], + }); ``` `waitForTransaction` is a top-level method on both `SuiGrpcClient` and `SuiGraphQLClient`. It can also accept the result of `executeTransaction` or `signAndExecuteTransaction` through its `result` option. ### Migrating `dryRunTransactionBlock` [#migrating-dryruntransactionblock] ```diff - const result = await jsonRpcClient.dryRunTransactionBlock({ - transactionBlock: tx, - }); + const result = await client.simulateTransaction({ + transaction: fromBase64(tx), + include: { + effects: true, + balanceChanges: true, + }, + }); ``` ### Migrating `devInspectTransactionBlock` [#migrating-devinspecttransactionblock] ```diff - const result = await jsonRpcClient.devInspectTransactionBlock({ - sender: '0xabc...', - transactionBlock: tx, - }); - const returnValues = result.results?.[0]?.returnValues; + const transaction = Transaction.fromKind(tx); + transaction.setSender('0xabc...'); + const result = await client.simulateTransaction({ + transaction, + checksEnabled: false, + include: { commandResults: true }, + }); + const returnValues = result.commandResults?.[0]?.returnValues; ``` ## Transaction and event queries [#transaction-and-event-queries] `listTransactions` and `listEvents` are first-class methods on `SuiGrpcClient`, `SuiGraphQLClient`, and the Core API. Application code should call the top-level method on its chosen client. Reusable SDK code should call the same method through `client.core`. ### Migrating `queryTransactionBlocks` [#migrating-querytransactionblocks] ```diff - const result = await jsonRpcClient.queryTransactionBlocks({ - filter: { FromAddress: '0xabc...' }, - options: { showEffects: true }, - limit: 10, - }); - const digests = result.data.map((tx) => tx.digest); + const page = await client.listTransactions({ + filter: { sender: '0xabc...' }, + include: { effects: true }, + limit: 10, + order: 'descending', + }); + const digests = page.transactions.map( + (tx) => (tx.Transaction ?? tx.FailedTransaction).digest, + ); ``` Common transaction filter mappings: | JSON-RPC Filter | gRPC/GraphQL Top-Level Filter | | ----------------------------------------------------------------- | ----------------------------------------------------- | | `FromAddress` | `sender` | | `MoveFunction` | `function` | | `ToAddress`, `FromOrToAddress`, `ChangedObject`, `AffectedObject` | Use raw gRPC ledger filters or a custom GraphQL query | The response contains normalized transaction results plus ledger-position cursors: ```typescript for (const result of page.transactions) { const transaction = result.Transaction ?? result.FailedTransaction; console.log(transaction.digest, result.$kind); } const nextPage = page.hasNextPage ? await client.listTransactions({ filter: { sender: '0xabc...' }, include: { effects: true }, before: page.endCursor, limit: 10, }) : null; ``` The legacy JSON-RPC transaction and event queries default to descending order, while `listTransactions` and `listEvents` default to ascending order. Pass `order: 'descending'` when preserving the legacy default. ### Migrating `queryEvents` [#migrating-queryevents] ```diff - const result = await jsonRpcClient.queryEvents({ - query: { MoveEventType: '0x2::coin::CoinCreated' }, - limit: 10, - order: 'descending', - }); + const result = await client.listEvents({ + filter: { eventType: '0x2::coin::CoinCreated' }, + limit: 10, + order: 'descending', + }); + for (const event of result.events) { + console.log(event.eventType, event.transactionDigest, event.json); + } ``` Common event filter mappings: | JSON-RPC Filter | gRPC/GraphQL Top-Level Filter | | ----------------- | ------------------------------------- | | `Sender` | `sender` | | `MoveModule` | `emitModule: 'package::module'` | | `MoveEventModule` | `eventType: 'package::module'` | | `MoveEventType` | `eventType: 'package::module::Event'` | The top-level query methods handle pagination and cursor normalization. For richer filters, such as combined predicates, affected addresses, affected objects, or checkpoint ranges, use the raw [`ledgerService`](/sui/clients/grpc#using-service-clients) on `SuiGrpcClient` or a custom GraphQL query. Use `after: result.endCursor` to continue an ascending query and `before: result.endCursor` to continue a descending query. `startCursor` identifies the first item in a page and can be used with `after` to poll for newer transactions or events. ## Native gRPC replacements [#native-grpc-replacements] Some JSON-RPC methods map to gRPC service clients rather than top-level methods: | JSON-RPC Method | gRPC Replacement | | ----------------------------------- | --------------------------------------------------------------- | | `getCheckpoint` | `ledgerService.getCheckpoint` | | `getCheckpoints` | `ledgerService.listCheckpoints` | | `getLatestCheckpointSequenceNumber` | `ledgerService.getServiceInfo` and read `checkpointHeight` | | `getCurrentEpoch` | `ledgerService.getEpoch` with no `epoch` argument | | `getCommitteeInfo` | `ledgerService.getEpoch` with `committee` in the read mask | | `getLatestSuiSystemState` | `client.core.getCurrentSystemState` or `ledgerService.getEpoch` | | `getProtocolConfig` | `client.core.getProtocolConfig` or `ledgerService.getEpoch` | | `getTotalSupply` | `stateService.getCoinInfo` and read `treasury.totalSupply` | | `getNormalizedMoveModule` | `movePackageService.getPackage` | | `getNormalizedMoveModulesByPackage` | `movePackageService.getPackage` | | `getNormalizedMoveStruct` | `movePackageService.getDatatype` | ```typescript import { SuiGrpcClient } from '@mysten/sui/grpc'; const client = new SuiGrpcClient({ baseUrl: 'https://fullnode.mainnet.sui.io:443', network: 'mainnet', }); const { response: info } = await client.ledgerService.getServiceInfo({}); const latestCheckpoint = info.checkpointHeight; if (latestCheckpoint == null) { throw new Error('The server did not return a checkpoint height'); } const { response } = await client.ledgerService.getCheckpoint({ checkpointId: { oneofKind: 'sequenceNumber', sequenceNumber: latestCheckpoint }, readMask: { paths: ['sequence_number', 'digest', 'summary.timestamp'] }, }); console.log(response.checkpoint?.sequenceNumber, response.checkpoint?.summary?.timestamp); ``` ## Subscriptions [#subscriptions] Replace deprecated JSON-RPC websocket subscriptions with the gRPC `subscriptionService`: | JSON-RPC Method | gRPC Service Replacement | | ---------------------- | ------------------------------------------- | | `subscribeTransaction` | `subscriptionService.subscribeTransactions` | | `subscribeEvent` | `subscriptionService.subscribeEvents` | ```typescript 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 begin at the current tip of the chain and do not resume automatically. For gap recovery, retain the last `frame.watermark.cursor`, open the new subscription to establish its first-frame position, and replay with the paired raw `client.ledgerService.listEvents()` call using the same protobuf filter and `options.after` cursor. Repeat the raw list call as the index advances until it reaches the new subscription's start position. Do not pass the subscription filter or cursor to top-level `client.listEvents()`: its Core filter and base64 cursor are different types. ## When to use GraphQL [#when-to-use-graphql] Use `SuiGraphQLClient` when the replacement needs a custom indexed query, a historical object version, or a custom selection set. Standard transaction and event history does not require a custom GraphQL query; call `graphqlClient.listTransactions()` or `graphqlClient.listEvents()` directly. | JSON-RPC Method | Alternative | | -------------------- | -------------------------------------------------------------- | | `getEpochs` | GraphQL `epochs` query | | `tryGetPastObject` | GraphQL `object(address:, version:)` query | | `getStakes` | No current gRPC/Core/GraphQL equivalent; use a staking indexer | | `getStakesByIds` | No current gRPC/Core/GraphQL equivalent; use a staking indexer | | `getNetworkMetrics` | Use an indexer or analytics-specific GraphQL schema | | `getAddressMetrics` | Use an indexer or analytics-specific GraphQL schema | | `getMoveCallMetrics` | Use an indexer or analytics-specific GraphQL schema | ```typescript import { SuiGraphQLClient } from '@mysten/sui/graphql'; import { graphql } from '@mysten/sui/graphql/schema'; const graphqlClient = new SuiGraphQLClient({ url: 'https://sui-mainnet.mystenlabs.com/graphql', network: 'mainnet', }); const historicalObjectQuery = graphql(` query GetObjectAtVersion($id: SuiAddress!, $version: UInt53!) { object(address: $id, version: $version) { address version digest asMoveObject { contents { type { repr } bcs } } } } `); const result = await graphqlClient.query({ query: historicalObjectQuery, variables: { id: '0x123...', version: 42, }, }); ``` ## Validator APY [#validator-apy] There is no direct SDK replacement for `getValidatorsApy`. There is no canonical definition of validator APY, so compute the metric from validator staking-pool exchange rates or use an application-specific indexer. Two reference implementations: * The `jsonrpc-alt` implementation in [`sui-indexer-alt-jsonrpc`](https://github.com/MystenLabs/sui/blob/31537d4d9235b9f61dc07a3a71b05ed61a2bda7b/crates/sui-indexer-alt-jsonrpc/src/api/governance.rs#L422-L440) * [A GraphQL approach](https://github.com/MystenLabs/sui/issues/23832#issuecomment-4437791087) Treat whichever formula you adopt as *a* definition of validator APY, not *the* definition. ## Response format differences [#response-format-differences] gRPC and GraphQL top-level methods return the Core API response format, which differs from legacy JSON-RPC response shapes. ```diff // Transaction result access - const status = result.effects?.status?.status; + const tx = result.Transaction ?? result.FailedTransaction; + const success = tx.status.success; // Include options - { showEffects: true, showEvents: true } + { effects: true, events: true } ``` See the [`@mysten/sui` migration guide](/sui/migrations/sui-2.0/sui#transaction-executors-now-accept-any-client) for transaction executor response changes. ## Client extensions [#client-extensions] Client extensions work with `SuiGrpcClient` and any client that implements `ClientWithCoreApi`: ```typescript import { deepbook } from '@mysten/deepbook-v3'; import { suins } from '@mysten/suins'; import { SuiGrpcClient } from '@mysten/sui/grpc'; const client = new SuiGrpcClient({ baseUrl: 'https://fullnode.mainnet.sui.io:443', network: 'mainnet', }).$extend(deepbook({ address: myAddress }), suins()); await client.deepbook.checkManagerBalance(manager, asset); await client.suins.getNameRecord('example.sui'); ``` ## See also [#see-also] * [SuiGrpcClient](/sui/clients/grpc) * [SuiGraphQLClient](/sui/clients/graphql) * [Querying data](/sui/clients/querying) * [Core API](/sui/clients/core) * [Building SDKs](/sui/sdk-building) --- # @mysten/kiosk (/sui/migrations/sui-2.0/kiosk) This package now exports a client extension that integrates with Sui clients. The Kiosk SDK accepts `SuiGrpcClient`, `SuiGraphQLClient`, and other clients that implement `ClientWithCoreApi`. Use `SuiGrpcClient` for new Kiosk code. The gRPC object API returns Display v2 metadata; use GraphQL or JSON-RPC for object types that still rely on legacy Display metadata. ```diff - import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'; - import { KioskClient, Network } from '@mysten/kiosk'; + import { SuiGrpcClient } from '@mysten/sui/grpc'; + import { kiosk } from '@mysten/kiosk'; - const suiClient = new SuiClient({ url: getFullnodeUrl('mainnet') }); - const kioskClient = new KioskClient({ - client: suiClient, - network: Network.MAINNET, - }); + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.mainnet.sui.io:443', + network: 'mainnet', + }).$extend(kiosk()); - const ownedKiosks = await kioskClient.getOwnedKiosks({ address: myAddress }); + const ownedKiosks = await client.kiosk.getOwnedKiosks({ address: myAddress }); ``` ## Removed: `transactionBlock` parameter [#removed-transactionblock-parameter] The deprecated `transactionBlock` parameter has been removed from `KioskTransaction`, `TransferPolicyTransaction`, and rule resolving functions. Use `transaction` instead: ```diff const kioskTx = new KioskTransaction({ - transactionBlock: tx, + transaction: tx, kioskClient, cap, }); const tpTx = new TransferPolicyTransaction({ - transactionBlock: tx, + transaction: tx, kioskClient, cap, }); ``` ## Removed: low-level helper functions [#removed-low-level-helper-functions] The low-level helper functions have been removed in favor of the `KioskTransaction` and `TransferPolicyTransaction` builder classes. ### Kiosk functions [#kiosk-functions] | Removed Function | Use Instead | | ------------------- | ------------------------ | | `createKiosk` | `kioskTx.create()` | | `shareKiosk` | `kioskTx.share()` | | `place` | `kioskTx.place()` | | `lock` | `kioskTx.lock()` | | `take` | `kioskTx.take()` | | `list` | `kioskTx.list()` | | `delist` | `kioskTx.delist()` | | `placeAndList` | `kioskTx.placeAndList()` | | `purchase` | `kioskTx.purchase()` | | `withdrawFromKiosk` | `kioskTx.withdraw()` | | `borrowValue` | `kioskTx.borrow()` | | `returnValue` | `kioskTx.return()` | ### Transfer policy functions [#transfer-policy-functions] | Removed Function | Use Instead | | ------------------------------------ | ------------------------------------------------------- | | `createTransferPolicyWithoutSharing` | `tpTx.create()` | | `shareTransferPolicy` | `tpTx.shareAndTransferCap()` | | `confirmRequest` | Handled automatically by `kioskTx.purchaseAndResolve()` | | `removeTransferPolicyRule` | `tpTx.removeRule()` | ### Personal Kiosk functions [#personal-kiosk-functions] | Removed Function | Use Instead | | ----------------------- | --------------------------------------------- | | `convertToPersonalTx` | `kioskTx.convertToPersonal()` | | `transferPersonalCapTx` | Handled automatically by `kioskTx.finalize()` | ### Rule attachment functions [#rule-attachment-functions] | Removed Function | Use Instead | | --------------------------- | ----------------------------- | | `attachKioskLockRuleTx` | `tpTx.addLockRule()` | | `attachRoyaltyRuleTx` | `tpTx.addRoyaltyRule()` | | `attachPersonalKioskRuleTx` | `tpTx.addPersonalKioskRule()` | | `attachFloorPriceRuleTx` | `tpTx.addFloorPriceRule()` | ## Migration example [#migration-example] ```diff - import { createKiosk, shareKiosk, placeAndList } from '@mysten/kiosk'; + import { kiosk, KioskTransaction } from '@mysten/kiosk'; + import { SuiGrpcClient } from '@mysten/sui/grpc'; - const [kiosk, cap] = createKiosk(tx); - shareKiosk(tx, kiosk); - placeAndList(tx, itemType, kiosk, cap, item, price); + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.mainnet.sui.io:443', + network: 'mainnet', + }).$extend(kiosk()); + + const kioskTx = new KioskTransaction({ transaction: tx, kioskClient: client.kiosk }); + kioskTx + .create() + .placeAndList({ itemType, item, price }) + .shareAndTransferCap(address) + .finalize(); ``` --- # SDK Maintainers (/sui/migrations/sui-2.0/sdk-maintainers) # Upgrading SDKs to @mysten/sui\@2.0.0 [#upgrading-sdks-to-mystensui200] This guide covers the key breaking changes for SDK maintainers building on top of `@mysten/sui`. For comprehensive SDK development patterns, see the [Building SDKs guide](/sui/sdk-building). ## Use `ClientWithCoreApi` [#use-clientwithcoreapi] Accept `ClientWithCoreApi` instead of `SuiClient` so applications can pass a `SuiGrpcClient`, `SuiGraphQLClient`, or a legacy `SuiJsonRpcClient` during migration: ```diff - import { SuiClient } from '@mysten/sui/client'; + import type { ClientWithCoreApi } from '@mysten/sui/client'; export class MySDKClient { - client: SuiClient; + client: ClientWithCoreApi; } ``` ## Access data through `client.core` methods [#access-data-through-clientcore-methods] SDKs should access shared client methods through `client.core`. Application code can use the same methods at the top level of its concrete client, but `client.core` is the stable contract for libraries that should work across transports: ```diff - const result = await this.client.getObject({ objectId }); + const result = await this.client.core.getObject({ objectId }); - const result = await this.client.getOwnedObjects({ owner }); + const result = await this.client.core.listOwnedObjects({ owner }); ``` | v1.x Method | v2.0 Method | | -------------------------------- | ------------------------------------------------------------------------ | | `client.getObject()` | `client.core.getObject()` | | `client.getOwnedObjects()` | `client.core.listOwnedObjects()` | | `client.getDynamicFieldObject()` | `client.core.getDynamicField()` or `client.core.getDynamicObjectField()` | | `client.getDynamicFields()` | `client.core.listDynamicFields()` | | `client.multiGetObjects()` | `client.core.getObjects()` | Use `getDynamicField()` for regular dynamic fields and when you need the field entry or BCS-encoded value. Use `getDynamicObjectField()` only for dynamic object fields when you want the referenced child object returned directly. See the [Core API documentation](/sui/clients/core) for all available methods. ## Use peer dependencies [#use-peer-dependencies] Declare `@mysten/*` packages as peer dependencies: ```json { "peerDependencies": { "@mysten/sui": "^2.0.0" }, "devDependencies": { "@mysten/sui": "^2.0.0" } } ``` ## Client extensions [#client-extensions] v2.0 introduces client extensions that let users add your SDK to any Sui client: ```typescript import type { ClientWithCoreApi } from '@mysten/sui/client'; export function mySDK() { return { name: 'mySDK', register: (client: ClientWithCoreApi) => { return new MySDKClient({ client }); }, }; } // Users can then extend any client const client = new SuiGrpcClient({ ... }).$extend(mySDK()); await client.mySDK.doSomething(); ``` See the [Building SDKs guide](/sui/sdk-building#client-extensions) for the complete extension pattern. ## Code generation [#code-generation] Use [`@mysten/codegen`](/codegen) to generate type-safe TypeScript bindings from your Move packages. See the [codegen documentation](/codegen) for setup instructions. For complete SDK development patterns including client extensions, transaction thunks, and best practices, see the [Building SDKs guide](/sui/sdk-building). --- # @mysten/seal (/sui/migrations/sui-2.0/seal) The deprecated `SealClient.asClientExtension()` static method has been removed. Use the `seal()` registration function instead: ```diff - import { SealClient } from '@mysten/seal'; + import { seal } from '@mysten/seal'; - const client = suiClient.$extend(SealClient.asClientExtension()); + const client = suiClient.$extend(seal()); ``` --- # @mysten/sui (/sui/migrations/sui-2.0/sui) ## Removal of `SuiClient` exports [#removal-of-suiclient-exports] The old `SuiClient` export has been removed from `@mysten/sui/client`. For application code, migrate to [`SuiGrpcClient`](/sui/clients/grpc) and use top-level methods such as `client.getObject()`, `client.listCoins()`, and `client.signAndExecuteTransaction()`. Legacy JSON-RPC functionality moved to `@mysten/sui/jsonRpc`, but JSON-RPC APIs are deprecated in the Sui TypeScript SDK. Use the JSON-RPC exports only when maintaining code that still needs the old JSON-RPC method names or response shapes during migration. **Removed exports:** * `SuiClient` (use `SuiGrpcClient`; legacy JSON-RPC code can use `SuiJsonRpcClient`) * `SuiClientOptions` (use `SuiGrpcClientOptions`; legacy JSON-RPC code can use `SuiJsonRpcClientOptions`) * `isSuiClient` (use `isSuiGrpcClient`, `isSuiGraphQLClient`, or legacy `isSuiJsonRpcClient`) * `SuiTransport` (legacy JSON-RPC code can use `JsonRpcTransport`) * `SuiTransportRequestOptions` (use `JsonRpcTransportRequestOptions` instead) * `SuiTransportSubscribeOptions` (removed; use the gRPC subscription service for streaming APIs) * `SuiHTTPTransportOptions` (use `JsonRpcHTTPTransportOptions` instead) * `SuiHTTPTransport` (use `JsonRpcHTTPTransport` instead) * `getFullnodeUrl` (pass the full node URL to `SuiGrpcClient.baseUrl`; legacy JSON-RPC code can use `getJsonRpcFullnodeUrl`) * All JSON-RPC types (now exported from `@mysten/sui/jsonRpc`) **Migration:** ```diff - import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'; + import { SuiGrpcClient } from '@mysten/sui/grpc'; - const client = new SuiClient({ - url: getFullnodeUrl('devnet'), + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.devnet.sui.io:443', network: 'devnet', }); ``` ## Network parameter required [#network-parameter-required] When creating a new `SuiGrpcClient`, `SuiGraphQLClient`, or legacy `SuiJsonRpcClient`, provide a `network` parameter: ```ts const grpcClient = new SuiGrpcClient({ baseUrl: 'https://...', network: 'mainnet', // Required }); const graphqlClient = new SuiGraphQLClient({ url: 'https://...', network: 'mainnet', // Required }); const jsonRpcClient = new SuiJsonRpcClient({ url: 'https://...', network: 'mainnet', // Required }); ``` ## BCS schema changes [#bcs-schema-changes] Several BCS schemas in `@mysten/sui/bcs` have been updated to align exactly with the Rust implementation. These changes affect serialization and deserialization of transaction effects and objects. ### `ExecutionStatus` changes [#executionstatus-changes] **BCS Schema** (when parsing raw effects): The variant was renamed from `Failed` to `Failure`: ```diff - effects.status.Failed.error + effects.status.Failure.error ``` **Core API and top-level client methods** (gRPC and GraphQL responses): Use a simplified structure with a `success` boolean: ```typescript // Top-level gRPC and GraphQL methods return this structure. const result = await client.getTransaction({ digest, include: { effects: true } }); const tx = result.Transaction ?? result.FailedTransaction; if (tx.effects.status.success) { // Transaction succeeded } else { const error = tx.effects.status.error; } ``` ### Object BCS schema changes [#object-bcs-schema-changes] Several changes to object BCS schemas: ```diff // Renamed Owner enum variant const owner = { - ConsensusV2: { owner: addr, startVersion: 1 } + ConsensusAddressOwner: { startVersion: 1, owner: addr } }; // Renamed Data enum variant const data = { - MoveObject: { ... } + Move: { ... } }; // Renamed exported schema - import { ObjectBcs } from '@mysten/sui/bcs'; + import { bcs } from '@mysten/sui/bcs'; - const bytes = ObjectBcs.serialize(obj); + const bytes = bcs.Object.serialize(obj); ``` **This affects serialization.** Any existing serialized data with `ConsensusV2` will need to be re-serialized with the new `ConsensusAddressOwner` variant. ### `UnchangedSharedKind` to `UnchangedConsensusKind` [#unchangedsharedkind-to-unchangedconsensuskind] Transaction effects field renamed: ```diff // Field name change - effects.unchangedSharedObjects + effects.unchangedConsensusObjects ``` **Removed variants:** `MutateDeleted`, `ReadDeleted` **New variants:** `MutateConsensusStreamEnded`, `ReadConsensusStreamEnded`, `Cancelled`, `PerEpochConfig` ## Experimental client API stabilization [#experimental-client-api-stabilization] The experimental client API has been stabilized and moved from `@mysten/sui/experimental` to `@mysten/sui/client`. All `Experimental_` prefixes have been removed. **Breaking changes:** * The `@mysten/sui/experimental` module has been removed * All `Experimental_` prefixed types and classes have been renamed * Client types namespace changed from `Experimental_SuiClientTypes` to `SuiClientTypes` **Migration:** ```diff - import { - Experimental_BaseClient, - Experimental_CoreClient, - type Experimental_SuiClientTypes, - type Experimental_CoreClientOptions, - } from '@mysten/sui/experimental'; + import { + BaseClient, + CoreClient, + type SuiClientTypes, + type CoreClientOptions, + } from '@mysten/sui/client'; // Update class extensions - class MyClient extends Experimental_CoreClient { + class MyClient extends CoreClient { async getObjects( - options: Experimental_SuiClientTypes.GetObjectsOptions, - ): Promise { + options: SuiClientTypes.GetObjectsOptions, + ): Promise { // ... } } ``` **Common renames:** | Old Name | New Name | | -------------------------------- | ------------------- | | `Experimental_BaseClient` | `BaseClient` | | `Experimental_CoreClient` | `CoreClient` | | `Experimental_SuiClientTypes` | `SuiClientTypes` | | `Experimental_CoreClientOptions` | `CoreClientOptions` | ## Commands renamed to `TransactionCommands` [#commands-renamed-to-transactioncommands] The `Commands` type exported from `@mysten/sui/transactions` has been renamed to `TransactionCommands` because `Commands` is a reserved keyword in React Native. ```diff - import { Commands } from '@mysten/sui/transactions'; + import { TransactionCommands } from '@mysten/sui/transactions'; - const coin = tx.add(Commands.SplitCoins(tx.gas, [tx.pure.u64(100)])); + const coin = tx.add(TransactionCommands.SplitCoins(tx.gas, [tx.pure.u64(100)])); - tx.add(Commands.TransferObjects([coin], recipient)); + tx.add(TransactionCommands.TransferObjects([coin], recipient)); ``` ## GraphQL schema consolidation [#graphql-schema-consolidation] The SDK now exports a single unified GraphQL schema instead of multiple versioned schemas. **Removed exports:** * `@mysten/sui/graphql/schemas/2024.1` * `@mysten/sui/graphql/schemas/2024.4` * `@mysten/sui/graphql/schemas/latest` **Migration:** ```diff - import { graphql } from '@mysten/sui/graphql/schemas/latest'; - import { graphql } from '@mysten/sui/graphql/schemas/2024.4'; - import { graphql } from '@mysten/sui/graphql/schemas/2024.1'; + import { graphql } from '@mysten/sui/graphql/schema'; ``` ## Named packages plugin removed [#named-packages-plugin-removed] The `namedPackagesPlugin` and global plugin registry APIs have been removed. MVR (Move Registry) resolution is now built directly into the core client. **Removed:** * `namedPackagesPlugin` function * `NamedPackagesPluginOptions` type (from `@mysten/sui/transactions`) * `Transaction.registerGlobalSerializationPlugin()` static method * `Transaction.unregisterGlobalSerializationPlugin()` static method * `Transaction.registerGlobalBuildPlugin()` static method * `Transaction.unregisterGlobalBuildPlugin()` static method **How it works now:** MVR name resolution happens automatically during transaction building. The SDK detects `.move` names like `@org/package::module::Type` and resolves them using the client's MVR resolver. **Migration:** ```diff - import { Transaction, namedPackagesPlugin } from '@mysten/sui/transactions'; - - Transaction.registerGlobalSerializationPlugin( - 'namedPackages', - namedPackagesPlugin({ - url: 'https://mainnet.mvr.mystenlabs.com', - overrides: myOverrides, - }) - ); + import { SuiGrpcClient } from '@mysten/sui/grpc'; + + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.mainnet.sui.io:443', + network: 'mainnet', + mvr: { + overrides: myOverrides, + }, + }); ``` ## Transaction executors now accept any client [#transaction-executors-now-accept-any-client] The transaction executor classes now accept any client implementing `ClientWithCoreApi` instead of requiring `SuiJsonRpcClient` specifically. **Affected classes:** * `CachingTransactionExecutor` * `SerialTransactionExecutor` * `ParallelTransactionExecutor` **Breaking changes:** * Constructor `client` parameter type changed from `SuiJsonRpcClient` to `ClientWithCoreApi` * Return type of `executeTransaction()` changed from the legacy `{ digest, effects, data }` wrapper to a Core API `TransactionResult` discriminated union returned directly * The second parameter changed from JSON-RPC options to core API include options **Migration:** ```diff import { SerialTransactionExecutor } from '@mysten/sui/transactions'; - import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc'; + import { SuiGrpcClient } from '@mysten/sui/grpc'; + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.devnet.sui.io:443', + network: 'devnet', + }); const executor = new SerialTransactionExecutor({ - client: jsonRpcClient, + client, // Any ClientWithCoreApi-compatible client signer, }); const result = await executor.executeTransaction(tx); // Accessing the transaction result now returned directly - console.log(result.data.effects?.status.status); + const transaction = result.Transaction ?? result.FailedTransaction; + console.log(transaction.effects.status.success); ``` Include options have also changed: ```diff - const result = await executor.executeTransaction(tx, { - showEffects: true, - showEvents: true, - }); + const result = await executor.executeTransaction(tx, { + effects: true, + events: true, + }); ``` ## zkLogin changes [#zklogin-changes] ### `legacyAddress` parameter required [#legacyaddress-parameter-required] The `legacyAddress` parameter is now **required** for all zkLogin address computation functions. **Migration (to preserve existing behavior):** ```diff // computeZkLoginAddressFromSeed (previous default: true) - computeZkLoginAddressFromSeed(seed, iss) + computeZkLoginAddressFromSeed(seed, iss, true) // jwtToAddress (previous default: false) - jwtToAddress(jwt, userSalt) + jwtToAddress(jwt, userSalt, false) // computeZkLoginAddress (previous default: false) - computeZkLoginAddress({ claimName, claimValue, iss, aud, userSalt }) + computeZkLoginAddress({ claimName, claimValue, iss, aud, userSalt, legacyAddress: false }) // toZkLoginPublicIdentifier (no previous default) - toZkLoginPublicIdentifier(addressSeed, iss) + toZkLoginPublicIdentifier(addressSeed, iss, { legacyAddress: false }) ``` ## Default transaction expiration [#default-transaction-expiration] Transactions now default the expiration to the current epoch + 1 using `ValidDuring` when built with a client. This provides replay protection for all transactions without requiring explicit configuration. **To preserve the old behavior** (no expiration), explicitly set the expiration to `None`: ```typescript const tx = new Transaction(); tx.setExpiration({ None: true }); ``` --- # @mysten/suins (/sui/migrations/sui-2.0/suins) This package now exports a client extension that integrates with Sui clients. ```diff - import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'; - import { SuinsClient } from '@mysten/suins'; + import { SuiGrpcClient } from '@mysten/sui/grpc'; + import { suins } from '@mysten/suins'; - const suiClient = new SuiClient({ url: getFullnodeUrl('mainnet') }); - const suinsClient = new SuinsClient({ - client: suiClient, - network: 'mainnet', - }); + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.mainnet.sui.io:443', + network: 'mainnet', + }).$extend(suins()); - const nameRecord = await suinsClient.getNameRecord('example.sui'); + const nameRecord = await client.suins.getNameRecord('example.sui'); ``` ## Custom package IDs [#custom-package-ids] For custom deployments or networks other than Mainnet or Testnet, you can provide custom package info: ```ts import { SuiGrpcClient } from '@mysten/sui/grpc'; import { suins, type PackageInfo } from '@mysten/suins'; const customPackageInfo: PackageInfo = { packageId: '0x...', packageIdV1: '0x...', // ... other required fields }; const client = new SuiGrpcClient({ baseUrl: 'http://localhost:9000', network: 'localnet', }).$extend(suins({ packageInfo: customPackageInfo })); ``` --- # Wallet Builders (/sui/migrations/sui-2.0/wallet-builders) This guide covers the breaking changes for wallet builders implementing the `@mysten/wallet-standard` interface. ## Key changes [#key-changes] ### Removal of `sui:reportTransactionEffects` [#removal-of-suireporttransactioneffects] The `sui:reportTransactionEffects` feature has been removed entirely. If your wallet implements this feature, remove it. ### New core API response format [#new-core-api-response-format] The most significant change is how you obtain BCS-encoded effects for the `signAndExecuteTransaction` response. The new core API returns effects in a different structure. ## Migrating `signAndExecuteTransaction` [#migrating-signandexecutetransaction] The wallet standard output format hasn't changed. What's different is how you obtain the BCS effects when using the new Sui client APIs. ```diff #signAndExecuteTransaction: SuiSignAndExecuteTransactionMethod = async ({ transaction, signal, }) => { - const { bytes, signature } = await Transaction.from( - await transaction.toJSON(), - ).sign({ client: suiClient, signer: keypair }); - - const { rawEffects, digest } = await suiClient.executeTransactionBlock({ - signature, - transactionBlock: bytes, - options: { showRawEffects: true }, - }); + const parsedTransaction = Transaction.from(await transaction.toJSON()); + const bytes = await parsedTransaction.build({ client }); + + const result = await this.#keypair.signAndExecuteTransaction({ + transaction: parsedTransaction, + client, + }); + + const tx = result.Transaction ?? result.FailedTransaction; return { - bytes, - signature, - digest, - effects: toBase64(new Uint8Array(rawEffects!)), + bytes: toBase64(bytes), + signature: tx.signatures[0], + digest: tx.digest, + effects: toBase64(tx.effects.bcs!), }; }; ``` Key changes: * Use `signer.signAndExecuteTransaction()` instead of `suiClient.executeTransactionBlock()` * Response is a union type - unwrap with `result.Transaction ?? result.FailedTransaction` * BCS effects are in `tx.effects.bcs` (Uint8Array) instead of `rawEffects` (number array) --- # @mysten/walrus (/sui/migrations/sui-2.0/walrus) ## Breaking changes [#breaking-changes] * **Client required**: `WalrusClient` can no longer be created with just an RPC URL. You must pass a Sui client. * **Network from client**: The `network` parameter has been removed from `walrus()`. The network is now inferred from the client. * **Removed deprecated method**: `WalrusClient.experimental_asClientExtension()` has been removed. Use the `walrus()` function instead. ## Updated usage [#updated-usage] If you were creating `WalrusClient` directly: ```diff - import { WalrusClient } from '@mysten/walrus'; + import { SuiGrpcClient } from '@mysten/sui/grpc'; + import { walrus } from '@mysten/walrus'; - const walrusClient = new WalrusClient({ - suiRpcUrl: 'https://fullnode.testnet.sui.io:443', - network: 'testnet', - }); + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.testnet.sui.io:443', + network: 'testnet', + }).$extend(walrus()); - await walrusClient.getBlob(blobId); + await client.walrus.getBlob({ blobId }); ``` If you were passing `network` to `walrus()`, remove it: ```diff - client.$extend(walrus({ network: 'testnet' })); + client.$extend(walrus()); ``` --- # @mysten/zksend (/sui/migrations/sui-2.0/zksend) This package now exports a client extension that integrates with Sui clients through the Core API. Use `SuiGrpcClient` for most applications. ## Breaking changes [#breaking-changes] * **Client extension**: The zkSend SDK is now a client extension (`client.$extend(zksend())`) * **Non-contract links removed**: Only contract-based links are now supported. The `contract` option no longer accepts `null` * **`isContractLink` removed**: The `isContractLink` option has been removed from `ZkSendLink` * **`calculateGas` removed**: The `calculateGas` option has been removed from `CreateZkSendLinkOptions` * **Data fetching helpers removed**: `getAssetsFromTransaction`, `isOwner`, and `ownedAfterChange` are no longer exported ## Migration [#migration] Update your code to use the client extension: ```diff - import { ZkSendLinkBuilder, ZkSendLink } from '@mysten/zksend'; + import { zksend } from '@mysten/zksend'; + import { SuiGrpcClient } from '@mysten/sui/grpc'; + const client = new SuiGrpcClient({ + baseUrl: 'https://fullnode.testnet.sui.io:443', + network: 'testnet', + }).$extend(zksend()); ``` ### Creating a link builder [#creating-a-link-builder] ```diff - const builder = new ZkSendLinkBuilder({ - client, - sender: address, - network: 'testnet', - }); + const link = client.zksend.linkBuilder({ + sender: address, + }); ``` ### Loading a link [#loading-a-link] ```diff - const link = new ZkSendLink({ - client, - keypair, - network: 'testnet', - }); + const link = await client.zksend.loadLink({ + address: linkAddress, + // or: keypair: linkKeypair, + }); ``` ### Loading from URL [#loading-from-url] ```diff - const link = await ZkSendLink.fromUrl(url, { - client, - network: 'testnet', - }); + const link = await client.zksend.loadLinkFromUrl(url); ``` ## Complete example [#complete-example] ```ts import { zksend } from '@mysten/zksend'; import { SuiGrpcClient } from '@mysten/sui/grpc'; // Create client with zkSend extension const client = new SuiGrpcClient({ baseUrl: 'https://fullnode.testnet.sui.io:443', network: 'testnet', }).$extend(zksend()); // Create a new link const linkBuilder = client.zksend.linkBuilder({ sender: myAddress, }); // Add assets to the link linkBuilder.addClaimableMist(1_000_000_000n); // 1 SUI // Create the transaction and get the claim URL const tx = await linkBuilder.createSendTransaction(); const linkUrl = linkBuilder.getLink(); // Later, load an existing link const existingLink = await client.zksend.loadLinkFromUrl(linkUrl); const assets = existingLink.assets; ```