-
Notifications
You must be signed in to change notification settings - Fork 207
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
Conversation
Caution Review failedThe pull request is closed. WalkthroughThis update introduces new version 2 (V2) gRPC API methods for fetching transactions, account transactions, blocks, and contract transactions in the Changes
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[]
Poem
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
packages/sdk-ts/src/client/indexer/grpc/IndexerGrpcExplorerApi.spec.tsOops! 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.tsOops! 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.tsOops! 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.
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit 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. Note ⚡️ Faster reviews with cachingCodeRabbit 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 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (4)
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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 inconsistencyAs 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 testsThe 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 forany
fieldsThe interface uses
any
type forlogs
andmessages
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[] | nullpackages/sdk-ts/src/client/indexer/transformers/IndexerGrpcExplorerTransformer.ts (2)
63-76
: Type-safety & fallback fortransactionV2MessagesToMessages
Buffer.from(messages)
will happily accept astring
, but whenmessages
is already aUint8Array
, converting first to string and then to JSON is fine.
However, whenmessages
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
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.
logs
parsing (line 647) lacks atry
, same risk as above.
time
isparseInt(tx.blockTimestamp, 10)
. If the backend ever switches to
RFC-3339 timestamps (as other endpoints already do), this will yieldNaN
.
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
⛔ 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 goodThe 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 testingChanged 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 goodThe
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 goodThe implementation follows the established pattern of the codebase:
- Creates and configures a request object
- Uses retry logic for the gRPC call
- Transforms the response
- 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 goodThe 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 goodThe method provides cursor-based pagination for retrieving blocks. The pattern follows the established conventions in the codebase.
751-820
: New fetchContractTxsV2 method implementation looks goodThe implementation correctly handles contract-specific transaction retrieval with appropriate filtering options and pagination. Parameter handling and error management follow established patterns.
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) | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
static getTxsV2ResponseToTxs( | ||
response: InjectiveExplorerRpc.GetTxsV2Response, | ||
) { | ||
return { | ||
data: response.data.map((tx) => | ||
IndexerGrpcExplorerTransformer.grpcTxV2ToTransaction(tx), | ||
), | ||
paging: response.paging, | ||
} | ||
} |
There was a problem hiding this comment.
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.
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.
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, | ||
} | ||
} |
There was a problem hiding this comment.
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 atry
.
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 producesdata: '/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.
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'.
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() | ||
} |
There was a problem hiding this comment.
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.
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.
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, | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Un-handled JSON parse + radix omission may crash or mis-parse
-
tx.txMsgTypes
is parsed without a try/catch (line 489). One malformed
payload will throw and abort the entire pagination request. -
parseInt(tx.blockNumber)
(line 522) omits the radix → strings with a
leading0
will be parsed as octal in legacy runtimes. -
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.
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.
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores