mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 23:05:54 +00:00
fix: TRC-20 balance always read 0 for never-activated holders
Trc20AssetBalance/RealTronGridApi.fetchTrc20Balance() read through
TronGrid's /v1/accounts/{address} REST endpoint - but a TRC-20 balance
lives entirely in the token contract's own storage, not the holder's
Account object. An address that has only ever received TRC-20 tokens
(never native TRX, never otherwise "activated" on-chain) has no Account
object at all, so /v1/accounts silently returns `data: []` for it
regardless of its real token balance, and the old code treated that the
same as a genuinely empty/zero account.
Found via a live test: a real wallet received 5 USDT-TRC20, the exchange
confirmed the transfer complete on-chain, but the app kept showing 0.
Independently verified live: /v1/accounts returned empty for the address,
while a direct balanceOf(address) contract call (triggerconstantcontract)
correctly returned 5000000.
Rewrote fetchTrc20Balance() to read via balanceOf(address) instead -
reuses the existing triggerConstantContract() call already used for TRC-20
transfer fee estimation, plus a new encodeBalanceOfParameters() ABI helper
alongside the existing transfer() one. Added a regression test pinned to
this exact real address/balance so this can't silently regress again.
This commit is contained in:
+22
-1
@@ -1,5 +1,6 @@
|
||||
package io.novafoundation.nova.balances
|
||||
|
||||
import io.novafoundation.nova.common.utils.tronAddressToAccountId
|
||||
import io.novafoundation.nova.feature_wallet_impl.data.network.tron.RealTronGridApi
|
||||
import io.novafoundation.nova.feature_wallet_impl.data.network.tron.RetrofitTronGridApi
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -29,6 +30,14 @@ class TronBalancesIntegrationTest {
|
||||
private val usdtContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
|
||||
private val baseUrl = "https://api.trongrid.io"
|
||||
|
||||
// A real wallet that received exactly 5 USDT-TRC20 (2026-07-11) and has NEVER had any native TRX/other
|
||||
// on-chain activity - confirmed live to have no Account object at all (`/v1/accounts` returns `data: []`)
|
||||
// despite genuinely holding the token (`balanceOf` correctly returns 5000000). Regression coverage for the
|
||||
// exact bug this uncovered: fetchTrc20Balance used to read through `/v1/accounts` and silently returned 0
|
||||
// for any address in this state, well after it was live and had already deceived a real user mid-transfer.
|
||||
private val unactivatedHolderAddress = "TUdvwdGeqcag51XkhgRK21KmhH2qw37LZG"
|
||||
private val unactivatedHolderExpectedUsdtBalance = BigInteger.valueOf(5_000_000L)
|
||||
|
||||
private val maxAmount = BigInteger.valueOf(10).pow(30)
|
||||
|
||||
private val tronGridApi = run {
|
||||
@@ -68,9 +77,21 @@ class TronBalancesIntegrationTest {
|
||||
|
||||
@Test
|
||||
fun testTrc20UsdtBalanceLoading() = runBlocking {
|
||||
val freeBalance = retryOn429 { tronGridApi.fetchTrc20Balance(baseUrl, testAddress, usdtContractAddress) }
|
||||
val freeBalance = retryOn429 { tronGridApi.fetchTrc20Balance(baseUrl, testAddress.tronAddressToAccountId(), usdtContractAddress) }
|
||||
|
||||
assertTrue("USDT-TRC20 balance: $freeBalance is less than $maxAmount", maxAmount > freeBalance)
|
||||
assertTrue("USDT-TRC20 balance: $freeBalance is greater than 0", BigInteger.ZERO < freeBalance)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTrc20BalanceLoadingForNeverActivatedHolder() = runBlocking {
|
||||
val freeBalance = retryOn429 {
|
||||
tronGridApi.fetchTrc20Balance(baseUrl, unactivatedHolderAddress.tronAddressToAccountId(), usdtContractAddress)
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
"USDT-TRC20 balance for a never-activated holder: expected $unactivatedHolderExpectedUsdtBalance, got $freeBalance",
|
||||
freeBalance == unactivatedHolderExpectedUsdtBalance
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user