> For the complete documentation index, see [llms.txt](https://suigar.gitbook.io/suigar-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://suigar.gitbook.io/suigar-docs/migration-run-order/sui-grpc-migration.md).

# Sui gRPC migration

> **Package versions**: `@mysten/sui@^2.4.0`, `@mysten/dapp-kit-react@^1.0.2`, `@mysten/dapp-kit-core@^1.0.4`\
> **Scope**: `@mysten/sui` 1.x -> 2.x (assumes `@mysten/sui.js` migration already completed) **Runtime requirement**: `@mysten/sui@2.4.0` requires Node `>=22`

***

## 0. Validation Baseline

Use your target project's installed `node_modules` as the local source of truth when validating generated migration code.

Validation precedence for this guide:

1. Installed SDK types/source (`node_modules/@mysten/*`) for exact API shape.
2. Official docs for migration direction and architecture guidance.
3. If docs and installed SDK conflict, follow installed SDK and annotate the mismatch.

* Installed baseline example: `@mysten/sui@2.4.0`, `@mysten/dapp-kit-react@1.0.2`, `@mysten/dapp-kit-core@1.0.4`
* gRPC client method surface: `node_modules/@mysten/sui/dist/grpc/client.d.mts`
* Execute/simulate option fields: `node_modules/@mysten/sui/dist/client/types.d.mts`
* Legacy JSON-RPC-only methods: `node_modules/@mysten/sui/dist/jsonRpc/client.d.mts` (verification-only, not migration target)
* React export surface: `node_modules/@mysten/dapp-kit-react/dist/index.mjs`

```bash
# Verify runtime baseline first (must be >= 22 for official support)
node -v

# Verify SuiGrpcClient prototype methods
node -e "import { SuiGrpcClient } from '@mysten/sui/grpc'; console.log(Object.getOwnPropertyNames(SuiGrpcClient.prototype).filter(k => k !== 'constructor').sort().join('\n'));"

# Verify React package exports using static parse
rg '^export' node_modules/@mysten/dapp-kit-react/dist/index.mjs
```

> `@mysten/dapp-kit-react` may fail direct Node runtime import in non-browser environments due to `window` usage. Use static export checks for CLI-side validation.

***

## 1. Background

This guide covers migration from `@mysten/sui` 1.x to 2.x. `@mysten/sui.js` legacy naming (for example `executeTransactionBlock`) is out of scope.

* Transaction execution/simulation path -> gRPC (`@mysten/sui/grpc`)
* React wallet integration -> migrate to `@mysten/dapp-kit-react`
* Event/complex queries -> separate into GraphQL or an indexer
* Migration policy: do not introduce `SuiJsonRpcClient` or imports from `@mysten/sui/jsonRpc`

Reference: <https://sdk.mystenlabs.com/sui/clients/grpc>

***

## 2. Pre-migration Scan

Run the following to identify files that need migration.

All commands scan from repository root `.` with explicit include/exclude globs (monorepo-safe: works for `src/`, `app/`, `server/`, `lib/`, etc. without modification).

```bash
# Legacy dapp-kit traces
# @mysten/dapp-kit['"] - matches both quote styles, avoids false-positives on dapp-kit-react
rg "@mysten/dapp-kit['\"]" . -g "**/*.{ts,tsx}" -g "!**/node_modules/**" -g "!.git/**"

# Backend migration traces (1.x -> 2.x)
rg "devInspectTransactionBlock|dryRunTransactionBlock|queryEvents|queryTransactionBlocks|multiGetTransactionBlocks|getOwnedObjects|multiGetObjects|getCoins" . -g "**/*.{ts,tsx,js,jsx,mjs,cjs}" -g "!**/node_modules/**" -g "!.git/**"

# Disallow in migrated output
rg "SuiJsonRpcClient|@mysten/sui/jsonRpc" . -g "**/*.{ts,tsx,js,jsx,mjs,cjs}" -g "!**/node_modules/**" -g "!.git/**"

# Legacy frontend hook traces
rg "useSignAndExecuteTransaction|useSuiClient" . -g "**/*.{ts,tsx}" -g "!**/node_modules/**" -g "!.git/**"
```

***

## 3. Package Update

```bash
pnpm add @mysten/sui@^2.4.0 @mysten/dapp-kit-react @mysten/dapp-kit-core
pnpm remove @mysten/dapp-kit

# React Query cleanup (usage-gated):
# 1) detect direct app usage first
rg "from ['\"]@tanstack/react-query['\"]|require\\(['\"]@tanstack/react-query['\"]\\)" . -g "*.{ts,tsx,js,jsx,mjs,cjs}" -g "!node_modules/**" -g "!.git/**"

# 2) if NO matches from step 1, remove dependency
pnpm remove @tanstack/react-query
```

```diff
- "@mysten/dapp-kit": "^x.x"
+ "@mysten/dapp-kit-react": "^1.0.2"
+ "@mysten/dapp-kit-core": "^1.0.4"
  "@mysten/sui": "^2.4.0"   # bump to latest
```

> Legacy `@mysten/dapp-kit` required `@tanstack/react-query` as a peer dependency. `@mysten/dapp-kit-react` / `@mysten/dapp-kit-core` do not require it. Keep `@tanstack/react-query` only when your application code still uses it directly. Official migration docs still show `useMutation` / `useQuery` examples for advanced UX patterns. Conclusion: `@tanstack/react-query` is optional for dApp Kit React itself and useful as a UI state-management wrapper. In `@mysten/dapp-kit-react` 1.x, internal React state hooks are implemented with `nanostores` (`useStore`) rather than React Query hooks.

React Query decision contract:

* Step 1: if no direct imports exist, remove `@tanstack/react-query` from manifests/lockfiles.
* Step 2: if direct imports exist, run hook-level triage.
  * if `useQuery`/`useMutation`/`useInfiniteQuery`/`useQueryClient` (or similar hooks) are used, keep dependency and report usage files.
  * if only `QueryClientProvider`/`QueryClient` wrappers remain, treat as possible legacy peer-dep residue from old `@mysten/dapp-kit`.
* Step 3: if wrapper-only and old `@mysten/dapp-kit` is removed while `@mysten/dapp-kit-react` is present, remove wrapper code, rerun import scan, then remove `@tanstack/react-query` when clean.
* Rule: import presence alone is not sufficient proof of active dependency need.

## 4. Backend Migration (Node.js)

### 4.1 Client Setup

```typescript
import { SuiGrpcClient } from '@mysten/sui/grpc';

const client = new SuiGrpcClient({
  network: 'testnet', // 'mainnet' | 'testnet' | 'devnet' | 'localnet'
  baseUrl: 'https://fullnode.testnet.sui.io:443',
});
```

`fullnode.<network>.sui.io:443` is the official fullnode host used in gRPC client examples. Use this `baseUrl` format for `SuiGrpcClient`.

### 4.2 Simulation / Execution API Replacement

```typescript
// simulate (replaces devInspect / dryRun)
const sim = await client.simulateTransaction({
  transaction: txBytes,
});

// If you used devInspect returnValues, include commandResults:
const simWithResults = await client.simulateTransaction({
  transaction: txBytes,
  include: { commandResults: true },
});
// Access results: simWithResults.commandResults?.[n].returnValues

// execute (method name remains executeTransaction in 1.x/2.x paths)
const exec = await client.executeTransaction({
  transaction: txBytes,   // field name: NOT "bytes"
  signatures: [signature], // string[], plural
});
```

### 4.3 Method Mapping

| 1.x pattern                  | 2.x target                                                           |
| ---------------------------- | -------------------------------------------------------------------- |
| `devInspectTransactionBlock` | `client.simulateTransaction({ include: { commandResults: true } })`  |
| `dryRunTransactionBlock`     | `client.simulateTransaction`                                         |
| `executeTransaction`         | `client.executeTransaction({ transaction, signatures })`             |
| `getCoins`                   | `client.listCoins`                                                   |
| `multiGetObjects`            | `client.getObjects` — returns `(Object \| Error)[]`; see §4.3.1      |
| `getOwnedObjects`            | `client.listOwnedObjects`                                            |
| `getTransactionBlock`        | Two-stage loader: gRPC `getTransaction` → GraphQL fallback; see §4.5 |
| `queryTransactionBlocks`     | **Not on `SuiGrpcClient`.** GraphQL only — see §4.3.1                |
| `queryEvents`                | GraphQL-first                                                        |

> `executeTransactionBlock` is JSON-RPC only, not on `SuiGrpcClient`. Official gRPC docs may show `getCoins`; use `listCoins` per installed SDK types (`@mysten/sui@2.4.0`).

### 4.3.1 Breaking Change Catalog

**`queryTransactionBlocks` → GraphQL address-scoped query**

Not available on `SuiGrpcClient` (*verified: grpc/client.d.mts*). Use address-scoped GraphQL:

```graphql
# Prefer: per-address index (fast)
{ address(address: "0x...") { transactions(last: 20) { nodes { digest } } } }
# Avoid: global affectedAddress scan (slow)
{ transactionBlocks(filter: { affectedAddress: "0x..." }) { nodes { digest } } }
```

***

**`getObjects` — return type is `(Object | Error)[]`**

2.x returns a mixed array; `Error` items are silently dropped if not checked explicitly.

Anti-pattern — `'objectId' in obj` does not reliably distinguish `Error` from valid objects. Use `instanceof Error`:

```typescript
const { objects } = await client.getObjects({ objectIds, include: { json: true } });
for (const obj of objects) {
  if (obj instanceof Error) throw obj; // surface, do not skip
  // obj.json, obj.objectId are safe here
}
```

***

**`Transaction.getData().inputs` — shape change**

|       | 1.x                                     | 2.x                                       |
| ----- | --------------------------------------- | ----------------------------------------- |
| Check | `inputs[0].type !== 'pure'`             | `inputs[0].$kind !== 'Pure'`              |
| Value | `inputs[0].value` (`number[]`)          | `inputs[0].Pure.bytes` (`string`, base64) |
| Parse | `bcs.Bool.parse(new Uint8Array(value))` | `bcs.bool().parse(fromBase64(bytes))`     |

Anti-pattern: `new Uint8Array(inputs[0].Pure.bytes)` silently produces wrong bytes — `Pure.bytes` is a base64 string, not a byte array (*verified: Transaction.d.mts line 120–122*).

**Critical — all Pure inputs are indistinguishable at the `getData()` level:**

In 1.x (`showInput: true` JSON-RPC), `type: 'pure'` inputs included SDK-level type context that allowed filtering to a specific pure type (e.g. bool vs. address vs. `vector<u8>`).

In 2.x `Transaction.from(bcs).getData()`, every pure input — `bool`, `address`, `vector<u8>`, or any other BCS-serialized value — has exactly the same shape: `{ $kind: 'Pure', Pure: { bytes: '<base64>' } }`. There is no type discriminator. The BCS payload alone determines what the value is.

Consequence: filtering by `$kind === 'Pure'` alone matches all pure inputs including address inputs. Attempting to BCS-parse an address as `vector<u8>` (or any other wrong type) silently produces garbled data or a parse error at runtime.

Correct 2.x pattern — use BCS parse success as the filter:

```typescript
const inputs = tx.getData().inputs;
for (const input of inputs) {
  if (input.$kind !== 'Pure') continue;
  try {
    const value = bcs.vector(bcs.u8()).parse(fromBase64(input.Pure.bytes));
    // handle vector<u8>
  } catch {
    // not a vector<u8> — skip or handle as different type
  }
}
```

### 4.4 JSON-RPC `options` -> Core API `include` Mapping

| 1.x `options` key          | 2.x `include` key                             | Notes                                                                            |
| -------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------- |
| `showRawInput: true`       | `bcs: true`                                   | Raw BCS bytes                                                                    |
| `showInput: true`          | `transaction: true`                           | Structured transaction data                                                      |
| `showEffects: true`        | `effects: true`                               | Transaction effects                                                              |
| `showEvents: true`         | `events: true`                                | Events                                                                           |
| `showBalanceChanges: true` | `balanceChanges: true`                        | Balance changes                                                                  |
| `showObjectChanges: true`  | `objectTypes: true`                           | For changed objects, use `include: { effects: true }` + `effects.changedObjects` |
| `showContent: true`        | `json: true` (or `content: true` for raw BCS) | **Breaking change — see below**                                                  |
| `showBcs: true`            | `content: true`                               | Raw BCS `Uint8Array`                                                             |

> **Breaking change — object JSON shape (`showContent` → `include: { json: true }`):**
>
> Migration: replace `content.fields.X` with `obj.json.X`. TypeScript does **not** catch shape mismatches — `obj.json` is typed `Record<string, unknown>`; optional-chain and `as` casts compile without error even when the path is wrong.
>
> SDK evidence: `grpc/core.mjs` `Value.toJson(object.result.object.json)` — no additional wrapping beyond protobuf deserialization. Runtime rule: log `obj.json` before finalizing field paths. Do not rely on tsc alone.

|                  | 1.x (`obj.data.content.fields`)                           | 2.x (`obj.json`)                      |
| ---------------- | --------------------------------------------------------- | ------------------------------------- |
| Shape            | `{ value: { fields: { blob_id, id: { id: '0x...' } } } }` | `{ value: { blob_id, id: '0x...' } }` |
| `fields` wrapper | Present (SDK-added)                                       | Removed                               |
| `id` field       | `{ id: '0x...' }` object                                  | `'0x...'` string directly             |

### 4.5 Transaction Result Shape Changes

Core API returns a discriminated union. Do not assume top-level fields like `res.digest`. The snippet below is for **core response shape reference** only; for digest-based loading policy, follow section 4.6 (two-stage loader policy).

```typescript
const result = await client.getTransaction({
  digest,
  include: { bcs: true, effects: true, transaction: true },
});

const tx =
  result.$kind === 'Transaction' ? result.Transaction : result.FailedTransaction;

const digest = tx.digest;
const bcsBytes = tx.bcs; // Uint8Array | undefined
const changed = tx.effects?.changedObjects ?? [];
```

| 1.x field path              | 2.x field path                                                     | Notes                                                                                                                                                                                                                                                                                                                                                              |
| --------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `receipt.rawTransaction`    | `tx.bcs`                                                           | `Uint8Array` (when `include: { bcs: true }`). **Breaking change**: 1.x `rawTransaction` was BCS-encoded `SenderSignedData` with a 4-byte ULEB128 list-count prefix `[0x01,0x00,0x00,0x00]`. 2.x gRPC `tx.bcs` is pure `TransactionData` bytes with no prefix. Remove any `slice(4)` that was applied to `rawTransaction`. Use `Transaction.from(tx.bcs)` directly. |
| `receipt.objectChanges`     | `tx.effects?.changedObjects`                                       | No top-level `objectChanges` in core result                                                                                                                                                                                                                                                                                                                        |
| `receipt.effects.created[]` | Filter `tx.effects?.changedObjects` by `idOperation === 'Created'` | Use `outputState` / `outputOwner` as needed                                                                                                                                                                                                                                                                                                                        |
| `receipt.timestampMs`       | `tx.epoch`                                                         | Epoch identifier, not millisecond timestamp                                                                                                                                                                                                                                                                                                                        |

### 4.6 Digest-Based Transaction Loading (Project Policy)

This policy is for loading **already-existing transactions by digest** (for example digest from DB/search/user input). Treat this separately from immediate post-submit confirmation. This section defines this guide's migration policy, not a universal protocol mandate.

| Use case                                                       | Recommended path                                                    |
| -------------------------------------------------------------- | ------------------------------------------------------------------- |
| Just executed/published in the same flow                       | gRPC `executeTransaction` + gRPC `waitForTransaction`               |
| Unknown-age digest loading                                     | gRPC `getTransaction` first -> GraphQL fallback                     |
| Verification/audit (for example publish/upgrade digest checks) | gRPC first -> GraphQL fallback -> explicit error if both miss       |
| Analytics/archive pipelines                                    | GraphQL-primary (indexer-backed), optional gRPC for very fresh data |

Rules:

* Do not ship single-source digest loaders (gRPC-only or GraphQL-only) for verification paths.
* Stage 1: call gRPC `getTransaction({ digest, include: { bcs: true } })`.
* Stage 2 fallback: query GraphQL `transaction(digest)` and read `transactionBcs`.
* Keep loader network caller-driven (`network` parameter or equivalent context), not fixed module-scope literal.
* If flow semantics are chain-fixed, explicit hardcoded network is allowed only with a short rationale comment.
* Do not use deployment-target config as implicit chain-verification context unless explicitly intended.
* In GraphQL fallback, check `response.errors` before reading `response.data`.
* Treat GraphQL `effects.objectChanges` / `effects.balanceChanges` as connection fields and read items via `nodes`/`edges`.
* Include `network` and failed stage (`grpc`/`graphql`) in thrown loader errors.
* If both miss, raise an explicit `pruned/not found` error. Do not silently return null/empty success.
* Log source (`grpc` or `graphql`) for observability.

Reason:

* Fullnode data can be pruned over time (older transactions might not be available through gRPC fullnodes).
* GraphQL/indexer retention and availability are also operator-dependent and should not be assumed as infinite.
* Dual-source fallback reduces single-endpoint failure risk for mixed-age digest workloads.

False-negative traps (static checks may miss):

1. GraphQL field-name mismatch can pass `tsc`.
   * `SuiGraphQLClient.query<Result>()` uses developer-declared generics; it does not validate query-string fields against schema.
   * Example mismatch class: gRPC `Transaction.bcs` vs GraphQL `Transaction.transactionBcs`.
   * Mitigation: schema introspection + query smoke checks (Gate F), and always check `response.errors`.
2. Byte-format assumptions from Sui 1.x will break silently at runtime.
   * **Root cause**: This is a Sui SDK version change, not a gRPC vs GraphQL difference.
     * Sui 1.x JSON-RPC `rawTransaction` → BCS-encoded `SenderSignedData` with a 4-byte ULEB128 list-count prefix (`[0x01,0x00,0x00,0x00]`). Legacy code often stripped these with `slice(4)` before passing to `Transaction.from()`.
     * Sui 2.x gRPC `tx.bcs` → pure `TransactionData` bytes, no prefix. Applying `slice(4)` here truncates real data and causes BCS parse failure.
   * Do not carry over `rawTransaction.slice(4)` patterns into 2.x code.
   * Mitigation: always use `Transaction.from(rawBytes)` directly — the SDK handles format detection internally. Run runtime tests for both gRPC and GraphQL fallback paths (Gate F).

```typescript
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { SuiGraphQLClient } from '@mysten/sui/graphql';
import { fromBase64 } from '@mysten/sui/utils';

type Network = 'mainnet' | 'testnet' | 'devnet';

const GRPC_URLS: Record<Network, string> = {
  mainnet: 'https://fullnode.mainnet.sui.io:443',
  testnet: 'https://fullnode.testnet.sui.io:443',
  devnet: 'https://fullnode.devnet.sui.io:443',
};

const GQL_URLS: Record<Network, string> = {
  mainnet: 'https://graphql.mainnet.sui.io/graphql',
  testnet: 'https://graphql.testnet.sui.io/graphql',
  devnet: 'https://graphql.devnet.sui.io/graphql',
};

function createGrpcClient(network: Network) {
  return new SuiGrpcClient({ network, baseUrl: GRPC_URLS[network] });
}

function createGraphQLClient(network: Network) {
  return new SuiGraphQLClient({ network, url: GQL_URLS[network] });
}

const GET_TX_QUERY = `
  query GetTransaction($digest: String!) {
    transaction(digest: $digest) {
      digest
      transactionBcs
      effects {
        status
      }
    }
  }
`;

// Post-submit confirmation path (not historical loading):
// Prefer gRPC wait in the same execution/publish workflow.
async function executeAndConfirm(network: Network, txBytes: Uint8Array, signature: string) {
  const grpcClient = createGrpcClient(network);
  const exec = await grpcClient.executeTransaction({
    transaction: txBytes,
    signatures: [signature],
  });

  const digest =
    exec.$kind === 'Transaction' ? exec.Transaction.digest : exec.FailedTransaction.digest;

  return grpcClient.waitForTransaction({
    digest,
    include: { effects: true },
  });
}

async function fetchFromGraphQL(network: Network, digest: string) {
  const gqlClient = createGraphQLClient(network);
  const r = await gqlClient.query<
    { transaction: { digest: string; transactionBcs: string } | null },
    { digest: string }
  >({
    query: GET_TX_QUERY,
    variables: { digest },
  });
  if (r.errors?.length) {
    throw new Error(
      `[loader][graphql] errors (network=${network}, digest=${digest}): ${JSON.stringify(r.errors)}`
    );
  }
  return { source: 'graphql', tx: r.data?.transaction ?? null };
}

async function fetchTransactionBcs(network: Network, digest: string) {
  const grpcClient = createGrpcClient(network);
  let grpcReason: unknown = 'miss';

  // Stage 1) gRPC for fresh/recent data.
  try {
    const result = await grpcClient.getTransaction({
      digest,
      include: { bcs: true },
    });
    const tx =
      result.$kind === 'Transaction' ? result.Transaction : result.FailedTransaction;
    if (tx.bcs instanceof Uint8Array && tx.bcs.length > 0) {
      return { source: 'grpc' as const, bcs: tx.bcs };
    }
    grpcReason = 'empty bcs';
  } catch (e) {
    grpcReason = e;
    // fall through
  }

  // Stage 2) GraphQL fallback.
  const gql = await fetchFromGraphQL(network, digest);
  const bcs64 = gql.tx?.transactionBcs;
  if (!bcs64) {
    throw new Error(
      `[loader] transaction not found/pruned (network=${network}, digest=${digest}) after grpc->graphql fallback; grpc=${String(
        grpcReason
      )}, graphql=null`
    );
  }
  return { source: 'graphql' as const, bcs: fromBase64(bcs64) };
}
```

> Operational note: if your project parses transaction bytes manually, use `Transaction.from(rawBytes)` directly — do not add byte-offset logic. **Byte format changed between SDK versions**: Sui 1.x JSON-RPC `rawTransaction` was BCS-encoded `SenderSignedData` including a 4-byte prefix (`[0x01,0x00,0x00,0x00]`). Sui 2.x gRPC `tx.bcs` and GraphQL `transactionBcs` are pure `TransactionData` bytes with no prefix. Any `slice(4)` applied to `rawTransaction` must be removed — **this applies equally to the GraphQL fallback path**: `transactionBcs` from GraphQL also has no prefix and must not be sliced. `Transaction.from(...)` accepts `Uint8Array` directly (gRPC `tx.bcs`) or a base64-encoded string (GraphQL `transactionBcs`). Both are valid: the SDK handles `fromBase64` internally when given a string. The code examples in this guide use `fromBase64(bcs64)` before passing to produce `Uint8Array` — either approach is correct. Official `SuiGraphQLClient` docs currently show both:
>
> * mainnet example: `https://sui-mainnet.mystenlabs.com/graphql`
> * testnet query example: `https://graphql.testnet.sui.io/graphql`
> * Many teams also use `https://graphql.<network>.sui.io/graphql` patterns in production.
> * Do not assume one fixed hostname pattern for every network. Re-verify endpoint and CORS behavior in your runtime environment.
> * Browser CORS validation example (preflight): `curl -sSI -X OPTIONS "<your-graphql-url>" -H "Origin: http://localhost:3000" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: content-type"`
> * Migration review check: verify your `createGraphQLClient()` (or equivalent) mainnet URL is set to the endpoint that passes your browser preflight checks. If direct browser calls fail CORS, route GraphQL through your app/server proxy.
> * Schema validation example for field name: `curl -sS "<your-graphql-url>" -H "content-type: application/json" --data '{"query":"{ __type(name:\"Transaction\"){ fields { name } } }"}'` Confirm `transactionBcs` exists.
> * Re-validation example for effects structure: `curl -sS "<your-graphql-url>" -H "content-type: application/json" --data '{"query":"{ __type(name:\"TransactionEffects\"){ fields { name } } }"}'` Confirm `objectChanges` exists, then verify `nodes` through your concrete transaction query output.
> * Connection-shape reminder: `objectChanges { outputState { ... } }` is invalid on connection types. Use `objectChanges { nodes { outputState { ... } } }` (or `edges { node { ... } }`).

### 4.7 Single vs Batch Object Reads

* `getObject(...)` is still valid for a single object.
* `getObjects(...)` is the batch API (replacement for legacy `multiGetObjects` patterns).

***

## 5. Frontend Migration (React)

> **Note**: `@mysten/dapp-kit-react` is a **separate package** from `@mysten/dapp-kit`. Having both installed simultaneously may cause conflicts.

### 5.1 dapp-kit.ts - Configuration File

```typescript
import { createDAppKit } from '@mysten/dapp-kit-react';
import { SuiGrpcClient } from '@mysten/sui/grpc';

const GRPC_URLS = {
  testnet: 'https://fullnode.testnet.sui.io:443',
  mainnet: 'https://fullnode.mainnet.sui.io:443',
};

export const dAppKit = createDAppKit({
  networks: ['testnet', 'mainnet'] as const,
  createClient: (network) =>
    new SuiGrpcClient({ network, baseUrl: GRPC_URLS[network] }),
});

// Register for TypeScript autocomplete (recommended)
declare module '@mysten/dapp-kit-react' {
  interface Register {
    dAppKit: typeof dAppKit;
  }
}
```

### 5.2 App.tsx - Provider Replacement

```diff
- import { SuiClientProvider, WalletProvider } from '@mysten/dapp-kit';
+ import { DAppKitProvider } from '@mysten/dapp-kit-react';
+ import { dAppKit } from './dapp-kit';

  function App() {
    return (
-     <SuiClientProvider networks={networkConfig}>
-       <WalletProvider>
-         <YourApp />
-       </WalletProvider>
-     </SuiClientProvider>
+     <DAppKitProvider dAppKit={dAppKit}>
+       <YourApp />
+     </DAppKitProvider>
    );
  }
```

### 5.3 Hook / Sign & Execute Pattern

`@mysten/dapp-kit-react` hook state is backed by `nanostores` (`stores.$wallets`, `stores.$connection`, `stores.$currentClient`, `stores.$currentNetwork`).

```diff
- import { useSignAndExecuteTransaction } from '@mysten/dapp-kit';
+ import { useDAppKit } from '@mysten/dapp-kit-react';

  function MyComponent() {
-   const { mutate: signAndExecute } = useSignAndExecuteTransaction();
+   const dAppKit = useDAppKit();

    async function handleTx() {
-     signAndExecute({ transaction: tx });
+     const result = await dAppKit.signAndExecuteTransaction({ transaction: tx });
    }
  }
```

Official docs show two valid patterns:

* Pattern A (recommended when you want mutation lifecycle state): `useMutation` + `dAppKit.signAndExecuteTransaction(...)`
* Pattern B (alternative): direct `await dAppKit.signAndExecuteTransaction(...)`

If your UI needs mutation lifecycle handling, wrap action calls with `useMutation`:

```typescript
import { useMutation } from '@tanstack/react-query';
import { useDAppKit } from '@mysten/dapp-kit-react';
import { Transaction } from '@mysten/sui/transactions';

function MyComponent() {
  const dAppKit = useDAppKit();
  const { mutateAsync: signAndExecute } = useMutation({
    mutationFn: (tx: Transaction) =>
      dAppKit.signAndExecuteTransaction({ transaction: tx }),
  });

  async function handleTx(tx: Transaction) {
    await signAndExecute(tx);
  }
}
```

If mutation state handling is not needed, direct calls are valid:

```typescript
const result = await dAppKit.signAndExecuteTransaction({ transaction });
```

### 5.4 Common Frontend API Mismatches (Validated)

* There is no `useDisconnectWallet` hook in `@mysten/dapp-kit-react`. Use `await dAppKit.disconnectWallet()` instead.
* `ConnectModal` does not expose a `trigger` prop as a typed React API (*verified: `ComponentProps<ReactWebComponent<DAppKitConnectModal, {}>>` — no trigger in type surface*). Use `ConnectButton` as the default trigger surface, or control `ConnectModal` via the `open` prop.
* `show()` / `close()` are Web Component methods on `DAppKitConnectModal`, not typed React ref APIs in `@mysten/dapp-kit-react`; treat ref-method use as runtime-verified only.
* `dAppKit.network` does not exist.
  * `dAppKit.networks` → **array of supported networks** (not the current network).
  * Current network in React: `useCurrentNetwork()` hook (*verified: exported from `dapp-kit-react/dist/index.d.mts`*).
  * Current network outside React: `dAppKit.stores.$currentNetwork.get()` (nanostores readable atom).
* `dAppKit.setNetwork(...)` does not exist. Use `dAppKit.switchNetwork(network)`.
* `dAppKit.client` does not exist. Use `useCurrentClient()` (React) or `dAppKit.getClient()` (non-React).
* `dAppKit.signAndExecuteTransaction(...)` returns a discriminated union (`TransactionResult`). Do not assume `res.digest` at top-level.
* **Breaking change — `signTransaction` / `signPersonalMessage` no longer accept `chain` parameter** (*verified: `SignTransactionArgs = Omit<SuiSignTransactionInput, 'account' | 'chain' | 'transaction'>` in dapp-kit-core types*). Remove `chain: \`sui:${network}\``from any call site migrated from 1.x`useSignTransaction`/`useSignPersonalMessage\` hooks.
* **`executeTransaction` vs `signAndExecuteTransaction`**: use `dAppKit.signAndExecuteTransaction` when the wallet must sign (user interaction required). Use `client.executeTransaction` when you already have signatures (for example sponsored or pre-signed flows). Do not swap the two.

```typescript
const res = await dAppKit.signAndExecuteTransaction({ transaction: tx });
const digest =
  res.$kind === 'Transaction' ? res.Transaction.digest : res.FailedTransaction.digest;
```

***

## 6. Event / Query Strategy

Do not port legacy polling-based event queries as-is - redesign them.

* `SuiGrpcClient` (2.4.0) does not expose `subscribeEvents`
* gRPC subscription exists at service level (`subscriptionService.subscribeCheckpoints`)
* **Real-time streaming**: check gRPC subscription availability in the release notes before adopting
* **Complex queries / search**: use GraphQL or a dedicated indexer

***

## 7. Infrastructure Checklist

```
[OK] gRPC endpoint - port 443, TLS, HTTP/2 required
[OK] LB/Nginx - enable grpc_pass or http2
[OK] Firewall - allow long-lived connections
[OK] ENV variables - update RPC URL to gRPC endpoint
```

| Network | Endpoint                              |
| ------- | ------------------------------------- |
| mainnet | `https://fullnode.mainnet.sui.io:443` |
| testnet | `https://fullnode.testnet.sui.io:443` |
| devnet  | `https://fullnode.devnet.sui.io:443`  |

***

## 8. Runtime Pitfalls (Result Shapes / Wait APIs)

* `waitForTransaction(...)` uses `include`/`timeout` options (core API style), not JSON-RPC `showObjectChanges`.
* Use `include: { objectTypes: true }` when you need object type information.
* In gRPC/core result shapes, `objectChanges` is not a top-level field. Prefer `effects.changedObjects` first.
* If `effects.changedObjects` is insufficient, add a follow-up digest query and leave a TODO.
* For digest-based follow-up reads, use a two-stage loader: gRPC `getTransaction(...)` first, then GraphQL fallback with explicit error when both miss.
* For shared digest loaders, pass network from caller context and avoid module-scope fixed network literals.
* For GraphQL fallback, check `response.errors` and include `network` + failing stage in loader errors.

***

## 9. Recommended Rollout Order

```
1. Replace backend code + stabilize compilation
2. Replace frontend Provider / hooks
3. staging E2E (simulate -> execute -> wallet sign)
4. Phased deployment (partial traffic)
5. Full cutover, then remove all legacy 1.x-only code paths
```

***

## 10. References

* [Data Access / sunset notice](https://docs.sui.io/concepts/data-access/data-serving)
* [Sui Core client API](https://sdk.mystenlabs.com/sui/clients/core)
* [Sui gRPC client API](https://sdk.mystenlabs.com/sui/clients/grpc)
* [Sui GraphQL client API](https://sdk.mystenlabs.com/sui/clients/graphql)
* [Sui Clients overview (compatibility notes)](https://sdk.mystenlabs.com/sui/clients)
* [dApp Kit migration](https://sdk.mystenlabs.com/sui/migrations/sui-2.0/dapp-kit)
* [dApp Kit React hooks](https://sdk.mystenlabs.com/dapp-kit/react/hooks/use-dapp-kit)
* [Sign and Execute Transaction (dApp Kit action)](https://sdk.mystenlabs.com/dapp-kit/actions/sign-and-execute-transaction)
* [dApp Kit React Getting Started (direct sign-and-execute example)](https://sdk.mystenlabs.com/dapp-kit/getting-started/react)
* [Fullnode gRPC reference](https://docs.sui.io/references/fullnode-protocol)

Local package verification sources (for your pinned version):

* `node_modules/@mysten/sui/package.json`
* `node_modules/@mysten/sui/dist/grpc/client.d.mts`
* `node_modules/@mysten/sui/dist/client/types.d.mts`
* `node_modules/@mysten/sui/dist/graphql/client.d.mts`
* `node_modules/@mysten/sui/dist/jsonRpc/client.d.mts`
* `node_modules/@mysten/dapp-kit-react/dist/index.d.mts` (fallback: `dist/index.mjs`)
* `node_modules/@mysten/dapp-kit-core/dist/types-*.d.mts`
