Commit Graph

224 Commits

Author SHA1 Message Date
pezkuwichain e716e6e277 Fix Pending Signatures: query the bridge's own hardcoded multisig, not the generic multisig-account feature
The previous commit reused MultisigPendingOperationsService, but that
service only syncs accounts formally registered as a MultisigMetaAccount
in this wallet - it silently returns an empty NoOpSyncer for every other
account type. The bridge's 5 signatories (Serok etc.) use their own
regular wallets to approve calls against the bridge's separate,
hardcoded multisig (BridgeMultisigConstants) - they never "are" that
multisig account, so the feature was structurally guaranteed to show
nothing, not just empty by coincidence.

Fix queries the bridge's own multisig accounts directly, mirroring the
exact pattern this screen's existing renewal-signing code already uses
(BridgeMultisigRuntimeApi's runtime-metadata accessors, a local mirror
of feature-account-impl's off-chain call-data indexer client - same
module-boundary reasoning already documented in that file) rather than
the generic, differently-scoped service:

- BridgeMultisigInteractor.getPendingApprovals(): enumerates
  Multisig.Multisigs entries for both bridge multisig accounts via
  QueryableStorageEntry2.keys()/entries() (a general runtime-storage
  capability, not multisig-feature-specific), fetches real call content
  from the same off-chain indexer the generic feature uses (also
  general-purpose, keyed by address+hashes - never required the account
  to be a MultisigMetaAccount either), and filters to calls this
  signatory hasn't approved yet.
- BridgeMultisigInteractor.submitApproval(): reuses
  composeBridgeMultisigAsMulti generalized to an arbitrary call+
  timepoint (previously only used for the renewal call) - refuses to
  proceed if the call content is unknown, never blind-signs.
- BridgeViewModel formats the real parsed transfer (amount+destination)
  via AssetSourceRegistry.tryParseTransfer, already used elsewhere for
  the exact same "decode a transfer call" job - no fabricated preview
  data.

Reverts the feature-multisig:operations cross-feature dependency and
FeatureApi wiring added in the previous commit - no longer needed since
everything now lives in this screen's own existing self-contained
domain layer, matching how the rest of BridgeMultisigInteractor already
works (and already explains, in its own doc comments, why a synthetic
MultisigMetaAccount was deliberately avoided for exactly this reason).
2026-07-17 16:42:40 -07:00
pezkuwichain 6f14c8bf05 Move Pending Signatures from the wallet home screen to the Bridge screen
Wrong screen in the previous commit - moved it to sit right below the
existing per-leg renewal sign buttons on the Bridge input screen, which
is where it was actually asked for: a signatory renewing their own
wUSDT/Polkadot USDT allowance there should also see (and be able to
jump to) other pending multisig operations - e.g. someone else's large
bridge swap - waiting on their signature, without navigating elsewhere.

BalanceListFragment/ViewModel/Module are reverted to their prior state.
The reusable pieces (PendingSignatureModel, item_pending_signature_row
layout) are kept and repurposed for the Bridge screen; the RecyclerView
SingleItemAdapter/card layout built for the home screen are removed
since Bridge is a plain (non-RecyclerView) fragment - rows are inflated
directly into a LinearLayout container instead, same underlying data
and MultisigCallFormatter reuse as before.

The feature-assets -> feature-multisig:operations dependency and the
FeatureApi wiring (AssetsFeatureComponent/Dependencies/Holder) from the
previous commit stay as-is - Bridge is the same Gradle module, so no
change needed there.
2026-07-17 14:43:18 -07:00
pezkuwichain 707d1baba4 Add a Pending Signatures card to the wallet home screen
Surfaces multisig operations awaiting the current signatory's own
approval directly on the main balance screen, instead of requiring
navigation to the separate pending-operations list. Each row shows the
formatted amount, destination, chain, and approval progress, with a
red Sign button that opens the existing operation-details/approve flow
(fee estimation, signatory balance check, and validation are all
already handled there - not reimplemented here).

Reuses the already-tested MultisigCallFormatter from
feature-multisig:operations (added as a new feature-assets dependency,
exposed via the standard FeatureApi/FeatureContainer pattern - see
AssetsFeatureHolder/AssetsFeatureComponent/AssetsFeatureDependencies)
rather than re-deriving call-preview formatting independently. The
card follows the same SingleItemAdapter + ConcatAdapter pattern as the
existing Pezkuwi Dashboard card on the same screen.

Once a pending operation is actually signed, it naturally drops off
this list on the next sync (no separate client-side "signed" state is
tracked in parallel with the real on-chain approval status).

String only added to the base (English) strings.xml for now - not yet
translated into the other ~14 locales this app otherwise maintains.
2026-07-17 13:03:12 -07:00
pezkuwichain e1a010b779 Fix: keep net.i2p.crypto.eddsa classes from being stripped by R8
Signer.kt/SignatureVerifier.kt in substrate-sdk-android look up the
"EdDSA" JCA provider by name at runtime (Signature.getInstance(...,
"EdDSA")), backed by net.i2p.crypto:eddsa's EdDSASecurityProvider. With
no keep rule, R8 stripped/renamed its classes in the minified release
build, so the provider's own registered service implementation could no
longer be found - "class configured for Signature (provider: EdDSA)
cannot be found", reproduced on-device sending SOL (every Solana
transaction signs with Ed25519, so this path is always hit).
2026-07-17 10:40:01 -07:00
pezkuwichain b09ba4c303 Remove dead, non-functional BizinikiwSr25519Signer stub
Never referenced anywhere (self-declaration only, confirmed via grep),
sitting since the initial commit. Its RistrettoPoint.multiply()/add() are
both no-ops returning `this` unchanged, BASE is all-zero bytes, and
keccakF1600() never writes output back into the Strobe state - none of
this could ever produce a valid signature. The real, live bizinikiwi
signing path is the native JNI binding in bindings/sr25519-bizinikiwi/
(PezkuwiKeyPairSigner -> BizinikiwSr25519), unaffected by this removal.
Leaving broken placeholder crypto sitting in a wallet's signer package
is its own audit red flag even when provably unused.
2026-07-17 09:07:35 -07:00
pezkuwichain 16427654ae Remove CHANGELOG_PEZKUWI.md dev-notes file, not app source/APK-related
Was a context-recovery scratch doc from the initial rebrand port (Feb 2026):
self-contradicted (top says debug cleanup done, bottom checklist still
unchecked), and its only non-obvious content (SDK/runtime type-lookup
gotchas) is already explained in the actual source docstrings
(PezkuwiCheckMortality.kt, PezkuwiAddressConstructor.kt) or preserved
in memory for future reference.
2026-07-17 08:13:51 -07:00
pezkuwichain fc6bfa2867 Revert "Gitignore scratch/planning-note naming patterns"
This reverts commit 335859c9a0.
2026-07-17 07:39:12 -07:00
pezkuwichain 335859c9a0 Gitignore scratch/planning-note naming patterns
Prevents a repeat of docs/PACKAGE_STRUCTURE_REBRAND.md (a never-executed
planning draft that sat in the repo for months) - working notes belong
in a scratchpad, not the tracked tree. Real docs (BRAND.md, README.md,
docs/*) are unaffected.
2026-07-17 07:38:07 -07:00
pezkuwichain 43af39bfe7 Remove stale, never-executed package-rebrand planning draft; gitignore this pattern
docs/PACKAGE_STRUCTURE_REBRAND.md was a 2026-01-23 planning note (status:
BEKLEMEDE, never actioned, references a contributor's personal machine
path) that had been sitting in the repo as dead weight since. Real,
maintained docs (BRAND.md, README.md, docs/*) are unaffected.
2026-07-17 07:37:38 -07:00
pezkuwichain 163e68dcd3 Correct the previous crash fix: revert schema, add a real tolerant reader instead
The previous commit's SafeOptional/custom() field wrapper was wrong and
broke real tests (RealLocalAccountsCloudBackupFacadeTest, Solana/Tron
backfill migration tests - Mockito WantedButNotInvoked, IllegalArgumentException).

Root cause of that failure: EncodableStruct.get() (in the external
substrate-sdk-android library) only returns null for an unset field
when field.dataType is specifically an instance of the library's own
`optional<*>` class - not any DataType that merely behaves like one.
optional<T> is not open, so no wrapper class can satisfy that check;
swapping in a custom DataType just moves the same crash from read-time
to access-time.

Correct fix: leave the MetaAccountSecrets schema exactly as it always
was (plain `.optional()`, real library instances, so get() keeps
working), and instead add readMetaAccountSecrets() - a resilient
top-level reader that tries the normal Schema.read() first and, only
if that throws, falls back to reading each field by hand in schema
order, stopping at the first one whose bytes don't exist and leaving
every field after it genuinely unset. Because those fields were never
set, EncodableStruct.get()'s existing (correct) optional-field handling
returns null for them exactly as it already does for any account that
was never migrated - no library behavior is being routed around,
just extended to tolerate a shorter/older stored blob.
2026-07-17 06:08:17 -07:00
pezkuwichain 9a11dc2c3d Fix crash reading MetaAccountSecrets for accounts predating Tron/Bitcoin/Solana
Found on a real device: opening the backup/export screen for an
existing wallet crashed with
java.lang.IndexOutOfBoundsException: Cannot read 412 of 412
  at .../scale/dataType/optional.read
  at .../scale/Schema.read (x3)
  at SecretStoreV2$getMetaAccountSecrets$2.invokeSuspend

Root cause: MetaAccountSecrets is a positional (SCALE) binary schema.
optional<T>.read() unconditionally reads a presence-flag byte with no
EOF handling - reading a blob that predates a trailing field (written
before that field existed) throws instead of yielding null. This is
also why Solana's own address was never getting backfilled: the
migration's own read of the account's stored secrets was throwing on
every attempted run, silently caught by its own per-account try/catch.

Confirmed via substrate-sdk-android's actual source (Dsl.kt/Delegate.kt)
that Tron/Bitcoin's identical schema-growth pattern carried the exact
same latent risk - it likely only avoided crashing so far by chance
byte-alignment in previously-stored blobs, not because the format
tolerates it.

Fix: read Tron/Bitcoin/Solana's keypair/derivationPath fields (every
field added after the original Substrate+Ethereum schema) through a
SafeOptional DataType that catches a failed read and returns null,
via the library's own custom() field-declaration escape hatch. Write
behavior is byte-for-byte unchanged - only reading a shorter/older
blob now correctly yields null for fields it doesn't have, instead of
crashing every caller of getMetaAccountSecrets (balance sync, signing,
backup/export, this Solana backfill migration itself).
2026-07-17 02:19:10 -07:00
pezkuwichain db9962a00e Add SOL to default token order, right after TRX
Mirrors how TRX/BTC were inserted into this list when they shipped
(351114b, 55d405c) - SOL was missed when Solana support was added.
2026-07-17 02:16:31 -07:00
pezkuwichain 71e4c8dc7a Fix CI: sender/recipient hex fixtures in Solana transaction tests were 1 hex digit short
Both SolanaTransactionTest and RealSolanaTransactionServiceTest carried
a transcription error from the earlier solders cross-validation: the
sender/recipient pubkey hex strings (and one of expectedMessageHex's
concatenated chunks) were each missing their trailing hex digit,
producing odd-length hex that BouncyCastle's decoder throws on
(StringIndexOutOfBoundsException) - all 5 SolanaTransactionTest cases
failed at field-init time as a result. Re-derived every fixture
programmatically from the same solders-verified reference message
(rather than hand-splitting the hex string again) and confirmed the
full round trip (build -> compare) matches byte-for-byte.
2026-07-16 20:54:39 -07:00
pezkuwichain 086812345e Fix CI: missing Solana address imports in ChainExt.kt, unused import in RealSolanaTransactionService.kt
ChainExt.kt called toSolanaAddress/solanaAddressToAccountId/
emptySolanaAccountId/isValidSolanaAddress without importing them - an
earlier-session oversight that only surfaced once CI actually compiled
the runtime module (no local Kotlin compiler available to catch this
sooner). ktlint separately flagged an unused AccountId import.
2026-07-16 20:00:36 -07:00
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 d35687f2ce Add native Solana support: address derivation, accounts, signing, backfill migration, CloudBackup wiring
Same read/send-capable chain-family pattern as Tron/Bitcoin, but for
Ed25519/SLIP-0010 instead of secp256k1/BIP32 (Solana's account id IS its
raw public key, so no separate address-hash derivation step is needed).

- SLIP-0010 Ed25519 keypair derivation (Bip32Ed25519KeypairFactory),
  cross-validated against an independent slip10 reference implementation
  and pinned to a known-good test vector.
- Chain.isSolanaBased / Chain.Asset.Type.SolanaNative wired through every
  chain-mapper, account-model, and signer dispatch site (mirrors the
  existing Tron/Bitcoin wiring exactly).
- SolanaAddressBackfillMigration derives Solana keys for wallets created
  before this support existed, same self-healing no-stuck-flag design as
  the Tron/Bitcoin backfills - also fixed a latent bug in those two
  migrations where MetaAccountSecrets/MetaAccountLocal.addXAccount()
  dropped sibling chain-family fields on a re-run.
- Room migration 75->76 adds isSolanaBased/solanaPublicKey/solanaAddress
  columns.
- CloudBackup's already-reserved solanaAddress/SolanaSecrets fields are
  now actually read/written by RealLocalAccountsCloudBackupFacade; also
  fixed isCompletelyEmpty() to check solana, so a Solana-only wallet
  doesn't get silently dropped from backup.
2026-07-16 18:49:09 -07:00
pezkuwichain 6fd7a56382 Bridge: add retry on origin failure, dedupe fee constant
Retry: OriginFailed only offered "Done" (start over from the input
screen) - the origin transfer never left the wallet in that state, so
retrying is just re-running submit() from scratch, same as Swap's own
retryClicked(). The execution screen's action button now shows "Try
again"/retries when OriginFailed, "Done" otherwise - derived from the
same state observer already driving the alert banner, not a second
LiveData to keep in sync.

FEE_PERCENT was duplicated as separate 0.001 literals in BridgeViewModel
and BridgeExecutionViewModel. Moved to BridgeMultisigConstants (already
the shared home for this feature's cross-cutting constants) so there's
one number to change if the server-side fee_basis_points ever does.

Deliberately NOT touching the deeper architecture items (LiveData vs
StateFlow, extracting submit logic to a domain interactor) - Bridge's
sealed BridgeExecutionState already gives it the single-source-of-truth
property that motivated Swap's StateFlow design, and its submit/wait
logic isn't shared with any other screen, so there's no duplication or
desync bug those changes would actually fix here. This scope is staying
USDT.p<->USDT only going forward (other bridge/swap pairs route through
the separate pezsnowbridge DApp) - the real trigger for that refactor
(a second bridge pair reusing this logic) will not happen in this
screen, so it's not deferred tech debt, it's a closed decision.
2026-07-16 15:57:12 -07:00
pezkuwichain ec0ec9153f Bridge: lock down execution navigation, predict manual review upfront
Execution screen safety fix: hardware back and the toolbar home button
could navigate away mid-submission (the transfer's own ViewModel scope
gets cancelled on leaving, same class of risk as leaving Swap's execution
screen mid-flight, which that screen has always prevented). Now matches
SwapExecutionFragment exactly - back suppressed, home button hidden, the
only way out is the Done button once the operation has actually resolved.

Bridge previously only found out "this needs manual 3-of-5 review" after
watching the destination balance for up to 90s and giving up - honest, but
slow to tell the user something the input screen's own on-chain checks
already knew before submission. The consent gate checked the automation
key's remaining on-chain allowance but not usdt-bridge's separate hard
per-tx cap (max_single_tx, 50,000 USDT) - a large single swap could read
as "fast" against a freshly-renewed 200,000 allowance while the backend
would still force it to manual review. Added that check
(BridgeMultisigConstants.MAX_SINGLE_TX), forwarded the resulting
prediction through BridgeExecutionPayload.expectManualReview, and added a
new AwaitingManualReview state so the execution screen shows the same
clear message ("needs approval from 3 of 5 signatories, ~2 hours,
t.me/pezkuwidestek") immediately instead of after a false wait -
regardless of whether manual review is needed because the amount itself
is large or because other users' swaps have temporarily used up the
automation key's approval.
2026-07-16 10:23:01 -07:00
pezkuwichain d0db0a54f7 Update launcher icon to the latest goat badge design
Source: pwap-web/shared/images/pezWallet - white_background_last.png -
a refined round-badge design (wallet flap detail added). Same crop+resize
process as the previous icon passes, matched to the exact scale/position
already in use so only the artwork changes.
2026-07-15 22:19:02 -07:00
pezkuwichain 272921dcdf Match the execution screen's visual polish to the app's own Swap flow
The structural separation (dedicated screen, no amount input) was already
in place, but the screen itself was much sparser than Swap's own execution
UI - just a label and a timer, no "Do not close the app!" warning and no
summary of what's actually being bridged. Added both, reusing components
already proven on Bridge's own input screen (BridgeAssetInputView in
non-editable mode, same as the main screen's fromCard/toCard) rather than
reaching into feature-swap-impl's Swap-specific components.

Also added the signatories' contact channel (t.me/pezkuwidestek) to the
existing "still processing" message, and made explicit that funds are safe
and on-chain even when auto-pay doesn't complete within the wait window -
this is the message shown when a real transfer lands in manual review, not
just the pre-submit consent gate.
2026-07-15 21:47:32 -07:00
pezkuwichain 3c06ae00e0 Add pre-submit consent gate for amounts exceeding auto-pay approval
Symmetric across both bridge directions, checked in priority order before
any funds move:
1. Real reserve exceeded (withdrawal only) - the multisig doesn't hold
   enough real USDT on Polkadot Asset Hub. No signature can fix this, so
   this stays a hard block (existing bridge_wusdt_to_usdt_blocked message),
   never offered as "proceed anyway".
2. Automation-key approval exceeded (either direction) - funds exist, the
   automation key just isn't currently approved to auto-pay that much.
   Resolvable by 3-of-5 signing, so this is now an explicit opt-in: a
   warning banner names the ~2h typical review window and the signatories'
   contact channel (t.me/pezkuwidestek), gated behind a WarningCheckBox the
   user must tick before Swap re-enables. Previously there was no signal at
   all here - a large amount would just silently queue for manual review
   after debiting the user, with no way to know that going in.

Both getWusdtRemainingAllowance/getPolkadotUsdtRemainingAllowance query the
real on-chain Assets.Approvals amount per leg - not a guess. Confirmed
on-chain (2026-07-16) the Polkadot leg has never had an approval granted at
all, so every wUSDT->USDT withdrawal currently hits the consent gate
regardless of amount, symmetric with what happens once a signatory grants
one (mirrors the existing wUSDT-side renewal flow exactly, including the
same 40,000/200,000 20%-threshold pattern) - added a second sign button so
a signatory can actually grant it from this screen.

swapClicked()'s confirmation gate now also enforces the consent requirement
server-side (well, client-domain-side) rather than only via the button's
enabled look, consistent with how it already re-checks balance/reserve.
2026-07-15 19:46:24 -07:00
pezkuwichain cf8127c8f8 Fix missing imports (launch, getAssetIconOrFallback) from the Bridge split
Dropped while rewriting BridgeViewModel/adding BridgeExecutionViewModel -
first real CI run caught it.
2026-07-15 16:31:44 -07:00
pezkuwichain 52e5645409 Split Bridge into input and dedicated execution screens, matching Swap
The single-screen design (amount input + live balance validation + submit
+ destination-wait, all in one ViewModel) was structurally fragile: the
live origin-balance observer and the amount-vs-balance validation ran in
the same reactive scope as execution, so a real successful transfer could
flash a false "insufficient balance" error the instant the balance dropped
post-submission. Patching that race (clearing the field, then an isExecuting
flag) kept fixing symptoms without addressing why the class of bug existed:
the app's own Swap flow avoids it entirely by keeping amount entry and
execution as separate screens/ViewModels, so validation code for the input
screen simply doesn't exist on the execution screen.

BridgeExecutionViewModel/Fragment now own the actual submit + destination-
wait, reached by navigating with a BridgeExecutionPayload once
BridgeViewModel.swapClicked() re-validates amount against the latest known
balance/reserve - a real confirmation gate, not just a UI button-disabled
look the domain layer never itself checked. A single BridgeExecutionState
sealed class (SubmittingOrigin/OriginFailed/WaitingForDestination/
DestinationConfirmed/DestinationPendingReview) replaces what used to be six
independently-updated LiveData flags that had to be kept in sync by hand.

Also fixed two real bugs found via live device testing with real funds:
- BridgeMultisigConstants.POLKADOT_USDT_ASSET_ID was 1 (the wallet's own
  chains.json ordinal for USDT on Polkadot Asset Hub) instead of 1984 (the
  real on-chain Assets pallet id, confirmed directly against the chain).
  getPolkadotUsdtReserve() queried asset 1, which the multisig has never
  held anything at, so the wUSDT->USDT reserve check always reported 0
  USDT available regardless of the multisig's real (and growing) holdings.
- The deposit-wait label's text was static XML, never updated on the
  success path - a genuinely completed bridge (destination balance
  confirmed, checkmark shown) still read "Waiting for confirmation..."
  forever, indistinguishable from actually being stuck. The new execution
  screen's label is driven by BridgeExecutionState instead.
2026-07-15 16:12:03 -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 14c8b3c27c Change launcher icon badge background from black to white
Source: pwap-web/shared/images/pezWallet - white_background.png, same
goat artwork as the current icon with the badge recolored. Cropped to
its badge bounds and resized to match the exact scale/position the
black version already used (from the shrink+center fix), so only the
color changes, not the composition. Regenerated every foreground/
legacy/round slot across all densities and both build flavors, plus
the separate solid-fill adaptive background layer so it matches the
new badge color instead of showing dark corners through the mask.
2026-07-15 12:08:04 -07:00
pezkuwichain 6e07348695 Bridge: clear entered amount after a successful origin-chain submit
The origin transfer already happened once submitBridgeTransfer's dispatch
result comes back success, so the amount input still holding the just-spent
value made the live balance-vs-amount check flag a false "insufficient
balance" error right next to the real success/waiting state - reading as if
the transfer failed or was retried, when it had already gone through once.
Clearing the field via the existing fillAmountEvent mechanism removes the
stale comparison instead of adding new state.
2026-07-15 09:33:53 -07:00
pezkuwichain 974296704f Fix missing viewModelScope import in BridgeViewModel
androidx.lifecycle.viewModelScope is a KTX extension property - it must be
imported in every file that uses it directly, not just inherited via
BaseViewModel (which imports it for its own use only). CI caught this on
the previous commit's real-submission wiring.
2026-07-15 08:12:22 -07:00
pezkuwichain 437dfff813 Bridge: submit deposit via real submit-and-await execution, not generic Send
swapClicked() used to hand off to the generic Send flow's confirm screen,
which only fire-and-forget submits and pops back immediately regardless of
real dispatch outcome - a real user hit exactly that: tapped confirm, the
screen closed like it succeeded, funds never arrived. Now the bridge builds
its own WeightedAssetTransfer and calls
SendUseCase.performOnChainTransferAndAwaitExecution directly, mirroring the
same real submit-and-await pattern the app's own Swap execution screen
already uses, so Success/Error reflect what actually happened on-chain.
2026-07-15 07:59:35 -07:00
pezkuwichain b51e2ecf58 Add real deposit-wait progress to Bridge screen, reusing the swap timer UX
Tapping Swap on the Bridge screen previously gave zero feedback afterward -
the same countdown/success UX already used for swap execution didn't exist
here, so a user had no way to tell whether anything was happening. Moved
ExecutionTimerView (+ its layout) from feature-swap-impl to common, since it
was already fully self-contained (no swap-specific coupling, all its
resources already lived in common) - both features now share the one
component instead of duplicating it.

Wired into BridgeViewModel: after a swap is submitted, actively observe the
destination balance (walletInteractor.assetFlow) for a real increase within
a bounded window, showing the countdown while watching and Success only on
a confirmed balance delta - never a cosmetic timer that "completes"
regardless of whether funds arrived. If the window elapses without
confirmation, it says so plainly (may need manual 3-of-5 review) rather than
showing a false success or a false error.
2026-07-15 06:10:41 -07:00
pezkuwichain a5454b38f0 Fix icon: shrink+center the goat artwork instead of edge-to-edge fill
The horns were touching the canvas corners - looked cramped and got cropped
tightly by circular/squircle adaptive-icon masks on real devices. Now
scaled to 62% of the canvas and centered on transparent padding, matching
how other apps' icons actually look on a real home screen.
2026-07-15 05:14:22 -07:00
pezkuwichain f655ef5a33 Fix ktlint: blank line before commented declaration in BridgeMultisigConstants 2026-07-14 22:20:33 -07:00
pezkuwichain c60967e7af Fix ktlint: blank line before commented declaration in BridgeViewModel 2026-07-14 21:50:18 -07:00
pezkuwichain 6c70c91730 Fix Bridge swap destination + add confirmed Kurdish translation
BRIDGE_ADDRESS_GENERIC was still the retired legacy single-key bot's
address (5C5CW7xD...), not the real 3-of-5 multisig custody
(BridgeMultisigConstants.MULTISIG_ADDRESS). Confirmed live: a real user
swap sent there landed at the dead address and was never detected by
usdt-bridge's listener (which only watches the multisig account). Funds
were recovered separately using the still-intact legacy seed; this fixes
the actual destination so future swaps land where the listener watches.

Also added the user-confirmed Kurmancî translation for
bridge_wusdt_to_usdt_blocked (values-ku), replacing the now-stale text left
untouched in the previous commit pending native review.
2026-07-14 21:22:04 -07:00
pezkuwichain ae2754d7a3 Update launcher icon to the cleaner goat-head logo (v2)
Source updated in pwap-web/shared/images/pezWallet.png - simpler design,
no border/text baked in this time. Regenerated the same way as the first
icon pass: padded to square, resized per density into every existing slot.
2026-07-14 21:21:44 -07:00
pezkuwichain a8dc9add24 Replace dead legacy-bot status check with a real per-amount reserve check
BridgeViewModel's wUSDT->USDT gate called http://217.77.6.126:3030/status -
the legacy single-key bridge bot's endpoint. That service was stopped this
same session (see res/validators-tiki.md), so the check always threw,
always caught, and always reported "inactive" - the red warning reflected a
dead dependency, not real reserve state.

Replaced with a direct on-chain read of the multisig's actual USDT balance
on Polkadot Asset Hub (BridgeMultisigInteractor.getPolkadotUsdtReserve, via
a new Assets.Account storage query mirroring the existing Approvals one).
The gate is now per-request: a specific withdrawal is blocked only if it
exceeds the real reserve, not on a global on/off flag requiring full 1:1
parity between total circulating supply and total reserve - matching how
the Rust listener's own reserve-shortfall check already works. Re-checked
on every amount change, not just once per screen load.

bridge_wusdt_to_usdt_blocked now takes the available reserve amount as a
parameter; updated the English and Turkish strings. Left values-ku
untouched deliberately - not fabricating Kurdish translation text without
native review.
2026-07-14 18:57:59 -07:00
pezkuwichain 6d27949cbf Replace launcher icon with the new goat-head PezkuwiWallet brand logo
Source: pwap-web/shared/images/pezWallet.png. Padded to square (source is
979x1053, corners already fully transparent so the padding is seamless),
then resized per density into every existing icon slot - legacy ic_launcher/
ic_launcher_round and the adaptive foreground layer all use the same full
graphic, matching how the previous icon was already structured; the solid
dark adaptive background layer is untouched since it's still a compatible
fill color for the new logo.
2026-07-14 18:44:28 -07:00
pezkuwichain 09b1b37b49 Sync bridge renewal threshold/topup with server-side 40K/200K config
Server-side (pezbridge_bot_config.json, usdt-bridge's bridge_config.json)
was already bumped to a 40,000/200,000 wUSDT renewal threshold/topup this
session, but this app's own BridgeMultisigConstants still had the old
3,000/10,000 values - it construct/checks the exact same on-chain call, so
the mismatch wasn't just a stale display, it meant a signer's app would
consider a 20,000-remaining allowance "fine" while PezbridgeBot was already
paging everyone to renew it.
2026-07-14 16:50:35 -07:00
pezkuwichain 7a2d330a0a Retire DOT-HEZ bridge from the Bridge screen
The DOT<->HEZ bridge backend (the legacy single-key bot) is being
permanently decommissioned - it had zero multisig protection and no
liquidity gate at all on that leg (unconditionally "active" regardless
of pool state, the single least-safe path in the whole bridge). Once
USDT bridging is solid, HEZ is reachable indirectly (bridge USDT, swap
for HEZ within Pezkuwi's own ecosystem) without a dedicated native
bridge that would need its own from-scratch security work (native
currency has no Assets.approve_transfer-style delegation to reuse).

- BridgePair/BridgeDirection collapsed to USDT-only (USDT_TO_WUSDT/
  WUSDT_TO_USDT) - kept the pair/pairOptions/picker structure itself
  since it costs nothing and is the natural seam for a future pair,
  rather than hardcoding a single value inline.
- Removed the now-dead DOT/HEZ exchange-rate fetching (CoinGecko
  polling, fallback rate) and liquidity-gate logic entirely, not just
  the unreachable enum branches.
- Removed the separate "Bridge DOT ↔ HEZ" entry from the asset
  selector bottom sheet (BuySellSelectorMixin) - both it and "Bridge
  USDT" opened the exact same screen with no preset, so keeping a
  DOT-HEZ-labeled entry pointing at a USDT-only screen would have been
  actively misleading, not just redundant.
- bridge_title was the generic toolbar/picker-sheet title but hardcoded
  "DOT ↔ HEZ Bridge" regardless of pair - fixed to "USDT Bridge".
- Removed now-unreferenced strings (wallet_asset_bridge,
  bridge_pair_dot_hez, bridge_rate_format(_reverse),
  bridge_hez_to_dot_note/warning/blocked) from the base strings.xml.

Confirmed via research: feature-bridge-api/feature-bridge-impl modules
exist but are unrelated orphaned scaffolding, not depended on by :app,
zero code overlap with this screen - correctly out of scope here.
2026-07-14 14:41:56 -07:00
pezkuwichain 2ac8005c10 Skip (not fail) BalancesIntegrationTest when a chain's RPC is unreachable
Aleph Zero's only configured public node (wss://ws.azero.dev) is
currently down (confirmed via direct WebSocket handshake - real,
external, isolated outage on their side, chain itself is healthy and
active). No free/working alternative public RPC was found to add as
failover. Rather than hard-fail CI on an external dependency neither
our code nor config controls, convert connectivity-class exceptions
(TimeoutCancellationException, directly or wrapped) into a JUnit
assumption-skip - this mirrors how production balance-sync code
already isolates per-chain RPC failures instead of treating them as
hard errors. A real bug (bad decoding, wrong assertion, etc.) still
fails the test exactly as before.
2026-07-14 09:52:45 -07:00
pezkuwichain 4b454cc884 Add missing query() to QueryableStorageEntry3
QueryableStorageEntry2 has always had a single-key-tuple query(k1, k2)
alongside entries()/keys(), but QueryableStorageEntry3 only ever grew
entries()/keys() - nothing in the codebase needed a direct 3-key point
query until BridgeMultisigRuntimeApi's Assets.Approvals lookup. Added
query(k1, k2, k3), mirroring QueryableStorageEntry2's implementation
exactly.
2026-07-14 07:59:24 -07:00
pezkuwichain 40c867b22c ci: pass TRONGRID_API_KEY through to the ktlint job too
PR #13 fixed this for android_build.yml's Gradle invocation, but
code-quality.yml's ktlint job runs its own separate ./gradlew ktlint
without the same env passthrough - runtime/build.gradle reads this
secret unconditionally during project configuration, so ktlint failed
outright as soon as Tron support (which needs it) was merged into
this branch.
2026-07-14 07:43:41 -07:00
pezkuwichain be2e1088e5 Merge branch 'feature/btc-native-send' into fix/multisig-first-signer-deep-link
# Conflicts:
#	feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeFragment.kt
#	feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeViewModel.kt
#	feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/di/BridgeModule.kt
2026-07-14 07:36:14 -07:00
pezkuwichain 3def96792a Fix Bridge multisig compile errors surfaced by CI
- module(Modules.X) needs substrate_sdk_android's module() extension
  imported explicitly, not just the Module type.
- accountIdIn is a MetaAccount member, not a top-level import.
- submitExtrinsicAndAwaitExecution(...).requireOk() resolves to the
  Result<T>-preserving overload, so wrapping it in runCatching produced
  Result<Result<T>>; unwrap with getOrThrow() first so the T-returning
  overload applies.
2026-07-14 07:29:35 -07:00
pezkuwichain 4941a95ee5 feat(dashboard): collapsible Pezkuwi card, minimal by default
Card now opens in a slim single-line pill showing only Trust Score.
Tapping it expands to the full card (citizen status, world Kurdish
count, referral actions); a chevron in the expanded header collapses
it back. Expand state persists across scroll/recycling within the
session but resets to collapsed on a fresh app launch.
2026-07-14 07:27:01 -07:00
pezkuwichain cfdeff8ab7 Add signatory-only sign button to Bridge screen
Any of the USDT bridge multisig's 5 known signatories now sees a
status button on the Bridge screen: green when the automation key's
spending allowance is healthy, red with a Sign action once it drops
below threshold. Signing submits the renewal's as_multi approval
directly from the wallet, mirroring the same on-chain action already
available via pwap-web and pezbridge-sign.pex.mom - a third
independent channel for the same operation, none of which is a
single point of failure.
2026-07-14 06:59:18 -07:00
pezkuwichain ee63f15dd9 Fix nullable AccountModel handling in onDepositorClicked
CI caught this: depositorAccountModel's inner type became AccountModel?
when the depositor field went nullable, but onDepositorClicked still
called it.address() directly instead of null-checking first.
2026-07-13 23:14:16 -07:00
pezkuwichain 68a5b3ed9e Support first-signer case for /open/multisigOperation deep link
The pending-operation model assumed a call already exists on-chain
(Multisig.Multisigs entry with a Timepoint) before it could be
reviewed or signed. A signatory who is the first to see a brand-new
call - reachable via the deep link before anyone has submitted
anything - hit "operation not found" and the screen refused to open,
even though the deep link already carried the call's data in its
callData param (previously decoded only to format Executed/Rejected
dialog text, then discarded for the Active case that needed it).

- PendingMultisigOperation.timePoint/depositor are now nullable to
  represent "not yet submitted"; composeMultisigAsMulti's
  maybeTimePoint parameter already accepted null (used for ordinary
  first-time multisig-origin submissions via MultisigSigner), so the
  pallet-call layer needed no changes.
- New PendingMultisigOperation.notYetSubmitted(...) factory and
  MultisigOperationDetailsInteractor.buildNotYetSubmittedOperation()
  build a synthetic operation from the deep link's callData, hash
  verified against the URL's callHash rather than trusted blindly.
- OperationIsStillPendingValidation now skips its on-chain
  "still pending" check for a not-yet-submitted operation, since it
  would otherwise always fail and block the very first submission.
- MultisigOperationPayload carries the callData through so both the
  details screen and the full-details screen (independently reachable
  via "Call Details", since that button is available whenever call
  data is known) can construct the synthetic operation.
2026-07-13 23:04:02 -07:00
pezkuwichain 7401783436 feat: block Bridge swap when send amount exceeds available balance
The Bridge screen let a user type any amount and enabled the Swap
button regardless of what they actually held, unlike the Swap screen
which shows a live "Max: X" and disables on overflow. The confirm-step
ValidationSystem would still catch it before signing, but only after
routing through Send - no immediate feedback on the Bridge screen
itself.

Wires WalletInteractor.assetFlow(chainId, assetId) to the currently
selected origin side (re-subscribed on every direction/pair change),
tracking Asset.transferable - the same field the real transfer
validation checks. Reuses the existing MaxAmountView/MaxAvailableView
widget the Swap screen already uses for the "Max: X" display, and adds
a matching error-bordered state to BridgeAssetInputView. The
"Insufficient balance" string (bridge_insufficient_balance) was
already translated into every locale but never wired to any code.
2026-07-13 12:10:16 -07:00
pezkuwichain e0cd1f5def fix: Bridge cards use AssetIconProvider and displayNameWithAssetStandard
The redesigned Bridge cards resolved asset icons via the chain-icon
helper (asset.icon.asIconOrFallback()), which treats the value as a
ready-to-load URL. Chain.Asset.icon is frequently a bare filename
resolved through AssetIconProvider's base-URL/color-mode logic, so
any asset relying on that path rendered a blank icon (Polkadot Asset
Hub's USDT, and the DOT/HEZ utility assets on the affected side).
Switched to assetIconProvider.getAssetIconOrFallback(asset), the same
path every other asset row in the app uses.

Also switched the card subtitle from chain.name to
chain.displayNameWithAssetStandard(), so Pezkuwi Asset Hub shows its
PEZ-20 token-standard label, matching Send/Receive/the balance list.
2026-07-13 11:43:03 -07:00
pezkuwichain 3b5e15457d fix: remove unused Chain import flagged by ktlint
The type is only used via inference (chainRegistry.getChain() return),
never referenced explicitly.
2026-07-13 09:33:29 -07:00