Impact: iOS + Android. Any transaction whose asset has no stored balance row ends on the confirm screen's error card with only Try Again, even when the amount rules never look at that balance. The user cannot proceed and has no way to understand why. Perpetuals were one instance of this (fixed in #1291 by asking for the right asset); the failure mode itself stays.
Where:
core/gemstone/src/services/confirm/rules.rs:265 — build_metadata
core/gemstone/src/services/confirm/rules.rs:273 — asset_balance, raises BalanceMissing
core/gemstone/src/services/confirm/mod.rs:335 — input_metadata
core/crates/primitives/src/transfer_amount.rs:100 — spends_balance
core/crates/primitives/src/transfer_amount.rs:128 — required is ZERO when it is false
Root cause: build_metadata resolves a balance row for the transaction asset and for the fee asset, and turns a missing row into a load-blocking BalanceMissing. Whether that row is used is decided elsewhere: TransferAmountInput::calculate sets required = BigInt::ZERO when spends_balance() is false, so the transaction asset's balance is never compared against anything for Perpetual, TokenApprove, Account, TransferNft and the non-spending stake types. The load demands data the rules will not read, and a row that is absent only because a balance has not synced yet is indistinguishable from one that can never exist.
Confirmed: the perpetual instance was reproduced — test_perpetual_metadata_reads_the_collateral_balance_the_wallet_stores fails without #1291 with BalanceMissing { hypercore / perpetual::ETH }, and both apps showed the error card. The remaining cases are read off the rules above, not reproduced — no other input type has been observed reaching confirm without a row.
Note on the fix: the cheap version (missing row -> zero) is wrong here: for a real spend it would turn "balances not synced" into a false "insufficient balance". The row should be required only when the rule reads it, which means build_metadata has to know the input type.
Impact: iOS + Android. Any transaction whose asset has no stored balance row ends on the confirm screen's error card with only Try Again, even when the amount rules never look at that balance. The user cannot proceed and has no way to understand why. Perpetuals were one instance of this (fixed in #1291 by asking for the right asset); the failure mode itself stays.
Where:
core/gemstone/src/services/confirm/rules.rs:265—build_metadatacore/gemstone/src/services/confirm/rules.rs:273—asset_balance, raisesBalanceMissingcore/gemstone/src/services/confirm/mod.rs:335—input_metadatacore/crates/primitives/src/transfer_amount.rs:100—spends_balancecore/crates/primitives/src/transfer_amount.rs:128—requiredisZEROwhen it is falseRoot cause:
build_metadataresolves a balance row for the transaction asset and for the fee asset, and turns a missing row into a load-blockingBalanceMissing. Whether that row is used is decided elsewhere:TransferAmountInput::calculatesetsrequired = BigInt::ZEROwhenspends_balance()is false, so the transaction asset's balance is never compared against anything forPerpetual,TokenApprove,Account,TransferNftand the non-spending stake types. The load demands data the rules will not read, and a row that is absent only because a balance has not synced yet is indistinguishable from one that can never exist.Confirmed: the perpetual instance was reproduced —
test_perpetual_metadata_reads_the_collateral_balance_the_wallet_storesfails without #1291 withBalanceMissing { hypercore / perpetual::ETH }, and both apps showed the error card. The remaining cases are read off the rules above, not reproduced — no other input type has been observed reaching confirm without a row.Note on the fix: the cheap version (missing row -> zero) is wrong here: for a real spend it would turn "balances not synced" into a false "insufficient balance". The row should be required only when the rule reads it, which means
build_metadatahas to know the input type.