Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions cw_bitcoin/lib/bitcoin_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:cw_bitcoin/.secrets.g.dart' as secrets;
import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import "package:cw_bitcoin/bitcoin_receive_page_option.dart";
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
Expand All @@ -33,6 +34,7 @@ import 'package:cw_core/output_info.dart';
import 'package:cw_core/payjoin_session.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import "package:cw_core/receive_page_option.dart";
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/utils/print_verbose.dart';
Expand Down Expand Up @@ -417,6 +419,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {

late final PayjoinManager payjoinManager;

@override
bool get hasPayjoinSupport => keys.privateKey.isNotEmpty;

@override
bool get hasLightningSupport => lightningWallet?.sdk != null;

bool get isPayjoinAvailable => unspentCoinsInfo.values
.where((element) => element.walletId == id && element.isSending && !element.isFrozen)
.isNotEmpty;
Expand Down Expand Up @@ -673,4 +681,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {

return super.signMessage(message, address: address);
}

@override
bool receiveOptionAvailable(ReceivePageOption option) {
if(option == BitcoinReceivePageOption.lightning) {
return hasLightningSupport;
}

if(option == BitcoinReceivePageOption.silent_payments) {
return hasSilentPaymentsScanning;
}

return true;
}
}
1 change: 1 addition & 0 deletions cw_bitcoin/lib/electrum_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ abstract class ElectrumWalletBase
@override
bool isTestnet;

@override
bool get hasSilentPaymentsScanning => type == WalletType.bitcoin && keys.privateKey.isNotEmpty;

@observable
Expand Down
6 changes: 6 additions & 0 deletions cw_core/lib/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ class SignSPLTokenTransactionRentException implements Exception {}

class NoAssociatedTokenAccountException implements Exception {}

class AmbiguousTokenSymbolException implements Exception {
AmbiguousTokenSymbolException(this.symbol);

final String symbol;
}

class RestoreFromSeedException implements Exception {
final String message;

Expand Down
1 change: 1 addition & 0 deletions cw_core/lib/pending_transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mixin PendingTransaction {

Money get amount;
Money get fee;
Money? get additionalCost => null;

String get amountFormatted;
String get feeFormatted => fee.toStringWithSymbol(fractionalDigits: 8);
Expand Down
59 changes: 31 additions & 28 deletions cw_core/lib/spl_token.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import "package:cw_core/db/sqlite.dart";
import "package:sqflite/sqflite.dart";

class SPLToken extends CryptoCurrency {

SPLToken({
required this.name,
required this.symbol,
Expand Down Expand Up @@ -33,17 +32,19 @@ class SPLToken extends CryptoCurrency {
required String mint,
required String symbol,
required String mintAddress,
required int decimal,
String? iconPath,
bool isPotentialScam = false,
}) => SPLToken(
name: name,
symbol: symbol,
mintAddress: mintAddress,
decimal: 0,
mint: mint,
iconPath: iconPath,
isPotentialScam: isPotentialScam,
);
}) =>
SPLToken(
name: name,
symbol: symbol,
mintAddress: mintAddress,
decimal: decimal,
mint: mint,
iconPath: iconPath,
isPotentialScam: isPotentialScam,
);

SPLToken.copyWith(SPLToken other, {String? icon, String? tag, bool? enabled, String? walletName})
: name = other.name,
Expand Down Expand Up @@ -125,18 +126,18 @@ class SPLToken extends CryptoCurrency {
}

Map<String, dynamic> toMap() => {
selfIdColumn: id,
"walletName": walletName,
"name": name,
"symbol": symbol,
"mintAddress": mintAddress,
"decimal": decimal,
"mint": mint,
"enabled": _enabled ? 1 : 0,
"iconPath": iconPath,
"tag": tag,
"isPotentialScam": isPotentialScam ? 1 : 0,
};
selfIdColumn: id,
"walletName": walletName,
"name": name,
"symbol": symbol,
"mintAddress": mintAddress,
"decimal": decimal,
"mint": mint,
"enabled": _enabled ? 1 : 0,
"iconPath": iconPath,
"tag": tag,
"isPotentialScam": isPotentialScam ? 1 : 0,
};

static String get tableName => "SPLToken";
static String get selfIdColumn => "${tableName}Id";
Expand Down Expand Up @@ -166,20 +167,22 @@ class SPLToken extends CryptoCurrency {
return List.generate(list.length, (index) => SPLToken.fromMap(list[index]));
}

static Future<List<SPLToken>> getAllForWallet(String walletName) async => selectList("walletName = ?", [walletName]);
static Future<List<SPLToken>> getAllForWallet(String walletName) async =>
selectList("walletName = ?", [walletName]);

static Future<SPLToken?> getByMint(String walletName, String mintAddress) async {
final list = await selectList("walletName = ? AND mintAddress = ?", [walletName, mintAddress]);
return list.isEmpty ? null : list.first;
}

static Future<int> deleteForWallet(String walletName, String mintAddress) => db!.delete(
tableName,
where: "walletName = ? AND mintAddress = ?",
whereArgs: [walletName, mintAddress],
);
tableName,
where: "walletName = ? AND mintAddress = ?",
whereArgs: [walletName, mintAddress],
);

static Future<int> deleteAllForWallet(String walletName) => db!.delete(tableName, where: "walletName = ?", whereArgs: [walletName]);
static Future<int> deleteAllForWallet(String walletName) =>
db!.delete(tableName, where: "walletName = ?", whereArgs: [walletName]);

static Future<void> renameWallet(String oldName, String newName) async {
await db!.delete(tableName, where: "walletName = ?", whereArgs: [newName]);
Expand Down
11 changes: 11 additions & 0 deletions cw_core/lib/wallet_base.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "package:cw_core/receive_page_option.dart";
import 'package:mobx/mobx.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_info.dart';
Expand Down Expand Up @@ -136,4 +137,14 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
/// Each wallet implementation should override this to make a single, efficient call
/// Returns true if the node is healthy, false otherwise
Future<bool> checkNodeHealth();

bool get hasPayjoinSupport => false;
bool get hasLightningSupport => false;
bool get hasSilentPaymentsScanning => false;

// hardware wallet - bitbox, ledger, trezor.
// we also have airgap wallets but those can't sign messages
bool get canSignMessages => walletInfo.hardwareWalletType == null || walletInfo.isHardwareWallet;

bool receiveOptionAvailable(ReceivePageOption option) => true;
}
13 changes: 13 additions & 0 deletions cw_core/test/parse_fixed_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ void main() {
test('should fail to parse `.`, missing value',
() => expect(() => parseFixed(".", 6), throwsFormatException));
});

group("parseFixed, zero decimal currency", () {
test("should parse 5 as 5", () => expect(parseFixed("5", 0), BigInt.from(5)));

test("should parse 0 as 0", () => expect(parseFixed("0", 0), BigInt.from(0)));

test("should parse 1. as 1", () => expect(parseFixed("1.", 0), BigInt.from(1)));

test("should parse -3 as -3", () => expect(parseFixed("-3", 0), BigInt.from(-3)));

test("should fail to parse 5.5, fractional component exceeds decimals",
() => expect(() => parseFixed("5.5", 0), throwsFormatException));
});
});

group('tryParseFixed', () {
Expand Down
104 changes: 104 additions & 0 deletions cw_core/test/spl_token_amount_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import "package:cw_core/amount/money.dart";
import "package:cw_core/spl_token.dart";
import "package:flutter_test/flutter_test.dart";

void main() {
group("SPL token raw amounts", () {
test("6 decimal token renders raw base units", () {
final jup = SPLToken(
name: "Jupiter",
symbol: "JUP",
mint: "jup",
mintAddress: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
decimal: 6,
);

expect(Money(BigInt.from(100000), jup).toStringWithPrecision(), "0.1");
expect(Money(BigInt.from(931614), jup).toStringWithPrecision(), "0.931614");
});

test("8 decimal token renders raw base units", () {
final xstock = SPLToken(
name: "Amazon xStock",
symbol: "AMZNX",
mint: "amznx",
mintAddress: "mintAddressWithEightDecimals",
decimal: 8,
);

expect(Money(BigInt.from(341694), xstock).toStringWithPrecision(), "0.00341694");
});

test("the same raw amount means different values per decimals", () {
final raw = BigInt.from(100000);

final six = SPLToken(
name: "Six",
symbol: "SIX",
mint: "six",
mintAddress: "mintAddressWithSixDecimals",
decimal: 6,
);

final nine = SPLToken(
name: "Nine",
symbol: "NINE",
mint: "nine",
mintAddress: "mintAddressWithNineDecimals",
decimal: 9,
);

final zero = SPLToken(
name: "Zero",
symbol: "ZERO",
mint: "zero",
mintAddress: "mintAddressWithZeroDecimals",
decimal: 0,
);

expect(Money(raw, six).toStringWithPrecision(), "0.1");
expect(Money(raw, nine).toStringWithPrecision(), "0.0001");
expect(Money(raw, zero).toStringWithPrecision(), "100000");
});
});

group("SPL token amount parsing", () {
test("parses whole amounts for a zero decimal token", () {
final zero = SPLToken(
name: "Zero",
symbol: "ZERO",
mint: "zero",
mintAddress: "mintAddressWithZeroDecimals",
decimal: 0,
);

expect(Money.parse("5", zero).amount, BigInt.from(5));
expect(Money.parse("0", zero).amount, BigInt.zero);
});

test("parses fractional amounts for a 6 decimal token", () {
final jup = SPLToken(
name: "Jupiter",
symbol: "JUP",
mint: "jup",
mintAddress: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
decimal: 6,
);

expect(Money.parse("0.1", jup).amount, BigInt.from(100000));
expect(Money.parse("10.339089", jup).amount, BigInt.from(10339089));
});

test("rejects more fractional digits than the token has decimals", () {
final zero = SPLToken(
name: "Zero",
symbol: "ZERO",
mint: "zero",
mintAddress: "mintAddressWithZeroDecimals",
decimal: 0,
);

expect(() => Money.parse("5.5", zero), throwsFormatException);
});
});
}
2 changes: 1 addition & 1 deletion cw_solana/lib/default_spl_tokens.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class DefaultSPLTokens {
symbol: 'GMT',
mintAddress: '7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx',
decimal: 9,
mint: 'ray',
mint: 'gmt',
iconPath: 'assets/images/gmt_icon.png',
enabled: false,
),
Expand Down
12 changes: 4 additions & 8 deletions cw_solana/lib/pending_solana_transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class PendingSolanaTransaction with PendingTransaction {
required this.serializedTransaction,
required this.destinationAddress,
required this.sendTransaction,
this.additionalCost,
});

@override
Expand All @@ -22,15 +23,10 @@ class PendingSolanaTransaction with PendingTransaction {
final Money fee;

@override
String get amountFormatted {
String stringifiedAmount = amount.toString();
final Money? additionalCost;

if (stringifiedAmount.toString().length >= 6) {
stringifiedAmount = stringifiedAmount.substring(0, 6);
}

return stringifiedAmount;
}
@override
String get amountFormatted => amount.toString();

@override
Future<void> commit() async {
Expand Down
Loading
Loading