Skip to content

Conversation

@epsjunior
Copy link
Contributor

@epsjunior epsjunior commented Aug 21, 2025

What

  • Replace unsafe unknown casts with precise types via new BaseActionsClient and capability-based typings.
  • Refactor createClient to use viem-style .extend(...) only for non-conflicting actions and typed object merges for conflicting ones.
  • Type action factories (accounts, chains, wallet) to accept BaseActionsClient.
  • Introduce ContractCapabilities to type contracts/actions.ts helpers (_encodeAddTransactionData, _sendTransaction, etc.).
  • Keep receiptActions typed with a client that includes our own getTransaction.
  • Localnet: call eth_getTransactionByHash directly and adapt to GenLayerTransaction before decodeLocalnetTransaction (with a short comment explaining viem signature/return-type conflict).
  • Relax wallet/connect client param to { chain: GenLayerChain }.
  • Import GenLayerRawTransaction from @/types for testnet path.

Why

  • Avoid unknown/any and align with viem’s extension model while preserving our API surface.
  • Prevent collisions with viem’s protected action names and signatures (getTransaction, readContract, waitForTransactionReceipt).
  • Fix runtime regression in waitForTransactionReceipt on localnet caused by decoding testnet-shaped data.

Testing done

  • Built the package and ran CLI flows:
    • Verified waitForTransactionReceipt on localnet.
    • Exercised getTransaction on localnet and studionet networks.
    • Sanity-checked getContractSchemaForCode, deployContract, and writeContract flows.
  • Linting: resolved all type errors across edited files.

Decisions made

  • Do not override viem’s protected actions via .extend when our signatures differ; merge custom actions instead.
  • Keep viem’s actions for base functionality; add GenLayer-specific behavior as separate typed actions.
  • For localnet getTransaction, do not use viem’s getTransaction due to signature and return-type differences; use direct RPC and normalize.
  • Relax wallet/connect input type to avoid over-constraining callers.

Checks

  • I have tested this code
  • I have reviewed my own PR
  • I have created an issue for this PR
  • I have set a descriptive PR title compliant with conventional commits

Reviewing tips

  • Start with src/types/clients.ts for the new BaseActionsClient.
  • Review src/client/client.ts to see the mixed extend/merge composition.
  • Skim action factories (accounts, chains, wallet, contracts, transactions) focusing on their input types and removal of casts.
  • Pay attention to the short comment in src/transactions/actions.ts explaining why viem’s getTransaction isn’t used on localnet.

User facing release notes

  • Improved TypeScript safety across client and actions; removed internal unknown casts.
  • Fixed a localnet regression in transaction receipt polling.
  • No breaking changes to the public API; behavior preserved with stronger typing.

Summary by CodeRabbit

  • New Features

    • Broader multi‑chain support across accounts, chains, wallets, contracts, transactions, and receipts for wider network compatibility.
  • Refactor

    • Rebuilt client assembly using a safer merge pipeline to avoid action name conflicts.
    • Streamlined local‑network transaction/receipt retrieval and generalized action factories; runtime behavior unchanged.
  • Chores

    • Consolidated and modernized public types/imports and API signatures for clearer typing and maintainability.

@coderabbitai
Copy link

coderabbitai bot commented Aug 21, 2025

Walkthrough

Refactors action factories and client assembly to use a new generic BaseActionsClient, widens public signatures (accounts/chains/wallet/transactions/receipts/contracts), introduces ContractCapabilities and BaseActionsClient types, changes client assembly to a mergeActions pipeline, and adjusts connect to accept a minimal {chain?} object. No runtime behavior changes intended.

Changes

Cohort / File(s) Summary
Core types: BaseActionsClient
src/types/clients.ts
Added BaseActionsClient<TChain> combining Client, selective PublicActions (omitting getTransaction/readContract/waitForTransactionReceipt), WalletActions, and a typed request overload.
Client assembly refactor
src/client/client.ts
Added mergeActions helper and rebuilt client via sequential merges of action sets (accounts, wallet, chain, contract, transaction, receipt); final client cast to GenLayerClient<GenLayerChain> and used for init.
Generic action factories (accounts/chains/wallet)
src/accounts/actions.ts, src/chains/actions.ts, src/wallet/actions.ts
Made factories generic <TChain extends GenLayerChain> and changed parameter type from GenLayerClient<GenLayerChain> to BaseActionsClient<TChain>.
Contracts capabilities interface
src/contracts/actions.ts
Added ContractCapabilities type; contractActions and internal helpers now accept ContractCapabilities instead of GenLayerClient.
Transactions & receipts typing and localnet path
src/transactions/actions.ts
Introduced ClientWithGetTransaction<TChain> and TransactionCapabilities<TChain>; made receiptActions and transactionActions generic; localnet branch now uses client.request({ method: "eth_getTransactionByHash", params: [hash] }) and removed an unnecessary cast when decoding.
Wallet connect signature
src/wallet/connect.ts
connect now accepts client: { chain?: GenLayerChain } instead of GenLayerClient<GenLayerChain>; behavior still assigns client.chain.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Build as createPublicClient
  participant Base as baseClient
  participant Pub as publicActions
  participant Wal as walletActions
  participant Acc as accountActions
  participant GWal as genlayerWalletActions
  participant Cha as chainActions
  participant Con as contractActions
  participant Tx as transactionActions
  participant Rc as receiptActions

  Note over Build: Client assembly via mergeActions (sequential merges)
  Build->>Base: start with baseClient
  Build->>Base: merge(Public + Wallet) -> baseWithViem
  Build->>Base: merge(accountActions)
  Build->>Base: merge(genlayerWalletActions)
  Build->>Base: merge(chainActions)
  Build->>Base: merge(contractActions)
  Build->>Base: merge(transactionActions)
  Build->>Base: merge(receiptActions)
  Note over Build,Base: finalClientTyped used for init and return
Loading
sequenceDiagram
  autonumber
  participant UI as Caller
  participant TxA as transactionActions
  participant Cl as BaseActionsClient<TChain>
  participant Pub as PublicClient

  alt Localnet branch
    UI->>TxA: getTransaction(hash)
    TxA->>Cl: request("eth_getTransactionByHash", [hash])
    Cl-->>TxA: GenLayerTransaction
    TxA->>TxA: decodeLocalnetTransaction(tx)
    TxA-->>UI: Decoded tx
  else Remote branch
    UI->>TxA: getTransaction(hash)
    TxA->>Pub: readContract(...)
    Pub-->>TxA: Encoded tx
    TxA->>TxA: decodeTransaction(...)
    TxA-->>UI: Decoded tx
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • kstroobants
  • cristiam86
  • danielrc888

Poem

A rabbit typed and hopped through code,
I merged the actions, lightened the load.
Types stretched wide, chains in a row,
Requests fetch localnets in gentle flow.
Hooray—small hops, and off I go 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dxp-108-create-genlayer-client-like-viem-examples

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/contracts/actions.ts (2)

194-219: Guard against missing consensus ABI before encoding

_encodeAddTransactionData calls encodeFunctionData with client.chain.consensusMainContract?.abi as any. If the contract wasn’t initialized, this will throw at runtime. Fail fast with a clear error.

Outside-the-hunk code change:

const _encodeAddTransactionData = ({
  client,
  senderAccount,
  recipient,
  data,
  consensusMaxRotations = client.chain.defaultConsensusMaxRotations,
}: {
  client: ContractCapabilities;
  senderAccount?: Account;
  recipient?: `0x${string}`;
  data?: `0x${string}`;
  consensusMaxRotations?: number;
}): `0x${string}` => {
  const validatedSenderAccount = validateAccount(senderAccount);

  if (!client.chain.consensusMainContract?.abi) {
    throw new Error("Consensus main contract not initialized. Call initializeConsensusSmartContract() first.");
  }

  return encodeFunctionData({
    abi: client.chain.consensusMainContract.abi as any,
    functionName: "addTransaction",
    args: [
      validatedSenderAccount.address,
      recipient,
      client.chain.defaultNumberOfInitialValidators,
      consensusMaxRotations,
      data,
    ],
  });
};

235-305: Unify return semantics between remote and local signing; also stop hardcoding gas

  • Remote path currently returns the Ethereum tx hash from eth_sendTransaction, while the local path waits for a receipt and returns the consensus txId parsed from NewTransaction. This asymmetry is surprising and likely breaks callers expecting a consensus txId.
  • Hardcoded gas: 21000n and '0x5208' are too low for contract calls and can cause OOG. Let viem/provider estimate or compute it explicitly.

Apply this diff to (a) remove hardcoded gas, and (b) also wait and parse the event in the remote path so both branches return the consensus txId:

   const transactionRequest = await client.prepareTransactionRequest({
     account: validatedSenderAccount,
     to: client.chain.consensusMainContract?.address as Address,
     data: encodedData,
     type: "legacy",
     nonce: Number(nonce),
     value: value,
-    gas: 21000n,
   });
@@
-    const formattedRequest = {
+    const formattedRequest = {
       from: transactionRequest.from,
       to: transactionRequest.to,
       data: encodedData,
       value: transactionRequest.value ? `0x${transactionRequest.value.toString(16)}` : "0x0",
-      gas: transactionRequest.gas ? `0x${transactionRequest.gas.toString(16)}` : "0x5208",
     };
-
-    return await client.request({
-      method: "eth_sendTransaction",
-      params: [formattedRequest as any],
-    });
+    const txHash = (await client.request({
+      method: "eth_sendTransaction",
+      params: [formattedRequest as any],
+    })) as `0x${string}`;
+
+    const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
+    if (receipt.status === "reverted") throw new Error("Transaction reverted");
+
+    const newTxEvents = parseEventLogs({
+      abi: client.chain.consensusMainContract?.abi as any,
+      eventName: "NewTransaction",
+      logs: receipt.logs,
+    }) as unknown as { args: { txId: `0x${string}` } }[];
+    if (newTxEvents.length === 0) throw new Error("Transaction not processed by consensus");
+    return newTxEvents[0].args["txId"];
src/client/client.ts (2)

46-55: Guard window.ethereum for non-browser environments.

Accessing window throws in Node/SSR. Use a typeof check.

-          const provider = config.provider || window.ethereum;
+          const provider =
+            config.provider ??
+            (typeof window !== "undefined" ? (window as any).ethereum : undefined);

56-58: Avoid “Chain is not set” when callers pass an empty config.

If createClient({}) is used, config.chain is undefined here, even though you computed chainConfig. Pass the resolved chain into the transport config.

-  const customTransport = custom(getCustomTransportConfig(config));
+  const customTransport = custom(getCustomTransportConfig({ ...config, chain: chainConfig }));

Also applies to: 96-96

♻️ Duplicate comments (1)
src/wallet/actions.ts (1)

5-8: Type mismatch with connect after its signature change

connect expects an object with a required chain property, but you pass the full BaseActionsClient<TChain> where chain is optional per viem. This will fail type-checking. After adjusting connect’s param to { chain?: GenLayerChain } (see connect.ts review), this call becomes valid and preserves the desired side-effect of mutating the original client’s chain.

🧹 Nitpick comments (16)
src/wallet/connect.ts (3)

33-39: Guard blockExplorerUrls to avoid passing [undefined] to MetaMask

If a chain doesn’t define a block explorer, blockExplorerUrls: [undefined] can cause the RPC to fail. Only include the property when present.

Apply this diff:

-  const chainParams = {
+  const chainParams: any = {
     chainId: chainIdHex,
     chainName: selectedNetwork.name,
     rpcUrls: selectedNetwork.rpcUrls.default.http,
     nativeCurrency: selectedNetwork.nativeCurrency,
-    blockExplorerUrls: [selectedNetwork.blockExplorers?.default.url],
   };
+  if (selectedNetwork.blockExplorers?.default?.url) {
+    chainParams.blockExplorerUrls = [selectedNetwork.blockExplorers.default.url];
+  }

41-51: Prefer “switch then add-if-4902” flow for better UX and fewer prompts

Best practice is to try wallet_switchEthereumChain first and only fall back to wallet_addEthereumChain when the chain is unknown (error code 4902). Your current flow always calls add, which can prompt the user unnecessarily.

Apply this diff:

-  if (currentChainId !== chainIdHex) {
-    await window.ethereum.request({
-      method: "wallet_addEthereumChain",
-      params: [chainParams],
-    });
-    await window.ethereum.request({
-      method: "wallet_switchEthereumChain",
-      params: [{chainId: chainIdHex}],
-    });
-  }
+  if (currentChainId !== chainIdHex) {
+    try {
+      await window.ethereum.request({
+        method: "wallet_switchEthereumChain",
+        params: [{chainId: chainIdHex}],
+      });
+    } catch (err: any) {
+      if (err?.code === 4902) {
+        await window.ethereum.request({
+          method: "wallet_addEthereumChain",
+          params: [chainParams],
+        });
+        await window.ethereum.request({
+          method: "wallet_switchEthereumChain",
+          params: [{chainId: chainIdHex}],
+        });
+      } else {
+        throw err;
+      }
+    }
+  }

53-64: Tighten types and error handling for Snaps discovery/installation

  • installedSnaps: any and snap: any lose the benefit of the PR’s type-safety work.
  • Consider catching and surfacing errors from wallet_requestSnaps (users often reject the prompt).

Example improvement (outside current hunk):

type InstalledSnap = { id: string; version: string; permissionName?: string };
const installedSnaps = (await window.ethereum.request({ method: "wallet_getSnaps" })) as Record<string, InstalledSnap>;
const isGenLayerSnapInstalled = Object.values(installedSnaps).some((snap) => snap.id === id);

if (!isGenLayerSnapInstalled) {
  try {
    await window.ethereum.request({ method: "wallet_requestSnaps", params: { [id]: {} } });
  } catch (e) {
    throw new Error(`Failed to install GenLayer Snap (${id}): ${(e as Error).message}`);
  }
}
src/types/clients.ts (1)

95-99: Give request a typed return to eliminate unknown at call sites

The overload constrains params, but it still returns Promise<unknown>. That forces downstream casts in contract actions. Add a method-to-result mapping for the subset you use (e.g., gen_call, gen_getContractSchema*, eth_sendRawTransaction) and fall back to unknown for the rest.

Apply this diff to refine the return type:

-    request: Client<Transport, TGenLayerChain>["request"] & {
-      <TMethod extends GenLayerMethod>(
-        args: Extract<GenLayerMethod, {method: TMethod["method"]}>,
-      ): Promise<unknown>;
-    };
+    request: Client<Transport, TGenLayerChain>["request"] & {
+      <TMethod extends GenLayerMethod>(
+        args: Extract<GenLayerMethod, { method: TMethod["method"] }>,
+      ): Promise<GenLayerMethodResult<TMethod>>;
+    };

And add this helper type (outside the hunk):

type GenLayerMethodResult<M extends GenLayerMethod> =
  M["method"] extends "gen_call" ? string :
  M["method"] extends "gen_getContractSchema" ? string :
  M["method"] extends "gen_getContractSchemaForCode" ? string :
  M["method"] extends "eth_sendRawTransaction" ? `0x${string}` :
  unknown;
src/contracts/actions.ts (2)

185-192: Nit: parameter name casing in validateAccount

validateAccount(Account?: Account) uses a capitalized variable name, which is easy to confuse with the type. Rename to account for readability.

Apply this diff (outside current hunk):

-const validateAccount = (Account?: Account): Account => {
-  if (!Account) {
+const validateAccount = (account?: Account): Account => {
+  if (!account) {
     throw new Error(
       "No account set. Configure the client with an account or pass an account to this function.",
     );
   }
-  return Account;
+  return account;
 };

49-92: Optional: tighten result typing in readContract to drop casts

Once BaseActionsClient.request is typed (see clients.ts comment), you can avoid (await client.request(...)) as string and as any casts here.

src/chains/actions.ts (1)

19-38: Add basic response shape validation and better error messaging

The RPC call assumes a JSON-RPC success envelope and result shape. Guard for malformed responses and surface the RPC error message if present.

Example (outside current hunk):

const json = await contractsResponse.json();
if ("error" in json) {
  throw new Error(`sim_getConsensusContract failed: ${json.error?.message ?? "Unknown error"}`);
}
if (!json?.result?.abi || !json?.result?.address) {
  throw new Error("Unexpected ConsensusMain contract payload");
}
client.chain.consensusMainContract = json.result;
src/accounts/actions.ts (2)

11-15: Consider stronger typing for RPC method/return.

You cast the result to Promise. Prefer tightening the request signature so this cast becomes unnecessary (e.g., add "sim_fundAccount" to your GenLayerMethod union with a specific return type).


23-27: Remove unreachable address fallback in getCurrentNonce

The wrapper for getCurrentNonce currently falls back to client.account?.address, but its TypeScript signature requires an address argument and all call sites (e.g. in src/contracts/actions.ts:254) always pass one. The fallback and associated error check are never used and can be removed to keep the API consistent.

• File: src/accounts/actions.ts (lines 23–27)
• Call sites verified: only client.getCurrentNonce({ address: … }) is ever invoked, so address is never falsy.

Minimal diff to simplify:

-      const addressToUse = address || client.account?.address;
-
-      if (!addressToUse) {
-        throw new Error("No address provided and no account is connected");
-      }
+      const addressToUse = address;
src/client/client.ts (5)

40-41: Fix account type detection for provider routing.

The current check treats “no account provided” the same as “address string,” which is brittle.

-  const isAddress = typeof config.account !== "object";
+  const isAddress = typeof config.account === "string";

61-85: Harden fetch branch with HTTP error handling.

response.json() will throw on non-JSON or mask HTTP errors. Check response.ok first and include status info in errors.

           const response = await fetch(config.chain.rpcUrls.default.http[0], {
@@
-          const data = await response.json();
+          if (!response.ok) {
+            const body = await response.text().catch(() => "");
+            throw new Error(`GenLayer RPC ${method} failed: ${response.status} ${response.statusText} ${body}`);
+          }
+          const data = await response.json();

118-121: Background init may race with actions that depend on consensusDataContract.

If a consumer calls a contract/tx action immediately after createClient, consensus contracts may be unset. Consider:

  • Awaiting initialization when not testnet, or
  • Lazily initializing inside the first call that needs it, or
  • Exposing an option { initialize: "eager" | "lazy" } with default eager.

1-12: Prefer viem’s EIP1193Provider over the custom EthereumProvider

While EthereumProvider is declared in src/global.d.ts, relying on a bespoke global interface can introduce subtle mismatches with viem’s APIs. To align with viem’s types and avoid duplication, update your client config as follows:

• In src/client/client.ts, import the standard provider type:

-import {
+import {
   Account,
   createClient as createViemClient,
   createPublicClient as createPublicViemClient,
   publicActions,
   custom,
   Address,
   walletActions,
   Transport,
   PublicClient,
   Chain,
+  type EIP1193Provider,
 } from "viem";

• And update the ClientConfig interface:

 interface ClientConfig {
   endpoint?: string; // Custom RPC endpoint
   account?: Account | Address;
-  provider?: EthereumProvider; // Custom provider for wallet framework integration
+  provider?: EIP1193Provider; // Standard EIP-1193 provider
 }

• (Optional) Once you’ve migrated all usages, consider deprecating or removing the EthereumProvider definition in src/global.d.ts to keep your type definitions DRY.


21-23: Use client.extend instead of mergeActions to preserve hidden viem properties

The current pipeline uses mergeActions, which internally calls Object.assign({}, base, ext). That drops non-enumerable and symbol properties (including any future prototype hooks or hidden markers viem may add). To guard against accidentally stripping those, prefer chaining .extend(...) calls, which preserves the client’s prototype and any non-enumerable/symbol fields.

Locations to update:

  • src/client/client.ts:108–114

    // current
    const baseWithViem = baseClient.extend(publicActions).extend(walletActions) as BaseActionsClient<GenLayerChain>;
    const withAccounts = mergeActions(baseWithViem, accountActions(baseWithViem));
    const withWallet = mergeActions(withAccounts, genlayerWalletActions(withAccounts));
    const withChain = mergeActions(withWallet, chainActions(withWallet));
    const withContracts = mergeActions(withChain, contractActions(withChain, publicClient));
    const withTx = mergeActions(withContracts, transactionActions(withContracts, publicClient));
    const finalClient = mergeActions(withTx, receiptActions(withTx, publicClient));

    Proposed refactor:

    - const baseWithViem = baseClient.extend(publicActions).extend(walletActions) as BaseActionsClient<GenLayerChain>;
    - const withAccounts = mergeActions(baseWithViem, accountActions(baseWithViem));
    - const withWallet = mergeActions(withAccounts, genlayerWalletActions(withAccounts));
    - const withChain = mergeActions(withWallet, chainActions(withWallet));
    - const withContracts = mergeActions(withChain, contractActions(withChain, publicClient));
    - const withTx = mergeActions(withContracts, transactionActions(withContracts, publicClient));
    - const finalClient = mergeActions(withTx, receiptActions(withTx, publicClient));
    + const finalClient = baseClient
    +   .extend(publicActions)
    +   .extend(walletActions)
    +   .extend(accountActions)
    +   .extend(genlayerWalletActions)
    +   .extend(chainActions)
    +   .extend((chain) => contractActions(chain, publicClient))
    +   .extend((chain) => transactionActions(chain, publicClient))
    +   .extend((chain) => receiptActions(chain, publicClient)) as GenLayerClient<GenLayerChain>;

This ensures all non-enumerable/symbol properties and the client’s prototype remain intact while composing your custom actions.

src/transactions/actions.ts (2)

48-50: Avoid double-decoding localnet transactions.

transactionActions already returns a decoded localnet tx. Decoding again here is redundant.

-      if (client.chain.id === localnet.id) {
-        finalTransaction = decodeLocalnetTransaction(transaction as unknown as GenLayerTransaction);
-      }

96-106: Remote branch depends on consensusDataContract; ensure it’s initialized.

If consensusDataContract is null, readContract will fail at runtime. Either guard and surface a better error or lazily initialize before the call.

Would you like me to add a small helper ensureConsensusContracts(client, publicClient) that initializes contracts when missing and call it here?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c4024b and c1aa876.

📒 Files selected for processing (8)
  • src/accounts/actions.ts (1 hunks)
  • src/chains/actions.ts (1 hunks)
  • src/client/client.ts (3 hunks)
  • src/contracts/actions.ts (4 hunks)
  • src/transactions/actions.ts (3 hunks)
  • src/types/clients.ts (1 hunks)
  • src/wallet/actions.ts (1 hunks)
  • src/wallet/connect.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
src/wallet/actions.ts (2)
src/types/chains.ts (1)
  • GenLayerChain (4-17)
src/types/clients.ts (1)
  • BaseActionsClient (89-100)
src/wallet/connect.ts (1)
src/types/chains.ts (1)
  • GenLayerChain (4-17)
src/client/client.ts (7)
src/chains/localnet.ts (1)
  • localnet (3990-4014)
src/wallet/actions.ts (1)
  • walletActions (5-10)
src/types/clients.ts (2)
  • BaseActionsClient (89-100)
  • GenLayerClient (28-86)
src/accounts/actions.ts (1)
  • accountActions (4-34)
src/chains/actions.ts (1)
  • chainActions (4-40)
src/contracts/actions.ts (1)
  • contractActions (24-183)
src/transactions/actions.ts (2)
  • transactionActions (76-107)
  • receiptActions (16-70)
src/accounts/actions.ts (2)
src/types/chains.ts (1)
  • GenLayerChain (4-17)
src/types/clients.ts (1)
  • BaseActionsClient (89-100)
src/chains/actions.ts (2)
src/types/chains.ts (1)
  • GenLayerChain (4-17)
src/types/clients.ts (1)
  • BaseActionsClient (89-100)
src/contracts/actions.ts (2)
src/types/chains.ts (1)
  • GenLayerChain (4-17)
src/types/clients.ts (1)
  • GenLayerClient (28-86)
src/transactions/actions.ts (5)
src/types/chains.ts (1)
  • GenLayerChain (4-17)
src/types/clients.ts (1)
  • BaseActionsClient (89-100)
src/types/transactions.ts (3)
  • TransactionHash (5-5)
  • GenLayerTransaction (142-256)
  • transactionsStatusNameToNumber (57-72)
src/chains/localnet.ts (1)
  • localnet (3990-4014)
src/transactions/decoders.ts (1)
  • decodeLocalnetTransaction (232-276)
src/types/clients.ts (1)
src/types/chains.ts (1)
  • GenLayerChain (4-17)
🔇 Additional comments (10)
src/types/clients.ts (1)

88-100: Good abstraction: BaseActionsClient aligns with viem’s extension model

The capability surface and removal of protected actions look solid.

src/contracts/actions.ts (1)

15-22: ContractCapabilities type is a clean, minimal surface

Nice decoupling from the full client; narrows only what contract actions need.

src/chains/actions.ts (2)

4-4: LGTM on generic client adoption

Using BaseActionsClient<TChain> cleanly decouples this action factory from the full GenLayer client.


19-19: Validate RPC URL presence

client.chain.rpcUrls.default.http[0] may be undefined for misconfigured chains. Add a guard to fail early with a helpful message.

Example (outside current hunk):

const url = client.chain.rpcUrls?.default?.http?.[0];
if (!url) throw new Error(`No RPC URL configured for chain ${client.chain.id}`);
const contractsResponse = await fetch(url, { ... });
src/accounts/actions.ts (2)

1-1: Good move: adopt BaseActionsClient and generic chain.

This aligns the action factory with the new capability-based client and viem-style extension.


4-4: Generic factory signature is consistent with multi-chain design.

No runtime behavior change; types become more precise.

src/client/client.ts (2)

97-100: LGTM: public client creation and scoping.

Keeping a separate publicClient for remote reads is clear and avoids signature conflicts.


107-116: Merge ordering looks correct to override protected viem actions with GenLayer variants.

transactionActions and receiptActions applied last will supersede viem’s getTransaction/waitForTransactionReceipt.

src/transactions/actions.ts (2)

12-14: Type alias for ClientWithGetTransaction is clear and scoped.

This keeps receiptActions narrowly typed yet composable.


16-19: Receipt actions generic signature looks good.

Accepting ClientWithGetTransaction avoids over-constraining on the full client.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/transactions/actions.ts (1)

85-95: Guard against null eth_getTransactionByHash responses and normalize unknown statuses.

Localnet can return null for unknown hashes. The current code will throw at (transaction.status as string) when null. Also, normalize unexpected status strings (e.g., "ACTIVATED") defensively.

Apply this diff:

       const transaction = (await client.request({
         method: "eth_getTransactionByHash",
         params: [hash],
-      })) as GenLayerTransaction;
-      const localnetStatus =
-        (transaction.status as string) === "ACTIVATED" ? TransactionStatus.PENDING : transaction.status;
-
-      transaction.status = Number(transactionsStatusNameToNumber[localnetStatus as TransactionStatus]);
-      transaction.statusName = localnetStatus as TransactionStatus;
-      return decodeLocalnetTransaction(transaction);
+      })) as GenLayerTransaction | null;
+      if (!transaction) {
+        throw new Error(`Transaction not found for hash ${hash}`);
+      }
+      const rawStatus = transaction.status as string | number | undefined;
+      const normalizedStatusName: TransactionStatus =
+        rawStatus === "ACTIVATED"
+          ? TransactionStatus.PENDING
+          : typeof rawStatus === "string" && rawStatus in transactionsStatusNameToNumber
+            ? (rawStatus as TransactionStatus)
+            : TransactionStatus.UNDETERMINED;
+      transaction.status = Number(transactionsStatusNameToNumber[normalizedStatusName]);
+      transaction.statusName = normalizedStatusName;
+      return decodeLocalnetTransaction(transaction);
🧹 Nitpick comments (6)
src/transactions/actions.ts (6)

48-50: Remove unnecessary double cast.

transaction is already GenLayerTransaction-typed. The extra as unknown as GenLayerTransaction is noise.

-        finalTransaction = decodeLocalnetTransaction(transaction as unknown as GenLayerTransaction);
+        finalTransaction = decodeLocalnetTransaction(transaction);

33-39: Drop redundant null-check here; centralize “not found” handling in getTransaction.

Given getTransaction now throws when the RPC returns null (localnet path), this check is unreachable. Prefer a single, consistent error source.

-    if (!transaction) {
-      throw new Error("Transaction not found");
-    }

57-59: Enrich the timeout error with context.

Include the hash and last-known status to aid debugging.

-      throw new Error("Transaction status is not " + status);
+      throw new Error(
+        `Transaction ${hash} did not reach status ${status}. ` +
+        `Last known: ${transaction.statusName ?? transaction.status}.`
+      );

16-19: publicClient is unused in receiptActions; mark intentionally unused to satisfy linters.

Renaming avoids “unused parameter” warnings while preserving the signature.

 export const receiptActions = <TChain extends GenLayerChain>(
   client: ClientWithGetTransaction<TChain>,
-  publicClient: PublicClient,
+  _publicClient: PublicClient,
 ) => ({
 ...
-    return receiptActions(client, publicClient).waitForTransactionReceipt({
+    return receiptActions(client, _publicClient).waitForTransactionReceipt({

Also applies to: 62-62


20-69: Optional: switch recursive polling to iterative loop to avoid re-instantiation.

Current recursion is safe (promise chaining), but an iterative loop is a bit clearer and avoids re-creating the actions object.

Example refactor:

   waitForTransactionReceipt: async ({
     hash,
     status = TransactionStatus.ACCEPTED,
     interval = transactionsConfig.waitInterval,
     retries = transactionsConfig.retries,
     fullTransaction = false,
   }: {
@@
-    if (retries === 0) {
-      throw new Error("Transaction status is not " + status);
-    }
-
-    await sleep(interval);
-    return receiptActions(client, _publicClient).waitForTransactionReceipt({
-      hash,
-      status,
-      interval,
-      retries: retries - 1,
-      fullTransaction,
-    });
+    let remaining = retries;
+    let last: GenLayerTransaction = transaction;
+    while (remaining > 0) {
+      await sleep(interval);
+      last = await client.getTransaction({hash});
+      const s = String(last.status);
+      const finalized = transactionsStatusNameToNumber[TransactionStatus.FINALIZED];
+      const requested = transactionsStatusNameToNumber[status];
+      if (s === requested || (status === TransactionStatus.ACCEPTED && s === finalized)) {
+        const tx = client.chain.id === localnet.id ? decodeLocalnetTransaction(last) : last;
+        return fullTransaction ? tx : simplifyTransactionReceipt(tx);
+      }
+      remaining--;
+    }
+    throw new Error(`Transaction ${hash} did not reach status ${status}. Last known: ${last.statusName ?? last.status}.`);

80-107: Unit tests to add (happy/unhappy paths).

To lock in behavior and avoid regressions:

  • Localnet: getTransaction returns null → expect Error("Transaction not found for hash ...").
  • Localnet: status "ACTIVATED" → normalize to PENDING; waitForTransactionReceipt should resolve immediately when requesting ACCEPTED and the tx is already FINALIZED.
  • Remote: readContract getTransactionData returns a valid GenLayerRawTransaction → decodeTransaction produces expected GenLayerTransaction.
  • waitForTransactionReceipt: retries exhausted → enriched error message includes hash and last-known status.

If you want, I can scaffold these tests with viem-mocked clients and a fake transport.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c1aa876 and 9f76db1.

📒 Files selected for processing (2)
  • src/chains/actions.ts (1 hunks)
  • src/transactions/actions.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/chains/actions.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/transactions/actions.ts (4)
src/types/clients.ts (1)
  • BaseActionsClient (89-100)
src/types/transactions.ts (3)
  • TransactionHash (5-5)
  • GenLayerTransaction (142-256)
  • transactionsStatusNameToNumber (57-72)
src/chains/localnet.ts (1)
  • localnet (3990-4014)
src/transactions/decoders.ts (1)
  • decodeLocalnetTransaction (232-276)
🔇 Additional comments (4)
src/transactions/actions.ts (4)

12-14: Type alias for ClientWithGetTransaction looks good.

Captures capability-based typing cleanly and keeps receiptActions narrow. No changes needed.


72-79: TransactionCapabilities scaffolding is fine.

The capability boundary is clear: localnet uses client.request; remote uses viem publicClient. No changes needed.


1-2: Imports look consistent with the new capability-based design.

Centralized types from "@/types" and BaseActionsClient from "../types/clients" are the right dependencies.


96-104: All chain definitions include consensusDataContract; no guard needed

I’ve verified that every defineChain call in src/chains (localnet.ts, studionet.ts, testnetAsimov.ts) declares a consensusDataContract, so there is no runtime risk of readContract being called with an undefined address or ABI.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/wallet/connect.ts (1)

33-39: Bug: blockExplorerUrls can become [undefined] and break wallet_addEthereumChain.

When selectedNetwork.blockExplorers?.default is absent (likely on localnet), this produces [undefined], which MetaMask rejects. Include the key only when a URL exists.

Apply this diff:

-  const chainParams = {
-    chainId: chainIdHex,
-    chainName: selectedNetwork.name,
-    rpcUrls: selectedNetwork.rpcUrls.default.http,
-    nativeCurrency: selectedNetwork.nativeCurrency,
-    blockExplorerUrls: [selectedNetwork.blockExplorers?.default.url],
-  };
+  const chainParams = {
+    chainId: chainIdHex,
+    chainName: selectedNetwork.name,
+    rpcUrls: selectedNetwork.rpcUrls.default.http,
+    nativeCurrency: selectedNetwork.nativeCurrency,
+    ...(selectedNetwork.blockExplorers?.default?.url
+      ? { blockExplorerUrls: [selectedNetwork.blockExplorers.default.url] }
+      : {}),
+  };
♻️ Duplicate comments (1)
src/wallet/connect.ts (1)

16-19: Thanks for relaxing the client param to accept an optional chain.

This aligns with BaseActionsClient where chain is optional and unblocks callers passing a viem-style client directly. Good change.

🧹 Nitpick comments (4)
src/wallet/connect.ts (4)

20-22: SSR/Non-browser safety: guard window usage.

Directly referencing window throws in SSR/Node contexts. Add a typeof guard.

-  if (!window.ethereum) {
+  if (typeof window === "undefined" || !window.ethereum) {
     throw new Error("MetaMask is not installed.");
   }

41-51: More robust chain switching flow: switch first, add on 4902.

Current flow always calls wallet_addEthereumChain which can cause redundant prompts. Prefer switching first and add only if the chain is unknown (error code 4902).

-  const currentChainId = await window.ethereum.request({method: "eth_chainId"});
-  if (currentChainId !== chainIdHex) {
-    await window.ethereum.request({
-      method: "wallet_addEthereumChain",
-      params: [chainParams],
-    });
-    await window.ethereum.request({
-      method: "wallet_switchEthereumChain",
-      params: [{chainId: chainIdHex}],
-    });
-  }
+  const currentChainId = await window.ethereum.request({ method: "eth_chainId" });
+  if (currentChainId !== chainIdHex) {
+    try {
+      await window.ethereum.request({
+        method: "wallet_switchEthereumChain",
+        params: [{ chainId: chainIdHex }],
+      });
+    } catch (err: any) {
+      if (err?.code === 4902) {
+        await window.ethereum.request({
+          method: "wallet_addEthereumChain",
+          params: [chainParams],
+        });
+        await window.ethereum.request({
+          method: "wallet_switchEthereumChain",
+          params: [{ chainId: chainIdHex }],
+        });
+      } else {
+        throw err;
+      }
+    }
+  }

54-56: Minor: simpler and better-typed snap detection.

Use the record key instead of scanning values, and give installedSnaps a minimal shape.

-  const installedSnaps: any = await window.ethereum.request({method: "wallet_getSnaps"});
-  const isGenLayerSnapInstalled = Object.values(installedSnaps).some((snap: any) => snap.id === id);
+  const installedSnaps: Record<string, { id: string }> =
+    await window.ethereum.request({ method: "wallet_getSnaps" });
+  const isGenLayerSnapInstalled = Boolean(installedSnaps?.[id]);

24-25: Nit: clarify the mainnet error message.

Message suggests “use localnet” even though studionet/testnetAsimov are supported. Make it explicit.

-    throw new Error(`${network} is not available yet. Please use localnet.`);
+    throw new Error(`${network} is not available yet. Please use one of: localnet, studionet, or testnetAsimov.`);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9f76db1 and 9b5b3de.

📒 Files selected for processing (1)
  • src/wallet/connect.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/wallet/connect.ts (1)
src/types/chains.ts (1)
  • GenLayerChain (4-17)
🔇 Additional comments (1)
src/wallet/connect.ts (1)

4-4: No changes needed for GenLayerChain import
The src/types/index.ts barrel file includes the line export * from "./chains";, which re-exports GenLayerChain from src/types/chains.ts. Therefore, the import

import { GenLayerChain } from "@/types";

is valid and does not need to be adjusted.

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.

2 participants