Skip to content

Feat/tx cursor pagination #559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 20, 2025
Merged

Feat/tx cursor pagination #559

merged 5 commits into from
May 20, 2025

Conversation

ivan-angjelkoski
Copy link
Contributor

@ivan-angjelkoski ivan-angjelkoski commented May 16, 2025

Summary by CodeRabbit

  • New Features

    • Added support for version 2 (V2) endpoints to fetch transactions, account transactions, blocks, and contract transactions from the explorer API.
    • Introduced new data structures and transformers for handling V2 transaction and block formats, improving data parsing and compatibility.
  • Bug Fixes

    • Enhanced error handling and parsing robustness for transaction and block data.
  • Tests

    • Added new test cases for all V2 explorer API methods and updated test network configuration.
  • Chores

    • Updated a dependency to the latest version.
    • Removed legacy ESLint configuration.

Copy link

coderabbitai bot commented May 16, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This update introduces new version 2 (V2) gRPC API methods for fetching transactions, account transactions, blocks, and contract transactions in the IndexerGrpcExplorerApi class, along with corresponding transformer logic and types. The test suite is expanded to cover these new methods. The ESLint configuration file is removed, and a dependency version is updated.

Changes

File(s) Change Summary
.eslintrc.js Deleted the entire ESLint configuration, removing centralized linting rules and environment settings for the project.
packages/sdk-ts/package.json Updated the @injectivelabs/indexer-proto-ts dependency version from 1.13.9 to 1.13.12.
packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.ts Added five new asynchronous V2 methods: fetchTxsV2, fetchAccountTxsV2, fetchBlocksV2, and fetchContractTxsV2, each handling new gRPC endpoints with robust error handling and response transformation.
packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts Introduced new static transformer methods to handle V2 response types for transactions, account transactions, blocks, and contract transactions, converting raw gRPC data into internal representations with safe parsing and error handling.
packages/sdk-ts/src/client/indexer/types/explorer.ts Added new TypeScript interfaces: ExplorerTxsV2Response and ExplorerTransactionV2 to represent the new V2 transaction data structures and responses.
packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.ts Updated tests to use the Devnet1 endpoint and added five new test cases for the V2 API methods, each verifying correct responses and error handling. The fetchTxsV2 test is marked as .only for exclusive execution.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant IndexerGrpcExplorerApi
    participant gRPC_Server
    participant Transformer

    Client->>IndexerGrpcExplorerApi: fetchTxsV2(params)
    IndexerGrpcExplorerApi->>gRPC_Server: GetTxsV2Request
    gRPC_Server-->>IndexerGrpcExplorerApi: GetTxsV2Response
    IndexerGrpcExplorerApi->>Transformer: getTxsV2ResponseToTxs(response)
    Transformer-->>IndexerGrpcExplorerApi: ExplorerTxsV2Response
    IndexerGrpcExplorerApi-->>Client: ExplorerTxsV2Response

    Client->>IndexerGrpcExplorerApi: fetchAccountTxsV2(params)
    IndexerGrpcExplorerApi->>gRPC_Server: GetAccountTxsV2Request
    gRPC_Server-->>IndexerGrpcExplorerApi: GetAccountTxsV2Response
    IndexerGrpcExplorerApi->>Transformer: getAccountTxsV2ResponseToAccountTxs(response)
    Transformer-->>IndexerGrpcExplorerApi: ExplorerTxsV2Response
    IndexerGrpcExplorerApi-->>Client: ExplorerTxsV2Response

    Client->>IndexerGrpcExplorerApi: fetchBlocksV2(params)
    IndexerGrpcExplorerApi->>gRPC_Server: GetBlocksV2Request
    gRPC_Server-->>IndexerGrpcExplorerApi: GetBlocksV2Response
    IndexerGrpcExplorerApi->>Transformer: getBlocksV2ResponseToBlocks(response)
    Transformer-->>IndexerGrpcExplorerApi: Block[]
    IndexerGrpcExplorerApi-->>Client: Block[]

    Client->>IndexerGrpcExplorerApi: fetchContractTxsV2(params)
    IndexerGrpcExplorerApi->>gRPC_Server: GetContractTxsV2Request
    gRPC_Server-->>IndexerGrpcExplorerApi: GetContractTxsV2Response
    IndexerGrpcExplorerApi->>Transformer: getContractTxsV2ResponseToContractTxs(response)
    Transformer-->>IndexerGrpcExplorerApi: ContractTransaction[]
    IndexerGrpcExplorerApi-->>Client: ContractTransaction[]
Loading

Poem

A rabbit hops with code anew,
V2 APIs now in view!
Transactions, blocks, and contracts too,
All transformed and tested through.
The linter’s gone, but tests are bright—
New endpoints ready, working right.
🐇✨ Onward to the next delight!

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.ts

Oops! Something went wrong! :(

ESLint: 7.32.0

ESLint couldn't find the config "./../../eslintrc.js" to extend from. Please check that the name of the config is correct.

The config "./../../eslintrc.js" was referenced from the config file in "/packages/sdk-ts/.eslintrc.js".

If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.

packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.ts

Oops! Something went wrong! :(

ESLint: 7.32.0

ESLint couldn't find the config "./../../eslintrc.js" to extend from. Please check that the name of the config is correct.

The config "./../../eslintrc.js" was referenced from the config file in "/packages/sdk-ts/.eslintrc.js".

If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.

packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts

Oops! Something went wrong! :(

ESLint: 7.32.0

ESLint couldn't find the config "./../../eslintrc.js" to extend from. Please check that the name of the config is correct.

The config "./../../eslintrc.js" was referenced from the config file in "/packages/sdk-ts/.eslintrc.js".

If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.

  • 1 others

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 019cdea and cb53979.

📒 Files selected for processing (4)
  • packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.ts (2 hunks)
  • packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.ts (1 hunks)
  • packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts (3 hunks)
  • packages/sdk-ts/src/client/indexer/types/explorer.ts (1 hunks)

🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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: 5

♻️ Duplicate comments (1)
packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts (1)

545-554: getAccountTxsV2ResponseToAccountTxs – paging inconsistency

As with getTxsV2ResponseToTxs, raw proto paging is returned. Convert it via
grpcPagingToPaging for consistency.

🧹 Nitpick comments (4)
packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.ts (1)

263-311: Remove console.log statements from tests

The tests contain console.log statements that print the full response JSON. These should be removed before merging, as they will clutter the test output and potentially expose sensitive information.

-      console.log(JSON.stringify(response, null, 2))

This applies to lines 270, 286, and 303.

packages/sdk-ts/src/client/indexer/types/explorer.ts (1)

399-414: Consider stronger typing for any fields

The interface uses any type for logs and messages properties, which loses type safety. Consider defining more specific types based on the expected structure of these fields.

-  logs: any
+  logs: EventLog[] | null
-  messages: any
+  messages: Message[] | null
packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts (2)

63-76: Type-safety & fallback for transactionV2MessagesToMessages

Buffer.from(messages) will happily accept a string, but when messages
is already a Uint8Array, converting first to string and then to JSON is fine.
However, when messages is already a JS object (e.g., pre-parsed by the caller)
the current implementation double-serialises and eventually throws.

Consider:

-  try {
-    return JSON.parse(Buffer.from(messages).toString('utf8')).map(
+  try {
+    const raw =
+      typeof messages === 'string' || messages instanceof Uint8Array
+        ? Buffer.from(messages).toString('utf8')
+        : JSON.stringify(messages)
+
+    return JSON.parse(raw || '[]').map(

This keeps the helper usable regardless of the input type and avoids needless
errors in unit tests where mocks often pass plain objects.


630-656: Fee calculation & JSON parsing safety for contract txs

  1. tx.gasFee?.amount[0]?.amount assumes at least one fee amount; when empty
    the fallback is '0', good, but the denom is ignored. Returning a fee
    without its denom is misleading.

  2. logs parsing (line 647) lacks a try, same risk as above.

  3. time is parseInt(tx.blockTimestamp, 10). If the backend ever switches to
    RFC-3339 timestamps (as other endpoints already do), this will yield NaN.
    A safer approach is to leave it as a string or detect unix vs RFC format.

Consider enhancing:

-      logs: JSON.parse(Buffer.from(tx.logs).toString('utf8')),
+      logs: (() => {
+        try {
+          return JSON.parse(Buffer.from(tx.logs).toString('utf8') || '[]')
+        } catch (e) {
+          console.error('Failed to parse logs:', e)
+          return []
+        }
+      })(),
-      fee: new BigNumberInWei(tx.gasFee?.amount[0]?.amount || '0').toBase(),
+      fee: new BigNumberInWei(
+        tx.gasFee?.amount?.[0]?.amount ?? '0',
+      ).toBase(),

Also expose denom alongside the numeric fee for completeness.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between de7c94d and 019cdea.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (6)
  • .eslintrc.js (0 hunks)
  • packages/sdk-ts/package.json (1 hunks)
  • packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.ts (2 hunks)
  • packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.ts (1 hunks)
  • packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts (3 hunks)
  • packages/sdk-ts/src/client/indexer/types/explorer.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • .eslintrc.js
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.ts (1)
packages/networks/src/network.ts (1)
  • getNetworkEndpoints (65-66)
packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts (2)
packages/sdk-ts/src/utils/helpers.ts (1)
  • isJsonString (154-166)
packages/sdk-ts/src/client/indexer/types/explorer.ts (4)
  • Message (53-56)
  • ExplorerTransaction (355-361)
  • Block (185-194)
  • ContractTransaction (338-353)
🪛 Biome (1.9.4)
packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.ts

[error] 245-245: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

🔇 Additional comments (7)
packages/sdk-ts/package.json (1)

131-131: Package dependency update looks good

The update to @injectivelabs/indexer-proto-ts from an earlier version to 1.13.12 aligns with the V2 API methods being introduced in this PR.

packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.ts (1)

9-9: Network endpoint changed for testing

Changed from MainnetSentry to Devnet1 for testing purposes, which is appropriate for these new API tests.

packages/sdk-ts/src/client/indexer/types/explorer.ts (1)

394-397: Interface for V2 response with pagination cursor looks good

The ExplorerTxsV2Response interface correctly defines the structure for V2 responses, including the cursor-based pagination.

packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.ts (4)

573-641: New fetchTxsV2 method implementation looks good

The implementation follows the established pattern of the codebase:

  1. Creates and configures a request object
  2. Uses retry logic for the gRPC call
  3. Transforms the response
  4. Properly handles errors

The method signature includes all necessary parameters for filtering transactions including block number, time range, pagination, status, and transaction type.


643-706: New fetchAccountTxsV2 method implementation looks good

The implementation correctly handles account-specific transaction filtering with cursor-based pagination and other filtering options. Error handling is consistent with other methods.


708-749: New fetchBlocksV2 method implementation looks good

The method provides cursor-based pagination for retrieving blocks. The pattern follows the established conventions in the codebase.


751-820: New fetchContractTxsV2 method implementation looks good

The implementation correctly handles contract-specific transaction retrieval with appropriate filtering options and pagination. Parameter handling and error management follow established patterns.

Comment on lines +245 to +261
test.only('fetchTxsV2', async () => {
try {
const response = await indexerGrpcExplorerApi.fetchTxsV2({
perPage: 10,
type: 'cosmos.gov.v1beta1.MsgVote,cosmos.gov.v1.MsgVote',
})

const { data: txs } = response

expect(response).toBeDefined()

expect(txs).toBeDefined()
expect(txs).toEqual(expect.objectContaining<typeof txs>(txs))
} catch (e) {
console.error('IndexerGrpcExplorerApi.fetchTxV2 => ' + (e as any).message)
}
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove .only from test

The test is currently set to run exclusively, which will skip all other tests. This is likely a development convenience but should be removed before merging.

-  test.only('fetchTxsV2', async () => {
+  test('fetchTxsV2', async () => {
📝 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.

Suggested change
test.only('fetchTxsV2', async () => {
try {
const response = await indexerGrpcExplorerApi.fetchTxsV2({
perPage: 10,
type: 'cosmos.gov.v1beta1.MsgVote,cosmos.gov.v1.MsgVote',
})
const { data: txs } = response
expect(response).toBeDefined()
expect(txs).toBeDefined()
expect(txs).toEqual(expect.objectContaining<typeof txs>(txs))
} catch (e) {
console.error('IndexerGrpcExplorerApi.fetchTxV2 => ' + (e as any).message)
}
})
test('fetchTxsV2', async () => {
try {
const response = await indexerGrpcExplorerApi.fetchTxsV2({
perPage: 10,
type: 'cosmos.gov.v1beta1.MsgVote,cosmos.gov.v1.MsgVote',
})
const { data: txs } = response
expect(response).toBeDefined()
expect(txs).toBeDefined()
expect(txs).toEqual(expect.objectContaining<typeof txs>(txs))
} catch (e) {
console.error('IndexerGrpcExplorerApi.fetchTxV2 => ' + (e as any).message)
}
})
🧰 Tools
🪛 Biome (1.9.4)

[error] 245-245: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)

🤖 Prompt for AI Agents
In packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.ts around
lines 245 to 261, the test case uses test.only which causes only this test to
run and skips all others. Remove the .only from the test declaration so that all
tests run normally during the test suite execution.

Comment on lines +465 to +474
static getTxsV2ResponseToTxs(
response: InjectiveExplorerRpc.GetTxsV2Response,
) {
return {
data: response.data.map((tx) =>
IndexerGrpcExplorerTransformer.grpcTxV2ToTransaction(tx),
),
paging: response.paging,
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Missing paging transformation – breaks SDK consistency

All V1 helpers convert gRPC paging via grpcPagingToPaging, but the new V2
counterpart returns the raw proto object. Down-stream consumers (UI, REST
wrapper, etc.) now have to branch on version or will silently break.

-    return {
-      data: response.data.map((tx) =>
-        IndexerGrpcExplorerTransformer.grpcTxV2ToTransaction(tx),
-      ),
-      paging: response.paging,
-    }
+    return {
+      data: response.data.map((tx) =>
+        IndexerGrpcExplorerTransformer.grpcTxV2ToTransaction(tx),
+      ),
+      paging: grpcPagingToPaging(response.paging),
+    }

(The same applies to the other *V2ResponseTo* helpers below.)

📝 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.

Suggested change
static getTxsV2ResponseToTxs(
response: InjectiveExplorerRpc.GetTxsV2Response,
) {
return {
data: response.data.map((tx) =>
IndexerGrpcExplorerTransformer.grpcTxV2ToTransaction(tx),
),
paging: response.paging,
}
}
static getTxsV2ResponseToTxs(
response: InjectiveExplorerRpc.GetTxsV2Response,
) {
return {
data: response.data.map((tx) =>
IndexerGrpcExplorerTransformer.grpcTxV2ToTransaction(tx),
),
paging: grpcPagingToPaging(response.paging),
}
}
🤖 Prompt for AI Agents
In
packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts
around lines 465 to 474, the paging field in the returned object is assigned the
raw proto paging object instead of converting it using grpcPagingToPaging like
in V1 helpers. To fix this, apply the grpcPagingToPaging transformation to
response.paging before returning it. This ensures consistency and prevents
downstream consumers from breaking due to differing paging formats. Apply the
same fix to other *V2ResponseTo* helper methods in this file.

Comment on lines 556 to 593
static grpcAccountTxV2ToTransaction(
tx: InjectiveExplorerRpc.TxDetailData,
): ExplorerTransaction {
return {
id: tx.id,
hash: tx.hash,
code: tx.code,
info: tx.info,
memo: tx.memo,
txType: tx.txType,
gasFee: {
amounts: (tx.gasFee?.amount || []).map((amount) => ({
amount: amount.amount,
denom: amount.denom,
})),
gasLimit: parseInt(tx.gasFee?.gasLimit ?? '0', 10),
granter: tx.gasFee?.granter ?? '',
payer: tx.gasFee?.payer ?? '',
},
events: tx.events,
gasUsed: parseInt(tx.gasUsed, 10),
errorLog: tx.errorLog,
claimIds: tx.claimIds.map((claimId) => parseInt(claimId, 10)),
gasWanted: parseInt(tx.gasWanted, 10),
signatures: tx.signatures.map((signature) => ({
address: signature.address,
pubkey: signature.pubkey,
signature: signature.signature,
sequence: parseInt(signature.sequence, 10),
})),
blockNumber: parseInt(tx.blockNumber, 10),
blockTimestamp: tx.blockTimestamp,
data: '/' + Buffer.from(tx.data).toString('utf8').split('/').pop(),
messages: transactionV2MessagesToMessages(tx.messages),
logs: JSON.parse(Buffer.from(tx.logs).toString('utf8')),
codespace: tx.codespace,
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential runtime errors when parsing logs and data

  • JSON.parse(Buffer.from(tx.logs)… ) (line 590) is not wrapped in a try.
    One bad JSON blob from the chain will crash the transformer.

  • data: '/' + Buffer.from(tx.data).toString('utf8').split('/').pop() assumes
    tx.data is a non-empty UTF-8 path containing at least one /. If it is an
    empty string, this produces data: '/undefined'.

Recommend:

-      data: '/' + Buffer.from(tx.data).toString('utf8').split('/').pop(),
+      data: (() => {
+        const decoded = Buffer.from(tx.data).toString('utf8') || ''
+        const last = decoded.split('/').pop()
+        return last ? `/${last}` : ''
+      })(),
...
-      logs: JSON.parse(Buffer.from(tx.logs).toString('utf8')),
+      logs: (() => {
+        try {
+          return JSON.parse(Buffer.from(tx.logs).toString('utf8') || '[]')
+        } catch (e) {
+          console.error('Failed to parse logs:', e)
+          return []
+        }
+      })(),

This shields the SDK from malformed chain data.

📝 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.

Suggested change
static grpcAccountTxV2ToTransaction(
tx: InjectiveExplorerRpc.TxDetailData,
): ExplorerTransaction {
return {
id: tx.id,
hash: tx.hash,
code: tx.code,
info: tx.info,
memo: tx.memo,
txType: tx.txType,
gasFee: {
amounts: (tx.gasFee?.amount || []).map((amount) => ({
amount: amount.amount,
denom: amount.denom,
})),
gasLimit: parseInt(tx.gasFee?.gasLimit ?? '0', 10),
granter: tx.gasFee?.granter ?? '',
payer: tx.gasFee?.payer ?? '',
},
events: tx.events,
gasUsed: parseInt(tx.gasUsed, 10),
errorLog: tx.errorLog,
claimIds: tx.claimIds.map((claimId) => parseInt(claimId, 10)),
gasWanted: parseInt(tx.gasWanted, 10),
signatures: tx.signatures.map((signature) => ({
address: signature.address,
pubkey: signature.pubkey,
signature: signature.signature,
sequence: parseInt(signature.sequence, 10),
})),
blockNumber: parseInt(tx.blockNumber, 10),
blockTimestamp: tx.blockTimestamp,
data: '/' + Buffer.from(tx.data).toString('utf8').split('/').pop(),
messages: transactionV2MessagesToMessages(tx.messages),
logs: JSON.parse(Buffer.from(tx.logs).toString('utf8')),
codespace: tx.codespace,
}
}
static grpcAccountTxV2ToTransaction(
tx: InjectiveExplorerRpc.TxDetailData,
): ExplorerTransaction {
return {
id: tx.id,
hash: tx.hash,
code: tx.code,
info: tx.info,
memo: tx.memo,
txType: tx.txType,
gasFee: {
amounts: (tx.gasFee?.amount || []).map((amount) => ({
amount: amount.amount,
denom: amount.denom,
})),
gasLimit: parseInt(tx.gasFee?.gasLimit ?? '0', 10),
granter: tx.gasFee?.granter ?? '',
payer: tx.gasFee?.payer ?? '',
},
events: tx.events,
gasUsed: parseInt(tx.gasUsed, 10),
errorLog: tx.errorLog,
claimIds: tx.claimIds.map((claimId) => parseInt(claimId, 10)),
gasWanted: parseInt(tx.gasWanted, 10),
signatures: tx.signatures.map((signature) => ({
address: signature.address,
pubkey: signature.pubkey,
signature: signature.signature,
sequence: parseInt(signature.sequence, 10),
})),
blockNumber: parseInt(tx.blockNumber, 10),
blockTimestamp: tx.blockTimestamp,
data: (() => {
const decoded = Buffer.from(tx.data).toString('utf8') || ''
const last = decoded.split('/').pop()
return last ? `/${last}` : ''
})(),
messages: transactionV2MessagesToMessages(tx.messages),
logs: (() => {
try {
return JSON.parse(Buffer.from(tx.logs).toString('utf8') || '[]')
} catch (e) {
console.error('Failed to parse logs:', e)
return []
}
})(),
codespace: tx.codespace,
}
}
🤖 Prompt for AI Agents
In
packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts
around lines 556 to 593, the parsing of tx.logs with JSON.parse and the
construction of the data field from tx.data can cause runtime errors if the
input is malformed or empty. To fix this, wrap the JSON.parse call for tx.logs
in a try-catch block to safely handle invalid JSON and return a default value on
failure. For the data field, add checks to ensure tx.data is a non-empty string
containing at least one '/' before processing; if not, set data to a safe
default string instead of concatenating '/undefined'.

Comment on lines +33 to +61
const ZERO_IN_BASE = new BigNumberInBase(0)

const getContractTransactionV2Amount = (
ApiTransaction: InjectiveExplorerRpc.TxDetailData,
): BigNumberInBase => {
const messages = JSON.parse(
Buffer.from(ApiTransaction.messages).toString('utf8'),
)

const { type, value } = messages[0]

const { msg } = value

if (!type.includes('MsgExecuteContract')) {
return ZERO_IN_BASE
}

if (typeof msg === 'string' && !isJsonString(msg)) {
return ZERO_IN_BASE
}

const msgObj = typeof msg === 'string' ? JSON.parse(msg) : msg

if (!msgObj.transfer) {
return ZERO_IN_BASE
}

return new BigNumberInWei(msgObj.transfer.amount).toBase()
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against empty/invalid messages to avoid runtime exceptions

getContractTransactionV2Amount assumes that
JSON.parse(Buffer.from(ApiTransaction.messages)…)[0] will always return a
truthy element with the expected { type, value } shape.
If messages is an empty array, malformed, or not UTF-8 JSON, the destructuring
on line 42 will throw and bring the whole transformer down.

-  const messages = JSON.parse(
-    Buffer.from(ApiTransaction.messages).toString('utf8'),
-  )
-
-  const { type, value } = messages[0]
+  let firstMsg: any
+  try {
+    const parsed = JSON.parse(
+      Buffer.from(ApiTransaction.messages).toString('utf8') || '[]',
+    )
+    firstMsg = parsed[0]
+  } catch (e) {
+    console.error('Failed to parse contract messages:', e)
+    return ZERO_IN_BASE
+  }
+
+  if (!firstMsg) {
+    return ZERO_IN_BASE
+  }
+
+  const { type, value } = firstMsg

This keeps the function resilient and avoids crashing the whole pagination
endpoint when a single tx contains malformed data.

📝 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.

Suggested change
const ZERO_IN_BASE = new BigNumberInBase(0)
const getContractTransactionV2Amount = (
ApiTransaction: InjectiveExplorerRpc.TxDetailData,
): BigNumberInBase => {
const messages = JSON.parse(
Buffer.from(ApiTransaction.messages).toString('utf8'),
)
const { type, value } = messages[0]
const { msg } = value
if (!type.includes('MsgExecuteContract')) {
return ZERO_IN_BASE
}
if (typeof msg === 'string' && !isJsonString(msg)) {
return ZERO_IN_BASE
}
const msgObj = typeof msg === 'string' ? JSON.parse(msg) : msg
if (!msgObj.transfer) {
return ZERO_IN_BASE
}
return new BigNumberInWei(msgObj.transfer.amount).toBase()
}
const ZERO_IN_BASE = new BigNumberInBase(0)
const getContractTransactionV2Amount = (
ApiTransaction: InjectiveExplorerRpc.TxDetailData,
): BigNumberInBase => {
let firstMsg: any
try {
const parsed = JSON.parse(
Buffer.from(ApiTransaction.messages).toString('utf8') || '[]',
)
firstMsg = parsed[0]
} catch (e) {
console.error('Failed to parse contract messages:', e)
return ZERO_IN_BASE
}
if (!firstMsg) {
return ZERO_IN_BASE
}
const { type, value } = firstMsg
const { msg } = value
if (!type.includes('MsgExecuteContract')) {
return ZERO_IN_BASE
}
if (typeof msg === 'string' && !isJsonString(msg)) {
return ZERO_IN_BASE
}
const msgObj = typeof msg === 'string' ? JSON.parse(msg) : msg
if (!msgObj.transfer) {
return ZERO_IN_BASE
}
return new BigNumberInWei(msgObj.transfer.amount).toBase()
}
🤖 Prompt for AI Agents
In
packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts
around lines 33 to 61, the function getContractTransactionV2Amount assumes
messages parsed from ApiTransaction.messages is a non-empty array with the
expected structure, which can cause runtime exceptions if messages is empty,
malformed, or invalid JSON. To fix this, add a guard clause after parsing
messages to check if it is a non-empty array and that the first element has the
required type and value properties before destructuring. Also, wrap the JSON
parsing in a try-catch block to handle invalid JSON gracefully and return
ZERO_IN_BASE if parsing fails or the structure is invalid.

Comment on lines 476 to 543
static grpcTxV2ToTransaction(
tx: InjectiveExplorerRpc.TxData,
): ExplorerTransaction {
let logs: any[] = []
try {
const rawLogs = Buffer.from(tx.logs).toString('utf8')

logs = JSON.parse(rawLogs || '[]')
} catch (e) {
console.error('Failed to parse logs')
logs = []
}

const txType = JSON.parse(Buffer.from(tx.txMsgTypes).toString('utf8'))

const signatures = tx.signatures.map((signature) => ({
address: signature.address,
pubkey: signature.pubkey,
signature: signature.signature,
sequence: (() => {
try {
return parseInt(signature.sequence, 10)
} catch (e) {
console.error('Failed to parse signature sequence:', e)
return 0
}
})(),
}))

const claimIds = tx.claimIds.map((claimId) => {
try {
return parseInt(claimId, 10)
} catch (e) {
console.error('Failed to parse claimId:', e)
return 0
}
})

let messages: Message[] = []
try {
messages = transactionV2MessagesToMessages(tx.messages)
} catch (e) {
console.error('Failed to parse messages:', e)
messages = []
}

const blockNumber = parseInt(tx.blockNumber)

return {
id: tx.id,
logs,
code: tx.code,
hash: tx.hash,
claimIds,
errorLog: tx.errorLog,
messages,
codespace: tx.codespace,
txType,
signatures,
blockNumber,
blockTimestamp: tx.blockTimestamp,
gasFee: { amounts: [], gasLimit: 0, granter: '', payer: '' },
info: '',
memo: '',
gasUsed: 0,
gasWanted: 0,
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Un-handled JSON parse + radix omission may crash or mis-parse

  1. tx.txMsgTypes is parsed without a try/catch (line 489). One malformed
    payload will throw and abort the entire pagination request.

  2. parseInt(tx.blockNumber) (line 522) omits the radix → strings with a
    leading 0 will be parsed as octal in legacy runtimes.

  3. Several numeric fields default to 0/'' despite the data being present in
    TxData (gasFee, gasWanted, gasUsed, memo, info). This silently
    drops information that callers relied on in V1.

-    const txType = JSON.parse(Buffer.from(tx.txMsgTypes).toString('utf8'))
+    let txType: any = []
+    try {
+      txType = JSON.parse(Buffer.from(tx.txMsgTypes).toString('utf8') || '[]')
+    } catch (e) {
+      console.error('Failed to parse txMsgTypes:', e)
+    }
...
-    const blockNumber = parseInt(tx.blockNumber)
+    const blockNumber = parseInt(tx.blockNumber, 10)

Also consider populating gasFee, memo, info, gasWanted, and gasUsed
when the proto includes them to maintain parity with the V1 representation.

📝 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.

Suggested change
static grpcTxV2ToTransaction(
tx: InjectiveExplorerRpc.TxData,
): ExplorerTransaction {
let logs: any[] = []
try {
const rawLogs = Buffer.from(tx.logs).toString('utf8')
logs = JSON.parse(rawLogs || '[]')
} catch (e) {
console.error('Failed to parse logs')
logs = []
}
const txType = JSON.parse(Buffer.from(tx.txMsgTypes).toString('utf8'))
const signatures = tx.signatures.map((signature) => ({
address: signature.address,
pubkey: signature.pubkey,
signature: signature.signature,
sequence: (() => {
try {
return parseInt(signature.sequence, 10)
} catch (e) {
console.error('Failed to parse signature sequence:', e)
return 0
}
})(),
}))
const claimIds = tx.claimIds.map((claimId) => {
try {
return parseInt(claimId, 10)
} catch (e) {
console.error('Failed to parse claimId:', e)
return 0
}
})
let messages: Message[] = []
try {
messages = transactionV2MessagesToMessages(tx.messages)
} catch (e) {
console.error('Failed to parse messages:', e)
messages = []
}
const blockNumber = parseInt(tx.blockNumber)
return {
id: tx.id,
logs,
code: tx.code,
hash: tx.hash,
claimIds,
errorLog: tx.errorLog,
messages,
codespace: tx.codespace,
txType,
signatures,
blockNumber,
blockTimestamp: tx.blockTimestamp,
gasFee: { amounts: [], gasLimit: 0, granter: '', payer: '' },
info: '',
memo: '',
gasUsed: 0,
gasWanted: 0,
}
}
static grpcTxV2ToTransaction(
tx: InjectiveExplorerRpc.TxData,
): ExplorerTransaction {
let logs: any[] = []
try {
const rawLogs = Buffer.from(tx.logs).toString('utf8')
logs = JSON.parse(rawLogs || '[]')
} catch (e) {
console.error('Failed to parse logs')
logs = []
}
// Wrap txMsgTypes parsing in try/catch and default to an empty array
let txType: any = []
try {
txType = JSON.parse(Buffer.from(tx.txMsgTypes).toString('utf8') || '[]')
} catch (e) {
console.error('Failed to parse txMsgTypes:', e)
}
const signatures = tx.signatures.map((signature) => ({
address: signature.address,
pubkey: signature.pubkey,
signature: signature.signature,
sequence: (() => {
try {
return parseInt(signature.sequence, 10)
} catch (e) {
console.error('Failed to parse signature sequence:', e)
return 0
}
})(),
}))
const claimIds = tx.claimIds.map((claimId) => {
try {
return parseInt(claimId, 10)
} catch (e) {
console.error('Failed to parse claimId:', e)
return 0
}
})
let messages: Message[] = []
try {
messages = transactionV2MessagesToMessages(tx.messages)
} catch (e) {
console.error('Failed to parse messages:', e)
messages = []
}
// Always specify radix for parseInt
const blockNumber = parseInt(tx.blockNumber, 10)
return {
id: tx.id,
logs,
code: tx.code,
hash: tx.hash,
claimIds,
errorLog: tx.errorLog,
messages,
codespace: tx.codespace,
txType,
signatures,
blockNumber,
blockTimestamp: tx.blockTimestamp,
gasFee: { amounts: [], gasLimit: 0, granter: '', payer: '' },
info: '',
memo: '',
gasUsed: 0,
gasWanted: 0,
}
}
🤖 Prompt for AI Agents
In
packages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts
between lines 476 and 543, add a try/catch block around the JSON.parse call for
tx.txMsgTypes to handle malformed payloads gracefully. Change
parseInt(tx.blockNumber) to parseInt(tx.blockNumber, 10) to explicitly specify
the radix and avoid legacy octal parsing issues. Also, update the returned
object to populate gasFee, memo, info, gasWanted, and gasUsed fields from the tx
data instead of defaulting them to empty or zero values, ensuring data
consistency with V1 representation.

@ivan-angjelkoski ivan-angjelkoski merged commit f8ef9a2 into dev May 20, 2025
5 of 6 checks passed
@ivan-angjelkoski ivan-angjelkoski deleted the feat/tx-cursor-pagination branch May 20, 2025 19:08
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