ThorChainApi.kt: multiple bugs, inconsistencies, and code smells (#4244)#4259
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR fixes Cosmos envelope parsing and broadcast body handling in ThorChainApi, extracts affiliate parameter logic to a helper, moves many Thorchain/Cosmos response models into dedicated files, updates import references, and adds tests for transaction and affiliate behaviors. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client Code
participant API as ThorChainApiImpl
participant Helper as ThorChainAffiliateHelper
participant HTTP as HTTP Client
Client->>API: getSwapQuotes(params)
API->>Helper: buildAffiliateParams(referralCode, discountBps)
Helper-->>API: Map(affiliate, affiliate_bps)
API->>HTTP: GET /swap?affiliate=...
HTTP-->>API: Swap quote response
API-->>Client: Quote data
Client->>API: broadcastTransaction(txBytes)
API->>HTTP: POST /broadcast (txBytes)
HTTP-->>API: rawBody (text)
API->>API: json.decodeFromString(rawBody) -> CosmosTransactionBroadcastResponse
alt tx_response.code == 0 or code == 19
API-->>Client: txhash (success)
else
API-->>Client: throw IllegalStateException(with rawBody)
end
Client->>API: getTransactionDetail(txId)
API->>HTTP: GET /tx/{txId}
HTTP-->>API: response (status + body)
alt HTTP 200
API->>API: parse body.tx_response -> ThorChainTransactionJson(code,codespace,raw_log)
API-->>Client: ThorChainTransactionJson
else HTTP 404
API-->>Client: ThorChainTransactionJson(code=null, codeSpace=null, rawLog=rawBody)
else
API-->>Client: throw error(...)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt (1)
334-354:⚠️ Potential issue | 🟠 MajorUse
bodyOrThrow()and avoid silent deserialization of non-success error bodies.Two concerns in the rewritten flow:
- Line 348 newly introduces
response.body<CosmosEnvelopedTxResponse>()instead of the project-standardbodyOrThrow().- Line 346 (
return response.body()for any non-success, non-404 status) will try to deserialize 5xx/4xx error payloads (often Cosmos LCD error JSON or HTML) intoThorChainTransactionJson, surfacing as a confusingSerializationExceptionrather than the actual HTTP failure. It also masks transient server errors as "no code/codespace" results. Surfacing the status code viabodyOrThrow()(or an expliciterror("...status=${response.status}")) is more diagnosable for the polling/keysign flow.♻️ Suggested change
override suspend fun getTransactionDetail(tx: String): ThorChainTransactionJson { val response = httpClient.get("https://thornode.thorchain.network/cosmos/tx/v1beta1/txs/$tx") if (!response.status.isSuccess()) { // The URL initially returns a 'not found' response but eventually // provides a successful response after some time if (response.status == HttpStatusCode.NotFound) return ThorChainTransactionJson( code = null, codeSpace = null, rawLog = response.bodyAsText(), ) - return response.body() + error("getTransactionDetail failed: status=${response.status}, body=${response.bodyAsText()}") } - val envelope = response.body<CosmosEnvelopedTxResponse>() + val envelope = response.bodyOrThrow<CosmosEnvelopedTxResponse>() return ThorChainTransactionJson( code = envelope.txResponse.code, codeSpace = envelope.txResponse.codeSpace, rawLog = envelope.txResponse.rawLog ?: "", ) }As per coding guidelines: "Use
safeLaunchfor network calls andbodyOrThrow()for API methods" and the retrieved learning applyingbodyOrThrow()todata/**/*.kt.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt` around lines 334 - 354, In getTransactionDetail, avoid deserializing error bodies and use bodyOrThrow() for non-success responses: replace the current non-success branch that does "return response.body()" with a call to response.bodyOrThrow() (or explicitly throw an error including response.status and body) so 4xx/5xx responses surface as HTTP failures; keep the NotFound special-case that returns ThorChainTransactionJson with rawLog from response.bodyAsText(), then on success call response.bodyOrThrow<CosmosEnvelopedTxResponse>() and map envelope.txResponse fields into ThorChainTransactionJson.
🧹 Nitpick comments (2)
data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt (2)
668-673: Named constant LGTM.
ERR_TX_IN_MEMPOOL_CACHE = 19matches the Cosmos SDKErrTxInMempoolCacheerror code and removes the magic number at the call site. Consider documenting the upstream reference in a comment, but not blocking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt` around lines 668 - 673, Add a short comment above the companion object constant ERR_TX_IN_MEMPOOL_CACHE explaining it maps to the Cosmos SDK ErrTxInMempoolCache (value 19) and, optionally, include a reference or link to the upstream source (e.g., Cosmos SDK error definition) so future readers know the origin of the value; update the comment near the ERR_TX_IN_MEMPOOL_CACHE constant in the companion object of ThorChainApi to document this mapping.
292-313: Broadcast refactor looks good; one heads-up on the catch-and-rethrow log.Switching to
bodyAsText()+json.decodeFromStringcleanly removes the double body read, and the namedERR_TX_IN_MEMPOOL_CACHEconstant reads much better than the bare19.The smart-cast on line 306 (
txResponse.txHashaftertxResponse?.code == 0 || txResponse?.code == ERR_TX_IN_MEMPOOL_CACHE) is fine — Kotlin propagates non-nullness through the comparisons.Note (out of scope for this PR, just flagging): the existing line 310
Timber.tag("THORChainService").e("Error broadcasting transaction: ${e.message}")swallows the exception object (no stack trace) and uses a string template. If you want to align with the new logging style adopted elsewhere in this PR,Timber.e(e, "Error broadcasting transaction")would be consistent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt` around lines 292 - 313, The catch block in broadcastTransaction currently logs only e.message via a string template and loses the stack trace; replace the Timber call so the throwable is passed to Timber's overload that records the exception (e.g., use the same tag "THORChainService" but call the Timber e(...) overload that accepts the Throwable and a message), i.e. change the Timber.tag("THORChainService").e("Error broadcasting transaction: ${e.message}") call to the form that passes e as the first argument and a static message "Error broadcasting transaction" so the stack trace is preserved while keeping context; leave the rest of broadcastTransaction and the ERR_TX_IN_MEMPOOL_CACHE logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt`:
- Around line 334-354: In getTransactionDetail, avoid deserializing error bodies
and use bodyOrThrow() for non-success responses: replace the current non-success
branch that does "return response.body()" with a call to response.bodyOrThrow()
(or explicitly throw an error including response.status and body) so 4xx/5xx
responses surface as HTTP failures; keep the NotFound special-case that returns
ThorChainTransactionJson with rawLog from response.bodyAsText(), then on success
call response.bodyOrThrow<CosmosEnvelopedTxResponse>() and map
envelope.txResponse fields into ThorChainTransactionJson.
---
Nitpick comments:
In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt`:
- Around line 668-673: Add a short comment above the companion object constant
ERR_TX_IN_MEMPOOL_CACHE explaining it maps to the Cosmos SDK ErrTxInMempoolCache
(value 19) and, optionally, include a reference or link to the upstream source
(e.g., Cosmos SDK error definition) so future readers know the origin of the
value; update the comment near the ERR_TX_IN_MEMPOOL_CACHE constant in the
companion object of ThorChainApi to document this mapping.
- Around line 292-313: The catch block in broadcastTransaction currently logs
only e.message via a string template and loses the stack trace; replace the
Timber call so the throwable is passed to Timber's overload that records the
exception (e.g., use the same tag "THORChainService" but call the Timber e(...)
overload that accepts the Throwable and a message), i.e. change the
Timber.tag("THORChainService").e("Error broadcasting transaction: ${e.message}")
call to the form that passes e as the first argument and a static message "Error
broadcasting transaction" so the stack trace is preserved while keeping context;
leave the rest of broadcastTransaction and the ERR_TX_IN_MEMPOOL_CACHE logic
unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2c8846a7-afdf-465a-b69b-cfa3dd656916
📒 Files selected for processing (1)
data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt
|
Picked up CodeRabbit feedback (actionable issues only, skipping nitpicks). Working on it. |
…zing error bodies (#4244) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Fixed the major issue in
The two 🧹 Nitpick comments (ERR_TX_IN_MEMPOOL_CACHE comment + Timber logging style in broadcastTransaction) are skipped per the review rules. |
|
Pushed coderabbit fixes. Ready for re-review. |
|
In |
|
In |
…R_TX_IN_MEMPOOL_CACHE (#4244) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Done. Added a two-line comment above |
|
Done. Changed |
Move all 19 response DTOs to data/api/models/thorchain/, the cosmos tx envelope to data/api/models/cosmos/, and the affiliate query-param logic to a new ThorChainAffiliateHelper. ThorChainApi is now strictly an HTTP-call surface that returns response models. Also drops buildAffiliateParams from the interface (no external callers) and makes the two denom-metadata helpers private on the impl. Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
|
CI failure detected. Working on a fix. |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (12)
data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardNetworkData.kt (1)
6-10: Redundant@SerialNameannotations.Both
@SerialNamevalues match the Kotlin property names exactly, so the annotations are no-ops and can be dropped to reduce noise. Keep them only if you anticipate renaming the Kotlin properties while preserving the wire format.♻️ Proposed simplification
`@Serializable` data class MidgardNetworkData( - `@SerialName`("bondingAPY") val bondingAPY: String, - `@SerialName`("nextChurnHeight") val nextChurnHeight: String, + val bondingAPY: String, + val nextChurnHeight: String, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardNetworkData.kt` around lines 6 - 10, The `@SerialName` annotations in the MidgardNetworkData data class are redundant because their values match the Kotlin property names; remove the `@SerialName`(...) wrappers from the properties bondingAPY and nextChurnHeight in MidgardNetworkData and leave the `@Serializable` annotation intact, and if the kotlin.serialization.SerialName import becomes unused after removal, delete that import as well.app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt (1)
49-49: Cross‑module import of a UI sentinel is a smell.
VerifySendScreen(underui/screens/send) reaching intoui/models/mappersto import a display string and use it as a control‑flow sentinel signals that the abstraction boundary is wrong. See the corresponding comments onTokenValueToDecimalUiStringMapper.ktand on lines 197–211 — the cleanest fix is to expose a typedisUnlimitedflag on the UI model so this import (and the string comparison) can go away entirely.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt` at line 49, VerifySendScreen is importing the UI sentinel UNLIMITED_TOKEN_AMOUNT from ui/models/mappers and using string comparison as control flow; replace this cross‑module string check by adding a typed boolean on the token UI model (e.g., add isUnlimited to the model produced by TokenValueToDecimalUiStringMapper/its output type) and update VerifySendScreen to use model.isUnlimited instead of comparing against UNLIMITED_TOKEN_AMOUNT so the import and string comparison can be removed.app/src/main/java/com/vultisig/wallet/ui/models/mappers/TokenValueToDecimalUiStringMapper.kt (2)
21-22: Sentinel-string coupling between mapper and screen — prefer a structured signal.Returning
"Unlimited"from the mapper and then havingVerifySendScreenstring‑match against the same constant to decide whether to render a warning icon couples UI rendering decisions to the formatter's output. This is fragile:
- Any future change to the display string (capitalization, localization, punctuation, etc.) silently breaks the warning.
- The mapper now has a UI‑policy concern ("which token values deserve a warning") that's outside its responsibility (decimal formatting).
Consider moving the "is this an unlimited/max approval" decision onto the data model or VM (e.g., a
Boolean isUnlimitedflag onTransactionDetailsUiModel.token/ValuedToken, computed once where the rawTokenValueis available). The mapper can then keep formatting concerns; the screen reads the boolean. Equality againstMAX_UINT256also becomes more discoverable than buried inside a formatter.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/models/mappers/TokenValueToDecimalUiStringMapper.kt` around lines 21 - 22, The mapper TokenValueToDecimalUiStringMapper.invoke currently returns the sentinel string UNLIMITED_TOKEN_AMOUNT when from.value == MAX_UINT256 which forces VerifySendScreen to string-match that value; instead, compute and expose a Boolean (e.g., isUnlimited) on the UI model (TransactionDetailsUiModel.token or ValuedToken) where the raw TokenValue is available (set isUnlimited = from.value == MAX_UINT256 in the VM/mapper layer), change TokenValueToDecimalUiStringMapper.invoke to always return the formatted decimal string (no sentinel), and update VerifySendScreen to read the new isUnlimited flag to decide rendering of the warning icon rather than comparing against UNLIMITED_TOKEN_AMOUNT.
21-42:MAX_UINT256short-circuits before thetry, so a malformedTokenValuewith that exact raw value still bypasses the safety net.Minor: the new check executes outside the
try/catch. In practiceBigInteger == BigIntegerwon't throw, so this is fine today, but if the equality check is later replaced with anything that can throw (e.g.,from.decimalbased detection), the catch‑all fallback toBigDecimal.ZEROwill be skipped. Worth keeping in mind if this branch grows.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/vultisig/wallet/ui/models/mappers/TokenValueToDecimalUiStringMapper.kt` around lines 21 - 42, Move the MAX_UINT256 short-circuit into the existing try block so the equality check is executed inside the try/catch in invoke(TokenValue), e.g., start the try before checking if (from.value == MAX_UINT256) and return UNLIMITED_TOKEN_AMOUNT there; this ensures any future changes to the detection logic (or exceptions thrown by TokenValue) are caught by the catch that logs via Timber and returns BigDecimal.ZERO.data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt (5)
535-535: Replace string-template in Timber call with format specifier.♻️ Suggested fix
- Timber.e(e, "Failed to fetch denom metadata for $denom") + Timber.e(e, "Failed to fetch denom metadata for %s", denom)As per coding guidelines: "Use Timber format specifiers (%s, %d) instead of string templates in log calls".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt` at line 535, Replace the string-template usage in the Timber error call with a format specifier: locate the Timber.e(...) call in ThorChainApi (the one logging "Failed to fetch denom metadata for $denom") and change it to use a format placeholder (e.g., "%s") and pass denom as an argument so the call becomes Timber.e(e, "Failed to fetch denom metadata for %s", denom).
156-187: Remaining hardcoded URLs andbody<T>()calls — follow-up cleanup.Per the PR objectives ("Remove hardcoded Thornode URLs in favor of companion-object constants" and "make consistent use of
bodyOrThrow<T>()"),getUnstakableTcyAmount(line 159) andgetTcyAutoCompoundAmount(line 174) still hardcodehttps://thornode.thorchain.network/...and use rawresponse.body<TcyStakerResponse>()/response.body<ThorTcyBalancesResponseJson>(). The companion already exposesTHORNODE_BASE. Same pattern applies to several other endpoints in this file (e.g.,getBalance,getAccountNumber,getTHORChainNativeTransactionFee,getTHORChainReferralFees,getNetworkChainId,resolveName,getTransactionDetail).Since the PR description explicitly says not to perform large unrelated refactors, this is acceptable to defer — but please track it as a follow-up so the codebase converges on the documented convention.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt` around lines 156 - 187, getUnstakableTcyAmount and getTcyAutoCompoundAmount still use hardcoded Thornode URLs and direct response.body<T>(); change their URL construction to use the companion constant THORNODE_BASE (e.g., "$THORNODE_BASE/thorchain/tcy_staker/$address" and "$THORNODE_BASE/cosmos/bank/v1beta1/balances/$address") and replace response.body<TcyStakerResponse>() / response.body<ThorTcyBalancesResponseJson>() with the standard response.bodyOrThrow<T>() helper so errors are propagated; apply the same pattern to other methods mentioned (getBalance, getAccountNumber, getTHORChainNativeTransactionFee, getTHORChainReferralFees, getNetworkChainId, resolveName, getTransactionDetail) and file these as follow-up cleanup tasks if not addressed in this PR.
612-613: Use Timber format args and include status inerror(...)message.Two nits in this branch:
- The format-arg passes a string-templated value (
"${httpResponse.status}") instead of letting Timber stringify it.- The thrown error message
"Error Fetching Tcy Staked: "has a dangling colon and no useful context.♻️ Suggested fix
- Timber.e("FetchTcyStakedAmount %s", "${httpResponse.status}") - error("Error Fetching Tcy Staked: ") + Timber.e("FetchTcyStakedAmount %s", httpResponse.status) + error("Error Fetching Tcy Staked: ${httpResponse.status}")As per coding guidelines: "Use Timber format specifiers (%s, %d) instead of string templates in log calls".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt` around lines 612 - 613, In ThorChainApi.kt replace the Timber call to use a format arg (e.g. Timber.e("FetchTcyStakedAmount %s", httpResponse.status)) instead of passing a string-templated status, and update the thrown error to include the HTTP status (e.g. error("Error fetching Tcy staked: status=%s".format(httpResponse.status)) or build the message with the status) so the error message is not dangling and contains useful context; locate the two statements around the fetchTcyStakedAmount handling (the Timber.e and the error(...) call) and change them accordingly.
286-290: Prefererror(...)over rawExceptionand avoid double-logging the response body.Two small things:
- As per coding guidelines ("Use require/check/error for preconditions instead of raw exceptions"), throwing a raw
Exceptionis discouraged; useerror(...)(which throwsIllegalStateException) or a domain-specific exception type.- The thrown message already contains
responseRawString, and the catch then re-logs viaTimber...e(e, "Error broadcasting transaction")— so the body is captured once via the exception's message. That's fine, but logging plus rethrow means the caller will likely log the same exception again. Consider either logging here and swallowing, or rethrowing without the local log to avoid duplicate entries upstream.♻️ Suggested fix
- throw Exception("Error broadcasting transaction: $responseRawString") + error("Error broadcasting transaction: $responseRawString")As per coding guidelines: "Use require/check/error for preconditions instead of raw exceptions".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt` around lines 286 - 290, Replace the raw throw Exception(...) with error("Error broadcasting transaction: $responseRawString") (which throws IllegalStateException) and remove the local catch/logging to avoid double-logging; specifically, in ThorChainApi.kt update the broadcasting code that currently throws Exception and calls Timber.tag("THORChainService").e(...) to instead use error(...) and eliminate the catch block (or at minimum stop calling Timber.e before rethrowing) so the exception message contains the response body without redundant local logging.
282-285: Adopt explicit null-check for clarity.Kotlin 2.1.20 (K2) does support smart-cast through
txResponse?.code == 0, so the current code is syntactically valid. However, Kotlin guidelines recommend explicit null handling (?.let,?:) for readability. The defensive pattern clarifies intent and aligns with the project's null-safety practices:- val txResponse = result.txResponse - if (txResponse?.code == 0 || txResponse?.code == ERR_TX_IN_MEMPOOL_CACHE) { - return txResponse.txHash - } + val txResponse = result.txResponse + ?: error("Missing tx_response in broadcast result") + if (txResponse.code == 0 || txResponse.code == ERR_TX_IN_MEMPOOL_CACHE) { + return txResponse.txHash + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt` around lines 282 - 285, The txResponse null-safety check in ThorChainApi should be made explicit: instead of relying on the nullable smart-cast in the conditional, first guard or unwrap result.txResponse (e.g., using txResponse?.let { } or an early if (txResponse == null) return) and then inspect its code against 0 and ERR_TX_IN_MEMPOOL_CACHE; update the block that currently references txResponse?.code so txResponse is non-null when accessing txHash to improve readability and follow the project's null-handling convention.data/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelper.kt (1)
5-41: Behavior preserved cleanly; consider documenting the 50 bps short-circuit.The discount tiering is correct (50+ bps → 0 bps affiliate, otherwise clamp via
calculateBpsAfterDiscount), and extracting this out ofThorChainApiImplis a good cohesion win. The only nit: thediscountBps >= 50branch has no comment explaining why a discount at/above 50 bps zeroes the affiliate address rate while still keeping the address — a brief KDoc above the function describing this contract (and what the upper bound 50 represents) would help future readers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelper.kt` around lines 5 - 41, Add a KDoc comment above buildAffiliateParams explaining the 50 bps short-circuit: state that when discountBps >= 50 the function returns the AFFILIATE_FEE_ADDRESS with affiliate_bps set to "0" (preserving the address but zeroing the rate), document what the 50 value represents (the maximum discount tier that nullifies affiliate fees), and mention interactions with calculateBpsAfterDiscount and constants like THORChainSwaps.AFFILIATE_FEE_ADDRESS and REFERRED_AFFILIATE_FEE_RATE_BP so future readers understand the contract and why the branch exists.data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorChainTransactionJson.kt (1)
6-11:@SerialName("rawLog")doesn't match the Cosmos SDK wire format.
tx_response.raw_login Cosmos REST is snake_case. The current@SerialNamemappings here (code,codespace,rawLog) are mixed: two snake/lowercase, one camelCase. Since this DTO is now populated programmatically fromCosmosEnvelopedTxResponse(deserialization happens there), the@Serializable/@SerialNameannotations are arguably unused — but if anything ever deserializes this class directly (e.g., a cached JSON, a future endpoint, or a test fixture),rawLogwill silently fail to map.Either drop the annotations or align with the wire format:
♻️ Suggested fix
`@Serializable` data class ThorChainTransactionJson( `@SerialName`("code") val code: Int?, `@SerialName`("codespace") val codeSpace: String?, - `@SerialName`("rawLog") val rawLog: String, + `@SerialName`("raw_log") val rawLog: String, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorChainTransactionJson.kt` around lines 6 - 11, The `@SerialName` for rawLog is mismatched to Cosmos wire format; update the ThorChainTransactionJson DTO so its serialized names match the REST fields (or remove the annotations entirely). Specifically, either remove all `@SerialName` annotations from data class ThorChainTransactionJson (letting default Kotlin names be used) or change `@SerialName`("rawLog") to `@SerialName`("raw_log") and ensure the codeSpace field uses `@SerialName`("codespace") and code uses `@SerialName`("code") to align with the Cosmos tx_response fields (code, codespace, raw_log).data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/RujiGraphQLResponse.kt (1)
22-27: Tighten nullability based on GraphQL schema.
StakingV2.bondedandStakingV2.accountare declared non-nullable, butstakingV2isList<StakingV2?>?(the inner element is nullable). If the GraphQL server can return a null element (or if any non-nullable field comes back missing),decodeFromStringwill throw at the call site ingetRujiStakeBalance, which currently has no catch. Either confirm bothbondedandaccountare guaranteed by the schema and drop the inner nullable onList<StakingV2?>?, or make these fields nullable to fail soft.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/RujiGraphQLResponse.kt` around lines 22 - 27, The StakingV2 model currently declares account and bonded as non-nullable while the response type used elsewhere is List<StakingV2?>? which allows null elements; update the model or the response typing to be consistent: either (A) if GraphQL guarantees account and bonded, change the response type to List<StakingV2>? (drop the inner nullable) where getRujiStakeBalance decodes results, or (B) make StakingV2.account and StakingV2.bonded nullable so decoding can tolerate missing fields and the caller in getRujiStakeBalance can handle nulls safely; pick one approach and change the StakingV2 data class and/or the response list type accordingly so decodeFromString no longer crashes on nullable elements.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@app/src/main/java/com/vultisig/wallet/ui/models/mappers/TokenValueToDecimalUiStringMapper.kt`:
- Line 14: UNLIMITED_TOKEN_AMOUNT is a hardcoded English UI string and also used
as a sentinel; extract the display text to strings.xml (e.g.,
R.string.token_amount_unlimited) and use
stringResource(R.string.token_amount_unlimited) when rendering in
TokenValueToDecimalUiStringMapper, but do not localize the sentinel used for
logic—introduce a separate non‑localized constant (e.g.,
UNLIMITED_TOKEN_SENTINEL) and replace usages of UNLIMITED_TOKEN_AMOUNT in
VerifySendScreen.kt and any tx.token.value comparisons with that sentinel so UI
shows the localized string while logic continues comparing against the stable
sentinel.
In `@app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt`:
- Around line 197-211: The warning is currently an icon‑only, string‑sentinel
check (tx.token.value == UNLIMITED_TOKEN_AMOUNT) which breaks localization,
accessibility, and layout; update the UI model to expose a typed flag (eg.
tx.token.isUnlimited set in the VM from the raw TokenValue/BigInteger), replace
the string comparison with that flag, and render a Row containing the warning
Icon plus a localized short Text (e.g., "You are approving an unlimited spend
amount") using Theme.v2 colors and Figma spacing; give the Icon a meaningful
contentDescription only if the Text is omitted (otherwise mark it decorative)
and remove the unused Arrangement.spacedBy if you only have one child or keep it
when Text is added.
In
`@data/src/main/kotlin/com/vultisig/wallet/data/repositories/BalanceRepository.kt`:
- Around line 418-429: The file fails ktfmt formatting around the lambda `find {
... }` usages in BalanceRepository (the `listCosmosBalance.find {
it.denom.equals(coin.ticker, ignoreCase = true) ||
it.denom.equals(coin.contractAddress, ignoreCase = true) }` and the
`mayaChainApi.getBalance(address)` `find { it.denom.equals(coin.ticker,
ignoreCase = true) }` block); fix by running the formatter (./gradlew
:data:ktfmtFormat) or manually reformat those `find { ... }` lambdas to match
ktfmt's preferred line breaks/indentation so the ktfmtCheckMain passes.
---
Nitpick comments:
In
`@app/src/main/java/com/vultisig/wallet/ui/models/mappers/TokenValueToDecimalUiStringMapper.kt`:
- Around line 21-22: The mapper TokenValueToDecimalUiStringMapper.invoke
currently returns the sentinel string UNLIMITED_TOKEN_AMOUNT when from.value ==
MAX_UINT256 which forces VerifySendScreen to string-match that value; instead,
compute and expose a Boolean (e.g., isUnlimited) on the UI model
(TransactionDetailsUiModel.token or ValuedToken) where the raw TokenValue is
available (set isUnlimited = from.value == MAX_UINT256 in the VM/mapper layer),
change TokenValueToDecimalUiStringMapper.invoke to always return the formatted
decimal string (no sentinel), and update VerifySendScreen to read the new
isUnlimited flag to decide rendering of the warning icon rather than comparing
against UNLIMITED_TOKEN_AMOUNT.
- Around line 21-42: Move the MAX_UINT256 short-circuit into the existing try
block so the equality check is executed inside the try/catch in
invoke(TokenValue), e.g., start the try before checking if (from.value ==
MAX_UINT256) and return UNLIMITED_TOKEN_AMOUNT there; this ensures any future
changes to the detection logic (or exceptions thrown by TokenValue) are caught
by the catch that logs via Timber and returns BigDecimal.ZERO.
In `@app/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.kt`:
- Line 49: VerifySendScreen is importing the UI sentinel UNLIMITED_TOKEN_AMOUNT
from ui/models/mappers and using string comparison as control flow; replace this
cross‑module string check by adding a typed boolean on the token UI model (e.g.,
add isUnlimited to the model produced by TokenValueToDecimalUiStringMapper/its
output type) and update VerifySendScreen to use model.isUnlimited instead of
comparing against UNLIMITED_TOKEN_AMOUNT so the import and string comparison can
be removed.
In
`@data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardNetworkData.kt`:
- Around line 6-10: The `@SerialName` annotations in the MidgardNetworkData data
class are redundant because their values match the Kotlin property names; remove
the `@SerialName`(...) wrappers from the properties bondingAPY and nextChurnHeight
in MidgardNetworkData and leave the `@Serializable` annotation intact, and if the
kotlin.serialization.SerialName import becomes unused after removal, delete that
import as well.
In
`@data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/RujiGraphQLResponse.kt`:
- Around line 22-27: The StakingV2 model currently declares account and bonded
as non-nullable while the response type used elsewhere is List<StakingV2?>?
which allows null elements; update the model or the response typing to be
consistent: either (A) if GraphQL guarantees account and bonded, change the
response type to List<StakingV2>? (drop the inner nullable) where
getRujiStakeBalance decodes results, or (B) make StakingV2.account and
StakingV2.bonded nullable so decoding can tolerate missing fields and the caller
in getRujiStakeBalance can handle nulls safely; pick one approach and change the
StakingV2 data class and/or the response list type accordingly so
decodeFromString no longer crashes on nullable elements.
In
`@data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorChainTransactionJson.kt`:
- Around line 6-11: The `@SerialName` for rawLog is mismatched to Cosmos wire
format; update the ThorChainTransactionJson DTO so its serialized names match
the REST fields (or remove the annotations entirely). Specifically, either
remove all `@SerialName` annotations from data class ThorChainTransactionJson
(letting default Kotlin names be used) or change `@SerialName`("rawLog") to
`@SerialName`("raw_log") and ensure the codeSpace field uses
`@SerialName`("codespace") and code uses `@SerialName`("code") to align with the
Cosmos tx_response fields (code, codespace, raw_log).
In `@data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.kt`:
- Line 535: Replace the string-template usage in the Timber error call with a
format specifier: locate the Timber.e(...) call in ThorChainApi (the one logging
"Failed to fetch denom metadata for $denom") and change it to use a format
placeholder (e.g., "%s") and pass denom as an argument so the call becomes
Timber.e(e, "Failed to fetch denom metadata for %s", denom).
- Around line 156-187: getUnstakableTcyAmount and getTcyAutoCompoundAmount still
use hardcoded Thornode URLs and direct response.body<T>(); change their URL
construction to use the companion constant THORNODE_BASE (e.g.,
"$THORNODE_BASE/thorchain/tcy_staker/$address" and
"$THORNODE_BASE/cosmos/bank/v1beta1/balances/$address") and replace
response.body<TcyStakerResponse>() /
response.body<ThorTcyBalancesResponseJson>() with the standard
response.bodyOrThrow<T>() helper so errors are propagated; apply the same
pattern to other methods mentioned (getBalance, getAccountNumber,
getTHORChainNativeTransactionFee, getTHORChainReferralFees, getNetworkChainId,
resolveName, getTransactionDetail) and file these as follow-up cleanup tasks if
not addressed in this PR.
- Around line 612-613: In ThorChainApi.kt replace the Timber call to use a
format arg (e.g. Timber.e("FetchTcyStakedAmount %s", httpResponse.status))
instead of passing a string-templated status, and update the thrown error to
include the HTTP status (e.g. error("Error fetching Tcy staked:
status=%s".format(httpResponse.status)) or build the message with the status) so
the error message is not dangling and contains useful context; locate the two
statements around the fetchTcyStakedAmount handling (the Timber.e and the
error(...) call) and change them accordingly.
- Around line 286-290: Replace the raw throw Exception(...) with error("Error
broadcasting transaction: $responseRawString") (which throws
IllegalStateException) and remove the local catch/logging to avoid
double-logging; specifically, in ThorChainApi.kt update the broadcasting code
that currently throws Exception and calls Timber.tag("THORChainService").e(...)
to instead use error(...) and eliminate the catch block (or at minimum stop
calling Timber.e before rethrowing) so the exception message contains the
response body without redundant local logging.
- Around line 282-285: The txResponse null-safety check in ThorChainApi should
be made explicit: instead of relying on the nullable smart-cast in the
conditional, first guard or unwrap result.txResponse (e.g., using
txResponse?.let { } or an early if (txResponse == null) return) and then inspect
its code against 0 and ERR_TX_IN_MEMPOOL_CACHE; update the block that currently
references txResponse?.code so txResponse is non-null when accessing txHash to
improve readability and follow the project's null-handling convention.
In
`@data/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelper.kt`:
- Around line 5-41: Add a KDoc comment above buildAffiliateParams explaining the
50 bps short-circuit: state that when discountBps >= 50 the function returns the
AFFILIATE_FEE_ADDRESS with affiliate_bps set to "0" (preserving the address but
zeroing the rate), document what the 50 value represents (the maximum discount
tier that nullifies affiliate fees), and mention interactions with
calculateBpsAfterDiscount and constants like
THORChainSwaps.AFFILIATE_FEE_ADDRESS and REFERRED_AFFILIATE_FEE_RATE_BP so
future readers understand the contract and why the branch exists.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3a63cbc6-9ad7-47b0-8f20-6490198ed5f4
📒 Files selected for processing (32)
app/src/main/java/com/vultisig/wallet/ui/models/ChainTokensViewModel.ktapp/src/main/java/com/vultisig/wallet/ui/models/deposit/DepositFormViewModel.ktapp/src/main/java/com/vultisig/wallet/ui/models/mappers/TokenValueToDecimalUiStringMapper.ktapp/src/main/java/com/vultisig/wallet/ui/screens/send/VerifySendScreen.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/errors/SwapException.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/cosmos/CosmosEnvelopedTxResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/BlockNumber.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/BondedNodesResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ChurnEntry.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardHealth.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardNetworkData.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/NodeDetailsResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/RujiGraphQLResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/RujiStakeBalances.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/THORChainInboundAddress.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyDistribution.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyModuleBalanceResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyStakeResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyStakersResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyUserDistributionsResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorChainPoolJson.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorChainTransactionJson.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorNameResponseJson.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorOwnerData.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorchainConstantsResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/VaultRedemptionResponseJson.ktdata/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelper.ktdata/src/main/kotlin/com/vultisig/wallet/data/repositories/AccountsRepository.ktdata/src/main/kotlin/com/vultisig/wallet/data/repositories/BalanceRepository.ktdata/src/main/kotlin/com/vultisig/wallet/data/repositories/ThorchainBondRepository.ktdata/src/main/kotlin/com/vultisig/wallet/data/usecases/ThorchainBondUseCase.kt
✅ Files skipped from review due to trivial changes (21)
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/BlockNumber.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorNameResponseJson.kt
- data/src/main/kotlin/com/vultisig/wallet/data/usecases/ThorchainBondUseCase.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/cosmos/CosmosEnvelopedTxResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyDistribution.kt
- data/src/main/kotlin/com/vultisig/wallet/data/repositories/ThorchainBondRepository.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyStakeResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ChurnEntry.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardHealth.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/BondedNodesResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyModuleBalanceResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/VaultRedemptionResponseJson.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorchainConstantsResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyStakersResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/THORChainInboundAddress.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyUserDistributionsResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorOwnerData.kt
- app/src/main/java/com/vultisig/wallet/ui/models/ChainTokensViewModel.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/errors/SwapException.kt
- app/src/main/java/com/vultisig/wallet/ui/models/deposit/DepositFormViewModel.kt
- data/src/main/kotlin/com/vultisig/wallet/data/repositories/AccountsRepository.kt
|
Picked up CodeRabbit feedback (actionable issues only, skipping nitpicks). Working on it. |
…#4244) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Added KDoc one-liners to all 20 new public classes and the |
…roadcast paths (#4244) ThorChainAffiliateHelperTest pins every branch of buildAffiliateParams (no-code, with-code, partial discount, the >=50bps clean-referral short-circuit, and the maxOf(0, ...) clamp). ThorChainApiImplTest pins the two correctness bugs the PR fixes: getTransactionDetail parses the Cosmos tx_response envelope (not HTTP metadata) and treats 404 as pending; broadcastTransaction returns the txhash on code=0 and code=19 (ErrTxInMempoolCache) and throws on other codes. Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
Three files crept into this PR via earlier bot commits but belong to unrelated work: - VerifySendScreen.kt + TokenValueToDecimalUiStringMapper.kt: add the "Unlimited" sentinel and the warning icon for unlimited approval amounts. This is the dApp/spender-allowance feature, not THORChain. - SwapException.kt: adds @Suppress("SerialVersionUIDInSerializableClass") annotations to every subclass. Lint cleanup, not in #4244 scope. Restore all three to their main-branch state so the PR is scoped to the ThorChainApi correctness fixes and refactor. Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
- ThorChainApi: replace raw Exception with error(), explicit null guard for txResponse, Timber format-arg fixes, include status in fetchTcyStakedAmount error - Align ThorChainTransactionJson.rawLog SerialName to Cosmos wire format - Drop redundant SerialName on MidgardNetworkData - Drop inner-nullable on RujiGraphQLResponse.stakingV2 list Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.kt (2)
27-33: Consider one missing case: null/emptytx_responseenvelope.
broadcastTransactionguardsresult.txResponse ?: error(...)(Line 282-283 ofThorChainApi.kt), andgetTransactionDetailmay need the same guard depending on theCosmosEnvelopedTxResponse.txResponsenullability (see the separate comment on Line 329-334 ofThorChainApi.kt). A test feeding{}or{"tx_response": null}to either method would lock in the "structured error, not NPE" contract.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.kt` around lines 27 - 33, Add tests in ThorChainApiImplTest that exercise the null/empty tx_response envelope path to ensure structured errors (not NPE) are returned: use newApi(...) with MockHttpClient.respondingWith(HttpStatusCode.OK, "{}") and with "{\"tx_response\": null}" and call ThorChainApiImpl.broadcastTransaction(...) and ThorChainApiImpl.getTransactionDetail(...), asserting they surface the expected error/exception class or message rather than throwing NullPointerException; reference the broadcastTransaction and getTransactionDetail methods and the CosmosEnvelopedTxResponse txResponse field to locate where the guard is needed and to lock in the contract.
7-7: Optional: preferrunTestoverrunBlockingfor suspend-test bodies.
runBlockingparks the calling thread;kotlinx.coroutines.test.runTestis the modern idiom for suspending tests, gives you aTestScope, virtual-time advancement, and faster execution. The existingMockHttpClientlikely doesn't need scheduler control here, but usingrunTestfuture-proofs these tests against any addeddelay/withTimeoutpaths and aligns with the project's stated "avoid runBlocking" preference.As per coding guidelines: "Avoid runBlocking — use suspend functions and proper coroutines with viewModelScope".
Also applies to: 35-37, 58-59, 80-81, 91-97, 100-101, 119-120, 138-139
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.kt` at line 7, Replace usages of kotlinx.coroutines.runBlocking in ThorChainApiImplTest.kt with kotlinx.coroutines.test.runTest: add the import for runTest and change each test body that currently uses runBlocking (including the top-level import and the test functions referenced) to use runTest so tests run in a TestScope with virtual-time support; update any test functions (involving the ThorChainApiImplTest test methods) to be suspend lambdas compatible with runTest.data/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelper.kt (2)
15-15: Nit: tighten the short-circuit comment.The comment reads a little awkwardly. Consider rewording to make the intent obvious — e.g.
// Full discount: drop all affiliate fees and ignore the referral code.This also documents why the referral code is intentionally ignored on this branch (which the test on Line 80-87 ofThorChainAffiliateHelperTest.ktexercises).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelper.kt` at line 15, Update the terse comment inside ThorChainAffiliateHelper (same file/class) to clearly state the branch's intent; replace the current line "// For ultimate clean referral, and return 0 bps affiliate" with a clearer phrase such as "// Full discount: drop all affiliate fees and ignore the referral code." so readers and the ThorChainAffiliateHelperTest.kt case (the test that exercises the full-discount branch) immediately understand why the referral code is intentionally ignored.
13-48: Optional: simplify withbuildMap/mapOffor clearer intent.The
mutableMapOf+ branch-mutate pattern works correctly, but each branch ends up populating a fixed pair of keys. AbuildMap/mapOfper branch makes it obvious that the result is always exactly{affiliate, affiliate_bps}(which is what the contract test on Line 99-110 ofThorChainAffiliateHelperTest.ktasserts) and removes the need for themutableMapOfand explicitreturns.♻️ Proposed refactor
- fun buildAffiliateParams(referralCode: String, discountBps: Int): Map<String, String> { - val affiliateParams = mutableMapOf<String, String>() - - // For ultimate clean referral, and return 0 bps affiliate - if (discountBps >= 50) { - affiliateParams["affiliate"] = THORChainSwaps.AFFILIATE_FEE_ADDRESS - affiliateParams["affiliate_bps"] = "0" - - return affiliateParams - } - - if (referralCode.isNotEmpty()) { - val affiliateFeeRateBp = - calculateBpsAfterDiscount( - baseBps = THORChainSwaps.REFERRED_AFFILIATE_FEE_RATE_BP, - discountBps = discountBps, - ) - - // Build nested affiliate with new thorchain structure - val affiliates = "$referralCode/${THORChainSwaps.AFFILIATE_FEE_ADDRESS}" - val affiliateBps = "${THORChainSwaps.REFERRED_USER_FEE_RATE_BP}/$affiliateFeeRateBp" - - affiliateParams["affiliate"] = affiliates - affiliateParams["affiliate_bps"] = affiliateBps - } else { - val affiliateFeeRateBp = - calculateBpsAfterDiscount( - baseBps = THORChainSwaps.AFFILIATE_FEE_RATE_BP, - discountBps = discountBps, - ) - - affiliateParams["affiliate"] = THORChainSwaps.AFFILIATE_FEE_ADDRESS - affiliateParams["affiliate_bps"] = affiliateFeeRateBp.toString() - } - - return affiliateParams - } + fun buildAffiliateParams(referralCode: String, discountBps: Int): Map<String, String> { + // Ultimate clean referral: zero affiliate fee. + if (discountBps >= 50) { + return mapOf( + "affiliate" to THORChainSwaps.AFFILIATE_FEE_ADDRESS, + "affiliate_bps" to "0", + ) + } + + return if (referralCode.isNotEmpty()) { + val affiliateFeeRateBp = calculateBpsAfterDiscount( + baseBps = THORChainSwaps.REFERRED_AFFILIATE_FEE_RATE_BP, + discountBps = discountBps, + ) + mapOf( + "affiliate" to "$referralCode/${THORChainSwaps.AFFILIATE_FEE_ADDRESS}", + "affiliate_bps" to "${THORChainSwaps.REFERRED_USER_FEE_RATE_BP}/$affiliateFeeRateBp", + ) + } else { + val affiliateFeeRateBp = calculateBpsAfterDiscount( + baseBps = THORChainSwaps.AFFILIATE_FEE_RATE_BP, + discountBps = discountBps, + ) + mapOf( + "affiliate" to THORChainSwaps.AFFILIATE_FEE_ADDRESS, + "affiliate_bps" to affiliateFeeRateBp.toString(), + ) + } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelper.kt` around lines 13 - 48, The function currently constructs affiliateParams with mutableMapOf and mutates it in branches; instead simplify by returning an immutable map per branch (use buildMap or mapOf) so each path explicitly returns a Map containing exactly the "affiliate" and "affiliate_bps" keys; update the logic in the method that builds affiliateParams (the branch handling discountBps >= 50, the referralCode.isNotEmpty() branch, and the else branch where affiliateFeeRateBp is computed) to return a map literal from each branch rather than mutating a shared mutableMapOf and using early returns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.kt`:
- Around line 152-158: Replace the JVM-only kotlin.assert usage with JUnit's
assertion so the test always runs: in the test method that assigns ex from
assertThrows (the block calling runBlocking { api.broadcastTransaction(tx =
"{}") }), change the final check that currently uses
kotlin.assert(ex.message?.contains("\"code\": 5") == true) to use
org.junit.jupiter.api.Assertions.assertTrue(ex.message?.contains("\"code\": 5"),
"actual: ${ex.message}") (or the static import assertTrue) to ensure the
assertion is executed and produces a proper test failure message.
In
`@data/src/test/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelperTest.kt`:
- Around line 29-35: The test name and its assertion disagree: the function
ThorChainAffiliateHelper.buildAffiliateParams is being tested with discountBps =
49 and empty referralCode but the assertion expects affiliate_bps == "1" (no
clamping), while the name says “clamps to zero”; either rename the test function
or change inputs to actually trigger the clamp. Fix by either renaming the test
method from `no referral code, discount exceeding base clamps to zero` to
something like `partial discount close to base produces small affiliate bps` to
reflect the current assertion, or change the test input so discountBps >= 50
(and ensure AFFILIATE_FEE_RATE_BP < 50 in test setup) and assert the clamped
value (e.g., "0"), referencing ThorChainAffiliateHelper.buildAffiliateParams and
the AFFILIATE_FEE_RATE_BP behavior when updating the assertion accordingly.
---
Nitpick comments:
In
`@data/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelper.kt`:
- Line 15: Update the terse comment inside ThorChainAffiliateHelper (same
file/class) to clearly state the branch's intent; replace the current line "//
For ultimate clean referral, and return 0 bps affiliate" with a clearer phrase
such as "// Full discount: drop all affiliate fees and ignore the referral
code." so readers and the ThorChainAffiliateHelperTest.kt case (the test that
exercises the full-discount branch) immediately understand why the referral code
is intentionally ignored.
- Around line 13-48: The function currently constructs affiliateParams with
mutableMapOf and mutates it in branches; instead simplify by returning an
immutable map per branch (use buildMap or mapOf) so each path explicitly returns
a Map containing exactly the "affiliate" and "affiliate_bps" keys; update the
logic in the method that builds affiliateParams (the branch handling discountBps
>= 50, the referralCode.isNotEmpty() branch, and the else branch where
affiliateFeeRateBp is computed) to return a map literal from each branch rather
than mutating a shared mutableMapOf and using early returns.
In `@data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.kt`:
- Around line 27-33: Add tests in ThorChainApiImplTest that exercise the
null/empty tx_response envelope path to ensure structured errors (not NPE) are
returned: use newApi(...) with MockHttpClient.respondingWith(HttpStatusCode.OK,
"{}") and with "{\"tx_response\": null}" and call
ThorChainApiImpl.broadcastTransaction(...) and
ThorChainApiImpl.getTransactionDetail(...), asserting they surface the expected
error/exception class or message rather than throwing NullPointerException;
reference the broadcastTransaction and getTransactionDetail methods and the
CosmosEnvelopedTxResponse txResponse field to locate where the guard is needed
and to lock in the contract.
- Line 7: Replace usages of kotlinx.coroutines.runBlocking in
ThorChainApiImplTest.kt with kotlinx.coroutines.test.runTest: add the import for
runTest and change each test body that currently uses runBlocking (including the
top-level import and the test functions referenced) to use runTest so tests run
in a TestScope with virtual-time support; update any test functions (involving
the ThorChainApiImplTest test methods) to be suspend lambdas compatible with
runTest.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d20ac445-64f8-4ab7-8455-43548e5e2064
📒 Files selected for processing (23)
data/src/main/kotlin/com/vultisig/wallet/data/api/ThorChainApi.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/BlockNumber.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/BondedNodesResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ChurnEntry.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardHealth.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardNetworkData.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/NodeDetailsResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/RujiGraphQLResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/RujiStakeBalances.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/THORChainInboundAddress.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyDistribution.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyModuleBalanceResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyStakeResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyStakersResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyUserDistributionsResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorChainPoolJson.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorChainTransactionJson.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorOwnerData.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorchainConstantsResponse.ktdata/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/VaultRedemptionResponseJson.ktdata/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelper.ktdata/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.ktdata/src/test/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelperTest.kt
✅ Files skipped from review due to trivial changes (16)
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyStakeResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/BlockNumber.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ChurnEntry.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorchainConstantsResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorChainTransactionJson.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyModuleBalanceResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyDistribution.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/RujiStakeBalances.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardHealth.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/BondedNodesResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/VaultRedemptionResponseJson.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyUserDistributionsResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/NodeDetailsResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorOwnerData.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/THORChainInboundAddress.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/RujiGraphQLResponse.kt
🚧 Files skipped from review as they are similar to previous changes (3)
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/MidgardNetworkData.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/TcyStakersResponse.kt
- data/src/main/kotlin/com/vultisig/wallet/data/api/models/thorchain/ThorChainPoolJson.kt
|
Picked up CodeRabbit feedback (actionable issues only, skipping nitpicks). Working on it. |
- Replace kotlin.assert (no-op without -ea) with JUnit assertTrue in ThorChainApiImplTest - Rename misleading test name in ThorChainAffiliateHelperTest to accurately describe the partial-discount behaviour (no clamping occurs with discountBps=49 when AFFILIATE_FEE_RATE_BP=50) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Picked up CodeRabbit feedback (actionable issues only, skipping nitpicks). Working on it. |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
data/src/test/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelperTest.kt (1)
100-109: Optional: consider@ParameterizedTestfor the case loop.The
forloop on Lines 102–109 effectively runs multiple sub-cases under a single@Test. Migrating to@ParameterizedTestwith@MethodSource/@CsvSourcewould surface each case as an independent test result (clearer failure attribution in CI), without changing coverage. Non-blocking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/test/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelperTest.kt` around lines 100 - 109, Convert the looped assertions into a parameterized test so each case reports separately: replace the current for-loop inside ThorChainAffiliateHelperTest with a `@ParameterizedTest` (e.g., using `@CsvSource` or `@MethodSource`) that feeds (referralCode, discountBps) pairs into a test method which calls ThorChainAffiliateHelper.buildAffiliateParams and asserts the keys equal setOf("affiliate", "affiliate_bps"); ensure the new test method signature accepts the two parameters and preserves the existing assertion message (or derive case description from the parameters) so CI shows individual failing cases.data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.kt (2)
28-34: Considerrelaxed = trueon the swap-quote serializer mock for safety.
mockk<ThorChainSwapQuoteResponseJsonSerializer>()is a strict mock with no stubs. None of the current tests exercise swap-quote paths so this is fine today, but if a future test in this class accidentally hits a code path that uses the serializer, the failure will be aMockKExceptionrather than something obvious. A relaxed mock (or passingrelaxed = true) would degrade more gracefully.- thorChainSwapQuoteResponseJsonSerializer = - mockk<ThorChainSwapQuoteResponseJsonSerializer>(), + thorChainSwapQuoteResponseJsonSerializer = + mockk<ThorChainSwapQuoteResponseJsonSerializer>(relaxed = true),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.kt` around lines 28 - 34, The mock for ThorChainSwapQuoteResponseJsonSerializer used when constructing ThorChainApi in newApi is strict and can throw MockKException if later tests hit serializer behavior; change the mock creation to a relaxed mock (use mockk<ThorChainSwapQuoteResponseJsonSerializer>(relaxed = true)) when instantiating ThorChainApiImpl so the serializer degrades gracefully without explicit stubs; update the mock passed to ThorChainApiImpl in the newApi helper to use relaxed = true.
59-79: Test name vs. assertions: "surfaces" suggests throwing — clarify intent.The name
surfaces non-zero tx_response code and codespacereads as if a non-zero code should be propagated as an error, but the body just verifies the fields are mapped onto the DTO (which is the correct behavior per the PR —getTransactionDetaildoes not throw for on-chain failures, onlybroadcastTransactiondoes). Consider renaming for clarity, e.g.getTransactionDetail maps non-zero tx_response code, codespace, and raw_log to DTO. This also makes the contrast withbroadcastTransaction throws on non-zero non-mempool-cache code(line 140) explicit.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.kt` around lines 59 - 79, Rename the test function to clarify it checks mapping rather than error propagation: change the test name `getTransactionDetail surfaces non-zero tx_response code and codespace` to something like `getTransactionDetail maps non-zero tx_response code, codespace, and raw_log to DTO` so it accurately reflects that ThorChainApiImpl.getTransactionDetail maps the tx_response fields (code, codespace, raw_log) onto the DTO rather than throwing (contrast to broadcastTransaction behavior); update the test function identifier and any references to the old name accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.kt`:
- Around line 28-34: The mock for ThorChainSwapQuoteResponseJsonSerializer used
when constructing ThorChainApi in newApi is strict and can throw MockKException
if later tests hit serializer behavior; change the mock creation to a relaxed
mock (use mockk<ThorChainSwapQuoteResponseJsonSerializer>(relaxed = true)) when
instantiating ThorChainApiImpl so the serializer degrades gracefully without
explicit stubs; update the mock passed to ThorChainApiImpl in the newApi helper
to use relaxed = true.
- Around line 59-79: Rename the test function to clarify it checks mapping
rather than error propagation: change the test name `getTransactionDetail
surfaces non-zero tx_response code and codespace` to something like
`getTransactionDetail maps non-zero tx_response code, codespace, and raw_log to
DTO` so it accurately reflects that ThorChainApiImpl.getTransactionDetail maps
the tx_response fields (code, codespace, raw_log) onto the DTO rather than
throwing (contrast to broadcastTransaction behavior); update the test function
identifier and any references to the old name accordingly.
In
`@data/src/test/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelperTest.kt`:
- Around line 100-109: Convert the looped assertions into a parameterized test
so each case reports separately: replace the current for-loop inside
ThorChainAffiliateHelperTest with a `@ParameterizedTest` (e.g., using `@CsvSource`
or `@MethodSource`) that feeds (referralCode, discountBps) pairs into a test
method which calls ThorChainAffiliateHelper.buildAffiliateParams and asserts the
keys equal setOf("affiliate", "affiliate_bps"); ensure the new test method
signature accepts the two parameters and preserves the existing assertion
message (or derive case description from the parameters) so CI shows individual
failing cases.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e0c7bc5f-210b-478b-881d-efb4717a7684
📒 Files selected for processing (2)
data/src/test/kotlin/com/vultisig/wallet/data/api/ThorChainApiImplTest.ktdata/src/test/kotlin/com/vultisig/wallet/data/chains/helpers/ThorChainAffiliateHelperTest.kt
|
Picked up CodeRabbit feedback (actionable issues only, skipping nitpicks). Working on it. |
- Replace kotlin.assert with JUnit assertTrue in ThorChainApiImplTest so the check actually runs (kotlin.assert is a no-op without -ea). - Rename misleading ThorChainAffiliateHelperTest case: discountBps=49 with AFFILIATE_FEE_RATE_BP=50 leaves a small fee (1 bp), not a clamp to zero. - Simplify ThorChainAffiliateHelper.buildAffiliateParams to return immutable mapOf per branch and clarify the 50 bps short-circuit comment. Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…4244) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gation (#4244) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes #4244
Changes
getTcyStaker: Replaced a silent swallowed-exception comment with an actualTimber.e(e, ...)log call so errors are now surfaced.getTHORChainSwapQuote: Collapsed a two-lineTimber.tag(...).e(...)into the standardTimber.e(e, message)form, removing the exception message from the string interpolation.broadcastCosmosTransaction: Switched fromresponse.body<CosmosTransactionBroadcastResponse>()(Ktor content negotiation) to explicitjson.decodeFromString(responseRawString)for consistency; replaced the magic literal19with the named constantERR_TX_IN_MEMPOOL_CACHE.getThorChainTransactionStatus: Fixed inverted success/failure logic — successful responses now deserialize intoCosmosEnvelopedTxResponseand extractcode/codeSpace/rawLogfromtx_response; non-success non-404 responses now correctly callresponse.body()instead.CosmosEnvelopedTxResponse/TxResponseBody: Added to represent the{ "tx_response": { "code", "codespace", "raw_log" } }envelope returned by the Cosmos tx query endpoint.Checklist
Summary by CodeRabbit
New Features
Bug Fixes
Refactoring
Tests