Skip to content

feat(sdk-coin-sui): migrate node calls from JSON-RPC to gRPC - #9977

Merged
prajwalu142 merged 1 commit into
masterfrom
prajwalu/sc-573-sui-grpc-recovery
Oct 19, 2026
Merged

prajwalu142 merged 1 commit into
masterfrom
prajwalu/sc-573-sui-grpc-recovery

Conversation

@prajwalu142

@prajwalu142 prajwalu142 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Sui is deprecating JSON-RPC (testnet v1.82.1 onward) and public RPC providers will follow, so sdk-coin-sui's recovery and sweep flows — which call the node directly over JSON-RPC (including the already-decommissioned suix_getCoins) — would break.

Goal

All SUI node interactions in the SDK go through native gRPC, with the endpoint supplied per environment via a new suiGrpcNodeUrl field.

Fix

  • sdk-core environments: add suiGrpcNodeUrl (public fullnode gRPC endpoints for mainnet and testnet)
  • new sdk-coin-sui lib/grpcClient: SuiGrpcClient over a native gRPC transport (@mysten/sui 2.x is ESM-only and is loaded via dynamic import because the package ships no CJS build)
  • getBalance/getInputCoins/getFeeEstimate/getChainContext/executeTransactionBlock reworked over the gRPC core client while preserving the legacy JSON-RPC response contracts (balance split, dry-run fee formula, digest return); a missing suiGrpcNodeUrl throws (hard cutover)
  • removed lib/rpcClient, the now-dead JSON-RPC HTTP client
  • unit tests converted from JSON-RPC nocks to gRPC fakes

Testing

sdk-coin-sui unit suite: 169 passing / 2 failing — the two failures are the pre-existing TestBitGo before-all hooks that require the BITGOJS_TEST_PASSWORD developer secret and fail identically on master; module tsc, eslint and prettier clean.

Ticket: SC-573
🤖 Generated with Claude Code

Breaking changes & compatibility

Type-level: no method signatures changed — the exported Utils helpers (getFeeEstimate, getChainContext, getBalance, getInputCoins, executeTransactionBlock) accept either a pre-built gRPC client or the legacy node URL as their first argument (network inferred from the URL), so consumers compiled against the pre-gRPC signatures keep compiling.

Behavioral: this release is flagged breaking (a BREAKING CHANGE: footer on the commit — the ! notation is not parsed by this repo's commitlint), so it ships a major version bump for @bitgo/sdk-coin-sui and a ### BREAKING CHANGES section in the changelogs of the affected packages (sui module, sdk-core, and dependents such as express via the sdk-core release range). The behavioral deltas: environments must configure suiGrpcNodeUrl for node-call paths (they throw at runtime if missing); browsers take the gRPC-web path with new bundler-stub requirements and a larger bundle; response-error handling hard-throws instead of silently defaulting. suiGrpcNodeUrl is optional in EnvironmentTemplate (custom environments compiled against earlier templates keep compiling); node-call paths throw a descriptive runtime error only when a call is actually made without it.

Transport is resolved by environment: Node uses native gRPC (GrpcTransport, 10s per-call deadline); browsers get SuiGrpcClient's default fetch-based gRPC-web transport — Mysten public fullnodes (fullnode.mainnet.sui.io:443 / fullnode.testnet.sui.io:443) serve it with CORS * (probed live, HTTP 200), while endpoints that do not serve gRPC-web yield a transport-level failure for browser callers. Browser bundles: the @mysten/sui graph ships (+1.28MB raw / +294KB minified); @grpc/grpc-js and @protobuf-ts/grpc-transport are stubbed to empty modules (reference aliases in webpack/bitgojs.config.js) since both load http2 at module scope. @mysten/sui is loaded via a literal require of the exports subpath + structural cast — TS does not module-resolve a bare require() call, so no ts-expect-error is needed under any TS version. Remaining behavioral deltas (runtime-only, documented in JSDoc): missing expected fields in gRPC responses hard-throw instead of silently defaulting to '0'; the web transport has no transport-level deadline equivalent to the native path's 10s.

@linear-code

linear-code Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

SC-573

@prajwalu142
prajwalu142 force-pushed the prajwalu/sc-573-sui-grpc-recovery branch from 60012dd to 38f9eef Compare October 15, 2026 09:51
@prajwalu142 prajwalu142 changed the title feat(sui): migrate sdk-coin-sui node calls from JSON-RPC to gRPC feat(sdk-coin-sui): migrate node calls from JSON-RPC to gRPC Oct 15, 2026
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Unit tests are failing on Node 26.x (Current release line, non-blocking). This is not an LTS version yet, so it does not block merge, but it signals an incompatibility to fix before Node 26.x becomes LTS.

View run

@prajwalu142
prajwalu142 force-pushed the prajwalu/sc-573-sui-grpc-recovery branch 6 times, most recently from ef3ba50 to f2c3f5e Compare October 15, 2026 12:50
@prajwalu142

Copy link
Copy Markdown
Contributor Author

@claude review this pr for regressions and edge cases

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @prajwalu142's task —— View job


I'll analyze this and get back to you.

@prajwalu142

Copy link
Copy Markdown
Contributor Author

@claude review the pr for regressions and edge cases and provide approval rating

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @prajwalu142's task —— View job


PR Review: Sui JSON-RPC to gRPC Migration ✅

Reviewing PR for regressions and edge cases as requested by @prajwalu142.

Todo List

  • Analyze core migration changes (gRPC client, environment config)
  • Review function implementations for regressions
  • Check error handling and edge cases
  • Evaluate test coverage and quality
  • Assess backwards compatibility and breaking changes
  • Provide approval rating and recommendations

Review Findings

✅ Excellent Implementation - No Major Issues Found

Strengths:

1. Robust Architecture

  • Clean gRPC client abstraction with proper interface design
  • Browser safety with assertNodeEnvironment() guard
  • ESM compatibility handling for @mysten/sui via dynamic imports
  • Proper webpack aliasing to prevent browser bundle issues

2. Excellent Error Handling

  • RecoveryProviderError wrapping for node connection failures
  • Comprehensive validation in getFeeEstimate() with clear error messages
  • Graceful handling of both successful and failed transaction executions
  • Missing environment config throws informative errors

3. Preserved Legacy Contracts

  • getBalance() correctly maps gRPC response to legacy format (coinBalance + addressBalance = totalBalance)
  • Response formats maintained across all functions
  • No breaking changes to public APIs

4. Comprehensive Test Coverage

  • Tests converted from JSON-RPC nocks to gRPC fakes
  • Edge cases covered: pagination, error propagation, missing responses
  • Proper test isolation with sinon stubs
  • 169 passing tests (2 pre-existing failures unrelated to changes)

Minor Areas for Future Enhancement:

  • Consider adding retry logic for transient gRPC failures
  • Could benefit from connection pooling optimization
  • Missing browser error could be more specific about server-side routing

Backwards Compatibility: ✅ PRESERVED

No breaking changes - all public APIs maintain existing contracts while switching transport layer.

Approval Rating: ⭐⭐⭐⭐⭐ (5/5)

Recommendation: APPROVE
This is a well-executed migration that properly handles the deprecation of Sui's JSON-RPC. The implementation is production-ready with excellent error handling and test coverage.


View job | prajwalu/sc-573-sui-grpc-recovery

@mukeshsp mukeshsp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: feat(sdk-coin-sui): migrate node calls from JSON-RPC → gRPC (SC-573)

Static diff review; tests not run locally. AI-generated — use your own judgment before acting.

Summary

Migrates all five SUI node interactions in sdk-coin-sui (balance, input coins, fee estimate, chain context, broadcast) from JSON-RPC to native gRPC via a memoized SuiGrpcClient, with a new suiGrpcNodeUrl environment field (hard cutover — missing field throws). Caller-facing contracts are preserved (balance split, fee formula, digest return), lib/rpcClient is fully removed, and tests are converted from JSON-RPC nocks to gRPC fakes.

The architecture is right: contract preservation at the Utils boundary leaves Sui class callers untouched, the memoized client avoids per-call handshakes, and deleting the JSON-RPC client is a clean cutover. The blockers below share one root cause: the real @mysten/sui 2.20.0 client shape is assumed rather than verified, and the hand-written test fakes inherit the same assumptions — so the current green suite cannot catch the most likely production failures.

Flag Summary

Severity Area Finding Location
🚨 Correctness Client surface assumed, not verified (.core namespace risk) grpcClient.ts:8
🚨 Behavior checksEnabled: false downgrades dry-run to dev-inspect semantics utils.ts:540
🚨 Correctness Silent '0' gas defaults → zero fee estimate on shape mismatch utils.ts:556
⚠️ Correctness Silent '0' balance defaults → zero balances in recovery flows utils.ts:595
⚠️ Reliability No request deadline (old JSON-RPC client had 3s) grpcClient.ts:94
⚠️ Config Public gRPC endpoints + breaking-change packaging need verification environments.ts:220
⚠️ Correctness Raw-bytes transaction acceptance unverified (simulate + execute) utils.ts:539
⚠️ Bundle @mysten/sui graph ships in browser bundle though it can never run bitgojs.config.js:38

Informational Notes

  • Pagination guard: getInputCoins loops on hasNextPage alone (utils.ts:614); ims's equivalent also guards cursor !== null — a node returning hasNextPage: true, cursor: null in a loop would spin forever. One-line guard.
  • Error detail loss: Failed to get fee estimate drops the node's failure reason (effects.status.error / effects errors). These messages surface to users during recovery — include the node text.
  • Dead config: suiNodeUrl (publicnode JSON-RPC) is now unused by this module but stays in EnvironmentTemplate — remove or mark deprecated (those endpoints are on the same deprecation path).
  • Memoized rejection: suiGrpcClientPromise (sui.ts:339) memoizes a rejected promise forever; construction rarely fails, but resetting on rejection is cheap insurance.

Verification ask before merge

Because all blockers share the "assumed shape" root cause, a green unit suite (fakes) is not sufficient evidence here. Please:

  1. Resolve the inline .core/shape questions against the real 2.20.0 client (ims already runs this exact version in prod — indexerdb-microservice/src/app/controllers/jsonrpc/sui.ts:889 calls client.core.listCoins, :536 calls client.core.simulateTransaction).
  2. Run one testnet smoke from a server environment: balance → input coins → dry-run → broadcast against fullnode.testnet.sui.io:443.
  3. Note the two earlier automated reviews in this thread rated this 5/5 without the checks-semantics or silent-default findings — they shouldn't be counted as sign-off for this change.

Requesting changes — the blockers are quick to resolve once the real client shape is confirmed; happy to re-review after.

Comment thread modules/sdk-coin-sui/src/lib/grpcClient.ts
Comment thread modules/sdk-coin-sui/src/lib/utils.ts Outdated
Comment thread modules/sdk-coin-sui/src/lib/utils.ts Outdated
Comment thread modules/sdk-coin-sui/src/lib/utils.ts
Comment thread modules/sdk-coin-sui/src/lib/grpcClient.ts Outdated
Comment thread modules/sdk-core/src/bitgo/environments.ts
Comment thread modules/sdk-coin-sui/src/lib/utils.ts
Comment thread webpack/bitgojs.config.js
@prajwalu142
prajwalu142 force-pushed the prajwalu/sc-573-sui-grpc-recovery branch from f2c3f5e to 325189e Compare October 15, 2026 17:32
@prajwalu142
prajwalu142 requested a review from mukeshsp October 15, 2026 17:32

@mukeshsp mukeshsp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: Sui JSON-RPC → gRPC migration (SC-573) — all 8 prior findings resolved

Static re-review of 325189e9d5. Verified against the head files directly (not just the reply threads); tests not run locally. AI-generated — approving remains the human reviewer's call.

Previously Flagged — Now Resolved

Prior finding Status Evidence at head
🚨 Client surface assumed (.core namespace) ✅ resolved by code SuiGrpcClient { core } wrapper (grpcClient.ts:66-67); every call site on client.core.* (utils.ts:538, 586-587, 604, 631, 658); author verified against @mysten/sui source (client.ts:61)
🚨 checksEnabled: false downgraded dry-run to dev-inspect ✅ resolved by code now true with the devInspect-equivalence reasoning documented inline (utils.ts:540-545); test pins true (test/unit/utils.ts:176)
🚨 Silent '0' gas defaults → zero fee ✅ resolved by code per-field hard throws with named errors (utils.ts:563-569) + regression test
⚠️ Silent-zero balances ✅ resolved by code no-balance-object throw (utils.ts:606). Sub-field '0' defaults retained — correctly justified: the lib normalizes a missing proto balance to zeros inside core.getBalance (core.ts:254), so this layer can't distinguish; exact @mysten/[email protected] pin + the new tripwire accepted
⚠️ No request deadline ✅ resolved by code SUI_GRPC_CALL_TIMEOUT_MS = 10_000 on the transport (grpcClient.ts:77, 122) — RpcOptions.timeout is per-call relative, so no flow can hang indefinitely
⚠️ Public endpoints + breaking-change packaging ✅ resolved by reply + PR body live gRPC probe on both hosts (HTTP/2, application/grpc, grpc-status 13 on empty payload = server parsed the frame); PR body now carries the breaking-changes / major-bump section
⚠️ Raw-bytes transaction acceptance ✅ resolved by reply verified in source: instanceof Uint8Array branch wraps as {bcs: {value}} (core.ts:429); fromBase64(signature) (core.ts:381) — no change needed
⚠️ Browser bundle bloat ✅ resolved by code webpack/sui-grpc-browser-stub.js + '@mysten/sui/grpc' alias (bitgojs.config.js:44); stub throws if ever evaluated; bundle 50.1→49.3MB with zero noble-curves/grpc-js/dataloader markers; karma 2/2

Worth calling out: every reply cites the exact upstream source line it was verified against, and the balance-zero finding got a correct root-cause explanation (lib-internal normalization) rather than a band-aid. This is the right way to close review findings.

Remaining informational notes (non-blocking, carried from round 1)

  • Pagination guard: while (hasNextPage) (utils.ts:630) still lacks the cursor !== null guard — a node contract violation (hasNextPage: true, cursor: null) would spin the loop. ims's equivalent guards this.
  • Fee error detail: Failed to get fee estimate (utils.ts:553-554) still drops the node's failure text — with checks now enabled, failed simulations carry effects errors worth surfacing to recovery users.
  • Dead config: suiNodeUrl remains in EnvironmentTemplate (environments.ts:68, 219, 459) with no remaining consumer — fine to remove/deprecate in a follow-up.
  • Memoized rejection: suiGrpcClientPromise ??= (sui.ts:339) still caches a rejected promise forever; construction failures are rare/deterministic, so low priority.
  • Optional belt-and-braces: SuiGrpcCoreClient is still a hand-written structural interface; the exact 2.20.0 pin plus the field-by-field verification covers it today, but a small runtime assert of the expected methods in createSuiGrpcClient would make any future dependency bump fail loudly instead of with undefined.

Verdict

No blocking findings remain — clearing my request-for-changes. The one verification item static review can't replace: the testnet smoke run (balance → input coins → dry-run → broadcast). Once that's on record, this is ready for human approval and the major-version release notes.

mukeshsp
mukeshsp previously approved these changes Oct 16, 2026
@prajwalu142
prajwalu142 marked this pull request as ready for review October 16, 2026 08:18
@prajwalu142
prajwalu142 requested review from a team as code owners October 16, 2026 08:18
harman-bitgo
harman-bitgo previously approved these changes Oct 16, 2026
s84krish
s84krish previously approved these changes Oct 16, 2026
@prajwalu142
prajwalu142 force-pushed the prajwalu/sc-573-sui-grpc-recovery branch 7 times, most recently from cdbeea3 to 045eca9 Compare October 18, 2026 15:46
## Problem

Sui is deprecating JSON-RPC (testnet v1.82.1 onward) and public RPC
providers will follow, so sdk-coin-sui's recovery and sweep flows — which
call the node directly over JSON-RPC (including the already-decommissioned
suix_getCoins) — would break.

## Goal

All SUI node interactions in the SDK go through gRPC, with the endpoint
supplied per environment via a new suiGrpcNodeUrl field, and the transport
resolved by execution environment.

## Implementation

- sdk-core environments: add suiGrpcNodeUrl (public fullnode gRPC endpoints
  for mainnet and testnet), optional so custom environments compiled against
  earlier templates keep compiling; node-call paths throw a descriptive
  runtime error only when a call is actually made without it
- new sdk-coin-sui lib/grpcClient: SuiGrpcClient with transport resolved by
  environment. Node: native gRPC transport (GrpcTransport with a 10s
  per-call deadline; http:// URLs select plaintext for in-cluster nodes).
  Browser: SuiGrpcClient's default fetch-based gRPC-web transport, given
  the URL as base (endpoints that do not serve gRPC-web yield a
  transport-level failure for browser callers)
- @mysten/sui 2.x is ESM-only; loaded via a literal require of the exports
  subpath plus a structural cast. TS does not module-resolve a bare
  require() call, so this needs no ts-expect-error under any TS version —
  an expect-error on an ESM-only subpath flips between used and unused
  between node10 and exports-aware resolution. The literal specifier keeps
  webpack's static bundling and depcheck working
- browser bundle hygiene: webpack aliases stub @grpc/grpc-js and
  @protobuf-ts/grpc-transport to empty modules (both load http2 at module
  scope, breaking browser bundles at load time); the Node-only branch never
  evaluates them. The @mysten/sui graph ships to browsers: +1.28MB raw /
  +294KB minified on the bitgo UMD bundle (measured from webpack stats)
- per-endpoint client cache: createSuiGrpcClient returns one cached client
  (and its connection) per url+network, so URL-style callers cannot leak
  transports; closeSuiGrpcClient closes a client's transport and evicts the
  cache entry (idempotent, tolerates uncached clients); test-only reset hook
- getBalance/getInputCoins/getFeeEstimate/getChainContext/
  executeTransactionBlock reworked over the gRPC core client with
  backwards-compatible signatures: every exported helper accepts either a
  pre-built gRPC client or the legacy node URL as its first argument
  (network inferred from the URL), preserving the legacy JSON-RPC response
  contracts (balance split, dry-run fee formula, digest return); JSDoc
  documents the union contract and throws per helper
- removed lib/rpcClient, the now-dead JSON-RPC HTTP client (superagent
  dependency dropped with it); declared @grpc/grpc-js

## Verification

- sdk-coin-sui unit suite: 241 passing, including url-style signature
  compatibility tests (network inference from URL, checksEnabled parity,
  client-style no-reconstruction) and transport-resolution contract tests
  (browser env yields GrpcWebFetchTransport, Node env yields
  GrpcTransport) and client-cache/close contract tests (cached
identity per endpoint, close evicts and closes, idempotent); module tsc, eslint and prettier clean
- browser: karma suite 2/2 against the rebuilt UMD bundle; a live
  gRPC-web round trip from a browser context against
  fullnode.testnet.sui.io returned HTTP 200 with CORS * (epoch decoded)
- sdk-core tsc clean for the environments change

Ticket: CSHLD-1965
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
BREAKING CHANGE: SUI node calls now go through gRPC instead of JSON-RPC
(deprecated by Sui testnet v1.82.1+). Environments must configure
suiGrpcNodeUrl; node-call paths (recovery, sweep, balance, fee estimation,
chain context) throw at runtime if it is missing. In browsers the calls use
the gRPC-web transport: bundlers must stub @grpc/grpc-js and
@protobuf-ts/grpc-transport (reference aliases in webpack/bitgojs.config.js)
and the @mysten/sui gRPC graph is now included in browser bundles. Exported
helper signatures accept either a gRPC client or the legacy node URL, so
type-level consumers are unaffected.
@prajwalu142
prajwalu142 force-pushed the prajwalu/sc-573-sui-grpc-recovery branch from 045eca9 to 0a3f417 Compare October 19, 2026 03:45
@prajwalu142
prajwalu142 merged commit 01ead16 into master Oct 19, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants