feat(CORE-2970): worm-cli sui migration#4880
Conversation
0c2cbe1 to
35855da
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
35855da to
377840a
Compare
882b4d4 to
25be270
Compare
| transferCoin, | ||
| tx.pure("u16", recipientChainId), | ||
| tx.pure("vector<u8>", [...recipient]), | ||
| tx.pure("u64", BigInt(0)), |
There was a problem hiding this comment.
Even that we know the fee is zero, the right would be reading from core bridge state and passing the fee here.
9c4fd9d to
339b307
Compare
| console.log(`Published package ID: ${mainPackage.packageId}`); | ||
|
|
||
| return result; | ||
| output = execSync(cmd, { encoding: "utf-8" }); |
There was a problem hiding this comment.
Can you try to refactor this using execFileSync here unless there's a specific reason to invoke a shell? Using that method instead is more resistant to command injection.
| // `--build-env testnet` selects testnet dependency pins (localnet is not a | ||
| // pinned environment); `--publish-unpublished-deps` publishes dependencies | ||
| // that are not yet on-chain for this network. | ||
| const cmd = `sui client test-publish ${packagePath} --publish-unpublished-deps --build-env testnet --json 2>&1`; |
There was a problem hiding this comment.
Can you add a check that packagePath is a directory first using a variant of fs's realpath functions? This would make sure that this is actually a directory on the system before invoking the command.
| // `--build-env testnet` selects testnet dependency pins (localnet is not a | ||
| // pinned environment); `--publish-unpublished-deps` publishes dependencies | ||
| // that are not yet on-chain for this network. | ||
| const cmd = `sui client test-publish ${packagePath} --publish-unpublished-deps --build-env testnet --json 2>&1`; |
There was a problem hiding this comment.
Does using 2>&1 make sense here? It looks like you're merging the stdout with stderr and then working around that using the first-brace parsing above. If you leave these split, can you just grab the pure JSON from one of those output sources?
| * This handles dependencies automatically and doesn't require Published.toml manipulation. | ||
| * Note: Uses the locally configured Sui CLI signer, not a programmatic signer. | ||
| * Extract the transaction digest from `sui client test-publish --json` output. | ||
| * The CLI prepends human-readable build lines before the JSON object, so the |
There was a problem hiding this comment.
Are you sure there's nothing like --quiet that would return only the JSON? It would be very unusual for such a tool to require all consumers to manually parse out text from JSON
| typeArguments: [coinType], | ||
| }); | ||
|
|
||
| // Random 32-bit transfer nonce. |
There was a problem hiding this comment.
| // Random 32-bit transfer nonce. | |
| // Pseudo-random 32-bit transfer nonce. |
Note that Math.random() is not cryptographically random and can be predicted
| client: SuiGrpcClient, | ||
| tokenBridgeStateObjectId: string | ||
| ): Promise<string> => { | ||
| ): Promise<string | undefined> => { |
There was a problem hiding this comment.
Maybe better to throw an error here if the emitter cap id is undefined?
| const type = obj.object.type; | ||
| const value = (obj.object.json as any)?.value; | ||
|
|
||
| if (type.includes("wrapped_asset::WrappedAsset<")) { |
There was a problem hiding this comment.
Any way to use Sui's functions to do this? My LLM is suggesting the following might help:
isValidStructTag,
isValidSuiAddress,
normalizeStructTag,
normalizeSuiAddress,
parseStructTag,
| if (!registryId) { | ||
| throw new Error("Unable to fetch token registry object ID"); | ||
| } | ||
| const originalPackageId = state.object.type.split("::")[0]; |
There was a problem hiding this comment.
Are you able to use the built-in Sui SDK to do this parsing instead?
| // defines `token_registry`, which is the package embedded in the state | ||
| // object's type (Move type origins are stable across upgrades), not the | ||
| // upgraded package returned by `getPackageId`. | ||
| const originalPackageId = state.object.type.split("::")[0]; |
There was a problem hiding this comment.
Same here, can we use Sui's parsing instead of a manual split?
| fieldId = res.dynamicField.fieldId; | ||
| } catch { | ||
| throw new Error( | ||
| `Token of type ${coinType} has not been registered with the token bridge` |
There was a problem hiding this comment.
I'm not sure this error message is correct for all instances of getDynamicField errors. It's probably better to surface and log the actual error here instead of assume that it's a registration issue.
worm CLI: migrate Sui support to
@mysten/suiv2 gRPCLinear Ticket
Ticket: CORE-2970
Summary
Migrates the
wormCLI's Sui support from@mysten/suiv1 (SuiClient, JSON-RPC) to v2.19(
SuiGrpcClient, the gRPC fullnode API). The Sui RPC surface changed substantially betweenmajors — method names, response envelopes, the
jsonrepresentation of Move values, BCS, andtransaction building — so every Sui code path (provider, object/coin reads, registrations,
deploy, init, attest/createWrapped, transfer, governance submit) was updated. Adds a network
safety guard and hermetic unit tests, and resolves the ESM-only nature of the v2 package in the
TypeScript/Jest/esbuild toolchain.
Major Changes
@mysten/sui^1.45.0→ exact2.19.0; lockfile updated.getProviderForChain/getProvidernow constructnew SuiGrpcClient({ network, baseUrl })instead of
new SuiClient({ url }).ChainProvider<"Sui">is nowSuiGrpcClient.jsonrepresentation flattens Move structs (nonested
.fields), collapses UIDid.idto a plainid, and encodesvector<u8>as base64.getObjectFields,getOriginalAssetSui(wrapped/native branches),getTokenCoinType,getPackageId,getOriginalPackageId, and the registrations reader were rewritten againstthis shape.
executeTransactionBlocknow unwraps the gRPCTransaction | FailedTransactionresult, readsstatus.success, and flattenseffects.changedObjectstogether with theobjectTypesmap into the CLI's normalizedSuiTransactionResult.CoinTypeKeyBcs/RegistryKeyBcsschemas fordynamic-field key lookups; switches VAA arguments to typed
tx.pure("vector<u8>", ...)andpublishing to
tx.publish. Pagination is rewritten as explicitlistOwnedObjects/listDynamicFields/listCoinscursor loops.sui client test-publish --publish-unpublished-depswith afromCliJsonnormalizer that maps the legacy CLIobjectChangesshape ontoSuiTransactionResult; persistent networks use an SDKtx.publishflow.getSuiNetworkis now exhaustive (explicitDevnet→localnet,throws on unknown).
assertSuiNetworkverifies the endpoint's chain identifier matches theselected
--network(mainnet/testnet) and is invoked insideexecuteTransactionBlock, so any--network/--rpcmismatch is refused before signing. The signer carries itsnetwork.tsconfig.jsonaddspathsoverrides mapping thev2 subpath types (
@mysten/sui/{grpc,graphql,transactions,...}) to their.d.mtsfiles; a newtsconfig.build.jsonclearspathsfor esbuild (which resolves the packageexportsmapnatively); the build script passes
--tsconfig=tsconfig.build.json; the Jest transform nowhandles
.mjs/.mtsand transpiles@mysten/@noble/@scureESM.sui-utils.test.ts(object filters, published-packageextraction, address/type normalization+validation, and a
CoinTypeKeyBcswire-byte assertionagainst on-chain ground truth) and
smoke.test.ts(a toolchain canary that loads the v2subpath modules).
Files Modified
clients/js/src/chains/sui/utils.ts— provider, signer,executeTransactionBlockenvelope,pagination helpers,
getSuiNetwork/assertSuiNetworkguard.clients/js/src/sdk/sui.ts—getOriginalAssetSui/getForeignAssetSui/getTokenCoinType,getObjectFields, BCS schemas, type/address validators.clients/js/src/chains/sui/{publish,submit,transfer,registrations,buildCoin,log}.ts— deploynormalization, governance submit/createWrapped, transfer PTB, registration listing, coin build.
clients/js/src/cmds/sui/{deploy,init,setup,publishMessage,utils}.ts— command wiring to thenew client and result shape.
clients/js/src/chains/generic/{provider,getWrappedAssetAddress}.ts,clients/js/src/chains/generic/getOriginalAsset.ts—SuiGrpcClientprovider type andChainlookups.clients/js/{tsconfig.json,tsconfig.build.json,jest.config.json,package.json,package-lock.json}— ESM resolution, build/test config, dependency pin.
clients/js/src/chains/sui/__tests__/{sui-utils,smoke}.test.ts— new unit tests.Testing
tsc --noEmit) and build (esbuild) clean; unit suites green.mainnet (object fields, wrapped/native original-asset decode, coin-type forward/reverse
round-trip, registration listing, error paths).
executeTransactionBlockenvelope mappingon a real signed transaction;
worm sui deployend-to-end through realsui client test-publishoutput; theWormholeMessage-style event decode confirmed (gRPC encodes thevector<u8>event fields as base64); and the network guard confirmed to refuse a mismatched--network/endpoint before signing.wormartifact runs read-only Sui subcommands against testnet, confirming theESM-only dependency loads at runtime.
Additional Notes
@mysten/suiv2 is ESM-only withengines.node >= 22; thetsconfig.jsonpathsoverridesare pinned to the exact
2.19.0distlayout. A follow-up tomoduleResolution: "bundler"would let
tscread theexportsmap natively and remove the overrides.:443endpoints, so the default RPCsin
networks.tsare unchanged.queryRegistrationsSuikeys results by chainname via
chainIdToChain, which returnsundefinedfor chain ids newer than the installedSDK, collapsing all such rows under a single
undefinedkey. Falling back to the numeric chainid as the key would preserve them.