Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
00a8191
fix possible nano send amount bug
julian-CStack Aug 14, 2026
0c74bbd
firo fix segwit address validation
julian-CStack Aug 14, 2026
8163840
use payInAmount if available, not as a fallback
julian-CStack Aug 14, 2026
5e2eeba
better handling of localized group and decimal separators in amounts
julian-CStack Aug 14, 2026
2bba546
fix desktop token amount parsing
julian-CStack Aug 14, 2026
5c54df9
fix mobile SPL token amount parsing
julian-CStack Aug 14, 2026
467f610
clean up isar wallet data delete
julian-CStack Aug 14, 2026
0e56467
fix mwc first block
julian-CStack Aug 14, 2026
9c37a8f
fix frost prev keys issue
julian-CStack Aug 14, 2026
11c5bdf
fix test with platform specific check
julian-CStack Aug 14, 2026
f372d52
ensure marked used UTXOs do not get included in frost txn
julian-CStack Aug 14, 2026
c6f639c
fix checkTrades early return
julian-CStack Aug 14, 2026
3b57511
fix exchange refund gating
julian-CStack Aug 14, 2026
9cd17db
fix mweb fee provider
julian-CStack Aug 14, 2026
598898a
clean up epicbox/http/https check
julian-CStack Aug 14, 2026
5348187
fix missing xelis stagenet case
julian-CStack Aug 14, 2026
97ddbfb
return false on ping timeout
julian-CStack Aug 14, 2026
e1354a8
fix sorting to match comment
julian-CStack Aug 14, 2026
75d0e18
fix duplicate trade details dialog appearing on desktop
julian-CStack Aug 14, 2026
ea722de
update mocks
julian-CStack Aug 14, 2026
c12e533
add paynym notif tx input check
julian-CStack Aug 14, 2026
a4ea73b
fix dash dust limit
julian-CStack Aug 14, 2026
696a54e
ppc default fee rate update
julian-CStack Aug 14, 2026
68b206b
fix double decoding which can cause weird issues
julian-CStack Aug 14, 2026
a6531a9
fix address copy button value
julian-CStack Aug 14, 2026
e0dbdb8
handle zero restore chain height
julian-CStack Aug 14, 2026
f32a921
tighten up amount parsing
julian-CStack Aug 15, 2026
f5bfe88
tighten up payment uri parsing
julian-CStack Aug 15, 2026
f918252
fix swap rate sorting
julian-CStack Aug 15, 2026
9685de5
fix btc frost price
julian-CStack Aug 15, 2026
f8c2bb1
fix node backup restores
julian-CStack Aug 15, 2026
1c85b97
ensure frost wallet data is fully deleted on wallet delete
julian-CStack Aug 16, 2026
d84aba4
clean up node uri handling
julian-CStack Aug 16, 2026
24d5800
fix nanswap networks
julian-CStack Aug 16, 2026
6876df3
add extra check to swap pay in amount parsing
julian-CStack Aug 16, 2026
0fde946
add extra check to nano interface account info parsing
julian-CStack Aug 16, 2026
0144f91
add extra check to handle amount formatter invalid selection
julian-CStack Aug 16, 2026
6541cdc
fix autobackup location
julian-CStack Aug 16, 2026
898c56d
tighten up DPS persistence
julian-CStack Aug 16, 2026
78eced1
handle edgecase where estimate could be stale in the 1.5s window
julian-CStack Aug 17, 2026
f232514
formatting
julian-CStack Aug 17, 2026
9bb5aca
tighten localized number string parsing
julian-CStack Aug 17, 2026
c31d828
fix payment uri amount parsing
julian-CStack Aug 17, 2026
09ac90f
memo/extraid swap support
julian-CStack Aug 18, 2026
a8ca183
fix custom fee ui state drift on mobile
julian-CStack Aug 18, 2026
d00afa7
refactor electrumx based coin transaction building and fee calculation
julian-CStack Aug 19, 2026
143ae53
handle mweb 6 required min confirms
julian-CStack Aug 19, 2026
2466857
some dependency clean up
julian-CStack Aug 19, 2026
f7a7a60
fix wifi check (due to connectivity_plus package updated)
julian-CStack Aug 19, 2026
000c3fe
another set of dep upgrades for SPM as well as android/gradle stuff
julian-CStack Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,14 @@ RUN mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" \
&& sdkmanager \
"platform-tools" \
"build-tools;35.0.0" \
"build-tools;36.0.0" \
"build-tools;37.0.0" \
"platforms;android-32" \
"platforms;android-33" \
"platforms;android-34" \
"platforms;android-35" \
"platforms;android-36" \
"platforms;android-37" \
"ndk;28.0.13004108" \
"ndk;28.2.13676358" \
"cmake;3.22.1" \
Expand Down Expand Up @@ -148,11 +151,14 @@ RUN mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" \
&& sdkmanager \
"platform-tools" \
"build-tools;35.0.0" \
"build-tools;36.0.0" \
"build-tools;37.0.0" \
"platforms;android-32" \
"platforms;android-33" \
"platforms;android-34" \
"platforms;android-35" \
"platforms;android-36" \
"platforms;android-37" \
"ndk;28.0.13004108" \
"ndk;28.2.13676358" \
"cmake;3.22.1" \
Expand Down
5 changes: 4 additions & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
# Jetifier removed in AGP 9; all deps are AndroidX already.
# Preserve Flutter's legacy Kotlin and Android DSL compatibility on AGP 9.
android.newDsl=false
android.builtInKotlin=false
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip
4 changes: 2 additions & 2 deletions android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ pluginManagement {

plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '8.11.1' apply false
id "org.jetbrains.kotlin.android" version "2.2.20" apply false
id "com.android.application" version '9.1.1' apply false
id "org.jetbrains.kotlin.android" version "2.3.20" apply false
}

include ":app"
65 changes: 7 additions & 58 deletions lib/db/isar/main_db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ class MainDB {
blockTime: utxo.blockTime,
blockHeight: utxo.blockHeight,
blockHash: utxo.blockHash,
otherData: utxo.otherData,
// passing null keeps the stored value
isBlocked: applyAutoBlock ? true : null,
blockedReason: applyAutoBlock ? utxo.blockedReason : null,
Expand Down Expand Up @@ -463,52 +464,14 @@ class MainDB {
//
Future<void> deleteWalletBlockchainData(String walletId) async {
await isar.writeTxn(() async {
final transactionCount = await getTransactions(walletId).count();
final transactionCountV2 = await isar.transactionV2s
.where()
.walletIdEqualTo(walletId)
.count();
final addressCount = await getAddresses(walletId).count();
final utxoCount = await getUTXOs(walletId).count();

const paginateLimit = 100;

// transactions
for (int i = 0; i < transactionCount; i += paginateLimit) {
final txnIds = await getTransactions(
walletId,
).offset(i).limit(paginateLimit).idProperty().findAll();
await isar.transactions.deleteAll(txnIds);
}

await getTransactions(walletId).deleteAll();
// transactions V2
for (int i = 0; i < transactionCountV2; i += paginateLimit) {
final txnIds = await isar.transactionV2s
.where()
.walletIdEqualTo(walletId)
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.transactionV2s.deleteAll(txnIds);
}

await isar.transactionV2s.where().walletIdEqualTo(walletId).deleteAll();
// addresses
for (int i = 0; i < addressCount; i += paginateLimit) {
final addressIds = await getAddresses(
walletId,
).offset(i).limit(paginateLimit).idProperty().findAll();
await isar.addresses.deleteAll(addressIds);
}

await getAddresses(walletId).deleteAll();
// utxos
for (int i = 0; i < utxoCount; i += paginateLimit) {
final utxoIds = await getUTXOs(
walletId,
).offset(i).limit(paginateLimit).idProperty().findAll();
await isar.utxos.deleteAll(utxoIds);
}

await getUTXOs(walletId).deleteAll();
// spark coins
await isar.sparkCoins
.where()
Expand All @@ -518,28 +481,14 @@ class MainDB {
}

Future<void> deleteAddressLabels(String walletId) async {
final addressLabelCount = await getAddressLabels(walletId).count();
await isar.writeTxn(() async {
const paginateLimit = 50;
for (int i = 0; i < addressLabelCount; i += paginateLimit) {
final labelIds = await getAddressLabels(
walletId,
).offset(i).limit(paginateLimit).idProperty().findAll();
await isar.addressLabels.deleteAll(labelIds);
}
await getAddressLabels(walletId).deleteAll();
});
}

Future<void> deleteTransactionNotes(String walletId) async {
final noteCount = await getTransactionNotes(walletId).count();
await isar.writeTxn(() async {
const paginateLimit = 50;
for (int i = 0; i < noteCount; i += paginateLimit) {
final labelIds = await getTransactionNotes(
walletId,
).offset(i).limit(paginateLimit).idProperty().findAll();
await isar.transactionNotes.deleteAll(labelIds);
}
await getTransactionNotes(walletId).deleteAll();
});
}

Expand Down
4 changes: 3 additions & 1 deletion lib/db/special_migrations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ abstract class CampfireMigration {
final myHive = HiveImpl();
myHive.init(appDirectory.path);
_wallets = await myHive.openBox<dynamic>('wallets');
_secureStore = const FlutterSecureStorage();
_secureStore = const FlutterSecureStorage(
aOptions: AndroidOptions(resetOnError: false, migrateWithBackup: true),
);
} else {
await setDidRun();
}
Expand Down
16 changes: 11 additions & 5 deletions lib/electrumx_rpc/electrumx_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,9 @@ class ElectrumXClient {

Future<bool> _allow() async {
if (_prefs.wifiOnly) {
return (await Connectivity().checkConnectivity()) ==
ConnectivityResult.wifi;
return (await Connectivity().checkConnectivity()).contains(
ConnectivityResult.wifi,
);
}
return true;
}
Expand Down Expand Up @@ -519,7 +520,11 @@ class ElectrumXClient {
/// Ping the server to ensure it is responding
///
/// Returns true if ping succeeded
Future<bool> ping({String? requestID, int retryCount = 1}) async {
Future<bool> ping({
String? requestID,
int retryCount = 1,
Duration timeout = const Duration(seconds: 30),
}) async {
try {
// This doesn't work because electrum_adapter only returns the result:
// (which is always `null`).
Expand All @@ -535,14 +540,15 @@ class ElectrumXClient {
return await request(
requestID: requestID,
command: 'server.ping',
requestTimeout: const Duration(seconds: 30),
requestTimeout: timeout,
retries: retryCount,
).timeout(
const Duration(seconds: 30),
timeout,
onTimeout: () {
Logging.instance.d(
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host",
);
return false;
},
)
as bool;
Expand Down
7 changes: 6 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,12 @@ void main(List<String> args) async {
await DbVersionMigrator().migrate(
dbVersion,
secureStore: const SecureStorageWrapper(
store: FlutterSecureStorage(),
store: FlutterSecureStorage(
aOptions: AndroidOptions(
resetOnError: false,
migrateWithBackup: true,
),
),
isDesktop: false,
),
);
Expand Down
26 changes: 26 additions & 0 deletions lib/models/exchange/incomplete_exchange.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class IncompleteExchangeModel extends ChangeNotifier {
final Decimal sendAmount;
final Decimal receiveAmount;

String get payInAmount => trade?.payInAmount ?? sendAmount.toString();

Decimal? get payInDecimal => Decimal.tryParse(payInAmount);

final ExchangeRateType rateType;

final bool reversed;
Expand Down Expand Up @@ -55,6 +59,28 @@ class IncompleteExchangeModel extends ChangeNotifier {
}
}

String? _extraId;

String? get extraId => _extraId;

set extraId(String? extraId) {
if (_extraId != extraId) {
_extraId = extraId;
notifyListeners();
}
}

String? _refundExtraId;

String? get refundExtraId => _refundExtraId;

set refundExtraId(String? refundExtraId) {
if (_refundExtraId != refundExtraId) {
_refundExtraId = refundExtraId;
notifyListeners();
}
}

Estimate? _estimate;

Estimate? get estimate => _estimate;
Expand Down
27 changes: 22 additions & 5 deletions lib/models/isar/models/blockchain_data/utxo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,10 @@ class UTXO {
int? overrideMinConfirms, // added to handle namecoin name op outputs
}) {
final confirmations = getConfirmations(currentChainHeight);

if (overrideMinConfirms != null) {
return confirmations >= overrideMinConfirms;
}
return confirmations >=
final requiredConfirmations =
overrideMinConfirms ??
(isCoinbase ? minimumCoinbaseConfirms : minimumConfirms);
return confirmations >= max(requiredConfirmations, mwebPegoutMaturity ?? 0);
}

/// A lingering [blockedReason] on an unblocked utxo means the wallet
Expand All @@ -107,6 +105,24 @@ class UTXO {
return keyImage != null;
}

@ignore
int? get mwebPegoutMaturity {
if (otherData == null) {
return null;
}

try {
final value =
(jsonDecode(otherData!) as Map)[UTXOOtherDataKeys.mwebPegoutMaturity];
return value is int && value > 0 ? value : null;
} catch (_) {
return null;
}
}

@ignore
bool get isMwebPegout => mwebPegoutMaturity != null;

@ignore
String? get keyImage {
if (otherData == null) {
Expand Down Expand Up @@ -189,6 +205,7 @@ class UTXO {

abstract final class UTXOOtherDataKeys {
static const keyImage = "keyImage";
static const mwebPegoutMaturity = "mwebPegoutMaturity";
static const spent = "spent";
static const nameOpData = "nameOpData";
}
48 changes: 48 additions & 0 deletions lib/models/node_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,54 @@ class NodeModel {
this.nodeApiSecret,
});

factory NodeModel.fromStackBackup(
Map<String, dynamic> map, {
Set<String>? legacyPrimaryNodeIds,
}) {
final id = map['id'] as String;
return NodeModel(
host: map['host'] as String,
port: map['port'] as int,
name: map['name'] as String,
id: id,
useSSL: _backupBool(map['useSSL'], fallback: true),
loginName: map['loginName'] as String?,
enabled: _backupBool(map['enabled'], fallback: true),
coinName: map['coinName'] as String,
isFailover: _backupBool(map['isFailover'], fallback: false),
isDown: _backupBool(map['isDown'], fallback: false),
trusted: _nullableBackupBool(map['trusted']),
torEnabled: _backupBool(map['torEnabled'], fallback: true),
clearnetEnabled: _backupBool(
map['clearEnabled'] ?? map['plainEnabled'],
fallback: true,
),
forceNoTor: _backupBool(map['forceNoTor'], fallback: false),
isPrimary: _backupBool(
map['isPrimary'],
fallback: legacyPrimaryNodeIds?.contains(id) ?? false,
),
nodeApiSecret: map['nodeApiSecret'] as String?,
);
}

static bool _backupBool(Object? value, {required bool fallback}) =>
_nullableBackupBool(value) ?? fallback;

static bool? _nullableBackupBool(Object? value) {
if (value is bool) {
return value;
}
if (value is String) {
return switch (value.trim().toLowerCase()) {
'true' => true,
'false' => false,
_ => null,
};
}
return null;
}

NodeModel copyWith({
String? host,
int? port,
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/cakepay/cakepay_order_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ class _CakePayOrderViewState extends ConsumerState<CakePayOrderView> {
: STextStyles.itemSubtitle12(context),
),
const Spacer(),
IconCopyButton(data: order.orderId),
IconCopyButton(data: selected.address),
const SizedBox(width: 4),
Text("Copy", style: STextStyles.link2(context)),
],
Expand Down
Loading
Loading