Commit Graph

32 Commits

Author SHA1 Message Date
pezkuwichain ecdb5ee44c Add native Solana balance and transfer support
- SolanaTransaction: hand-rolled compact-u16/legacy-Message/signed-tx
  wire-format primitives, cross-validated byte-for-byte against the
  solders Python library's own serialization of an identical System
  Program transfer message.
- SolanaApi: JSON-RPC client (getBalance/getLatestBlockhash/
  getFeeForMessage/sendTransaction) over a single POST endpoint, unlike
  Tron/Bitcoin's path-per-resource REST style.
- SolanaNativeAssetBalance: getBalance polling, same design as Bitcoin's
  mempool.space polling (no push/subscription mechanism available).
- RealSolanaTransactionService: builds a single-instruction transfer
  message, signs it raw via the existing Ed25519 signer dispatch
  (skipMessageHashing - Solana signs the message directly, no external
  hashing), broadcasts via sendTransaction. Fee is Solana's actual
  getFeeForMessage answer, not a client-side estimate.
- TransactionExecution.Solana(hash) variant; wired into AssetsModule via
  a new SolanaAssetsModule, following the Bitcoin/Tron precedent exactly.
2026-07-16 18:49:29 -07:00
pezkuwichain cb8f0d8aae Replace TODO("Not yet implemented") with an explicit UnsupportedOperationException
subscribeAccountBalanceUpdatePoint() on Bitcoin/Tron-native/TRC20/EVM-native
balance sources is only ever called from RealCrossChainTransactor's XCM
arrival detection, which is Substrate-only - none of these chain families
can be an XCM cross-chain destination, so the method is intentionally never
reachable. A bare TODO() reads as forgotten/unfinished work; swapping it for
the same UnsupportedOperationException pattern UnsupportedAssetBalance
already uses elsewhere in this codebase makes the same never-called
guarantee explicit and documented instead of implicit.
2026-07-15 12:51:58 -07:00
pezkuwichain d4faa64c19 feat: support sending BTC to P2SH/P2PKH destination addresses
Confirmed live via logcat: the real "QR can't be decoded" error was
neither a QR format issue (the earlier BIP21 fix, while correct, wasn't
the cause here) nor a scan failure - the raw QR content was a bare P2SH
address (3...), which this wallet's native-SegWit-only address validation
rejected outright. A real exchange (OKX) withdrawal address turned out to
be P2SH-only with no way to request native SegWit instead.

Adds Base58Check decoding and BitcoinDestinationAddress.kt (P2WPKH/P2SH/
P2PKH, each producing the correct scriptPubKey shape - these differ from
each other, not just cosmetically). This wallet's OWN address/derivation
stays native-SegWit-only and untouched (bitcoinAddressToAccountId is
unaffected) - only the SEND path now recognizes wider destination types.

BitcoinTransactionService/RealBitcoinTransactionService now take the raw
recipient address string instead of a pre-resolved AccountId - converting
to AccountId this early would have discarded which script shape the
destination actually needs, silently building a P2WPKH output for a P2SH
recipient (a real fund-misdirection risk, not just a validation gap).

Test vectors: Satoshi's genesis P2PKH address, a well-known P2SH vanity
address, and the real OKX withdrawal address from this session's live QR
scan, all cross-verified against an independent Python implementation
before writing the Kotlin equivalent.

Reverts the temporary QrDiag logging added to find this - no longer needed.
2026-07-13 05:54:37 -07:00
pezkuwichain 671534de57 fix: add missing isBitcoinBased to Chain(...) in RealTronTransactionServiceTest
Test predates the isBitcoinBased field (added on the Bitcoin branch after
Tron's send branch already had this test file) - CI caught the missing
constructor argument.
2026-07-12 19:22:33 -07:00
pezkuwichain dc244fb0bb fix: remove duplicate SignerProvider binding from WalletFeatureDependencies
The merge silently combined two independent additions of the same
dependency (my fun signerProvider() and Tron's val signerProvider) since
they landed on non-conflicting lines - Dagger correctly caught this as
[Dagger/DuplicateBindings]. Keeping Tron's val form, since it was already
CI-verified on that branch.
2026-07-12 19:12:03 -07:00
pezkuwichain 60493f9aeb merge: reconcile with feature/trc20-tron-send into one combined branch
Bitcoin was branched off main independently instead of continuing on top
of the Tron send branch, which caused real divergence: both branches touch
the same shared signing/DI code (multiChainEncryptionFor, getMetaAccountKeypair,
NodeHealthStateTesterFactory, AssetsModule/TypeBasedAssetSourceRegistry,
CloudBackup schema, Fee model), and both need to ship together in one
coordinated release once wallet-util's master is unblocked. Merging now
reconciles that before it gets any harder, and gives one branch/versionCode
history for device testing instead of two that silently diverge.

Conflict resolutions of note:
- SecretStoreV2/SecretsSigner: getKeypair and multiChainEncryptionFor now
  recognize Tron AND Bitcoin accounts (isTronBased/isBitcoinBased both
  threaded through, checked before the Ethereum fallback).
- AccountDataSourceImpl: both address-backfill migrations now run
  sequentially in one coroutine (Tron's fix for a race against the legacy
  migration applies equally to Bitcoin's backfill).
- CloudBackup: kept Tron's forward-looking Solana schema reservation
  alongside Tron's and Bitcoin's now-real fields.
- ChainRegistryModule/NodeHealthStateTesterFactory: adopted Tron's
  self-contained short-lived OkHttpClient (simpler than threading the
  shared app-wide client through RuntimeDependencies, which is reverted).
- RealSecretsMetaAccount: extended Tron's chain-account MultiChainEncryption
  fix to also cover Bitcoin, for the same underlying reason.
- ChainExt.isValidAddress: Tron's branch independently closed the
  pre-existing Tron validation gap; kept alongside Bitcoin's own check.
2026-07-12 19:02:08 -07:00
pezkuwichain 1eaff6d0a7 fix: expose SignerProvider through WalletFeatureDependencies
CI caught [Dagger/MissingBinding] for SignerProvider in
WalletFeatureComponent - AccountFeatureApi already exposes it, but
WalletFeatureDependencies (feature-wallet-impl's own dependency interface)
never re-declared it, so it wasn't reachable by BitcoinAssetsModule's
provideBitcoinTransactionService.
2026-07-12 16:01:48 -07:00
pezkuwichain 6239526d43 feat: Bitcoin transaction construction, signing, and sending
RealBitcoinTransactionService builds and signs native SegWit transactions
client-side (mempool.space only exposes UTXOs/fee-rate/broadcast, not
construction, unlike TronGrid): greedy largest-first UTXO selection over
confirmed UTXOs, inputs*68 + outputs*31 + 11 vsize heuristic for fees
(matching pezkuwi-exchange's proven approach), RBF-signaled inputs, and a
546-sat dust threshold that folds sub-dust change into the fee rather than
creating an uneconomical output.

Each input gets its own BIP143 sighash, signed via the existing raw-hash
signing primitive (NovaSigner.signRaw with skipMessageHashing) already used
for Ethereum/Tron, then DER-encoded with low-S normalization - no new
signing call path was added, only the DER encoding step.

Also fixes multiChainEncryptionFor/getMetaAccountKeypair to recognize
Bitcoin accounts: previously neither function had any Bitcoin (or Tron)
branch, so signing would have silently used the wrong keypair. Threads a
bitcoin flag through mapMetaAccountSecretsToKeypair/DerivationPath and
AccountSecrets.keypair(chain)/derivationPath(chain) the same way ethereum
already is.
2026-07-12 15:51:37 -07:00
pezkuwichain 1830024ed0 fix: add BitcoinNative branch to exhaustive Chain.Asset.Type whens
CI caught onChainAssetId's when as non-exhaustive; a codebase-wide sweep
for every other exhaustive when on Chain.Asset.Type found one more
(existentialDepositError in Common.kt) that would have failed the same way.
2026-07-12 15:07:30 -07:00
pezkuwichain 69b311cb47 feat: Bitcoin API client, balance reading, and node health checks
Adds a mempool.space-style REST client (RealBitcoinApi, with the same
retry-on-429 pattern as TronGridApi), a polling AssetBalance implementation,
and BitcoinNodeHealthStateTester so the Networks screen can show live
health for Bitcoin nodes (mirrors TronNodeHealthStateTester's rationale:
mempool.space speaks plain REST, not JSON-RPC, so the existing Ethereum/
Substrate testers can't be reused).

Wires Chain.Asset.Type.BitcoinNative through the asset-type mappers and
TypeBasedAssetSourceRegistry, and BitcoinAssetsModule into AssetsModule -
transfers/history stay on the Unsupported stubs until send support lands.
2026-07-12 14:59:05 -07:00
pezkuwichain 32002db5da feat: send TronGrid API key on every request to raise the anonymous rate limit
Complements the retryOn429 fix - having a key means requests are far less likely to hit
the rate limit at all, rather than just retrying transparently after they do. Wired the
same way as INFURA_API_KEY/DWELLIR_API_KEY: buildConfigField read from local.properties or
CI secret, added TRONGRID_API_KEY to android_build.yml's secret passthrough (every caller
already uses secrets: inherit, so no per-workflow changes needed beyond this).
2026-07-12 05:08:22 -07:00
pezkuwichain 04c0c41a2d fix: transparently retry TronGrid calls on HTTP 429 instead of surfacing it to the user
TronGrid's public (no API key) endpoint rate-limits aggressively under normal, human-paced
usage - confirmed live during send-flow testing, where every Confirm tap (each re-running
fee estimation + broadcast) started hitting bare 429s after only a few attempts, with no
way through except retrying by hand until one happened to land outside the rate-limit window.

Added retryOn429 (exponential backoff, same shape as the existing test-only helper in
TronBalancesIntegrationTest) at the RealTronGridApi level so every call - fee estimation,
broadcast, balance reads - retries transparently. Broadcast is safe to retry on 429
specifically since it means TronGrid rejected the request before processing it, not that
the transaction may already be in flight.
2026-07-12 04:54:58 -07:00
pezkuwichain e1d199931c fix: native TRX fee estimation crashes when amount is not yet entered
TronGrid's createtransaction rejects amount=0 with a ContractValidateException.
estimateNativeFee called it unguarded (unlike the TRC-20 path, which already
wraps its dry run in runCatching), so the send screen's reactive fee loader
surfaced this as a generic "Network not responding" error whenever it ran
before the user typed an amount - found live testing the send flow end-to-end.
2026-07-11 18:08:33 -07:00
pezkuwichain 0eab8a8ea2 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.
2026-07-11 15:21:24 -07:00
pezkuwichain cc5d00c4ab fix: make local eq/any/argThat wrappers never return actual null
Switching eq/any/argThat to the raw org.mockito.Mockito statics did not
avoid the 'eq(...) must not be null' crash (it moved from the @Test bodies
to the shared @Before setup(), since JUnit's @Before runs before every
test and failed first there) - Mockito.eq()/any() genuinely return null
regardless of which Kotlin entry point calls them, and that null still
gets checked once it flows into a Kotlin non-null-typed parameter
downstream. Local wrappers now guarantee a non-null return instead:
eq() falls back to the real passed-in value (harmless - the matcher is
already recorded on Mockito's thread-local stack by then), any()/argThat()
return an unchecked-cast dummy, mirroring mockito-kotlin's own internal
implementation of the same helpers.
2026-07-10 10:23:56 -07:00
pezkuwichain a8d2636f66 fix: use raw org.mockito.Mockito statics instead of test_shared wrappers
Root cause of 'eq(...) must not be null' NPE: test_shared's eq()/any()/
argThat() are thin Kotlin wrappers with a declared non-null generic return
type T. Mockito.eq()/any() genuinely return null at runtime (that's how
their matcher-stack recording works) - fine when called directly from
Kotlin (a raw Java static call's return is a platform type, no null-check
inserted), but going through a Kotlin-declared wrapper whose T infers as
non-null at the call site (e.g. eq(baseUrl: String) here) gets a
compiler-inserted null-check on the wrapper's return, which then fires.

Scoped this fix to this test file only rather than touching test_shared's
shared implementation, which every other module's tests also depend on.
2026-07-10 09:36:41 -07:00
pezkuwichain fb1e0c9cf0 fix: add explicit : Unit return type to @Test functions using runBlocking
JUnit4's BlockJUnit4ClassRunner requires @Test methods to compile with a
void return type. 'fun test() = runBlocking { ... }' infers the function's
return type from the block's last expression - two of these tests ended
on a Mockito verify(...).someMethod(...) call, whose return type leaks
through as the inferred type (e.g. String, since TronGridApi.broadcastTransaction
returns String) instead of Unit, so the whole test class failed
ParentRunner validation before any test could even run. Declaring the
return type explicitly as Unit makes Kotlin discard the expression's
value (unit-coercion) rather than infer it - added to all three tests for
consistency, not just the two that were actually failing.
2026-07-10 09:05:03 -07:00
pezkuwichain c5c04807d4 ci: show full exception detail for feature-wallet-impl unit test failures
Default Gradle test logging only prints the exception class name + one
stack frame for a failing test, not its message or cause chain - that's
exactly what happened debugging RealTronTransactionServiceTest's
InvalidTestClassError just now (console showed nothing beyond the bare
exception type). Not useful for anyone hitting a real test failure in CI
without a local Gradle run to inspect the HTML report.
2026-07-10 08:34:06 -07:00
pezkuwichain 096d3d61ed fix: add missing test-shared dependency for feature-wallet-impl unit tests
RealTronTransactionServiceTest needed test-shared's Mockito helpers
(whenever/eq/any/argThat), but feature-wallet-impl never declared
testImplementation project(':test-shared') - every other module that uses
these helpers (feature-account-impl, runtime, common, etc.) already does.
Also pin two ambiguous-without-it type inferences (argThat's lambda param,
emptyMap's type args) explicitly rather than relying on the dependency fix
alone to un-cascade them.
2026-07-10 07:53:08 -07:00
pezkuwichain 9de7267a58 test: add native TRX transfer test - sign/broadcast pipeline had no coverage
RealTronTransactionService's native-TRX branch (transact/calculateFee for
TronTransactionIntent.Native) had zero automated coverage - only the TRC20
ABI encoding (Trc20TransferAbiTest) and address derivation/formatting
(TronDerivationTest, TronAddressTest) were tested. Send/transfer code
moving real funds shouldn't ship untested just because it happens to share
its sign/broadcast plumbing with an already-tested asset type.

Covers: the raw_data is hashed and signed exactly as documented (sha256,
not the raw bytes or txID), the r+s+v signature is assembled correctly
before being sent to broadcastTransaction, and - the one true security
invariant here - a TronGrid response whose txID doesn't match
sha256(raw_data) is refused before ever reaching the signer, not just
before broadcast. Also covers the native fee estimator's bandwidth-shortfall
math against the documented fallback price.

Fixture note: the raw_data_hex/txID pair here is self-consistent (computed
locally) rather than live-captured against TronGrid, unlike
Trc20TransferAbiTest's ABI vector - this class never encodes a real
TransferContract protobuf itself (see its own class doc), so what matters
is that this service correctly hashes/signs/forwards whatever raw_data
TronGrid returns, which a self-consistent fixture exercises identically.
2026-07-10 07:25:33 -07:00
pezkuwichain 54bea1234a fix: revert FullSyncPaymentUpdater's retryWhen wrapper - root cause found
Diffed the whole branch against main (the live, working Play Store
source) instead of continuing to guess from logs. Found the actual
regression: syncAsset() was changed from a suspend fun that EAGERLY
calls startSyncingBalance() (registering each asset's storage key
synchronously, during listenForUpdates()) into a plain fun returning a
lazy flow { } that only calls startSyncingBalance() once collected -
to support a retryWhen wrapper added earlier this session.

BalancesUpdateSystem.launchChainUpdaters() calls
subscriptionBuilder.subscribe(coroutineContext) - which seals the
shared per-chain subscription multiplexer - immediately after
listenForUpdates() returns, then only starts collecting the merged
result flow (which is what triggers the lazy startSyncingBalance()
calls) afterward. So every asset's key registration now races against
the seal instead of reliably happening before it, matching main's
original guaranteed ordering. Some assets win the race often enough to
look like they work; this is why the full ecosystem test - which
creates one account and races ALL of it at once - saw every asset fail,
while the older single-asset test mostly saw only HEZ fail.

Reverted to main's version: suspend syncAsset(), eager
startSyncingBalance() call, no retryWhen. Loses automatic retry of
transient full-sync failures (which the retryWhen wrapper was meant to
add), but that capability isn't worth reintroducing an ordering bug
that can silently break sync for an unpredictable subset of assets on
every chain, not just Pezkuwi's.
2026-07-09 14:44:30 -07:00
pezkuwichain ae8acb9d12 revert: NativeAssetBalance back to raw subscribe(key), typed DSL made it worse
CI proved the typed remoteStorage.subscribe DSL swap (previous commit)
was not a fix - it was a regression. All 8 assets across all 3 Pezkuwi
chains failed to sync in the follow-up test run (vs just HEZ-on-Asset-Hub
before), and the logs showed what looks like storage keys meant for
Pezkuwi Asset Hub's Assets pallet being sent to the Pezkuwi relay chain's
connection instead - some kind of cross-chain contamination introduced
by the DSL's interaction with the shared connection, not understood yet.

Reverting to the raw subscriptionBuilder.subscribe(key) form restores
the prior, narrower state: 5 of 6 assets on Pezkuwi Asset Hub sync fine,
HEZ specifically does not, and every other chain is unaffected. The
underlying HEZ bug is still open - this just stops the attempted fix
from actively making things worse while it's investigated further.
2026-07-09 13:32:17 -07:00
pezkuwichain c5174d0ccf fix: NativeAssetBalance uses typed subscribe DSL, not raw key subscription
Root cause found via 4 rounds of CI diagnostics: HEZ (native asset) on
Pezkuwi Asset Hub was correctly present+enabled in the Chain domain model,
correctly dispatched to NativeAssetBalance, and its System.Account storage
key was correctly computed - but the raw subscriptionBuilder.subscribe(key)
call never actually reached the RPC connection, with zero exceptions and
zero data, while the chain's 5 other assets (all statemine-type, all using
the same SharedRequestsBuilder) synced fine. The same NativeAssetBalance
code worked correctly for every OTHER native asset on every OTHER chain
tested, ruling out a generic bug in the class.

Switched startSyncingBalance() to the typed remoteStorage.subscribe { }
DSL (metadata.system.account.observeWithRaw) - the same mechanism this
class's own subscribeAccountBalanceUpdatePoint() and PooledBalanceUpdater/
BalanceLocksUpdater already use successfully on the same busy chain
connection, instead of the raw subscriptionBuilder.subscribe(key) call
that appears to silently drop registration in that specific configuration.

Also removes the diagnostic logging added during the investigation
(BalancesUpdateSystem's per-chain balancesSync trace, FullSyncPaymentUpdater's
per-chain asset dump) - kept the one genuinely valuable permanent addition,
the "listenForUpdates() threw synchronously" error log for a previously
silent failure mode.
2026-07-09 11:53:35 -07:00
pezkuwichain 7a6b93a323 diag: trace NativeAssetBalance.startSyncingBalance() entry/key/emission
Confirmed via 3 CI runs: HEZ (native asset) is present+enabled in the runtime
Chain object for Pezkuwi Asset Hub and gets dispatched to NativeAssetBalance
without any exception, yet its System.Account subscribe request never reaches
that chain's RPC connection at all - no error logged either. This traces
exactly how far execution gets: does startSyncingBalance() even get entered,
does getRuntime()/storageKey() complete, does subscribe()'s flow ever emit.
2026-07-09 10:51:57 -07:00
pezkuwichain d7cae696c8 diag: log full/enabled asset list per chain in FullSyncPaymentUpdater
HEZ (native type, assetId 0) on Pezkuwi Asset Hub never gets a System.Account
subscription sent to the RPC at all - no exception, no log, while the other 5
statemine-type assets on the same chain sync correctly. Ruled out via code
reading: enabledAssets() default, AssetSourceRegistry dispatch, type mapping,
Room composite PK. This logs the actual chain.assets contents at the exact
point enabledAssets() is consumed, to see directly whether HEZ is present/
enabled in the runtime Chain object or silently absent before this point.
2026-07-09 10:05:42 -07:00
pezkuwichain 7906a20f96 fix: stop swallowed errors from silently killing balance/updater sync
StatemineAssetBalance and NativeAssetBalance's startSyncingBalance()
wrapped setup and subscription failures in runCatching{}/catch{} that
returned emptyFlow()/NoCause instead of propagating. This meant
FullSyncPaymentUpdater's retryWhen (added for the Tron fix) never
actually engaged for these asset types - a transient failure silently
and permanently stopped sync for that asset (HEZ, PEZ, USDT, DOT, ...)
with only a logcat line as evidence. Let the exceptions propagate so
the existing retry boundary can do its job.

ChainRegistry.currentChains ran registerChain()/unregisterChain() per
chain, unguarded, inside a plain (non-Supervisor) CoroutineScope and an
Eagerly-shared flow that never restarts once it dies. One bad chain row
could permanently kill sync for every chain. Isolated each chain's
register/unregister in its own runCatching, and switched the scope to
a SupervisorJob as defense in depth.

ChainUpdaterGroupUpdateSystem.runUpdaters() called getRuntime() and
built per-chain updater flows with no error boundary; callers merge
several chains' results together, so one chain's failure (e.g. a
disabled chain throwing DisabledChainException) could kill
governance/staking/crowdloan sync for every other chain in the group.
Wrapped the body in flow{} + catch to isolate failures per chain.
2026-07-08 13:54:44 -07:00
pezkuwichain 4aebac305f fix: retry balance sync on failure for ALL chains, not just Tron
Generalizes today's Tron balance-polling fix - the same silent-death
bug exists in the shared Substrate sync path used by every chain
(Interlay, Kintsugi, Karura, Acala, Hydration, Polkadex, and any other
orml/statemine asset), not just Tron-specific code.

FullSyncPaymentUpdater.syncAsset() previously used runCatching around
the one-time startSyncingBalance() call and a separate .catch on the
resulting flow - either path failing (a transient WSS hiccup during
setup, an orml currencyId-decode edge case, a node not supporting a
specific RPC method during round-robin, a dropped connection later)
permanently ended that asset's sync for the Updater's lifetime: no
retry, only a Log.e line, and mapNotNull silently dropped the null
result. Since no balance update flow ever emits, AssetCache never
creates a DB row for that asset, and every UI surface (main list,
multi-chain picker) reads via an INNER JOIN that requires that row -
so the asset stays invisible with zero user-visible error until app
restart, which has the same odds of failing again.

Live-tested: Interlay/Kintsugi's node URLs are all reachable (manual
WSS handshake test, 101 Switching Protocols), and none of these chains
are blacklisted - so this wasn't a connectivity or config issue, it
was this retry gap.

Wraps both the initial subscription call and the ongoing flow in one
flow{} builder with retryWhen (30s fixed interval, matching Tron's
polling interval) instead of runCatching + .catch, so a transient
failure at either point just gets retried instead of permanently
killing sync for that asset.
2026-07-08 11:57:26 -07:00
pezkuwichain 21d2896493 fix: retry Tron balance polling on failure instead of permanently dying
Real user on a fresh install reported TRX and USDT-TRC20 never
appearing anywhere in the Tokens list (not "zero balance", genuinely
absent - including from the multi-chain USDT chain picker). Traced
the full pipeline; every layer up to and including
TypeBasedAssetSourceRegistry, ChainRegistry, address derivation, and
TronGridApi wiring was already correct.

Root cause: pollingBalanceFlow() had no retry around fetch() - any
single transient failure (DNS hiccup, timeout, momentary connectivity
loss during app cold start) threw out of the `while(true)` loop,
killing the flow permanently. The collector
(FullSyncPaymentUpdater.syncAsset()) only logs and gives up on
failure, it doesn't resubscribe. Since the asset's first balance
write never happens, AssetCache never inserts a DB row for it, and
every UI surface (main list, multi-chain picker, search) reads
exclusively via an INNER JOIN that requires that row to exist - so
the asset is invisible everywhere, permanently, with zero
user-visible error.

Swallow fetch() failures and retry on the next interval instead of
letting them escape the loop - one bad poll no longer blackholes the
asset for the rest of the app session.

Also fixes a related but separate gap found while tracing this:
RealSecretsMetaAccount.multiChainEncryptionIn() had no isTronBased
branch (only isEthereumBased), unlike DefaultMetaAccount's
hasAccountIn/accountIdIn which already handle Tron correctly. This
didn't cause the missing-tokens bug, but would have broken "export
account" (JSON key backup) for Tron - fixed by routing Tron through
MultiChainEncryption.Ethereum, matching what
RealTronTransactionService already does for actual transaction
signing (same secp256k1 keypair).
2026-07-08 11:33:54 -07:00
pezkuwichain 4537be4496 fix: ktlint violations (nested-comment doc text, line length)
Doc comments describing "/wallet/*" endpoints were parsed as opening
a nested block comment (Kotlin block comments nest, unlike Java/C),
leaving the outer KDoc unterminated - not just a lint nit, ktlint
flagged these as invalid Kotlin files. Reworded to drop the trailing
"*". Also wraps two over-long function signatures/calls.
2026-07-07 07:17:43 -07:00
pezkuwichain 3cd07abe55 Add Tron (TRC20/TRX) send/transfer support
Reuses the existing generic fee-sufficiency validation
(sufficientTransferableBalanceToPayOriginFee) and send flow
unchanged. Transaction construction goes through TronGrid's own
createtransaction/triggersmartcontract endpoints (verified live
against Shasta testnet) rather than a hand-rolled protobuf
implementation; signing reuses the existing secp256k1 path
(SignerPayloadRaw.skipMessageHashing) already used for Ethereum,
no new crypto library. Also fixes a Phase 1 gap: Chain.isValidAddress()
had no isTronBased branch, so every Tron send would have failed
address validation.

No Energy staking/delegation/rental UI - only Tron's default
protocol behavior (burn TRX to cover Bandwidth/Energy shortfall).
2026-07-07 07:08:22 -07:00
pezkuwichain 46d2a91510 Add read-only Tron (TRC20) chain family support (#9)
* Add read-only Tron (TRC20) chain family support

Adds Tron as a third chain family alongside Substrate and EVM:
address derivation (BIP44 coin type 195, secp256k1 reused from the
existing Ethereum path, Base58Check encoding), TronGrid-backed TRX
and TRC20 USDT balance display, and a Room migration for the new
per-account Tron key/address columns. Read-only only — no send,
signing, or broadcast support yet.

* fix: exhaustive when branches for Trc20/TronNative asset types

ChainExt.kt's onChainAssetId and Common.kt's existentialDepositError
both switch exhaustively over Chain.Asset.Type and didn't have cases
for the new Trc20/TronNative variants, breaking compilation. Trc20
mirrors EvmErc20 (contract address as the on-chain id, dust burns
rather than transfers on removal); TronNative mirrors EvmNative.
2026-07-07 06:03:20 -07:00
pezkuwichain a294aa1a6b Initial commit: Pezkuwi Wallet Android
Security hardened release:
- Code obfuscation enabled (minifyEnabled=true, shrinkResources=true)
- Sensitive files excluded (google-services.json, keystores)
- Branch.io key moved to BuildConfig placeholder
- Updated dependencies: OkHttp 4.12.0, Gson 2.10.1, BouncyCastle 1.77
- Comprehensive ProGuard rules for crypto wallet
- Navigation 2.7.7, Lifecycle 2.7.0, ConstraintLayout 2.1.4
2026-02-12 05:19:41 +03:00