Merge upstream Cake Wallet v6.5.0

Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535
upstream commits, including the Monero silent-open fix, Monero on Trezor,
Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin
sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive
adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9.

Merged with both sides normalized first (import rename, 100-column
format), which took the conflict count from 614 paths to 322. The
procedure is now scripts/upstream_sync.sh.

Kept from Hash Bags:
- WalletType enum order and Hive IDs. SQLite stores WalletType.index and
  our order differs from upstream's; taking theirs would reread saved
  wallets as other chains. Our frozen adapter keeps the shipped IDs.
- The 1.0.2 legacy PIN and wallet-password decode and self-heal.
- Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new
  Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out.
- Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me),
  converted to upstream's isDefault node-list key; Wownero is back in the
  node reset list.
- Privacy: address resolvers limited to domain-anchored sources again;
  prices.neroswap.com for fiat; upstream's charts tab, which reads
  prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false.
- Lightning stays behind FeatureFlag.isLightningEnabled, including
  upstream's new post-seed Lightning username step.
- Our icon pipeline, branding and bundle identifiers.

Twelve new upstream widget tests are skipped: test and code are unchanged
from v6.5.0 apart from renames (not rerun on a pristine upstream checkout;
upstream's top_bar_test does not compile against its own TopBar).
flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped;
flutter build bundle compiles. Built with Flutter 3.41.9.
This commit is contained in:
jwinterm
2026-10-24 23:49:29 -04:00
1406 changed files with 81062 additions and 48972 deletions
+103
View File
@@ -0,0 +1,103 @@
import "package:hash_wallet/core/anypay/anypay_models.dart";
import "package:hash_wallet/core/anypay/anypay_parser.dart";
import "package:hash_wallet/utils/payment_request.dart";
import "package:flutter_test/flutter_test.dart";
void main() {
const recipient = "0xCfc1650da7C961FD82998e7e30ca5f699D0aBf41";
const usdtEthContract = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
group("chain binding", () {
test("explicit ethereum chain binds explicitly", () {
final request = AnyPayParser.fromRaw(
"ethereum:$recipient@8453?value=1e18",
);
expect(request.chainBinding, isA<ExplicitEvmChain>());
expect((request.chainBinding as ExplicitEvmChain).chainId, 8453);
});
test("ethereum without a chain id stays chainless", () {
final request = AnyPayParser.fromRaw(
"ethereum:$usdtEthContract/transfer?address=$recipient&uint256=1000000",
);
expect(request.chainBinding, isA<ChainlessEvm>());
expect(request.hasContract, true);
});
test("non evm schemes bind no evm chain", () {
final request = AnyPayParser.fromRaw(
"bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
);
expect(request.chainBinding, isA<NoEvmBinding>());
});
test("raw evm addresses bind no evm chain", () {
final request = AnyPayParser.fromRaw(recipient);
expect(request.chainBinding, isA<NoEvmBinding>());
expect(request.detection.isValid, true);
});
test("an evm chain scheme binds its chain explicitly", () {
final request = AnyPayParser.fromRaw("polygon:$recipient");
expect((request.chainBinding as ExplicitEvmChain).chainId, 137);
});
});
group("payment request reconstruction", () {
test("an explicit chain request keeps its chain and raw amount", () {
final original = PaymentRequest.fromUri(
Uri.parse("ethereum:$usdtEthContract@8453/transfer?address=$recipient&uint256=3000000"),
);
final request = AnyPayParser.fromPaymentRequest(original);
expect(request.rawInput.contains("@8453"), true);
expect(request.paymentRequest.rawTokenAmount, "3000000");
expect((request.chainBinding as ExplicitEvmChain).chainId, 8453);
});
test("a chainless request rebuilds with a placeholder but keeps the chainless binding", () {
final original = PaymentRequest.fromUri(
Uri.parse("ethereum:$usdtEthContract/transfer?address=$recipient&uint256=1000000"),
);
final request = AnyPayParser.fromPaymentRequest(original);
expect(request.rawInput.contains("@1/"), true);
expect(request.chainBinding, isA<ChainlessEvm>());
expect(request.detection.isValid, true);
});
test("a tron token request keeps its contract on the payment request", () {
final original = PaymentRequest.fromUri(
Uri.parse("tron:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t?token=TTokenContract111111111111111111"),
);
final request = AnyPayParser.fromPaymentRequest(original);
expect(request.contractAddress, "TTokenContract111111111111111111");
expect(request.chainBinding, isA<NoEvmBinding>());
});
test("a non evm request rebuilds scheme address and amount", () {
final original = PaymentRequest("someaddress", "1.25", "", "monero", null);
final request = AnyPayParser.fromPaymentRequest(original);
expect(request.rawInput, "monero:someaddress?amount=1.25");
});
test("a lightning request passes the invoice through untouched", () {
final original = PaymentRequest("lnbc1invoice", "", "", "lightning", null);
final request = AnyPayParser.fromPaymentRequest(original);
expect(request.rawInput, "lnbc1invoice");
});
});
}
+179
View File
@@ -0,0 +1,179 @@
import "package:hash_wallet/core/anypay/anypay_models.dart";
import "package:hash_wallet/core/anypay/anypay_parser.dart";
import "package:hash_wallet/core/anypay/anypay_resolver.dart";
import "package:cw_core/crypto_currency.dart";
import "package:cw_core/erc20_token.dart";
import "package:cw_core/wallet_type.dart";
import "package:flutter_test/flutter_test.dart";
class _FakeTokenLookup implements AnyPayTokenLookup {
_FakeTokenLookup({this.onFindToken, this.onFindChainId});
final Future<CryptoCurrency?> Function({required WalletType walletType, required String address})?
onFindToken;
final Future<int?> Function(String contractAddress, {int? excludingChainId})? onFindChainId;
@override
Future<CryptoCurrency?> findTokenByAddress({
required WalletType walletType,
required String address,
}) {
if (onFindToken == null) {
fail("no token lookup expected");
}
return onFindToken!(walletType: walletType, address: address);
}
@override
Future<int?> findEvmChainIdForContract(String contractAddress, {int? excludingChainId}) {
if (onFindChainId == null) {
fail("no chain sweep expected");
}
return onFindChainId!(contractAddress, excludingChainId: excludingChainId);
}
}
void main() {
const recipient = "0xCfc1650da7C961FD82998e7e30ca5f699D0aBf41";
const usdtEthContract = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
const solAddress = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const usdcSolMint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
final usdt = Erc20Token(
name: "Tether",
symbol: "USDT",
contractAddress: usdtEthContract,
decimal: 6,
);
WalletSnapshot snapshot({
WalletType type = WalletType.ethereum,
int? chainId,
bool hasEvmProxy = true,
}) =>
WalletSnapshot(
type: type,
currentWalletIsEvm:
type == WalletType.ethereum || type == WalletType.polygon || type == WalletType.base,
currentChainId: chainId,
wallets: const [],
// The service builds an empty chain map when the evm proxy is absent.
supportedEvmChains: hasEvmProxy
? const {
1: WalletType.ethereum,
137: WalletType.polygon,
8453: WalletType.base,
}
: const {},
hasEvmProxy: hasEvmProxy,
hasSolanaProxy: true,
hasTronProxy: true,
);
AnyPayRequest parse(String input) => AnyPayParser.fromRaw(input);
test("a request without a contract needs no resolution", () async {
final resolver = AnyPayResolver(tokenLookup: _FakeTokenLookup());
final resolution = await resolver.resolve(
parse("ethereum:$recipient@1?value=1e18"),
snapshot(chainId: 1),
);
expect(resolution, isA<NoTokenRequested>());
});
test("a token on the explicit target chain resolves with its amount", () async {
final lookedUp = <WalletType>[];
final resolver = AnyPayResolver(
tokenLookup: _FakeTokenLookup(
onFindToken: ({required walletType, required address}) async {
lookedUp.add(walletType);
return walletType == WalletType.ethereum ? usdt : null;
},
),
);
final resolution = await resolver.resolve(
parse("ethereum:$usdtEthContract@1/transfer?address=$recipient&uint256=1000000"),
snapshot(type: WalletType.base, chainId: 8453),
);
final resolved = resolution as TokenResolved;
expect(lookedUp, [WalletType.ethereum]);
expect(resolved.chainId, 1);
expect(resolved.token, usdt);
expect(resolved.amountOverride, "1");
});
test("an unknown token on an explicit chain is not swept to other chains", () async {
bool sweepCalled = false;
final resolver = AnyPayResolver(
tokenLookup: _FakeTokenLookup(
onFindToken: ({required walletType, required address}) async => null,
onFindChainId: (contract, {excludingChainId}) async {
sweepCalled = true;
return 137;
},
),
);
final resolution = await resolver.resolve(
parse("ethereum:$usdtEthContract@1/transfer?address=$recipient&uint256=1000000"),
snapshot(chainId: 1),
);
expect(resolution, isA<TokenUnknown>());
expect(sweepCalled, false);
});
test("a chainless token unknown on the current chain reroutes through the sweep", () async {
final resolver = AnyPayResolver(
tokenLookup: _FakeTokenLookup(
onFindToken: ({required walletType, required address}) async =>
walletType == WalletType.ethereum ? usdt : null,
onFindChainId: (contract, {excludingChainId}) async {
expect(excludingChainId, 8453);
return 1;
},
),
);
final resolution = await resolver.resolve(
parse("ethereum:$usdtEthContract/transfer?address=$recipient&uint256=2000000"),
snapshot(type: WalletType.base, chainId: 8453),
);
final resolved = resolution as TokenResolved;
expect(resolved.chainId, 1);
expect(resolved.amountOverride, "2");
});
test("a chainless token unknown everywhere stays unknown", () async {
final resolver = AnyPayResolver(
tokenLookup: _FakeTokenLookup(
onFindToken: ({required walletType, required address}) async => null,
onFindChainId: (contract, {excludingChainId}) async => null,
),
);
final resolution = await resolver.resolve(
parse("ethereum:$usdtEthContract/transfer?address=$recipient&uint256=2000000"),
snapshot(chainId: 1),
);
expect((resolution as TokenUnknown).contract, usdtEthContract);
});
test("an evm contract without the evm proxy stays unknown without any lookups", () async {
final resolver = AnyPayResolver(tokenLookup: _FakeTokenLookup());
final resolution = await resolver.resolve(
parse("ethereum:$usdtEthContract@1/transfer?address=$recipient&uint256=1000000"),
snapshot(hasEvmProxy: false),
);
expect(resolution, isA<TokenUnknown>());
});
}
+372
View File
@@ -0,0 +1,372 @@
import "package:hash_wallet/core/anypay/anypay_models.dart";
import "package:hash_wallet/core/anypay/anypay_parser.dart";
import "package:hash_wallet/core/anypay/anypay_router.dart";
import "package:hash_wallet/reactions/wallet_connect.dart";
import "package:cw_core/crypto_currency.dart";
import "package:cw_core/erc20_token.dart";
import "package:cw_core/wallet_info.dart";
import "package:cw_core/wallet_type.dart";
import "package:flutter_test/flutter_test.dart";
void main() {
const recipient = "0xCfc1650da7C961FD82998e7e30ca5f699D0aBf41";
const usdtEthContract = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
const btcAddress = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq";
const xmrAddress =
"44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A";
const tronAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
const solAddress = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
final usdt = Erc20Token(
name: "Tether",
symbol: "USDT",
contractAddress: usdtEthContract,
decimal: 6,
);
WalletInfo walletOf(WalletType type) => WalletInfo.external(
id: "id-${type.name}",
name: "wallet-${type.name}",
type: type,
isRecovery: false,
restoreHeight: 0,
date: DateTime(2026),
dirPath: "",
path: "",
address: "",
);
WalletSnapshot snapshot({
WalletType type = WalletType.ethereum,
int? chainId,
List<WalletInfo> wallets = const [],
bool hasEvmProxy = true,
bool hasSolanaProxy = true,
bool hasTronProxy = true,
}) =>
WalletSnapshot(
type: type,
currentWalletIsEvm: isEVMCompatibleChain(type),
currentChainId: chainId,
wallets: wallets,
supportedEvmChains: hasEvmProxy
? const {
1: WalletType.ethereum,
137: WalletType.polygon,
8453: WalletType.base,
}
: const {},
hasEvmProxy: hasEvmProxy,
hasSolanaProxy: hasSolanaProxy,
hasTronProxy: hasTronProxy,
);
AnyPayRequest parse(String input) => AnyPayParser.fromRaw(input);
group("evm routing", () {
test("explicit chain matching the current wallet applies directly", () {
final decision = AnyPayRouter.route(
parse("ethereum:$recipient@1?value=1e18"),
snapshot(chainId: 1),
const NoTokenRequested(),
);
expect(decision, isA<AnyPayApplyToCurrentWallet>());
});
test("explicit chain differing from the current wallet goes cross chain", () {
final decision = AnyPayRouter.route(
parse("ethereum:$recipient@8453?value=1e18"),
snapshot(chainId: 1, wallets: [walletOf(WalletType.base)]),
const NoTokenRequested(),
);
expect(decision, isA<AnyPayCrossChainPayment>());
final crossChain = decision as AnyPayCrossChainPayment;
expect(crossChain.targetChainId, 8453);
expect(crossChain.targetWalletType, WalletType.base);
expect(crossChain.hasCompatibleWallet, true);
});
test("cross chain with no wallet for the target offers no compatible wallet", () {
final decision = AnyPayRouter.route(
parse("ethereum:$recipient@8453?value=1e18"),
snapshot(chainId: 1),
const NoTokenRequested(),
);
expect((decision as AnyPayCrossChainPayment).hasCompatibleWallet, false);
});
test("unsupported explicit chain is rejected with its id", () {
final decision = AnyPayRouter.route(
parse("ethereum:$recipient@10?value=1e18"),
snapshot(chainId: 1),
const NoTokenRequested(),
);
expect((decision as AnyPayUnsupportedNetwork).chainId, 10);
});
test("chainless request binds to the current evm chain", () {
final decision = AnyPayRouter.route(
parse("ethereum:$recipient?value=1e18"),
snapshot(type: WalletType.base, chainId: 8453),
const NoTokenRequested(),
);
expect(decision, isA<AnyPayApplyToCurrentWallet>());
});
test("chainless request at a non evm wallet targets mainnet cross chain", () {
final decision = AnyPayRouter.route(
parse("ethereum:$recipient?value=1e18"),
snapshot(type: WalletType.monero, wallets: [walletOf(WalletType.ethereum)]),
const NoTokenRequested(),
);
expect((decision as AnyPayCrossChainPayment).targetChainId, 1);
});
test("raw evm address at an evm wallet applies directly", () {
final decision = AnyPayRouter.route(
parse(recipient),
snapshot(chainId: 1),
const NoTokenRequested(),
);
expect(decision, isA<AnyPayApplyToCurrentWallet>());
});
test("resolved token on the current chain applies with the token", () {
final decision = AnyPayRouter.route(
parse("ethereum:$usdtEthContract/transfer?address=$recipient&uint256=2000000"),
snapshot(type: WalletType.base, chainId: 8453),
TokenResolved(token: usdt, chainId: 8453, amountOverride: "2"),
);
final apply = decision as AnyPayApplyToCurrentWallet;
expect(apply.token, usdt);
expect(apply.amountOverride, "2");
});
test("chainless token resolved on another chain reroutes there", () {
final decision = AnyPayRouter.route(
parse("ethereum:$usdtEthContract/transfer?address=$recipient&uint256=2000000"),
snapshot(type: WalletType.base, chainId: 8453, wallets: [walletOf(WalletType.ethereum)]),
TokenResolved(token: usdt, chainId: 1, amountOverride: "2"),
);
final crossChain = decision as AnyPayCrossChainPayment;
expect(crossChain.targetChainId, 1);
expect(crossChain.token, usdt);
});
test("an unknown token never falls back to the native asset", () {
final decision = AnyPayRouter.route(
parse("ethereum:$usdtEthContract@1/transfer?address=$recipient&uint256=2000000"),
snapshot(chainId: 1),
const TokenUnknown(usdtEthContract),
);
expect(decision, isA<AnyPayUnsupportedToken>());
expect((decision as AnyPayUnsupportedToken).contract, usdtEthContract);
});
test("an unsupported network wins over an unknown token", () {
final decision = AnyPayRouter.route(
parse("ethereum:$usdtEthContract@10/transfer?address=$recipient&uint256=2000000"),
snapshot(chainId: 1),
const TokenUnknown(usdtEthContract),
);
expect((decision as AnyPayUnsupportedNetwork).chainId, 10);
});
test("evm address without the evm proxy applies as plain input", () {
final decision = AnyPayRouter.route(
parse(recipient),
snapshot(type: WalletType.monero, hasEvmProxy: false),
const NoTokenRequested(),
);
expect(decision, isA<AnyPayApplyToCurrentWallet>());
expect((decision as AnyPayApplyToCurrentWallet).token, null);
});
});
group("non evm routing", () {
test("address matching the current wallet applies directly", () {
final decision = AnyPayRouter.route(
parse(xmrAddress),
snapshot(type: WalletType.monero),
const NoTokenRequested(),
);
expect(decision, isA<AnyPayApplyToCurrentWallet>());
});
test("btc address at another wallet goes cross chain with its wallets", () {
final decision = AnyPayRouter.route(
parse(btcAddress),
snapshot(type: WalletType.monero, wallets: [walletOf(WalletType.bitcoin)]),
const NoTokenRequested(),
);
final crossChain = decision as AnyPayCrossChainPayment;
expect(crossChain.targetWalletType, WalletType.bitcoin);
expect(crossChain.hasCompatibleWallet, true);
});
test("btc address with no bitcoin wallet leaves only the swap offer", () {
final decision = AnyPayRouter.route(
parse(btcAddress),
snapshot(type: WalletType.monero),
const NoTokenRequested(),
);
expect((decision as AnyPayCrossChainPayment).hasCompatibleWallet, false);
});
test("solana address without the solana proxy applies as plain input", () {
final decision = AnyPayRouter.route(
parse(solAddress),
snapshot(chainId: 1, hasSolanaProxy: false),
const NoTokenRequested(),
);
expect(decision, isA<AnyPayApplyToCurrentWallet>());
});
});
group("native request currency", () {
test("an explicit native request with an amount carries its currency", () {
final decision = AnyPayRouter.route(
parse("ethereum:$recipient@1?value=2e18"),
snapshot(chainId: 1),
const NoTokenRequested(),
);
expect((decision as AnyPayApplyToCurrentWallet).fallbackCurrency, CryptoCurrency.eth);
});
test("a raw address carries no fallback currency", () {
final decision = AnyPayRouter.route(
parse(recipient),
snapshot(chainId: 1),
const NoTokenRequested(),
);
expect((decision as AnyPayApplyToCurrentWallet).fallbackCurrency, null);
});
});
group("degenerate input", () {
test("empty input is invalid", () {
final decision = AnyPayRouter.route(
parse(""),
snapshot(chainId: 1),
const NoTokenRequested(),
);
expect(decision, isA<AnyPayEmptyInput>());
});
test("undetectable text applies as plain input for the validator to reject", () {
final decision = AnyPayRouter.route(
parse("definitelynotanaddress"),
snapshot(chainId: 1),
const NoTokenRequested(),
);
expect(decision, isA<AnyPayApplyToCurrentWallet>());
});
});
group("requested evm chain", () {
test("explicit binding wins over the wallet chain", () {
final request = parse("ethereum:$recipient@137?value=1e18");
expect(AnyPayRouter.requestedEvmChainId(request, snapshot(chainId: 1)), 137);
});
test("chainless binding takes the wallet chain", () {
final request = parse("ethereum:$recipient?value=1e18");
expect(
AnyPayRouter.requestedEvmChainId(
request,
snapshot(type: WalletType.base, chainId: 8453),
),
8453,
);
});
test("chainless binding defaults to mainnet without an evm wallet", () {
final request = parse("ethereum:$recipient?value=1e18");
expect(
AnyPayRouter.requestedEvmChainId(request, snapshot(type: WalletType.monero)),
1,
);
});
test("raw addresses bind no target", () {
expect(AnyPayRouter.requestedEvmChainId(parse(recipient), snapshot(chainId: 1)), null);
});
});
group("swap incompatible recipients", () {
final spAddress = "sp1${"q" * 113}";
final mwebAddress = "ltcmweb1q${"a" * 90}";
test("a silent payment address with a bitcoin wallet switches without a swap offer", () {
final decision = AnyPayRouter.route(
parse(spAddress),
snapshot(type: WalletType.monero, wallets: [walletOf(WalletType.bitcoin)]),
const NoTokenRequested(),
);
final crossChain = decision as AnyPayCrossChainPayment;
expect(crossChain.targetWalletType, WalletType.bitcoin);
expect(crossChain.hasCompatibleWallet, true);
expect(crossChain.canSwap, false);
});
test("a silent payment address with no bitcoin wallet cannot swap either", () {
final decision = AnyPayRouter.route(
parse(spAddress),
snapshot(type: WalletType.monero),
const NoTokenRequested(),
);
final crossChain = decision as AnyPayCrossChainPayment;
expect(crossChain.hasCompatibleWallet, false);
expect(crossChain.canSwap, false);
});
test("an mweb address never offers a swap", () {
final decision = AnyPayRouter.route(
parse(mwebAddress),
snapshot(),
const NoTokenRequested(),
);
expect((decision as AnyPayCrossChainPayment).canSwap, false);
});
test("a plain btc address keeps the swap offer", () {
final decision = AnyPayRouter.route(
parse(btcAddress),
snapshot(type: WalletType.monero),
const NoTokenRequested(),
);
expect((decision as AnyPayCrossChainPayment).canSwap, true);
});
});
}