fix: merge-coins size/count bugs, migrate scripts to gRPC, drop Pyth SDK - #171
Conversation
jangid
left a comment
There was a problem hiding this comment.
operate.md: clean — lint/test/build green, no unresolved security findings, no open change-requests. Walked the rewritten buildMergeCoinsTransaction branch by branch on the PR head: every path into sendCoinsToAddressBalance is guarded non-empty (SUI throws on coins.length === 0 before the useAddressBalanceGas branch; the reserved-gas-coin branch reaches it only when hasWork required rest.length > 0; the non-SUI branch throws on selected.length === 0), so the new const [target, ...rest] destructuring has no undefined-target path. Sizing the cap against serialized transaction bytes rather than the 512-argument command limit is the right binding constraint, and measuring (await tx.build({client})).length rather than trusting simulateTransaction is the correct verification given it returned SUCCESS for the 39KB transaction submission rejected.
Two intentional breaking changes worth a release note: CoinTypeCount gains hasMoreCoinObjects (breaking for constructors of the type), and coinObjectCount is now capped at MAX_COINS_PER_TX rather than being a true count — consumers rendering the number need the companion flag. AlphaFiTech/alphafi-admin#33 is the first such consumer and is currently red on both (MAX_COINS_PER_TX not exported yet, hasMoreCoinObjects missing), so it needs this merged and published.
Approvals: 2/2 — threshold met.
Fixes two bugs in the merge-coins helpers, then migrates the remaining JSON-RPC usage to gRPC and drops the Pyth SDK.
Merge-coins fixes
Both found against
0xee5360…4545(100K+ objects).Merge transactions exceeded the transaction size limit. A 500-object merge serializes to ~39KB against a 16384-byte cap on the gasless transaction, so it failed at submission with
Size limit exceeded: serialized gasless transaction size exceeded maximum of 16384. The old cap was sized against the 512-argument command limit, which is not the binding constraint — each coin is an owned-object input costing ~79 bytes.MAX_COINS_PER_TXis now 180 (~14.4KB worst case, 88% of limit).Note
simulateTransactiondoes not enforce this check — it returned SUCCESS for the 39KB transaction that submission rejected. Verification measures(await tx.build({client})).lengthinstead.getCoinObjectCountsreturned 1 of 72 coin types. It listed the address's objects capped at N and grouped by type, but objects are not listed grouped by type, so an address holding 100K SUI objects filled the entire listing and every other type was missed. It now enumerates types from the node's coin index (listBalances) and counts each type separately.Other changes
address-balanceoutput merges into one coin then makes a singlesend_fundscall, instead of one call per coin.CoinTypeCountgainshasMoreCoinObjectsso callers can render "more than N" without guessing at the cap. Breaking for anyone constructing the type.grpcUrlon both public functions, defaulting to the fullnode endpoints.RESOURCE_EXHAUSTED; the public fullnode rate-limits the per-type count burst.Verified on mainnet
grpcUrldefault and explicit produce identical results; a bad URL is rejected, so the parameter is not silently ignored.JSON-RPC → gRPC migration
JSON-RPC is deprecated, so the
scripts/helpers now useSuiGrpcClient, matching whatsrc/already did.dryRunTransactionBlock→core.simulateTransaction;devInspectTransactionBlock→simulateTransaction({ checksEnabled: false }).options: { show* }→include: { effects, balanceChanges, objectTypes }.requestType: "WaitForLocalExecution"is gone in v2, replaced by an explicitwaitForTransaction({ result }).$kinddiscriminant, soupdatePriceScriptunwrapsTransaction/FailedTransactionand reports the failure message.SUI_GRPC_URL. This removes the hardcoded private RPC URL (which carried an API key) from source.Pyth SDK removal
updatePricesroutes through Lazer, so@pythnetwork/pyth-sui-jswas no longer used and is removed fromdependencies.pythClient/pythConnectionfields onAlphalendClient, and the internal JSON-RPC client that existed only to constructSuiPythClient.src/now makes no JSON-RPC calls at all.src/utils/oracle.tsand deletedscripts/pythTest.ts.updatePriceTransaction→update_price_from_pyth) is untouched — it is a pure Move-call builder with no SDK dependency, and its test still passes.Breaking:
pythClientandpythConnectionwere public fields on the exportedAlphalendClient. Unused internally, but external consumers reading them will break.Note:
@pythnetwork/pyth-sui-jsremains installed as a peer dependency of@7kprotocol/sdk-ts, which hard-imports it at module load. npm 7+ auto-installs peers so this resolves normally, but installs using--legacy-peer-deps, Yarn 1, or strict-peer pnpm will not get it and will fail to import the SDK.Verification
tsc --noEmit,npm run build, andnpm run lintall clean. Test results are unchanged from before these commits — the same 25 pre-existing integration-test failures (live-network dependent), verified by diffing failing test names against the base commit.