fix(wallet): unblock Hive Engine token transfers and harden HE reads#3254
Conversation
Hive Engine token transfers (e.g. ARCHON) could not proceed — the NEXT button stayed disabled for every HE token. Token precision was read from the Engine balances table, which has no precision field (it lives on the tokens table), so tokenPrecision was always undefined and the transfer's precision gate never passed. - Source Engine precision from the tokens table (fetchTokens) instead of the balances row; fetch both in parallel. Preserve precision 0 (integer tokens). - Thread token precision through the HiveAuth op-builder path (buildTransferOpsArray -> getEngineActionOpArray -> getEngineActionJSON) so the broadcast quantity is truncated to the token's real precision on every auth type, not the 8-decimal fallback (over-precise quantities are rejected by the Engine sidechain). - Add a request timeout + bounded retry-with-backoff to the HE proxy reads, which previously had neither, so a transient proxy blip no longer surfaces as an empty wallet or a stuck transfer. - Surface wallet-load failures with a Retry instead of a blank list, and cap the portfolio query retry backoff. - Cover the engine precision threading with unit tests.
Greptile SummaryThis PR fixes two bugs in the Hive Engine token flow: the NEXT button was permanently disabled because token precision was read from the balances table (which has no
Confidence Score: 5/5Safe to merge — the precision source fix is targeted and correct, the retry/timeout wrapper is conservative, and the NEXT-button relaxation is guarded by both a positive-amount check and an integer test. All three changed paths (key/HiveSigner/HiveAuth) format the broadcast quantity correctly; the integer-amount bypass on the NEXT button cannot produce an over-precise quantity; the Promise.allSettled pattern tolerates one-leg failures without discarding valid data; and the new unit tests cover the precision-threading contract. No files require special attention; the one styling nit (Retry button blending into the error banner) does not affect functionality. Important Files Changed
Sequence DiagramsequenceDiagram
participant UI as TransferScreen
participant TC as TransferContainer
participant HE as hiveEngine (postEngineContract)
participant OB as transactionOpsBuilder
participant HA as hiveEngineActions
UI->>TC: mount (ENGINE token)
par Parallel fetch
TC->>HE: fetchTokenBalances(username)
HE-->>TC: TokenBalance[] (no precision field)
and
TC->>HE: fetchTokens([fundType])
Note over HE: timeout 15s, retry x2
HE-->>TC: Token[] (precision: number)
end
TC->>TC: "enginePrecision = tokens.find(fundType)?.precision"
TC-->>UI: tokenPrecision (from tokens table)
UI->>UI: nextBtnDisabled check
Note over UI: Allow if precision known OR amount is whole number
UI->>OB: "buildTransferOpsArray({..., precision})"
OB->>OB: formatTokenQuantity(amount, precision)
OB->>HA: getEngineActionOpArray(..., precision)
HA->>HA: getEngineActionJSON(..., precision)
HA->>HA: "quantity = formatTokenQuantity(parseToken(amount), precision)"
HA-->>UI: custom_json op with truncated quantity
Reviews (3): Last reviewed commit: "fix(wallet): coerce amount for integer g..." | Re-trigger Greptile |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughDerive ENGINE token precision from token definitions and pass it through transfer builders; add a Hive-Engine POST wrapper with timeout and sequential retries; cap asset-query retry delay; surface wallet query errors with a retry banner above the wallet list. ChangesEngine Token Precision Propagation & Engine Request Retry
Wallet Empty State and Retry Delay
Sequence Diagram(s)sequenceDiagram
participant TransferScreen
participant TransferContainer
participant postEngineContract
participant HiveEngineAPI
TransferScreen->>TransferContainer: request token balances/precision for symbol
TransferContainer->>postEngineContract: fetchTokenBalances / fetchTokens
postEngineContract->>HiveEngineAPI: POST /contract (with timeout & retries)
HiveEngineAPI-->>postEngineContract: response or error
postEngineContract-->>TransferContainer: token balances / token definitions or throw
TransferContainer->>TransferScreen: tokenPrecision (including 0) and balances
TransferScreen->>buildTransferOpsArray: include precision in op payload
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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
ESLint install failed due to a network error. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/screens/transfer/screen/transferScreen.tsx (1)
754-768:⚠️ Potential issue | 🟠 Major | ⚡ Quick winThis gate reintroduces the disabled-NEXT failure mode for unknown Engine precision.
The new builder path already handles
precision === undefinedby falling back to 8-decimal formatting, but this condition blocks the screen from ever reaching that path. If the token-definition lookup misses or degrades, the button stays disabled again instead of using the fallback you just added and tested. If the intent is only to block while precision is actively loading, this needs a dedicated loading flag rather thantokenPrecision !== undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/providers/hive-engine/hiveEngine.ts`:
- Around line 43-63: The metrics call still uses raw ecencyApi.post against
PATH_ENGINE_CONTRACTS (in fetchMetics()/fetchHiveEngineTokenBalances) and
therefore lacks the timeout/backoff behavior; replace that direct post with the
existing retry wrapper postEngineContract<T>() (or call it from
fetchMetics/fetchHiveEngineTokenBalances) so the metrics leg benefits from the
same timeout and exponential backoff, ensuring you remove the raw
ecencyApi.post(PATH_ENGINE_CONTRACTS, ...) usage and adapt the call/site to
await postEngineContract with the appropriate payload and type.
- Around line 79-83: The code is swallowing engine errors by normalizing result
to [] and catching all errors; in functions like fetchTokenBalances (and the
related fetchTokens block around lines 100-104) remove the result ?? []
normalization and remove the blanket .catch that returns [] so that
postEngineContract<TokenBalance[]>(data) is returned directly (or rethrow any
caught error) and failures propagate to callers instead of resolving as empty
arrays; ensure fetchTokens and fetchTokenBalances signatures still return the
promise from postEngineContract so callers can handle/retry on rejection.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e0b15a39-1463-4a30-b9a3-b2bbbd53e473
📒 Files selected for processing (9)
src/containers/transferContainer.tssrc/providers/hive-engine/hiveEngine.tssrc/providers/hive-engine/hiveEngineActions.tssrc/providers/queries/walletQueries/walletQueries.tssrc/screens/transfer/screen/transferScreen.tsxsrc/screens/wallet/screen/walletScreen.tsxsrc/screens/wallet/screen/walletScreenStyles.tssrc/utils/transactionOpsBuilder.test.tssrc/utils/transactionOpsBuilder.ts
| return postEngineContract<TokenBalance[]>(data) | ||
| .then((result) => result ?? []) | ||
| .catch(() => { | ||
| return []; | ||
| }); |
There was a problem hiding this comment.
Stop converting Engine read failures into empty successes.
The result ?? [] normalization plus the blanket catches still make fetchTokenBalances() / fetchTokens() resolve successfully on proxy failures. That means query consumers never enter an error state: src/screens/assetsSelect/screen/assetsSelect.tsx uses fetchTokenBalances() directly as its query function, so an outage still looks like an empty wallet, and fetchTokens() resolving [] leaves transfer precision unset instead of surfacing a retriable failure.
Also applies to: 100-104
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/providers/hive-engine/hiveEngine.ts` around lines 79 - 83, The code is
swallowing engine errors by normalizing result to [] and catching all errors; in
functions like fetchTokenBalances (and the related fetchTokens block around
lines 100-104) remove the result ?? [] normalization and remove the blanket
.catch that returns [] so that postEngineContract<TokenBalance[]>(data) is
returned directly (or rethrow any caught error) and failures propagate to
callers instead of resolving as empty arrays; ensure fetchTokens and
fetchTokenBalances signatures still return the promise from postEngineContract
so callers can handle/retry on rejection.
- transferScreen: allow whole-number Engine transfers through when token precision is unknown (precision-safe), so a degraded token-metadata lookup no longer dead-buttons NEXT; fractional amounts still wait for precision. - hiveEngine: stop swallowing engine read failures into [] — fetchTokenBalances/ fetchTokens now reject on a real transport failure (after retries) and resolve [] only for a genuine empty result, so the wallet query and assetsSelect can retry/surface errors instead of showing an outage as an empty wallet. - transferContainer: fetch balances and token metadata with allSettled so one leg failing no longer discards the other. - walletScreen: show the load-failure Retry as a banner above the list so a failed refresh of an already-loaded (stale) wallet is also surfaced, not just the first-load empty case. - hiveEngine: remove dead fetchHiveEngineTokenBalances/fetchMetics/ fetchUnclaimedRewards (no callers) and now-unused imports.
|
Addressed the review feedback in
Lint/types clean; engine precision threading unit tests still green. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/screens/transfer/screen/transferScreen.tsx`:
- Around line 759-764: The check using Number.isInteger(amount) is ineffective
because amount is a string; update the integer gate used with
isEngineToken/tokenPrecision to test the numeric value instead (e.g. replace
Number.isInteger(amount) with Number.isInteger(Number(amount)) &&
!Number.isNaN(Number(amount)) or an equivalent string-integer test like
/^\s*\d+(\.0+)?\s*$/) so that whole-number strings like "1" allow
unknown-precision Engine transfers to proceed.
In `@src/screens/wallet/screen/walletScreen.tsx`:
- Around line 257-259: The retry button is using a non-existent locale id
'alert.try_again'; update the intl call to use the existing key by replacing
intl.formatMessage({ id: 'alert.try_again' }) with intl.formatMessage({ id:
'try_again' }) in the Wallet screen where the retry Text (styled by
styles.headerActionButtonText) is rendered.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 43c9082d-e163-4f6e-8d77-d4f56cb8b974
📒 Files selected for processing (5)
src/containers/transferContainer.tssrc/providers/hive-engine/hiveEngine.tssrc/screens/transfer/screen/transferScreen.tsxsrc/screens/wallet/screen/walletScreen.tsxsrc/screens/wallet/screen/walletScreenStyles.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/containers/transferContainer.ts
| <Text style={styles.headerActionButtonText}> | ||
| {intl.formatMessage({ id: 'alert.try_again' })} | ||
| </Text> |
There was a problem hiding this comment.
Use an existing locale id for the retry button label.
On Line 258, intl.formatMessage({ id: 'alert.try_again' }) does not match the provided
locale structure (which includes alert.wallet_refresh_failed and a separate try_again key).
This can render fallback text instead of the translated label.
Suggested fix
- {intl.formatMessage({ id: 'alert.try_again' })}
+ {intl.formatMessage({ id: 'try_again' })}📝 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.
| <Text style={styles.headerActionButtonText}> | |
| {intl.formatMessage({ id: 'alert.try_again' })} | |
| </Text> | |
| <Text style={styles.headerActionButtonText}> | |
| {intl.formatMessage({ id: 'try_again' })} | |
| </Text> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/screens/wallet/screen/walletScreen.tsx` around lines 257 - 259, The retry
button is using a non-existent locale id 'alert.try_again'; update the intl call
to use the existing key by replacing intl.formatMessage({ id: 'alert.try_again'
}) with intl.formatMessage({ id: 'try_again' }) in the Wallet screen where the
retry Text (styled by styles.headerActionButtonText) is rendered.
- transferScreen: amount is string state, so Number.isInteger(amount) was always false and the whole-number degraded-mode escape never activated (engine integer transfers stayed blocked when precision was unknown). Coerce with Number(amount). - walletScreen: alert.try_again does not exist (try_again lives under another namespace); use the existing alert.something_wrong_reload so the retry label renders translated instead of a raw id.
|
Both follow-up findings were real — fixed in
One deliberate deviation: I kept |
Problem
Hive Engine (HE) token transfers — e.g. ARCHON — could not be sent: the NEXT button stayed disabled for every HE token, with a valid recipient, amount, and memo. Some users also saw the wallet intermittently fail to load.
Root cause: the transfer screen gates NEXT on the token's precision being known, but precision was read from the HE balances table, which has no
precisionfield — it lives on the tokens table. SotokenPrecisionwas alwaysundefinedand the gate never passed. (Even when it didn't block, an unknown precision let an over-precise quantity reach the sidechain, where it is silently rejected.)Separately, all HE reads go through a single proxy with no request timeout or retry, so a transient blip surfaced as an empty wallet or a stuck transfer — while the same operation works on hive-engine.com (which fails over across nodes).
Fix
Transfer (precision):
fetchTokens) instead of the balances row; fetch balances + token info in parallel. Precision0(integer tokens) is preserved, not defaulted away.buildTransferOpsArray→getEngineActionOpArray→getEngineActionJSON) so the broadcast quantity is truncated to the token's real precision on all auth types (key / HiveSigner / HiveAuth), not the 8-decimal fallback.Reliability (wallet load):
Tests
yarn lintclean;transactionOpsBuilder/number/walletsuites green (110 tests).Notes
Summary by CodeRabbit
New Features
Improvements
Tests