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.
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.
Neither the raw scanned QR string nor the InvalidFormatException from a
failed decode was ever logged - the failure path only shows a generic
toast, making it impossible to diagnose from logcat alone. Temporary,
will revert once the actual failure is identified.
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.
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.
Derives a BIP84 (native SegWit) keypair alongside the existing substrate/
ethereum/tron ones, stores it in MetaAccountSecrets/CloudBackup, persists
bitcoinPublicKey/bitcoinAddress on meta_accounts (DB migration 74->75), and
threads isBitcoinBased through Chain/ChainLocal/mappers/ChainExt/ChainRegistry
the same way isTronBased was wired. Adds BitcoinAddressBackfillMigration,
mirroring TronAddressBackfillMigration, so existing wallets get a Bitcoin
address without re-import.
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).
* 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.