Repository navigation
feat(sdk-coin-sui): migrate node calls from JSON-RPC to gRPC - #9977
prajwalu142 wants to merge 1 commit into
Conversation
60012dd to
38f9eef
Compare
|
|
ef3ba50 to
f2c3f5e
Compare
|
@claude review this pr for regressions and edge cases |
|
Claude finished @prajwalu142's task —— View job I'll analyze this and get back to you. |
|
@claude review the pr for regressions and edge cases and provide approval rating |
|
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
Review Findings✅ Excellent Implementation - No Major Issues FoundStrengths:1. Robust Architecture
2. Excellent Error Handling
3. Preserved Legacy Contracts
4. Comprehensive Test Coverage
Minor Areas for Future Enhancement:
Backwards Compatibility: ✅ PRESERVEDNo breaking changes - all public APIs maintain existing contracts while switching transport layer. Approval Rating: ⭐⭐⭐⭐⭐ (5/5)Recommendation: APPROVE |
mukeshsp
left a comment
There was a problem hiding this comment.
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:
getInputCoinsloops onhasNextPagealone (utils.ts:614); ims's equivalent also guardscursor !== null— a node returninghasNextPage: true, cursor: nullin a loop would spin forever. One-line guard. - Error detail loss:
Failed to get fee estimatedrops 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 inEnvironmentTemplate— 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:
- 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:889callsclient.core.listCoins,:536callsclient.core.simulateTransaction). - Run one testnet smoke from a server environment: balance → input coins → dry-run → broadcast against
fullnode.testnet.sui.io:443. - 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.
f2c3f5e to
325189e
Compare
mukeshsp
left a comment
There was a problem hiding this comment.
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 |
| ✅ 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/sui@2.20.0 pin + the new tripwire accepted |
|
| ✅ 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 |
|
| ✅ 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 |
|
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 |
| ✅ 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 thecursor !== nullguard — 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:
suiNodeUrlremains inEnvironmentTemplate(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:
SuiGrpcCoreClientis still a hand-written structural interface; the exact2.20.0pin plus the field-by-field verification covers it today, but a small runtime assert of the expected methods increateSuiGrpcClientwould make any future dependency bump fail loudly instead of withundefined.
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.
4e6f9ab
b744f5b to
cdbeea3
Compare
## 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) - 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: 238 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); 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 <noreply@anthropic.com> 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.
cdbeea3 to
045eca9
Compare
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
suiGrpcNodeUrlfield.Fix
suiGrpcNodeUrl(public fullnode gRPC endpoints for mainnet and testnet)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)suiGrpcNodeUrlthrows (hard cutover)lib/rpcClient, the now-dead JSON-RPC HTTP clientTesting
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
Utilshelpers (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-suiand a### BREAKING CHANGESsection 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 configuresuiGrpcNodeUrlfor 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.suiGrpcNodeUrlis optional inEnvironmentTemplate(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 getSuiGrpcClient'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/suigraph ships (+1.28MB raw / +294KB minified);@grpc/grpc-jsand@protobuf-ts/grpc-transportare stubbed to empty modules (reference aliases inwebpack/bitgojs.config.js) since both loadhttp2at module scope.@mysten/suiis loaded via a literalrequireof the exports subpath + structural cast — TS does not module-resolve a barerequire()call, so nots-expect-erroris 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.