refactor: use types from sdk-cosmos npm package#416
Conversation
📝 WalkthroughWalkthroughMigrates numerous modules from local/types to Zetachain SDK “SDKType” models, updates comparisons to enum-to-JSON helpers, and normalizes numeric/string/bigint handling. Adds @zetachain/sdk-cosmos dependency. Adjusts utilities, queries, commands, and tests to new types; minor logic tweaks (string coercions, RPC lookups, VM checks). Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant CLI as CLI (cctx query)
participant API as Observer API
participant SDK as SDK Type Helpers
User->>CLI: query cctx --hash <txHash>
CLI->>API: GET /cctx/<txHash>
API-->>CLI: QueryGetCctxResponseSDKType
CLI->>SDK: cctxStatusToJSON(status)
SDK-->>CLI: "Pending" | "OutboundMined" | "Reverted" | ...
alt Missing inbound_params or cctx_status
CLI->>User: No details available
else Status known
CLI->>User: Emit formatted CCTX (SDKType)
end
loop Poll pending until terminal
CLI->>API: GET /pending-nonces/<tss>
API-->>CLI: PendingNoncesSDKType[]
CLI->>CLI: Match by String(chain_id)/nonce ranges
CLI->>User: Update events/status
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (10)
src/query/fees.ts (1)
2-16: Use the camelCase response type or normalize snake_case fields
Replace the genericQueryAllForeignCoinsResponseSDKType(definesforeign_coins) with the non-SDK typeQueryAllForeignCoinsResponse(which usesforeignCoins), or explicitly mapresponse.data.foreign_coins→foreignCoinsbefore use.packages/commands/src/query/cctx.ts (5)
131-146: Guard outbound_params and fix snake_case field to prevent crashesoutbound_params is used without existence/length checks, and receiver_chainId should be receiver_chain_id for SDKType.
Apply:
} = cctx; - if (!inbound_params || !cctx_status) return ""; + if (!inbound_params || !cctx_status || !outbound_params?.length) return ""; const { sender_chain_id, sender, amount, coin_type } = inbound_params; - const { receiver_chainId, receiver } = outbound_params[0]; + const firstOutbound = outbound_params[0]; + const { receiver_chain_id, receiver } = firstOutbound; const { status, status_message, error_message = "" } = cctx_status;
169-177: Render human-readable status stringstatus appears to be enum numeric; print JSON name for readability.
- } else { - mainStatus = status; - } + } else { + mainStatus = cctxStatusToJSON(status); + }
179-183: Fix outbound hash rendering (guarded access + snake_case)Prevents undefined output and aligns with SDKType field names.
- const outboundHash = - outbound_params[0].hash === "" - ? `Waiting for a transaction on chain ${receiver_chainId}...` - : `${outbound_params[0].hash} (on chain ${receiver_chainId})`; + const outboundHash = + firstOutbound.hash === "" + ? `Waiting for a transaction on chain ${receiver_chain_id}...` + : `${firstOutbound.hash} (on chain ${receiver_chain_id})`;
241-246: Fix receiver chain field names in revert/abort detailsUse SDKType snake_case for both hops.
- if (isReverted || isPendingRevert) { - chainDetails = `${receiver_chainId} → ${outbound_params[1].receiver_chainId} ${statusIcon} ${statusMessage}`; + if (isReverted || isPendingRevert) { + chainDetails = `${receiver_chain_id} → ${outbound_params[1].receiver_chain_id} ${statusIcon} ${statusMessage}`;
257-259: Fix snake_case in Tx Hash chain renderingAlign with SDKType.
- revertOrAbortTx += `Tx Hash: ${outbound_params[1].hash} (on chain ${outbound_params[1].receiver_chainId}) + revertOrAbortTx += `Tx Hash: ${outbound_params[1].hash} (on chain ${outbound_params[1].receiver_chain_id})utils/trackCCTX.ts (1)
201-205: Normalize gateway response before storing in state.
getPendingNoncesForTssreturns the raw SDK-type payload with bigint fields. Persisting the response verbatim means serializingbigintvalues into state, which will break JSON serialization in consumers (and Jest snapshots). We should normalize them (e.g., convert tostring) right after retrieval, matching the pre-migration behaviour.utils/transactions.ts (1)
49-57: Don’t stringify possibly undefined fields; this breaks status-change detection.
String(...)turnsundefinedinto"undefined", sostatusDefinedbecomes true and diffing logic misfires.Apply this diff:
const tx: CCTX = { confirmed_on_destination: false, outbound_tx_hash: cctx.outbound_params[0].hash, outbound_tx_tss_nonce: Number(cctx.outbound_params[0].tss_nonce), - receiver_chainId: String(cctx.outbound_params[0].receiver_chainId), - sender_chain_id: String(cctx?.inbound_params?.sender_chain_id), - status: String(cctx?.cctx_status?.status), - status_message: String(cctx?.cctx_status?.status_message), + receiver_chainId: String(cctx.outbound_params[0].receiver_chainId), + sender_chain_id: cctx?.inbound_params?.sender_chain_id != null + ? String(cctx.inbound_params.sender_chain_id) + : undefined, + status: cctx?.cctx_status?.status, + status_message: cctx?.cctx_status?.status_message, };This preserves
undefinedand keeps your diffing logic correct.utils/balances.ts (2)
92-123: Fix VM matching against SDK enums
vmToJSON(Vm.evm)returns labels like"VM_EVM", whilesupportedChain.vmonChainSDKTypeis a numeric enum value (e.g.,1). Comparing"1"against"VM_EVM"always fails, so we never add the native ERC20/SPL/SUI tokens. Stringifying both sides doesn’t help—one side stays numeric. ConvertsupportedChain.vmwithvmToJSONbefore the comparison (or compare the numeric enums directly) so EVM/SVM/SUI tokens are restored.Apply this diff to restore correct matching:
- if (String(supportedChain?.vm) === vmToJSON(Vm.evm)) { + if (supportedChain && vmToJSON(supportedChain.vm) === vmToJSON(Vm.evm)) { const evmToken: Token = { chain_id: String(foreignCoin.foreign_chain_id), coin_type: "ERC20", contract: foreignCoin.asset, decimals: foreignCoin.decimals, symbol: foreignCoin.symbol, zrc20: foreignCoin.zrc20_contract_address, }; return [evmToken, zrc20Token]; - } else if (String(supportedChain?.vm) === vmToJSON(Vm.svm)) { + } else if (supportedChain && vmToJSON(supportedChain.vm) === vmToJSON(Vm.svm)) { const svmToken: Token = { chain_id: String(foreignCoin.foreign_chain_id), coin_type: "SPL", contract: foreignCoin.asset, decimals: foreignCoin.decimals, symbol: foreignCoin.symbol, zrc20: foreignCoin.zrc20_contract_address, }; return [svmToken, zrc20Token]; - } else if (String(supportedChain?.vm) === vmToJSON(Vm.mvm_sui)) { + } else if (supportedChain && vmToJSON(supportedChain.vm) === vmToJSON(Vm.mvm_sui)) { const svmToken: Token = { chain_id: String(foreignCoin.foreign_chain_id), coin_type: "SUI", contract: foreignCoin.asset, decimals: foreignCoin.decimals, symbol: foreignCoin.symbol, zrc20: foreignCoin.zrc20_contract_address, }; return [svmToken, zrc20Token]; }
195-216: Handle BigInt chain IDs when enriching tokens
ChainSDKType.chain_idis abigint, whiletoken.chain_idis stored as a string. Comparing them directly leaveschain_nameundefined, so every token gets filtered out. Convert the chain ID you compare against to a string (or coerce both sides tobigint) before matching.Use string comparison to retain tokens:
- const chain_name = supportedChains.find( - (c) => c.chain_id === token.chain_id - )?.name; + const chain_name = supportedChains.find( + (c) => String(c.chain_id) === String(token.chain_id) + )?.name;
🧹 Nitpick comments (15)
utils/uniswap.ts (1)
4-4: Import as type to avoid extraneous runtime importUse a type-only import for ForeignCoinsSDKType.
Apply:
-import { ForeignCoinsSDKType } from "@zetachain/sdk-cosmos/zetachain/zetacore/fungible/foreign_coins"; +import type { ForeignCoinsSDKType } from "@zetachain/sdk-cosmos/zetachain/zetacore/fungible/foreign_coins";packages/client/src/getBalances.ts (1)
1-5: Prefer type-only import for ChainSDKTypeAvoids bringing in modules at runtime unnecessarily.
Apply:
-import { - ChainSDKType, - Vm, - vmToJSON, -} from "@zetachain/sdk-cosmos/zetachain/zetacore/pkg/chains/chains"; +import type { ChainSDKType } from "@zetachain/sdk-cosmos/zetachain/zetacore/pkg/chains/chains"; +import { Vm, vmToJSON } from "@zetachain/sdk-cosmos/zetachain/zetacore/pkg/chains/chains";packages/tasks/src/tokens.ts (1)
26-26: Robust fallback for chain display namePrefer nullish-coalescing to support both SDKType name and chain_name during transition.
- const name = chain ? chain.name : "Unsupported Chain"; + const name = chain?.name ?? chain?.chain_name ?? "Unsupported Chain";packages/commands/src/sui/deposit.ts (1)
14-21: Compute numeric chainId once and reuse (optional)Minor cleanup; also consider passing number to suiDeposit if its options expect numeric chainId.
const main = async (options: DepositOptions) => { const keypair = getKeypair(options); + const chainIdNum = Number(options.chainId); const isConfirmed = await confirmTransaction({ amount: options.amount, receiver: options.receiver, - rpc: getSuiRpcByChainId(Number(options.chainId)), + rpc: getSuiRpcByChainId(chainIdNum), sender: keypair.toSuiAddress(), }); @@ - { - chainId: options.chainId, + { + chainId: chainIdNum, gasLimit: options.gasBudget,Confirm suiDeposit expects a number for chainId; if it requires string, skip the second change.
Also applies to: 30-31
types/balances.types.ts (1)
5-8: Widening chain_id to bigint: audit JSON/formatting pathsBigInt cannot be JSON.stringified and breaks string ops (e.g., concatenation, localeCompare). Ensure downstream code normalizes chain_id to string at display/boundary layers (parseTokenId, enrichTokens, table sort).
Option: introduce a ChainId = string | bigint alias and add a normalizeChainId(value: ChainId): string helper, using it wherever IDs are printed/compared/sorted.
Also applies to: 18-21
test/balances.test.ts (1)
97-109: Align test types consistently and drop ts-nocheckYou switched one case to ChainSDKType[] but still use ObserverSupportedChain elsewhere under @ts-nocheck. Prefer importing ChainSDKType and updating all occurrences; then remove ts-nocheck.
Example import:
import type { ChainSDKType } from "@zetachain/sdk-cosmos/zetachain/zetacore/observer/chain";test/transactions.test.ts (1)
130-131: Prefer runtime-shape mocks over SDK types in tests.The tests only need the minimal shape consumed by
updateEmitter. Importing and instantiatingPendingNoncesSDKTypebrings in the protobuf-specific bigint structure, forcing us to mock BigInts manually. Consider reverting to a local helper/mock shape (or narrowing the type viaPick) to keep the test data lightweight and focused.packages/commands/src/query/chains/list.ts (1)
42-47: Avoid anonymous response shapes; import the SDK query response type.Hard-coding
{ chain_params: { chain_params: ChainParamsSDKType[] } }is brittle. Prefer the official response type from the SDK (e.g.,QueryGetChainParamsResponseSDKType) if available.If that type exists, update:
-import { ChainParamsSDKType } from "@zetachain/sdk-cosmos/zetachain/zetacore/observer/params"; +import { ChainParamsSDKType } from "@zetachain/sdk-cosmos/zetachain/zetacore/observer/params"; +import type { QueryGetChainParamsResponseSDKType } from "@zetachain/sdk-cosmos/zetachain/zetacore/observer/query"; - const [chainsData, tokensData, chainParamsData] = await Promise.all([ + const [chainsData, tokensData, chainParamsData] = await Promise.all([ /* ... */ - fetchFromApi<{ - chain_params: { - chain_params: ChainParamsSDKType[]; - }; - }>(api, "/zeta-chain/observer/get_chain_params"), + fetchFromApi<QueryGetChainParamsResponseSDKType>( + api, + "/zeta-chain/observer/get_chain_params" + ), ]);If the exact type name differs, please pick the correct one from the SDK’s
observer/querymodule.utils/transactions.ts (1)
100-105: Prevent integer precision loss when computing queue size.
Number(...)on 64-bit nonces can overflow. Use BigInt arithmetic and stringify for display.Apply this diff:
- if (pendingNonce) { - const pending = pendingNonce.nonce_low || "0"; - const current = tx.outbound_tx_tss_nonce; - const diff = current - Number(pending); - queue = diff > 0 ? ` (${diff} in queue)` : ""; - } + if (pendingNonce) { + const pendingBI = BigInt(pendingNonce.nonce_low ?? "0"); + const currentBI = BigInt(tx.outbound_tx_tss_nonce ?? 0); + const diffBI = currentBI - pendingBI; + queue = diffBI > 0n ? ` (${diffBI.toString()} in queue)` : ""; + }packages/client/src/getFees.ts (1)
98-102: Fix misleading error context.This handler is for CCM fees, not CCTX by inbound hash.
Apply this diff:
- handleError({ - context: "Something failed fetching CCTX By Inbound hash", - error, - }); + handleError({ + context: "Something went wrong fetching CCM fees", + error, + });packages/commands/src/query/tokens/list.ts (1)
65-69: Safe numeric sort.Using
parseInt(String(...))is fine here. If you ever receive non-numeric IDs, add a fallback comparator to keep a stable order.utils/api.ts (1)
93-107: Consider server-side filtering for scalability.If the endpoint supports it, fetch with a
?tss=${tss}filter to avoid pulling all nonces and filtering client-side.Apply if supported:
- const data = await fetchFromApi<QueryAllPendingNoncesResponseSDKType>( - api, - `/zeta-chain/observer/pendingNonces` - ); - return data.pending_nonces.filter((n) => n.tss === tss); + const data = await fetchFromApi<QueryAllPendingNoncesResponseSDKType>( + api, + `/zeta-chain/observer/pendingNonces?tss=${encodeURIComponent(tss)}` + ); + return data.pending_nonces;packages/commands/src/query/tokens/show.ts (2)
25-36: Render human-readable coin type.Currently
String(token.coin_type)prints the enum’s numeric value. Prefer mapping to the enum key for UX.Apply this diff:
- ["Type", String(token.coin_type)], + ["Type", CoinType[Number(token.coin_type)] ?? String(token.coin_type)],Add the enum import at the top:
import { CoinType } from "@zetachain/sdk-cosmos/zetachain/zetacore/pkg/coin/coin";
41-77: Optional: Support friendly field aliases.To match the chains command UX, consider accepting aliases like
chain_id/chain-idby normalizingfieldbefore lookup.packages/commands/src/query/chains/show.ts (1)
47-56: RPC URL helpers: consistent normalization.Casting once improves clarity and avoids accidental type drift.
Apply:
- if (isSolanaChainId(String(chain.chain_id))) { - return getAPIbyChainId(String(chain.chain_id)); + const idStr = String(chain.chain_id); + if (isSolanaChainId(idStr)) { + return getAPIbyChainId(idStr); } - if (isSuiChainId(String(chain.chain_id))) { - return getSuiRpcByChainId(Number(chain.chain_id)); + if (isSuiChainId(idStr)) { + return getSuiRpcByChainId(Number(idStr)); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (24)
typechain-types/factories/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy__factory.tsis excluded by!typechain-types/**typechain-types/factories/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils__factory.tsis excluded by!typechain-types/**typechain-types/factories/@openzeppelin/contracts/token/ERC20/utils/SafeERC20__factory.tsis excluded by!typechain-types/**typechain-types/factories/@openzeppelin/contracts/utils/Address__factory.tsis excluded by!typechain-types/**typechain-types/factories/@zetachain/protocol-contracts/contracts/evm/ERC20Custody__factory.tsis excluded by!typechain-types/**typechain-types/factories/@zetachain/protocol-contracts/contracts/evm/GatewayEVM__factory.tsis excluded by!typechain-types/**typechain-types/factories/@zetachain/protocol-contracts/contracts/evm/ZetaConnectorNative__factory.tsis excluded by!typechain-types/**typechain-types/factories/@zetachain/protocol-contracts/contracts/evm/ZetaConnectorNonNative__factory.tsis excluded by!typechain-types/**typechain-types/factories/@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/EthZetaMock.sol/ZetaEthMock__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/SwapHelpers.sol/SwapLibrary__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/TestZRC20__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/shared/MockZRC20__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/EVMSetup.t.sol/EVMSetup__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/FoundrySetup.t.sol/FoundrySetup__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/TokenSetup.t.sol/TokenSetup__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/UniswapV2SetupLib.sol/UniswapV2SetupLib__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/UniswapV3SetupLib.sol/UniswapV3SetupLib__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/ZetaSetup.t.sol/ZetaSetup__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/mock/ERC20Mock__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/mockGateway/NodeLogicMock__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/mockGateway/WrapGatewayEVM__factory.tsis excluded by!typechain-types/**typechain-types/factories/contracts/testing/mockGateway/WrapGatewayZEVM__factory.tsis excluded by!typechain-types/**yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (31)
package.json(2 hunks)packages/client/src/getBalances.ts(2 hunks)packages/client/src/getFees.ts(4 hunks)packages/client/src/getForeignCoins.ts(1 hunks)packages/client/src/getQuote.ts(2 hunks)packages/client/src/getSupportedChains.ts(1 hunks)packages/commands/src/query/cctx.ts(10 hunks)packages/commands/src/query/chains/list.ts(4 hunks)packages/commands/src/query/chains/show.ts(9 hunks)packages/commands/src/query/tokens/list.ts(4 hunks)packages/commands/src/query/tokens/show.ts(3 hunks)packages/commands/src/sui/deposit.ts(1 hunks)packages/commands/src/sui/depositAndCall.ts(1 hunks)packages/tasks/src/tokens.ts(1 hunks)src/query/fees.ts(3 hunks)test/balances.test.ts(1 hunks)test/trackCCTXUtils.test.ts(2 hunks)test/transactions.test.ts(4 hunks)test/uniswap.test.ts(3 hunks)types/balances.types.ts(2 hunks)types/chains.types.ts(2 hunks)types/foreignCoins.types.ts(0 hunks)types/getFees.types.ts(0 hunks)types/supportedChains.types.ts(0 hunks)types/trackCCTX.types.ts(0 hunks)utils/api.ts(3 hunks)utils/balances.ts(7 hunks)utils/sui.ts(1 hunks)utils/trackCCTX.ts(3 hunks)utils/transactions.ts(4 hunks)utils/uniswap.ts(2 hunks)
💤 Files with no reviewable changes (4)
- types/getFees.types.ts
- types/supportedChains.types.ts
- types/foreignCoins.types.ts
- types/trackCCTX.types.ts
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: fadeev
PR: zeta-chain/toolkit#346
File: packages/commands/src/ton/depositAndCall.ts:0-0
Timestamp: 2025-06-13T15:33:54.781Z
Learning: fadeev prefers responses in English.
Learnt from: fadeev
PR: zeta-chain/toolkit#346
File: packages/commands/src/ton/deposit.ts:0-0
Timestamp: 2025-06-13T15:32:09.225Z
Learning: User fadeev prefers that responses be in English; avoid replying in Russian in future interactions.
🧬 Code graph analysis (8)
packages/client/src/getForeignCoins.ts (1)
packages/client/src/client.ts (1)
ZetaChainClient(100-293)
packages/commands/src/sui/depositAndCall.ts (1)
utils/sui.ts (1)
getSuiRpcByChainId(289-293)
packages/commands/src/sui/deposit.ts (1)
utils/sui.ts (1)
getSuiRpcByChainId(289-293)
packages/commands/src/query/chains/list.ts (2)
utils/api.ts (1)
fetchFromApi(15-33)types/chains.types.ts (2)
ChainTokenMap(5-7)ChainConfirmationMap(9-11)
utils/balances.ts (1)
types/balances.types.ts (1)
Token(18-27)
packages/client/src/getBalances.ts (2)
utils/balances.ts (1)
prepareMulticallContexts(223-260)utils/chains.ts (1)
getRpcUrl(3-15)
packages/commands/src/query/chains/show.ts (2)
utils/solana.commands.helpers.ts (1)
getAPIbyChainId(140-148)utils/sui.ts (1)
getSuiRpcByChainId(289-293)
packages/commands/src/query/cctx.ts (1)
src/schemas/commands/cctx.ts (1)
cctxOptionsSchema(5-10)
🔇 Additional comments (22)
packages/client/src/getQuote.ts (2)
5-5: Verify SDK import path stability for CoinTypeEnsure this path exists in the targeted sdk-cosmos version you plan to publish/use. Some versions differ in generated paths.
82-84: Good: robust type-safe gas token detectionSwitching to CoinType.Gas and normalizing foreign_chain_id to string improves correctness and clarity.
src/query/fees.ts (1)
87-87: LGTM: chain_id normalized to stringString(contract.foreign_chain_id) avoids type ambiguity and ensures stable sorting.
utils/uniswap.ts (1)
242-242: Type update looks correctSignature now aligns with SDKType usage across the codebase.
packages/client/src/getBalances.ts (3)
83-83: LGTM: explicit ChainSDKType in predicateKeeps typings tight while searching by chain name.
87-87: LGTM: Number() on chain_idNumber(chain.chain_id) is appropriate here for viem chain lookup.
75-78: Normalize VM comparison with vmToJSON
At packages/client/src/getBalances.ts:75-78 replaceString( supportedChains.find(c => c.name === token.chain_name)?.vm ) === vmToJSON(Vm.evm)with
(() => { const vmVal = supportedChains.find(c => c.name === token.chain_name)?.vm as Vm; return vmVal === Vm.evm || vmToJSON(vmVal) === vmToJSON(Vm.evm); })()Ensure
evmTokensis non-empty at runtime for known EVM chains.package.json (1)
153-153: No action needed: @zetachain/sdk-cosmos@0.0.7 is published npm registry shows 0.0.7 as the latest version.packages/commands/src/query/cctx.ts (2)
96-113: Endpoint contract check: querying index via inboundHashToCctxDataYou add tx.index to the frontier but keep calling /inboundHashToCctxData/. Confirm the endpoint accepts indexes; otherwise this will retry 404s forever.
1-9: No action needed: @zetachain/sdk-cosmos@0.0.7 (latest) is pinned in package.jsonpackages/commands/src/sui/deposit.ts (1)
19-19: LGTM: numeric chainId for RPC lookupCasting to number matches getSuiRpcByChainId signature.
types/chains.types.ts (1)
1-17: LGTM: central types now consistently use SDKType variants.The switch to
ForeignCoinsSDKType,ChainParamsSDKType, andChainSDKTypealigns with the rest of the refactor.packages/client/src/getFees.ts (1)
80-96: SDKType JSON field mismatch in CCM fees response
QueryConvertGasToZetaResponseSDKTypedefines snake_case fields; accessingdata.outboundGasInZeta/data.protocolFeeInZetawill be undefined.- Either use snake_case keys (
data.outbound_gas_in_zeta,data.protocol_fee_in_zeta) or switch to the non-SDK response type to keep camelCase.- Confirm which JSON shape your REST gateway returns and standardize across all clients.
packages/commands/src/query/chains/list.ts (1)
34-41: Useforeign_coinsinstead offoreignCoins.
Inpackages/commands/src/query/chains/list.ts, replace:- tokens: tokensData.foreignCoins, + tokens: tokensData.foreign_coins,Also apply the same change to the block at lines 49–52.
test/trackCCTXUtils.test.ts (2)
163-170: LGTM on SDKType migration and BigInt usage.Using
PendingNoncesSDKType[]and BigInt fields matches the SDKType shape; test intent is clear.
1-1: Verified export and version
@zetachain/sdk-cosmosv0.0.7 is published and the package exportsPendingNoncesSDKTypefromzetachain/zetacore/observer/pending_nonces.test/uniswap.test.ts (3)
1-2: LGTM: enum/type imports align with SDKType migration.
69-76: BigInt and enum values look correct.Using
CoinType.ERC20and BigInt forforeign_chain_id/gas_limitis consistent.Also applies to: 84-88
101-119: Good coverage of address case-insensitivity and unknown tokens.The tests exercise core edge cases for
formatPoolsWithTokenDetails.Also applies to: 128-148, 183-209
utils/api.ts (2)
1-3: Note: Package/version availability check.If package.json pins
@zetachain/sdk-cosmos@0.0.7, verify it exists publicly; otherwise CI will break. See earlier comment for a script.
39-47: Type return refactor looks correct.Returning
QueryGetCctxResponseSDKType["CrossChainTx"] | undefinedkeeps the surface aligned with SDK types.packages/commands/src/query/chains/show.ts (1)
261-265: Search by chain ID: good normalization.Using
String(c.chain_id) === searchValueis correct post-migration.
| if ( | ||
| status === "OutboundMined" || | ||
| (status === "PendingOutbound" && outbound_params[0].hash !== "") | ||
| String(status) === cctxStatusToJSON(CctxStatus.OutboundMined) || | ||
| (String(status) === cctxStatusToJSON(CctxStatus.PendingOutbound) && | ||
| outbound_params[0].hash !== "") | ||
| ) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use firstOutbound.hash after guarding array access
Avoid direct indexing; rely on the guarded alias.
- (String(status) === cctxStatusToJSON(CctxStatus.PendingOutbound) &&
- outbound_params[0].hash !== "")
+ (String(status) === cctxStatusToJSON(CctxStatus.PendingOutbound) &&
+ firstOutbound.hash !== "")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ( | |
| status === "OutboundMined" || | |
| (status === "PendingOutbound" && outbound_params[0].hash !== "") | |
| String(status) === cctxStatusToJSON(CctxStatus.OutboundMined) || | |
| (String(status) === cctxStatusToJSON(CctxStatus.PendingOutbound) && | |
| outbound_params[0].hash !== "") | |
| ) { | |
| if ( | |
| String(status) === cctxStatusToJSON(CctxStatus.OutboundMined) || | |
| (String(status) === cctxStatusToJSON(CctxStatus.PendingOutbound) && | |
| firstOutbound.hash !== "") | |
| ) { |
🤖 Prompt for AI Agents
In packages/commands/src/query/cctx.ts around lines 154 to 158, the condition
directly indexes outbound_params[0].hash after only partially guarding array
access; change it to first verify outbound_params.length > 0, create a guarded
alias (e.g. const firstOutbound = outbound_params[0]) and then use
firstOutbound.hash in the conditional, or rewrite the condition to check
outbound_params.length > 0 && firstOutbound.hash !== "" so array access is
always safe.
| if (String(coin_type) !== coinTypeToJSON(CoinType.NoAssetCall)) { | ||
| mainTx += `Amount: ${amount} ${coin_type} tokens\n`; | ||
| } |
There was a problem hiding this comment.
Fix coin type comparison and display
String(coin_type) will not match JSON names; use coinTypeToJSON consistently.
- if (String(coin_type) !== coinTypeToJSON(CoinType.NoAssetCall)) {
- mainTx += `Amount: ${amount} ${coin_type} tokens\n`;
- }
+ if (coinTypeToJSON(coin_type) !== coinTypeToJSON(CoinType.NoAssetCall)) {
+ mainTx += `Amount: ${amount} ${coinTypeToJSON(coin_type)} tokens\n`;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (String(coin_type) !== coinTypeToJSON(CoinType.NoAssetCall)) { | |
| mainTx += `Amount: ${amount} ${coin_type} tokens\n`; | |
| } | |
| if (coinTypeToJSON(coin_type) !== coinTypeToJSON(CoinType.NoAssetCall)) { | |
| mainTx += `Amount: ${amount} ${coinTypeToJSON(coin_type)} tokens\n`; | |
| } |
🤖 Prompt for AI Agents
In packages/commands/src/query/cctx.ts around lines 196 to 198, the code
compares String(coin_type) to coinTypeToJSON(CoinType.NoAssetCall) and prints
coin_type directly; change the comparison to use coinTypeToJSON(coin_type) !==
coinTypeToJSON(CoinType.NoAssetCall) and when building the display line use
coinTypeToJSON(coin_type) (or a mapped readable token name) instead of
String(coin_type), so both the equality check and the printed token name use the
same JSON name format.
| outbound_params[1] && | ||
| ["Reverted", "PendingRevert", "Aborted"].includes(status) | ||
| ["Reverted", "PendingRevert", "Aborted"].includes(String(status)) | ||
| ) { | ||
| const isReverted = status === "Reverted"; | ||
| const isPendingRevert = status === "PendingRevert"; | ||
| const isAborted = status === "Aborted"; | ||
| const isReverted = String(status) === cctxStatusToJSON(CctxStatus.Reverted); | ||
| const isPendingRevert = | ||
| String(status) === cctxStatusToJSON(CctxStatus.PendingRevert); | ||
| const isAborted = String(status) === cctxStatusToJSON(CctxStatus.Aborted); | ||
|
|
There was a problem hiding this comment.
Revert/abort block never executes due to wrong status includes
String(status) won’t equal "Reverted"/"PendingRevert"/"Aborted". Compute flags via cctxStatusToJSON and gate on them.
- if (
- outbound_params[1] &&
- ["Reverted", "PendingRevert", "Aborted"].includes(String(status))
- ) {
- const isReverted = String(status) === cctxStatusToJSON(CctxStatus.Reverted);
- const isPendingRevert =
- String(status) === cctxStatusToJSON(CctxStatus.PendingRevert);
- const isAborted = String(status) === cctxStatusToJSON(CctxStatus.Aborted);
+ const isReverted = cctxStatusToJSON(status) === cctxStatusToJSON(CctxStatus.Reverted);
+ const isPendingRevert = cctxStatusToJSON(status) === cctxStatusToJSON(CctxStatus.PendingRevert);
+ const isAborted = cctxStatusToJSON(status) === cctxStatusToJSON(CctxStatus.Aborted);
+ if (outbound_params[1] && (isReverted || isPendingRevert || isAborted)) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| outbound_params[1] && | |
| ["Reverted", "PendingRevert", "Aborted"].includes(status) | |
| ["Reverted", "PendingRevert", "Aborted"].includes(String(status)) | |
| ) { | |
| const isReverted = status === "Reverted"; | |
| const isPendingRevert = status === "PendingRevert"; | |
| const isAborted = status === "Aborted"; | |
| const isReverted = String(status) === cctxStatusToJSON(CctxStatus.Reverted); | |
| const isPendingRevert = | |
| String(status) === cctxStatusToJSON(CctxStatus.PendingRevert); | |
| const isAborted = String(status) === cctxStatusToJSON(CctxStatus.Aborted); | |
| const isReverted = cctxStatusToJSON(status) === cctxStatusToJSON(CctxStatus.Reverted); | |
| const isPendingRevert = cctxStatusToJSON(status) === cctxStatusToJSON(CctxStatus.PendingRevert); | |
| const isAborted = cctxStatusToJSON(status) === cctxStatusToJSON(CctxStatus.Aborted); | |
| if (outbound_params[1] && (isReverted || isPendingRevert || isAborted)) { |
🤖 Prompt for AI Agents
In packages/commands/src/query/cctx.ts around lines 214 to 221, the condition
uses literal strings ("Reverted","PendingRevert","Aborted") but status is
serialized via cctxStatusToJSON, so the branch never runs; compute the expected
string constants by calling cctxStatusToJSON(CctxStatus.Reverted/ PendingRevert/
Aborted) (or compute isReverted/isPendingRevert/isAborted first) and use those
values in the includes/gate expression (or directly gate on the boolean flags)
so the revert/abort block executes correctly.
| chain: ChainSDKType, | ||
| allTokens: ForeignCoinsSDKType[], | ||
| chainParams: ChainParamsSDKType[] | ||
| ): ChainInfo => { | ||
| const tokens = allTokens | ||
| .filter((t) => t.foreign_chain_id === chain.chain_id) | ||
| .filter((t) => t.foreign_chain_id === BigInt(chain.chain_id)) | ||
| .map((t) => t.symbol); | ||
|
|
||
| const confirmations = chainParams.find( | ||
| (p) => p.chain_id === chain.chain_id | ||
| )?.confirmation_count; | ||
| const confirmations = chainParams | ||
| .find((p) => p.chain_id === BigInt(chain.chain_id)) | ||
| ?.confirmation_count.toString(); | ||
|
|
There was a problem hiding this comment.
Bug: Mixed string/bigint comparison breaks token/confirmations lookup.
foreign_chain_id and chain_id may arrive as strings from JSON. Comparing to BigInt(...) will fail, yielding empty tokens/confirmations.
Use a consistent normalization (string or BigInt) on both sides. Minimal fix:
- const tokens = allTokens
- .filter((t) => t.foreign_chain_id === BigInt(chain.chain_id))
- .map((t) => t.symbol);
+ const chainIdStr = String(chain.chain_id);
+ const tokens = allTokens
+ .filter((t) => String(t.foreign_chain_id) === chainIdStr)
+ .map((t) => t.symbol);
-
- const confirmations = chainParams
- .find((p) => p.chain_id === BigInt(chain.chain_id))
- ?.confirmation_count.toString();
+ const confirmations = chainParams
+ .find((p) => String(p.chain_id) === chainIdStr)
+ ?.confirmation_count?.toString();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| chain: ChainSDKType, | |
| allTokens: ForeignCoinsSDKType[], | |
| chainParams: ChainParamsSDKType[] | |
| ): ChainInfo => { | |
| const tokens = allTokens | |
| .filter((t) => t.foreign_chain_id === chain.chain_id) | |
| .filter((t) => t.foreign_chain_id === BigInt(chain.chain_id)) | |
| .map((t) => t.symbol); | |
| const confirmations = chainParams.find( | |
| (p) => p.chain_id === chain.chain_id | |
| )?.confirmation_count; | |
| const confirmations = chainParams | |
| .find((p) => p.chain_id === BigInt(chain.chain_id)) | |
| ?.confirmation_count.toString(); | |
| chain: ChainSDKType, | |
| allTokens: ForeignCoinsSDKType[], | |
| chainParams: ChainParamsSDKType[] | |
| ): ChainInfo => { | |
| const chainIdStr = String(chain.chain_id); | |
| const tokens = allTokens | |
| .filter((t) => String(t.foreign_chain_id) === chainIdStr) | |
| .map((t) => t.symbol); | |
| const confirmations = chainParams | |
| .find((p) => String(p.chain_id) === chainIdStr) | |
| ?.confirmation_count?.toString(); |
🤖 Prompt for AI Agents
In packages/commands/src/query/chains/show.ts around lines 149 to 160, token and
confirmation lookups compare values with BigInt(...) while incoming JSON fields
may be strings, causing mismatches; normalize both sides to the same type
(prefer strings for safe JSON handling): convert chain.chain_id and p.chain_id/
t.foreign_chain_id to String(...) before comparing (e.g.
String(t.foreign_chain_id) === String(chain.chain_id)), and when reading
confirmation_count convert it robustly to string (e.g. confirmation_count !=
null ? String(p.confirmation_count) : undefined); update the filter/find
comparisons accordingly so mixed string/BigInt inputs match.
| import { ForeignCoinsSDKType } from "@zetachain/sdk-cosmos/zetachain/zetacore/fungible/foreign_coins"; | ||
| import { QueryAllForeignCoinsResponseSDKType } from "@zetachain/sdk-cosmos/zetachain/zetacore/fungible/query"; |
There was a problem hiding this comment.
🧩 Analysis chain
SDKType JSON field mismatch and inconsistent HTTP client.
QueryAllForeignCoinsResponseSDKTypeusesforeign_coins, notforeignCoins.- Prefer the shared
fetchFromApi(with timeout) over rawfetch.
Apply this diff:
import { z } from "zod";
import { DEFAULT_API_URL } from "../../../../../src/constants/api";
import { tokensListOptionsSchema } from "../../../../../src/schemas/commands/tokens";
+import { fetchFromApi } from "../../../../../utils/api";
@@
export const fetchForeignCoins = async (
apiUrl: string
-): Promise<ForeignCoinsSDKType[]> => {
- const response = await fetch(`${apiUrl}/zeta-chain/fungible/foreign_coins`);
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- const data = (await response.json()) as QueryAllForeignCoinsResponseSDKType;
- return data.foreignCoins;
-};
+): Promise<ForeignCoinsSDKType[]> => {
+ const data = await fetchFromApi<QueryAllForeignCoinsResponseSDKType>(
+ apiUrl,
+ "/zeta-chain/fungible/foreign_coins"
+ );
+ return data.foreign_coins;
+};Please sanity-check one response payload to confirm the field naming and ensure we didn’t miss other camelCase/snake_case spots.
Also applies to: 14-25
🏁 Script executed:
#!/bin/bash
set -e
echo ">>> Display packages/commands/src/query/tokens/list.ts (first 200 lines)"
sed -n '1,200p' packages/commands/src/query/tokens/list.ts
echo
echo ">>> Locate utils/api files"
fd -t f -I utils/api*
echo
echo ">>> Show definition of fetchFromApi in utils/api"
grep -R "fetchFromApi" utils/api*
echo
echo ">>> Search for fetchFromApi usage across repository"
rg -n "fetchFromApi"Length of output: 4880
🏁 Script executed:
#!/bin/bash
set -e
echo ">>> utils/api.ts (lines 1-200)"
sed -n '1,200p' utils/api.ts
echo
echo ">>> packages/commands/src/query/chains/list.ts (lines 1-200)"
sed -n '1,200p' packages/commands/src/query/chains/list.tsLength of output: 8301
Use shared fetchFromApi and snake_case JSON field
In packages/commands/src/query/tokens/list.ts (lines 14–25), replace the raw fetch call with fetchFromApi and access data.foreign_coins – QueryAllForeignCoinsResponseSDKType uses snake_case. No other camelCase/snake_case mismatches found in this file.
import { tokensListOptionsSchema } from "../../../../../src/schemas/commands/tokens";
+import { fetchFromApi } from "../../../../../utils/api";
@@
-export const fetchForeignCoins = async (
- apiUrl: string
-): Promise<ForeignCoinsSDKType[]> => {
- const response = await fetch(`${apiUrl}/zeta-chain/fungible/foreign_coins`);
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- const data = (await response.json()) as QueryAllForeignCoinsResponseSDKType;
- return data.foreignCoins;
-};
+export const fetchForeignCoins = async (
+ apiUrl: string
+): Promise<ForeignCoinsSDKType[]> => {
+ const data = await fetchFromApi<QueryAllForeignCoinsResponseSDKType>(
+ apiUrl,
+ "/zeta-chain/fungible/foreign_coins"
+ );
+ return data.foreign_coins;
+};🤖 Prompt for AI Agents
In packages/commands/src/query/tokens/list.ts around lines 14–25, the code uses
a raw fetch and accesses camelCase fields; replace the raw fetch call with the
shared fetchFromApi helper (import it from the shared utilities), call it with
the same endpoint, and read the response using snake_case: access
data.foreign_coins (not data.foreignCoins) which matches
QueryAllForeignCoinsResponseSDKType; keep existing SDK types/imports and adapt
any local variable names to use the snake_case property.
| message, | ||
| receiver: options.receiver, | ||
| rpc: getSuiRpcByChainId(options.chainId), | ||
| rpc: getSuiRpcByChainId(Number(options.chainId)), |
There was a problem hiding this comment.
Validate numeric chain ID parsing.
Number(options.chainId) will yield NaN if the CLI ever passes a non-numeric string (e.g., an unexpected enum extension). That would bubble into getSuiRpcByChainId, ultimately calling getFullnodeUrl(undefined). We should either assert the conversion result or keep passing the zod-validated string and let getSuiRpcByChainId handle string literals.
🤖 Prompt for AI Agents
In packages/commands/src/sui/depositAndCall.ts around line 35, the code uses
Number(options.chainId) which can produce NaN for non-numeric inputs; either
validate the conversion or leave the validated string through. Replace
Number(...) with a safe parse and guard: attempt to parse the numeric chainId
(e.g., parseInt) and assert it is a finite number before calling
getSuiRpcByChainId, throwing a clear error if parsing fails, or alternatively
pass options.chainId (the zod-validated string) directly to getSuiRpcByChainId
so that function handles string literals; ensure you do not call
getSuiRpcByChainId with undefined or NaN.
| .map((chain) => { | ||
| const chainLabel = Object.keys(chains).find( | ||
| (key) => chains[key].chain_id === parseInt(chain.chain_id) | ||
| (key) => chains[key].chain_id === Number(chain.chain_id) | ||
| ); | ||
|
|
||
| if (chainLabel) { | ||
| const contract = getAddress("zetaToken", chainLabel as ParamChainName); | ||
| if (contract) { | ||
| return { | ||
| chain_id: chain.chain_id, | ||
| chain_id: String(chain.chain_id), | ||
| coin_type: "ERC20", | ||
| contract, | ||
| decimals: 18, | ||
| symbol: "WZETA", | ||
| } as Token; | ||
| }; | ||
| } | ||
| } | ||
| return null; | ||
| }) | ||
| .filter((token): token is Token => token !== null); | ||
| .filter((token) => token !== null) as Token[]; |
There was a problem hiding this comment.
Preserve ChainSDKType chains without a contract
getAddress("zetaToken", chainLabel) returns undefined for networks without a deployed contract, so this change now filters those chains out entirely. Previously we returned a null token and skipped it later, leaving Zeta-only chains intact. With the .filter(...) as Token[] the corresponding chains vanish from wzetaTokens, so downstream balance displays lose entries.
Stick with the explicit null handling (filtering via type guard) so we don’t silently drop chains.
Apply this diff to keep unsupported chains without WZETA:
- const wzetaTokens = supportedChains
- .map((chain) => {
+ const wzetaTokens = supportedChains
+ .map((chain) => {
const chainLabel = Object.keys(chains).find(
(key) => chains[key].chain_id === Number(chain.chain_id)
);
if (chainLabel) {
const contract = getAddress("zetaToken", chainLabel as ParamChainName);
if (contract) {
return {
chain_id: String(chain.chain_id),
coin_type: "ERC20",
contract,
decimals: 18,
symbol: "WZETA",
};
}
}
return null;
- })
- .filter((token) => token !== null) as Token[];
+ })
+ .filter((token): token is Token => token !== null);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In utils/balances.ts around lines 154–173, the current .filter(... ) as Token[]
removes chains that lack a deployed zetaToken contract; revert to preserving
those entries so downstream Zeta-only chains remain visible by removing the
final filter/cast and returning the mapped array as (Token | null)[] (or
explicitly type the result to include nulls) so nulls are handled later via the
existing type-guard filtering instead of being dropped here.
| export const getSuiRpcByChainId = (chainId: number) => { | ||
| const network = | ||
| networks[chainIds.indexOf(String(chainId) as (typeof chainIds)[number])]; | ||
| return getFullnodeUrl(network); |
There was a problem hiding this comment.
Add explicit error when chain ID is not recognized.
If chainIds.indexOf(...) returns -1 (e.g., because the caller passed NaN or an unsupported ID), network becomes undefined, and getFullnodeUrl will throw a cryptic error. Let’s detect index === -1 up front and throw a clear message (Invalid chain ID), preserving prior type-safety after widening the function signature.
Apply this diff:
export const getSuiRpcByChainId = (chainId: number) => {
- const network =
- networks[chainIds.indexOf(String(chainId) as (typeof chainIds)[number])];
+ const index = chainIds.indexOf(
+ String(chainId) as (typeof chainIds)[number]
+ );
+ if (index === -1) {
+ throw new Error(`Invalid Sui chain ID: ${chainId}`);
+ }
+ const network = networks[index];
return getFullnodeUrl(network);
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const getSuiRpcByChainId = (chainId: number) => { | |
| const network = | |
| networks[chainIds.indexOf(String(chainId) as (typeof chainIds)[number])]; | |
| return getFullnodeUrl(network); | |
| export const getSuiRpcByChainId = (chainId: number) => { | |
| const index = chainIds.indexOf( | |
| String(chainId) as (typeof chainIds)[number] | |
| ); | |
| if (index === -1) { | |
| throw new Error(`Invalid Sui chain ID: ${chainId}`); | |
| } | |
| const network = networks[index]; | |
| return getFullnodeUrl(network); | |
| }; |
🤖 Prompt for AI Agents
In utils/sui.ts around lines 289 to 292, the code derives network via
networks[chainIds.indexOf(String(chainId))] which yields undefined when index
=== -1; add an explicit check: compute index = chainIds.indexOf(String(chainId))
and if index === -1 throw a clear Error('Invalid chain ID: ' + chainId);
otherwise use networks[index] and pass it to getFullnodeUrl; keep the function
signature as-is (number) but ensure you convert to string for the lookup so
type-safety is preserved.
| return ( | ||
| foreignCoin.foreign_chain_id === chainID && | ||
| foreignCoin.coin_type === "Gas" | ||
| String(foreignCoin.foreign_chain_id) === chainID && |
There was a problem hiding this comment.
I do understand why we need to do this (the new sdk type says foreign_chain_id is a bigint while our previous, custom type had it as a string), but I don't think we should be doing this, the real, runtime type is a string.
There was a problem hiding this comment.
The way I see it we could:
- Create a runtime accurate interface:
export interface ForeignCoinsRuntimeType extends Omit<ForeignCoinsSDKType, 'foreign_chain_id'> {
foreign_chain_id: string;
}- Fix the type on the sdk types lib.

Removing types from Toolkit, importing types from
@zetachain/sdk-cosmos.Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Chores
Tests