Merge branch 'main' into CW-607-Update-list-of-Trocador-providers-to-be-fetched-from-the-API

This commit is contained in:
Serhii 2024-05-23 14:20:06 +03:00
commit b54c755dd5
292 changed files with 10946 additions and 2386 deletions

View file

@ -42,7 +42,7 @@ jobs:
- name: Flutter action
uses: subosito/flutter-action@v1
with:
flutter-version: "3.10.x"
flutter-version: "3.19.5"
channel: stable
- name: Install package dependencies
@ -113,6 +113,7 @@ jobs:
touch lib/.secrets.g.dart
touch cw_evm/lib/.secrets.g.dart
touch cw_solana/lib/.secrets.g.dart
touch cw_tron/lib/.secrets.g.dart
echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
@ -150,6 +151,9 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
- name: Rename app
run: |

5
.gitignore vendored
View file

@ -94,9 +94,12 @@ android/app/key.jks
**/tool/.evm-secrets-config.json
**/tool/.ethereum-secrets-config.json
**/tool/.solana-secrets-config.json
**/tool/.nano-secrets-config.json
**/tool/.tron-secrets-config.json
**/lib/.secrets.g.dart
**/cw_evm/lib/.secrets.g.dart
**/cw_solana/lib/.secrets.g.dart
**/cw_tron/lib/.secrets.g.dart
vendor/
@ -132,6 +135,7 @@ lib/bitcoin_cash/bitcoin_cash.dart
lib/nano/nano.dart
lib/polygon/polygon.dart
lib/solana/solana.dart
lib/tron/tron.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
@ -152,6 +156,7 @@ assets/images/app_logo.png
macos/Runner/Info.plist
macos/Runner/DebugProfile.entitlements
macos/Runner/Release.entitlements
lib/core/secure_storage.dart
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png

View file

@ -9,6 +9,26 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--bibo01 : hardware option-->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
<!-- required for API 18 - 30 -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- API 31+ -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<application
android:name=".Application"
@ -67,6 +87,16 @@
<data android:scheme="polygon-wallet" />
<data android:scheme="polygon_wallet" />
<data android:scheme="solana-wallet" />
<data android:scheme="tron" />
<data android:scheme="tron-wallet" />
<data android:scheme="tron_wallet" />
</intent-filter>
<!-- nano-gpt link scheme -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nano-gpt" />
</intent-filter>
</activity>
<meta-data

View file

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.8.21'
repositories {
google()
jcenter()

View file

@ -0,0 +1,5 @@
-
uri: kaliumapi.appditto.com
path: /api
useSSL: true
is_default: true

BIN
assets/images/bluetooth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/images/quantex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/images/usb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,2 +1 @@
UI enhancements
Bug fixes
Bug fixes and generic enhancements

View file

@ -1,7 +1,3 @@
Add Replace-By-Fee to boost pending Bitcoin transactions
Enable WalletConnect for Solana
WalletConnect Enhancements
Enhancements for ERC-20 tokens and Solana tokens
Enhancements for Nano wallet
UI enhancements
Add Tron wallet
Hardware wallets enhancements
Bug fixes

View file

@ -0,0 +1,8 @@
-
uri: tron-rpc.publicnode.com:443
is_default: true
useSSL: true
-
uri: api.trongrid.io
is_default: false
useSSL: true

View file

@ -0,0 +1,43 @@
import 'dart:async';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
class BitcoinHardwareWalletService {
BitcoinHardwareWalletService(this.ledger, this.device);
final Ledger ledger;
final LedgerDevice device;
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
final bitcoinLedgerApp = BitcoinLedgerApp(ledger);
final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device);
print(masterFp);
final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index);
for (final i in indexRange) {
final derivationPath = "m/84'/0'/$i'";
final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath);
HDWallet hd = HDWallet.fromBase58(xpub).derive(0);
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
accounts.add(HardwareAccountData(
address: address,
accountIndex: i,
derivationPath: derivationPath,
masterFingerprint: masterFp,
xpub: xpub,
));
}
return accounts;
}
}

View file

@ -90,8 +90,7 @@ List<bool> prefixMatches(String source, List<String> prefixes) {
return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList();
}
Future<String> generateMnemonic(
{int strength = 264, String prefix = segwit}) async {
Future<String> generateElectrumMnemonic({int strength = 264, String prefix = segwit}) async {
final wordBitlen = logBase(wordlist.length, 2).ceil();
final wordCount = strength / wordBitlen;
final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil();
@ -106,22 +105,29 @@ Future<String> generateMnemonic(
return result;
}
Future<bool> checkIfMnemonicIsElectrum2(String mnemonic) async {
return prefixMatches(mnemonic, [segwit]).first;
}
Future<String> getMnemonicHash(String mnemonic) async {
final hmacSha512 = Hmac(sha512, utf8.encode('Seed version'));
final digest = hmacSha512.convert(utf8.encode(normalizeText(mnemonic)));
final hx = digest.toString();
return hx;
}
Future<Uint8List> mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) async {
final pbkdf2 = cryptography.Pbkdf2(
macAlgorithm: cryptography.Hmac.sha512(),
iterations: 2048,
bits: 512);
final pbkdf2 =
cryptography.Pbkdf2(macAlgorithm: cryptography.Hmac.sha512(), iterations: 2048, bits: 512);
final text = normalizeText(mnemonic);
// pbkdf2.deriveKey(secretKey: secretKey, nonce: nonce)
final key = await pbkdf2.deriveKey(
secretKey: cryptography.SecretKey(text.codeUnits),
nonce: 'electrum'.codeUnits);
secretKey: cryptography.SecretKey(text.codeUnits), nonce: 'electrum'.codeUnits);
final bytes = await key.extractBytes();
return Uint8List.fromList(bytes);
}
bool matchesAnyPrefix(String mnemonic) =>
prefixMatches(mnemonic, [segwit]).any((el) => el);
bool matchesAnyPrefix(String mnemonic) => prefixMatches(mnemonic, [segwit]).any((el) => el);
bool validateMnemonic(String mnemonic, {String prefix = segwit}) {
try {
@ -208,10 +214,8 @@ String removeCJKSpaces(String source) {
}
String normalizeText(String source) {
final res = removeCombiningCharacters(unorm.nfkd(source).toLowerCase())
.trim()
.split('/\s+/')
.join(' ');
final res =
removeCombiningCharacters(unorm.nfkd(source).toLowerCase()).trim().split('/\s+/').join(' ');
return removeCJKSpaces(res);
}

View file

@ -37,7 +37,7 @@ class BitcoinTransactionPriority extends TransactionPriority {
switch (this) {
case BitcoinTransactionPriority.slow:
label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs';
label = 'Slow ~24hrs+'; // '${S.current.transaction_priority_slow} ~24hrs';
break;
case BitcoinTransactionPriority.medium:
label = 'Medium'; // S.current.transaction_priority_medium;

View file

@ -1,17 +1,24 @@
import 'dart:convert';
import 'package:bip39/bip39.dart' as bip39;
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:convert/convert.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:mobx/mobx.dart';
part 'bitcoin_wallet.g.dart';
@ -19,19 +26,23 @@ class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinWalletBase({
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
Uint8List? seedBytes,
String? mnemonic,
String? xpub,
String? addressPageType,
BasedUtxoNetwork? networkParam,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
String? passphrase,
}) : super(
mnemonic: mnemonic,
passphrase: passphrase,
xpub: xpub,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
@ -44,6 +55,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btc) {
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
// String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
walletAddresses = BitcoinWalletAddresses(
walletInfo,
electrumClient: electrumClient,
@ -51,7 +67,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
sideHd: accountHD.derive(1),
network: networkParam ?? network,
);
autorun((_) {
@ -64,6 +80,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? passphrase,
String? addressPageType,
BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses,
@ -71,14 +88,29 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) async {
late Uint8List seedBytes;
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
seedBytes = await bip39.mnemonicToSeed(
mnemonic,
passphrase: passphrase ?? "",
);
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic);
break;
}
return BitcoinWallet(
mnemonic: mnemonic,
passphrase: passphrase ?? "",
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic),
seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
@ -97,18 +129,110 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
: BitcoinNetwork.mainnet;
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
walletInfo.derivationInfo ??= DerivationInfo(
derivationType: snp.derivationType ?? DerivationType.electrum,
derivationPath: snp.derivationPath,
);
// set the default if not present:
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'/0";
walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null;
if (snp.mnemonic != null) {
switch (walletInfo.derivationInfo!.derivationType) {
case DerivationType.electrum:
seedBytes = await mnemonicToSeedBytes(snp.mnemonic!);
break;
case DerivationType.bip39:
default:
seedBytes = await bip39.mnemonicToSeed(
snp.mnemonic!,
passphrase: snp.passphrase ?? '',
);
break;
}
}
return BitcoinWallet(
mnemonic: snp.mnemonic,
xpub: snp.xpub,
password: password,
passphrase: snp.passphrase,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
seedBytes: seedBytes,
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
networkParam: network,
);
}
Ledger? _ledger;
LedgerDevice? _ledgerDevice;
BitcoinLedgerApp? _bitcoinLedgerApp;
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
_ledger = setLedger;
_ledgerDevice = setLedgerDevice;
_bitcoinLedgerApp = BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs,
required BigInt fee,
required BasedUtxoNetwork network,
required List<UtxoWithAddress> utxos,
required Map<String, PublicKeyWithDerivationPath> publicKeys,
String? memo,
bool enableRBF = false,
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async {
final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!);
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
for (final utxo in utxos) {
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
utxo: utxo.utxo,
rawTx: rawTx,
ownerDetails: utxo.ownerDetails,
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
ownerMasterFingerprint: masterFingerprint,
ownerPublicKey: publicKeyAndDerivationPath.publicKey,
));
}
final psbt = PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
return BtcTransaction.fromRaw(hex.encode(rawHex));
}
@override
Future<String> signMessage(String message, {String? address = null}) async {
if (walletInfo.isHardwareWallet) {
final addressEntry = address != null
? walletAddresses.allAddresses.firstWhere((element) => element.address == address)
: null;
final index = addressEntry?.index ?? 0;
final isChange = addressEntry?.isHidden == true ? 1 : 0;
final accountPath = walletInfo.derivationInfo?.derivationPath;
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
final signature = await _bitcoinLedgerApp!
.signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath);
return base64Encode(signature);
}
return super.signMessage(message, address: address);
}
}

View file

@ -1,23 +1,58 @@
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class BitcoinNewWalletCredentials extends WalletCredentials {
BitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo})
: super(name: name, walletInfo: walletInfo);
BitcoinNewWalletCredentials(
{required String name,
WalletInfo? walletInfo,
DerivationType? derivationType,
String? derivationPath})
: super(
name: name,
walletInfo: walletInfo,
);
}
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
BitcoinRestoreWalletFromSeedCredentials(
{required String name, required String password, required this.mnemonic, WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
BitcoinRestoreWalletFromSeedCredentials({
required String name,
required String password,
required this.mnemonic,
WalletInfo? walletInfo,
required DerivationType derivationType,
required String derivationPath,
String? passphrase,
}) : super(
name: name,
password: password,
passphrase: passphrase,
walletInfo: walletInfo,
derivationInfo: DerivationInfo(
derivationType: derivationType,
derivationPath: derivationPath,
));
final String mnemonic;
}
class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
BitcoinRestoreWalletFromWIFCredentials(
{required String name, required String password, required this.wif, WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
BitcoinRestoreWalletFromWIFCredentials({
required String name,
required String password,
required this.wif,
WalletInfo? walletInfo,
}) : super(name: name, password: password, walletInfo: walletInfo);
final String wif;
}
class BitcoinRestoreWalletFromHardware extends WalletCredentials {
BitcoinRestoreWalletFromHardware({
required String name,
required this.hwAccountData,
WalletInfo? walletInfo,
}) : super(name: name, walletInfo: walletInfo);
final HardwareAccountData hwAccountData;
}

View file

@ -12,9 +12,13 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:collection/collection.dart';
import 'package:bip39/bip39.dart' as bip39;
class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> {
class BitcoinWalletService extends WalletService<
BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials,
BitcoinRestoreWalletFromHardware> {
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -29,8 +33,9 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
credentials.walletInfo?.network = network.value;
final wallet = await BitcoinWalletBase.create(
mnemonic: await generateMnemonic(),
mnemonic: await generateElectrumMnemonic(),
password: credentials.password!,
passphrase: credentials.passphrase,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
network: network,
@ -97,6 +102,25 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
{bool? isTestnet}) async {
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
credentials.walletInfo?.derivationInfo?.derivationPath = credentials.hwAccountData.derivationPath;
final wallet = await BitcoinWallet(password: credentials.password!,
xpub: credentials.hwAccountData.xpub,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
networkParam: network,
);
await wallet.save();
await wallet.init();
return wallet;
}
@override
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
{bool? isTestnet}) async =>
@ -105,7 +129,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
@override
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}
@ -114,6 +138,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
final wallet = await BitcoinWalletBase.create(
password: credentials.password!,
passphrase: credentials.passphrase,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,

View file

@ -0,0 +1,110 @@
import 'package:cw_core/wallet_info.dart';
Map<DerivationType, List<DerivationInfo>> electrum_derivations = {
DerivationType.electrum: [
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
description: "Electrum",
scriptType: "p2wpkh",
),
],
DerivationType.bip39: [
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/0'/0'",
description: "Standard BIP44",
scriptType: "p2pkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/49'/0'/0'",
description: "Standard BIP49 compatibility segwit",
scriptType: "p2wpkh-p2sh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'",
description: "Standard BIP84 native segwit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/86'/0'/0'",
description: "Standard BIP86 Taproot",
scriptType: "p2tr",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/0'",
description: "Non-standard legacy",
scriptType: "p2pkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/0'",
description: "Non-standard compatibility segwit",
scriptType: "p2wpkh-p2sh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/0'",
description: "Non-standard native segwit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/0'/0'",
description: "Samourai Deposit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/49'/0'/0'",
description: "Samourai Deposit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/2147483644'",
description: "Samourai Bad Bank (toxic change)",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/2147483645'",
description: "Samourai Whirlpool Pre Mix",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/2147483646'",
description: "Samourai Whirlpool Post Mix",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/0'/2147483647'",
description: "Samourai Ricochet legacy",
scriptType: "p2pkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/49'/0'/2147483647'",
description: "Samourai Ricochet compatibility segwit",
scriptType: "p2wpkh-p2sh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/2147483647'",
description: "Samourai Ricochet native segwit",
scriptType: "p2wpkh",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/2'/0'",
description: "Default Litecoin",
scriptType: "p2wpkh",
),
],
};

View file

@ -4,8 +4,8 @@ import 'dart:io';
import 'dart:math';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base;
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:collection/collection.dart';
import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
@ -37,9 +37,9 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:http/http.dart' as http;
part 'electrum_wallet.g.dart';
@ -53,15 +53,16 @@ abstract class ElectrumWalletBase
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required this.networkType,
required this.mnemonic,
required Uint8List seedBytes,
String? xpub,
String? mnemonic,
Uint8List? seedBytes,
this.passphrase,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumClient? electrumClient,
ElectrumBalance? initialBalance,
CryptoCurrency? currency})
: hd = currency == CryptoCurrency.bch
? bitcoinCashHDWallet(seedBytes)
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"),
: accountHD =
getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_feeRates = <int>[],
@ -78,20 +79,45 @@ abstract class ElectrumWalletBase
this.unspentCoinsInfo = unspentCoinsInfo,
this.network = _getNetwork(networkType, currency),
this.isTestnet = networkType == bitcoin.testnet,
this._mnemonic = mnemonic,
super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo;
transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
}
static bitcoin.HDWallet getAccountHDWallet(
CryptoCurrency? currency,
bitcoin.NetworkType networkType,
Uint8List? seedBytes,
String? xpub,
DerivationInfo? derivationInfo) {
if (seedBytes == null && xpub == null) {
throw Exception(
"To create a Wallet you need either a seed or an xpub. This should not happen");
}
if (seedBytes != null) {
return currency == CryptoCurrency.bch
? bitcoinCashHDWallet(seedBytes)
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
.derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? "m/0'"));
}
return bitcoin.HDWallet.fromBase58(xpub!);
}
static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) =>
bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/0");
bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'");
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10;
final bitcoin.HDWallet hd;
final String mnemonic;
final bitcoin.HDWallet accountHD;
final String? _mnemonic;
bitcoin.HDWallet get hd => accountHD.derive(0);
final String? passphrase;
@override
@observable
@ -120,10 +146,10 @@ abstract class ElectrumWalletBase
.map((addr) => scriptHash(addr.address, network: network))
.toList();
String get xpub => hd.base58!;
String get xpub => accountHD.base58!;
@override
String get seed => mnemonic;
String? get seed => _mnemonic;
bitcoin.NetworkType networkType;
BasedUtxoNetwork network;
@ -200,22 +226,38 @@ abstract class ElectrumWalletBase
int credentialsAmount = 0,
}) async {
final utxos = <UtxoWithAddress>[];
List<ECPrivate> privateKeys = [];
final privateKeys = <ECPrivate>[];
final publicKeys = <String, PublicKeyWithDerivationPath>{};
int allInputsAmount = 0;
bool spendsUnconfirmedTX = false;
for (int i = 0; i < unspentCoins.length; i++) {
final utx = unspentCoins[i];
if (utx.isSending) {
if (utx.isSending && !utx.isFrozen) {
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0;
allInputsAmount += utx.value;
final address = addressTypeFromStr(utx.address, network);
final privkey = generateECPrivate(
hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: utx.bitcoinAddressRecord.index,
network: network);
final hd =
utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
final derivationPath =
"${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
"/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
"/${utx.bitcoinAddressRecord.index}";
final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!;
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
if (!walletInfo.isHardwareWallet) {
final privkey =
generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network);
privateKeys.add(privkey);
}
utxos.add(
UtxoWithAddress(
@ -226,7 +268,7 @@ abstract class ElectrumWalletBase
scriptType: _getScriptType(address),
),
ownerDetails: UtxoAddressDetails(
publicKey: privkey.getPublic().toHex(),
publicKey: pubKeyHex,
address: address,
),
),
@ -264,6 +306,10 @@ abstract class ElectrumWalletBase
// Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change
int amount = allInputsAmount - fee;
if (amount <= 0) {
throw BitcoinTransactionWrongBalanceException();
}
// Attempting to send less than the dust limit
if (_isBelowDust(amount)) {
throw BitcoinTransactionNoDustException();
@ -283,11 +329,13 @@ abstract class ElectrumWalletBase
return EstimatedTxResult(
utxos: utxos,
privateKeys: privateKeys,
publicKeys: publicKeys,
fee: fee,
amount: amount,
isSendAll: true,
hasChange: false,
memo: memo,
spendsUnconfirmedTX: spendsUnconfirmedTX,
);
}
@ -297,27 +345,48 @@ abstract class ElectrumWalletBase
int feeRate, {
int? inputsCount,
String? memo,
bool? useUnconfirmed,
}) async {
final utxos = <UtxoWithAddress>[];
List<ECPrivate> privateKeys = [];
final privateKeys = <ECPrivate>[];
final publicKeys = <String, PublicKeyWithDerivationPath>{};
int allInputsAmount = 0;
bool spendsUnconfirmedTX = false;
int leftAmount = credentialsAmount;
final sendingCoins = unspentCoins.where((utx) => utx.isSending).toList();
final sendingCoins = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList();
final unconfirmedCoins = sendingCoins.where((utx) => utx.confirmations == 0).toList();
for (int i = 0; i < sendingCoins.length; i++) {
final utx = sendingCoins[i];
final isUncormirmed = utx.confirmations == 0;
if (useUnconfirmed != true && isUncormirmed) continue;
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = isUncormirmed;
allInputsAmount += utx.value;
leftAmount = leftAmount - utx.value;
final address = addressTypeFromStr(utx.address, network);
final privkey = generateECPrivate(
hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: utx.bitcoinAddressRecord.index,
network: network);
final hd =
utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
final derivationPath =
"${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
"/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
"/${utx.bitcoinAddressRecord.index}";
final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!;
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
if (!walletInfo.isHardwareWallet) {
final privkey =
generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network);
privateKeys.add(privkey);
}
utxos.add(
UtxoWithAddress(
@ -328,7 +397,7 @@ abstract class ElectrumWalletBase
scriptType: _getScriptType(address),
),
ownerDetails: UtxoAddressDetails(
publicKey: privkey.getPublic().toHex(),
publicKey: pubKeyHex,
address: address,
),
),
@ -345,11 +414,23 @@ abstract class ElectrumWalletBase
}
final spendingAllCoins = sendingCoins.length == utxos.length;
final spendingAllConfirmedCoins =
!spendsUnconfirmedTX && utxos.length == sendingCoins.length - unconfirmedCoins.length;
// How much is being spent - how much is being sent
int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount;
if (amountLeftForChangeAndFee <= 0) {
if (!spendingAllCoins) {
return estimateTxForAmount(
credentialsAmount,
outputs,
feeRate,
inputsCount: utxos.length + 1,
memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
);
}
throw BitcoinTransactionWrongBalanceException();
}
@ -403,6 +484,7 @@ abstract class ElectrumWalletBase
feeRate,
inputsCount: utxos.length + 1,
memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
);
}
@ -449,6 +531,7 @@ abstract class ElectrumWalletBase
feeRate,
inputsCount: utxos.length + 1,
memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
);
}
}
@ -456,11 +539,13 @@ abstract class ElectrumWalletBase
return EstimatedTxResult(
utxos: utxos,
privateKeys: privateKeys,
publicKeys: publicKeys,
fee: fee,
amount: amount,
hasChange: true,
isSendAll: false,
memo: memo,
spendsUnconfirmedTX: spendsUnconfirmedTX,
);
}
@ -522,6 +607,35 @@ abstract class ElectrumWalletBase
);
}
if (walletInfo.isHardwareWallet) {
final transaction = await buildHardwareWalletTransaction(
utxos: estimatedTx.utxos,
outputs: outputs,
publicKeys: estimatedTx.publicKeys,
fee: BigInt.from(estimatedTx.fee),
network: network,
memo: estimatedTx.memo,
outputOrdering: BitcoinOrdering.none,
enableRBF: true,
);
return PendingBitcoinTransaction(
transaction,
type,
electrumClient: electrumClient,
amount: estimatedTx.amount,
fee: estimatedTx.fee,
feeRate: feeRateInt.toString(),
network: network,
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot
)..addListener((transaction) async {
transactionHistory.addOne(transaction);
await updateBalance();
});
}
BasedBitcoinTransacationBuilder txb;
if (network is BitcoinCashNetwork) {
txb = ForkedTransactionBuilder(
@ -531,7 +645,7 @@ abstract class ElectrumWalletBase
network: network,
memo: estimatedTx.memo,
outputOrdering: BitcoinOrdering.none,
enableRBF: true,
enableRBF: !estimatedTx.spendsUnconfirmedTX,
);
} else {
txb = BitcoinTransactionBuilder(
@ -541,7 +655,7 @@ abstract class ElectrumWalletBase
network: network,
memo: estimatedTx.memo,
outputOrdering: BitcoinOrdering.none,
enableRBF: true,
enableRBF: !estimatedTx.spendsUnconfirmedTX,
);
}
@ -583,8 +697,23 @@ abstract class ElectrumWalletBase
}
}
Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs,
required BigInt fee,
required BasedUtxoNetwork network,
required List<UtxoWithAddress> utxos,
required Map<String, PublicKeyWithDerivationPath> publicKeys,
String? memo,
bool enableRBF = false,
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async =>
throw UnimplementedError();
String toJSON() => json.encode({
'mnemonic': mnemonic,
'mnemonic': _mnemonic,
'xpub': xpub,
'passphrase': passphrase ?? '',
'account_index': walletAddresses.currentReceiveAddressIndexByType,
'change_address_index': walletAddresses.currentChangeAddressIndexByType,
'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
@ -592,6 +721,8 @@ abstract class ElectrumWalletBase
? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(),
'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index,
'derivationPath': walletInfo.derivationInfo?.derivationPath,
});
int feeRate(TransactionPriority priority) {
@ -721,6 +852,7 @@ abstract class ElectrumWalletBase
final tx = await fetchTransactionInfo(
hash: coin.hash, height: 0, myAddresses: addressesSet);
coin.isChange = tx?.direction == TransactionDirection.outgoing;
coin.confirmations = tx?.confirmations;
updatedUnspentCoins.add(coin);
} catch (_) {}
}))));
@ -1037,9 +1169,11 @@ abstract class ElectrumWalletBase
return Future.wait(addressesByType.map((addressRecord) async {
final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight);
final balance = await electrumClient.getBalance(addressRecord.scriptHash!);
if (history.isNotEmpty) {
addressRecord.txCount = history.length;
addressRecord.balance = balance['confirmed'] as int? ?? 0;
historiesWithDetails.addAll(history);
final matchedAddresses =
@ -1221,7 +1355,7 @@ abstract class ElectrumWalletBase
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
@override
String signMessage(String message, {String? address = null}) {
Future<String> signMessage(String message, {String? address = null}) async {
final index = address != null
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
: null;
@ -1244,6 +1378,9 @@ abstract class ElectrumWalletBase
return BitcoinNetwork.mainnet;
}
static String _hardenedDerivationPath(String derivationPath) =>
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
}
class EstimateTxParams {
@ -1265,20 +1402,31 @@ class EstimatedTxResult {
EstimatedTxResult({
required this.utxos,
required this.privateKeys,
required this.publicKeys,
required this.fee,
required this.amount,
required this.hasChange,
required this.isSendAll,
this.memo,
required this.spendsUnconfirmedTX,
});
final List<UtxoWithAddress> utxos;
final List<ECPrivate> privateKeys;
final Map<String, PublicKeyWithDerivationPath> publicKeys; // PubKey to derivationPath
final int fee;
final int amount;
final bool hasChange;
final bool isSendAll;
final String? memo;
final bool spendsUnconfirmedTX;
}
class PublicKeyWithDerivationPath {
const PublicKeyWithDerivationPath(this.publicKey, this.derivationPath);
final String derivationPath;
final String publicKey;
}
BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {

View file

@ -3,6 +3,7 @@ import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_type.dart';
@ -12,11 +13,15 @@ class ElectrumWalletSnapshot {
required this.type,
required this.password,
required this.mnemonic,
required this.xpub,
required this.addresses,
required this.balance,
required this.regularAddressIndex,
required this.changeAddressIndex,
required this.addressPageType,
this.passphrase,
this.derivationType,
this.derivationPath,
});
final String name;
@ -24,11 +29,15 @@ class ElectrumWalletSnapshot {
final WalletType type;
final String? addressPageType;
String mnemonic;
String? mnemonic;
String? xpub;
List<BitcoinAddressRecord> addresses;
ElectrumBalance balance;
Map<String, int> regularAddressIndex;
Map<String, int> changeAddressIndex;
String? passphrase;
DerivationType? derivationType;
String? derivationPath;
static Future<ElectrumWalletSnapshot> load(
String name, WalletType type, String password, BasedUtxoNetwork network) async {
@ -36,7 +45,9 @@ class ElectrumWalletSnapshot {
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final addressesTmp = data['addresses'] as List? ?? <Object>[];
final mnemonic = data['mnemonic'] as String;
final mnemonic = data['mnemonic'] as String?;
final xpub = data['xpub'] as String?;
final passphrase = data['passphrase'] as String? ?? '';
final addresses = addressesTmp
.whereType<String>()
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
@ -46,6 +57,10 @@ class ElectrumWalletSnapshot {
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
final derivationType =
DerivationType.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
try {
regularAddressIndexByType = {
SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
@ -65,12 +80,16 @@ class ElectrumWalletSnapshot {
name: name,
type: type,
password: password,
passphrase: passphrase,
mnemonic: mnemonic,
xpub: xpub,
addresses: addresses,
balance: balance,
regularAddressIndex: regularAddressIndexByType,
changeAddressIndex: changeAddressIndexByType,
addressPageType: data['address_page_type'] as String?,
derivationType: derivationType,
derivationPath: derivationPath,
);
}
}

View file

@ -15,7 +15,9 @@ class BitcoinTransactionNoDustOnChangeException extends TransactionNoDustOnChang
BitcoinTransactionNoDustOnChangeException(super.max, super.min);
}
class BitcoinTransactionCommitFailed extends TransactionCommitFailed {}
class BitcoinTransactionCommitFailed extends TransactionCommitFailed {
BitcoinTransactionCommitFailed({super.errorMessage});
}
class BitcoinTransactionCommitFailedDustChange extends TransactionCommitFailedDustChange {}

View file

@ -14,7 +14,7 @@ import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/litecoin_network.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bip39/bip39.dart' as bip39;
part 'litecoin_wallet.g.dart';
@ -49,7 +49,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
sideHd: accountHD.derive(1),
network: network,
);
autorun((_) {
@ -62,11 +62,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? passphrase,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex}) async {
late Uint8List seedBytes;
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
seedBytes = await bip39.mnemonicToSeed(
mnemonic,
passphrase: passphrase ?? "",
);
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic);
break;
}
return LitecoinWallet(
mnemonic: mnemonic,
password: password,
@ -74,7 +89,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic),
seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
@ -90,13 +105,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final snp =
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
return LitecoinWallet(
mnemonic: snp.mnemonic,
mnemonic: snp.mnemonic!,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
seedBytes: await mnemonicToSeedBytes(snp.mnemonic!),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,

View file

@ -11,11 +11,12 @@ import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:collection/collection.dart';
import 'package:bip39/bip39.dart' as bip39;
class LitecoinWalletService extends WalletService<
BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials> {
BitcoinRestoreWalletFromWIFCredentials,BitcoinNewWalletCredentials> {
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -27,8 +28,9 @@ class LitecoinWalletService extends WalletService<
@override
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
final wallet = await LitecoinWalletBase.create(
mnemonic: await generateMnemonic(),
mnemonic: await generateElectrumMnemonic(),
password: credentials.password!,
passphrase: credentials.passphrase,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.save();
@ -92,6 +94,11 @@ class LitecoinWalletService extends WalletService<
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) {
throw UnimplementedError("Restoring a Litecoin wallet from a hardware wallet is not yet supported!");
}
@override
Future<LitecoinWallet> restoreFromKeys(
BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
@ -100,12 +107,13 @@ class LitecoinWalletService extends WalletService<
@override
Future<LitecoinWallet> restoreFromSeed(
BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
throw LitecoinMnemonicIsIncorrectException();
}
final wallet = await LitecoinWalletBase.create(
password: credentials.password!,
passphrase: credentials.passphrase,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource);

View file

@ -73,7 +73,9 @@ class PendingBitcoinTransaction with PendingTransaction {
if (error.contains("bad-txns-vout-negative")) {
throw BitcoinTransactionCommitFailedVoutNegative();
}
throw BitcoinTransactionCommitFailed(errorMessage: error);
}
throw BitcoinTransactionCommitFailed();
}

View file

@ -0,0 +1,96 @@
import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:convert/convert.dart';
import 'package:ledger_bitcoin/psbt.dart';
class PSBTTransactionBuild {
final PsbtV2 psbt = PsbtV2();
PSBTTransactionBuild(
{required List<PSBTReadyUtxoWithAddress> inputs, required List<BitcoinBaseOutput> outputs, bool enableRBF = true}) {
psbt.setGlobalTxVersion(2);
psbt.setGlobalInputCount(inputs.length);
psbt.setGlobalOutputCount(outputs.length);
for (var i = 0; i < inputs.length; i++) {
final input = inputs[i];
print(input.utxo.isP2tr());
print(input.utxo.isSegwit());
print(input.utxo.isP2shSegwit());
psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
psbt.setInputOutputIndex(i, input.utxo.vout);
psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff);
if (input.utxo.isSegwit()) {
setInputSegwit(i, input);
} else if (input.utxo.isP2shSegwit()) {
setInputP2shSegwit(i, input);
} else if (input.utxo.isP2tr()) {
// ToDo: (Konsti) Handle Taproot Inputs
} else {
setInputP2pkh(i, input);
}
}
for (var i = 0; i < outputs.length; i++) {
final output = outputs[i];
if (output is BitcoinOutput) {
psbt.setOutputScript(i, Uint8List.fromList(output.address.toScriptPubKey().toBytes()));
psbt.setOutputAmount(i, output.value.toInt());
}
}
}
void setInputP2pkh(int i, PSBTReadyUtxoWithAddress input) {
psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
psbt.setInputBip32Derivation(
i,
Uint8List.fromList(hex.decode(input.ownerPublicKey)),
input.ownerMasterFingerprint,
BIPPath.fromString(input.ownerDerivationPath).toPathArray());
}
void setInputSegwit(int i, PSBTReadyUtxoWithAddress input) {
psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
psbt.setInputBip32Derivation(
i,
Uint8List.fromList(hex.decode(input.ownerPublicKey)),
input.ownerMasterFingerprint,
BIPPath.fromString(input.ownerDerivationPath).toPathArray());
psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
}
void setInputP2shSegwit(int i, PSBTReadyUtxoWithAddress input) {
psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)),
input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray());
psbt.setInputRedeemScript(
i, Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
}
}
class PSBTReadyUtxoWithAddress extends UtxoWithAddress {
final String rawTx;
final String ownerDerivationPath;
final Uint8List ownerMasterFingerprint;
final String ownerPublicKey;
PSBTReadyUtxoWithAddress({
required super.utxo,
required this.rawTx,
required super.ownerDetails,
required this.ownerDerivationPath,
required this.ownerMasterFingerprint,
required this.ownerPublicKey,
});
}

View file

@ -5,29 +5,58 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:hex/hex.dart';
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, required int index}) =>
PaymentData(pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, int? index}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey)));
}
ECPrivate generateECPrivate(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
ECPrivate.fromWif(hd.derive(index).wif!, netVersion: network.wifNetVer);
{required bitcoin.HDWallet hd, required BasedUtxoNetwork network, int? index}) {
final wif = index != null ? hd.derive(index).wif! : hd.wif!;
return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
}
String generateP2WPKHAddress(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhAddress().toAddress(network);
String generateP2WPKHAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
}
String generateP2SHAddress(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhInP2sh().toAddress(network);
String generateP2SHAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
}
String generateP2WSHAddress(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wshAddress().toAddress(network);
String generateP2WSHAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
}
String generateP2PKHAddress(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
ECPublic.fromHex(hd.derive(index).pubKey!).toP2pkhAddress().toAddress(network);
String generateP2PKHAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
}
String generateP2TRAddress(
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
ECPublic.fromHex(hd.derive(index).pubKey!).toTaprootAddress().toAddress(network);
String generateP2TRAddress({
required bitcoin.HDWallet hd,
required BasedUtxoNetwork network,
int? index,
}) {
final pubKey = index != null ? hd.derive(index).pubKey! : hd.pubKey!;
return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network);
}

View file

@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.5.0"
asn1lib:
dependency: transitive
description:
@ -80,7 +80,7 @@ packages:
description:
path: "."
ref: cake-update-v2
resolved-ref: "3fd81d238b990bb767fc7a4fdd5053a22a142e2e"
resolved-ref: "01d844a5f5a520a31df5254e34169af4664aa769"
url: "https://github.com/cake-tech/bitcoin_base.git"
source: git
version: "4.2.0"
@ -153,10 +153,10 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.4.8"
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
@ -177,10 +177,10 @@ packages:
dependency: transitive
description:
name: built_value
sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://pub.dev"
source: hosted
version: "8.9.0"
version: "8.9.2"
characters:
dependency: transitive
description:
@ -217,10 +217,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -260,6 +260,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.4"
dart_varuint_bitcoin:
dependency: transitive
description:
name: dart_varuint_bitcoin
sha256: "4f0ccc9733fb54148b9d3688eea822b7aaabf5cc00025998f8c09a1d45b31b4b"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
encrypt:
dependency: transitive
description:
@ -309,10 +317,18 @@ packages:
dependency: "direct main"
description:
name: flutter_mobx
sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4"
sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e"
url: "https://pub.dev"
source: hosted
version: "2.2.0+2"
version: "2.2.1+1"
flutter_reactive_ble:
dependency: transitive
description:
name: flutter_reactive_ble
sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
flutter_test:
dependency: "direct dev"
description: flutter
@ -322,10 +338,18 @@ packages:
dependency: transitive
description:
name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "4.0.0"
functional_data:
dependency: transitive
description:
name: functional_data
sha256: aefdec4365452283b2a7cf420a3169654d51d3e9553069a22d76680d7a9d7c3d
url: "https://pub.dev"
source: hosted
version: "1.1.1"
glob:
dependency: transitive
description:
@ -422,6 +446,55 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.8.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
ledger_bitcoin:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab
url: "https://github.com/cake-tech/ledger-bitcoin"
source: git
version: "0.0.2"
ledger_flutter:
dependency: "direct main"
description:
name: ledger_flutter
sha256: f1680060ed6ff78f275837e0024ccaf667715a59ba7aa29fa7354bc7752e71c8
url: "https://pub.dev"
source: hosted
version: "1.0.1"
ledger_usb:
dependency: transitive
description:
name: ledger_usb
sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
logging:
dependency: transitive
description:
@ -434,26 +507,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.11.0"
mime:
dependency: transitive
description:
@ -466,10 +539,10 @@ packages:
dependency: "direct main"
description:
name: mobx
sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78"
sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e"
url: "https://pub.dev"
source: hosted
version: "2.3.0+1"
version: "2.3.3+2"
mobx_codegen:
dependency: "direct dev"
description:
@ -498,10 +571,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: "direct main"
description:
@ -570,10 +643,10 @@ packages:
dependency: transitive
description:
name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
url: "https://pub.dev"
source: hosted
version: "3.7.4"
version: "3.8.0"
pool:
dependency: transitive
description:
@ -582,14 +655,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
protobuf:
dependency: transitive
description:
name: protobuf
sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
provider:
dependency: transitive
description:
name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.1"
version: "6.1.2"
pub_semver:
dependency: transitive
description:
@ -606,6 +687,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.3"
reactive_ble_mobile:
dependency: transitive
description:
name: reactive_ble_mobile
sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
reactive_ble_platform_interface:
dependency: transitive
description:
name: reactive_ble_platform_interface
sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
rxdart:
dependency: "direct main"
description:
@ -663,26 +760,26 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@ -711,10 +808,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
timing:
dependency: transitive
description:
@ -747,8 +844,16 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher:
dependency: "direct overridden"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
@ -788,5 +893,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.10.0"

View file

@ -35,15 +35,22 @@ dependencies:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v2
blockchain_utils: ^2.1.1
ledger_flutter: ^1.0.1
ledger_bitcoin:
git:
url: https://github.com/cake-tech/ledger-bitcoin
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
build_runner: ^2.4.7
build_resolvers: ^2.0.9
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -51,7 +51,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/1"),
sideHd: accountHD.derive(1),
network: network,
initialAddressPageType: addressPageType,
);
@ -93,7 +93,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
final snp = await ElectrumWalletSnapshot.load(
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
return BitcoinCashWallet(
mnemonic: snp.mnemonic,
mnemonic: snp.mnemonic!,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
@ -118,7 +118,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
}
}).toList(),
initialBalance: snp.balance,
seedBytes: await Mnemonic.toSeed(snp.mnemonic),
seedBytes: await Mnemonic.toSeed(snp.mnemonic!),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: P2pkhAddressType.p2pkh,
@ -166,7 +166,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
}
@override
String signMessage(String message, {String? address = null}) {
Future<String> signMessage(String message, {String? address = null}) async {
final index = address != null
? walletAddresses.allAddresses
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))

View file

@ -12,7 +12,7 @@ import 'package:collection/collection.dart';
import 'package:hive/hive.dart';
class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials> {
BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials, BitcoinCashNewWalletCredentials> {
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -93,6 +93,11 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<BitcoinCashWallet> restoreFromHardwareWallet(BitcoinCashNewWalletCredentials credentials) {
throw UnimplementedError("Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!");
}
@override
Future<BitcoinCashWallet> restoreFromKeys(credentials, {bool? isTestnet}) {
// TODO: implement restoreFromKeys

View file

@ -62,7 +62,9 @@ class PendingBitcoinCashTransaction with PendingTransaction {
if (error.contains("bad-txns-vout-negative")) {
throw BitcoinTransactionCommitFailedVoutNegative();
}
throw BitcoinTransactionCommitFailed(errorMessage: error);
}
throw BitcoinTransactionCommitFailed();
}

View file

@ -39,10 +39,13 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
build_runner: ^2.4.7
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -103,6 +103,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.kaspa,
CryptoCurrency.digibyte,
CryptoCurrency.usdtSol,
CryptoCurrency.usdcTrc20,
];
static const havenCurrencies = [
@ -217,6 +218,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8);
static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
static final Map<int, CryptoCurrency> _rawCurrencyMap =
@ -257,10 +259,16 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
element.tag == walletCurrency?.tag));
} catch (_) {}
// search by fullName if not found by title:
try {
return CryptoCurrency.all.firstWhere((element) => element.fullName?.toLowerCase() == name);
} catch (_) {}
if (CryptoCurrency._nameCurrencyMap[name.toLowerCase()] == null) {
final s = 'Unexpected token: $name for CryptoCurrency fromString';
throw ArgumentError.value(name, 'name', s);
}
return CryptoCurrency._nameCurrencyMap[name.toLowerCase()]!;
}

View file

@ -23,7 +23,10 @@ CryptoCurrency currencyForWalletType(WalletType type) {
return CryptoCurrency.maticpoly;
case WalletType.solana:
return CryptoCurrency.sol;
case WalletType.tron:
return CryptoCurrency.trx;
default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
}
}

View file

@ -19,7 +19,11 @@ class TransactionNoDustOnChangeException implements Exception {
final String min;
}
class TransactionCommitFailed implements Exception {}
class TransactionCommitFailed implements Exception {
final String? errorMessage;
TransactionCommitFailed({this.errorMessage});
}
class TransactionCommitFailedDustChange implements Exception {}

View file

@ -0,0 +1,28 @@
import 'package:cw_core/wallet_type.dart';
enum DeviceConnectionType {
usb,
ble;
static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
[bool isIOS = false]) {
switch (walletType) {
case WalletType.bitcoin:
case WalletType.ethereum:
case WalletType.polygon:
if (isIOS) return [DeviceConnectionType.ble];
return [DeviceConnectionType.ble, DeviceConnectionType.usb];
default:
return [];
}
}
String get iconString {
switch (this) {
case ble:
return 'assets/images/bluetooth.png';
case usb:
return 'assets/images/usb.png';
}
}
}

View file

@ -0,0 +1,7 @@
class DeviceNotConnectedException implements Exception {
final String message;
DeviceNotConnectedException({
this.message = '',
});
}

View file

@ -0,0 +1,19 @@
import 'dart:typed_data';
class HardwareAccountData {
HardwareAccountData({
required this.address,
required this.accountIndex,
required this.derivationPath,
this.xpub,
this.masterFingerprint,
});
final String address;
final int accountIndex;
final String derivationPath;
// Bitcoin Specific
final Uint8List? masterFingerprint;
final String? xpub;
}

View file

@ -15,3 +15,6 @@ const NANO_ACCOUNT_TYPE_ID = 13;
const POW_NODE_TYPE_ID = 14;
const DERIVATION_TYPE_TYPE_ID = 15;
const SPL_TOKEN_TYPE_ID = 16;
const DERIVATION_INFO_TYPE_ID = 17;
const TRON_TOKEN_TYPE_ID = 18;
const HARDWARE_WALLET_TYPE_TYPE_ID = 19;

View file

@ -10,7 +10,7 @@ import 'package:http/io_client.dart' as ioc;
part 'node.g.dart';
Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!;
Uri createUriFromElectrumAddress(String address, String path) => Uri.tryParse('tcp://$address$path')!;
@HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable {
@ -83,7 +83,7 @@ class Node extends HiveObject with Keyable {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
return createUriFromElectrumAddress(uriRaw);
return createUriFromElectrumAddress(uriRaw, path ?? '');
case WalletType.nano:
case WalletType.banano:
if (isSSL) {
@ -94,7 +94,8 @@ class Node extends HiveObject with Keyable {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
return Uri.https(uriRaw, '');
case WalletType.tron:
return Uri.https(uriRaw, path ?? '');
default:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}
@ -152,6 +153,7 @@ class Node extends HiveObject with Keyable {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
return requestElectrumServer();
default:
return false;

View file

@ -14,6 +14,7 @@ class Unspent {
bool isChange;
bool isSending;
bool isFrozen;
int? confirmations;
String note;
bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc');

View file

@ -56,6 +56,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
bool get isEnabledAutoGenerateSubaddress => false;
bool get isHardwareWallet => walletInfo.isHardwareWallet;
Future<void> connectToNode({required Node node});
// there is a default definition here because only coins with a pow node (nano based) need to override this
@ -88,7 +90,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
Future<void> renameWalletFiles(String newWalletName);
String signMessage(String message, {String? address = null}) => throw UnimplementedError();
Future<String> signMessage(String message, {String? address = null}) => throw UnimplementedError();
bool? isTestnet;
}

View file

@ -7,15 +7,21 @@ abstract class WalletCredentials {
this.seedPhraseLength,
this.walletInfo,
this.password,
this.derivationType,
this.derivationPath,
});
this.passphrase,
this.derivationInfo,
this.hardwareWalletType,
}) {
if (this.walletInfo != null && derivationInfo != null) {
this.walletInfo!.derivationInfo = derivationInfo;
}
}
final String name;
final int? height;
int? seedPhraseLength;
String? password;
DerivationType? derivationType;
String? derivationPath;
String? passphrase;
WalletInfo? walletInfo;
DerivationInfo? derivationInfo;
HardwareWalletType? hardwareWalletType;
}

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:cw_core/address_info.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:cw_core/wallet_type.dart';
@ -17,28 +18,48 @@ enum DerivationType {
@HiveField(3)
bip39,
@HiveField(4)
electrum1,
@HiveField(5)
electrum2,
electrum,
}
class DerivationInfo {
@HiveType(typeId: HARDWARE_WALLET_TYPE_TYPE_ID)
enum HardwareWalletType {
@HiveField(0)
ledger,
}
@HiveType(typeId: DerivationInfo.typeId)
class DerivationInfo extends HiveObject {
DerivationInfo({
required this.derivationType,
this.derivationType,
this.derivationPath,
this.balance = "",
this.address = "",
this.height = 0,
this.script_type,
this.transactionsCount = 0,
this.scriptType,
this.description,
});
String balance;
static const typeId = DERIVATION_INFO_TYPE_ID;
@HiveField(0, defaultValue: '')
String address;
int height;
final DerivationType derivationType;
final String? derivationPath;
final String? script_type;
@HiveField(1, defaultValue: '')
String balance;
@HiveField(2, defaultValue: 0)
int transactionsCount;
@HiveField(3)
DerivationType? derivationType;
@HiveField(4)
String? derivationPath;
@HiveField(5)
final String? scriptType;
@HiveField(6)
final String? description;
}
@ -57,9 +78,9 @@ class WalletInfo extends HiveObject {
this.yatEid,
this.yatLastUsedAddressRaw,
this.showIntroCakePayCard,
this.derivationType,
this.derivationPath)
: _yatLastUsedAddressController = StreamController<String>.broadcast();
this.derivationInfo,
this.hardwareWalletType,
): _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external({
required String id,
@ -74,8 +95,8 @@ class WalletInfo extends HiveObject {
bool? showIntroCakePayCard,
String yatEid = '',
String yatLastUsedAddressRaw = '',
DerivationType? derivationType,
String? derivationPath,
DerivationInfo? derivationInfo,
HardwareWalletType? hardwareWalletType,
}) {
return WalletInfo(
id,
@ -90,8 +111,9 @@ class WalletInfo extends HiveObject {
yatEid,
yatLastUsedAddressRaw,
showIntroCakePayCard,
derivationType,
derivationPath);
derivationInfo,
hardwareWalletType,
);
}
static const typeId = WALLET_INFO_TYPE_ID;
@ -142,11 +164,13 @@ class WalletInfo extends HiveObject {
@HiveField(15)
List<String>? usedAddresses;
@deprecated
@HiveField(16)
DerivationType? derivationType;
DerivationType? derivationType; // no longer used
@deprecated
@HiveField(17)
String? derivationPath;
String? derivationPath; // no longer used
@HiveField(18)
String? addressPageType;
@ -154,6 +178,12 @@ class WalletInfo extends HiveObject {
@HiveField(19)
String? network;
@HiveField(20)
DerivationInfo? derivationInfo;
@HiveField(21)
HardwareWalletType? hardwareWalletType;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
set yatLastUsedAddress(String address) {
@ -170,6 +200,8 @@ class WalletInfo extends HiveObject {
return showIntroCakePayCard!;
}
bool get isHardwareWallet => hardwareWalletType != null;
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;

View file

@ -6,11 +6,13 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials,
RFK extends WalletCredentials> {
RFK extends WalletCredentials, RFH extends WalletCredentials> {
WalletType getType();
Future<WalletBase> create(N credentials, {bool? isTestnet});
Future<WalletBase> restoreFromHardwareWallet(RFH credentials);
Future<WalletBase> restoreFromSeed(RFS credentials, {bool? isTestnet});
Future<WalletBase> restoreFromKeys(RFK credentials, {bool? isTestnet});

View file

@ -15,6 +15,7 @@ const walletTypes = [
WalletType.banano,
WalletType.polygon,
WalletType.solana,
WalletType.tron,
];
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
@ -50,7 +51,10 @@ enum WalletType {
polygon,
@HiveField(10)
solana
solana,
@HiveField(11)
tron
}
int serializeToInt(WalletType type) {
@ -75,6 +79,8 @@ int serializeToInt(WalletType type) {
return 8;
case WalletType.solana:
return 9;
case WalletType.tron:
return 10;
default:
return -1;
}
@ -102,6 +108,8 @@ WalletType deserializeFromInt(int raw) {
return WalletType.polygon;
case 9:
return WalletType.solana;
case 10:
return WalletType.tron;
default:
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
}
@ -129,6 +137,8 @@ String walletTypeToString(WalletType type) {
return 'Polygon';
case WalletType.solana:
return 'Solana';
case WalletType.tron:
return 'Tron';
default:
return '';
}
@ -156,6 +166,8 @@ String walletTypeToDisplayName(WalletType type) {
return 'Polygon (MATIC)';
case WalletType.solana:
return 'Solana (SOL)';
case WalletType.tron:
return 'Tron (TRX)';
default:
return '';
}
@ -183,6 +195,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
return CryptoCurrency.maticpoly;
case WalletType.solana:
return CryptoCurrency.sol;
case WalletType.tron:
return CryptoCurrency.trx;
default:
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');

View file

@ -0,0 +1,22 @@
import 'dart:io';
import 'package:flutter/services.dart';
const MethodChannel _channel = MethodChannel('com.cake_wallet/native_utils');
Future<void> setDefaultMinimumWindowSize() async {
if (!Platform.isMacOS) return;
try {
final result = await _channel.invokeMethod(
'setMinWindowSize',
{'width': 500, 'height': 700},
) as bool;
if (!result) {
print("Failed to set minimum window size.");
}
} on PlatformException catch (e) {
print("Failed to set minimum window size: '${e.message}'.");
}
}

View file

@ -149,10 +149,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -331,6 +331,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.8.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
logging:
dependency: transitive
description:
@ -343,26 +367,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.11.0"
mime:
dependency: transitive
description:
@ -407,10 +431,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: "direct main"
description:
@ -564,26 +588,26 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@ -612,10 +636,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
timing:
dependency: transitive
description:
@ -640,8 +664,16 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
watcher:
dependency: "direct overridden"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
@ -681,5 +713,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.10.0"

View file

@ -28,11 +28,14 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
build_runner: ^2.4.7
build_resolvers: ^2.0.9
mobx_codegen: ^2.0.7
hive_generator: ^2.0.1
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -1,11 +1,12 @@
import 'package:bip39/bip39.dart' as bip39;
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_mnemonics_exception.dart';
import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
import 'package:cw_evm/evm_chain_wallet_service.dart';
import 'package:bip39/bip39.dart' as bip39;
class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
EthereumWalletService(super.walletInfoSource, {required this.client});
@ -82,6 +83,29 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<EthereumWallet> restoreFromHardwareWallet(
EVMChainRestoreWalletFromHardware credentials) async {
credentials.walletInfo!.derivationInfo = DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0"
);
credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
credentials.walletInfo!.address = credentials.hwAccountData.address;
final wallet = EthereumWallet(
walletInfo: credentials.walletInfo!,
password: credentials.password!,
client: client,
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
{bool? isTestnet}) async {

View file

@ -19,10 +19,18 @@ dependencies:
path: ../cw_evm
hive: ^2.2.3
dependency_overrides:
web3dart:
git:
url: https://github.com/cake-tech/web3dart.git
ref: cake
watcher: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
build_runner: ^2.4.7
flutter:
# assets:
# - images/a_dot_burr.jpeg

View file

@ -0,0 +1,209 @@
import 'dart:typed_data';
import 'package:web3dart/web3dart.dart' as web3;
final _contractAbi = web3.ContractAbi.fromJson(
'[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]',
'Erc20');
/// Interface of the ERC20 standard as defined in the EIP.
class ERC20 extends web3.GeneratedContract {
/// Constructor.
ERC20({
required web3.EthereumAddress address,
required web3.Web3Client client,
int? chainId,
}) : super(web3.DeployedContract(_contractAbi, address), client, chainId);
/// Returns the remaining number of tokens that [spender] will be allowed to spend on behalf of [owner] through [transferFrom]. This is zero by default. This value changes when [approve] or [transferFrom] are called.
///
/// The optional [atBlock] parameter can be used to view historical data. When
/// set, the function will be evaluated in the specified block. By default, the
/// latest on-chain block will be used.
Future<BigInt> allowance(
web3.EthereumAddress owner,
web3.EthereumAddress spender, {
web3.BlockNum? atBlock,
}) async {
final function = self.abi.functions[0];
assert(checkSignature(function, 'dd62ed3e'));
final params = [owner, spender];
final response = await read(function, params, atBlock);
return (response[0] as BigInt);
}
/// Sets [amount] as the allowance of [spender] over the caller's tokens. Returns a boolean value indicating whether the operation succeeded. IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 Emits an [Approval] event.
///
/// The optional [transaction] parameter can be used to override parameters
/// like the gas price, nonce and max gas. The `data` and `to` fields will be
/// set by the contract.
Future<Uint8List> approve(
web3.EthereumAddress spender,
BigInt amount, {
required web3.Credentials credentials,
web3.Transaction? transaction,
}) async {
final function = self.abi.functions[1];
assert(checkSignature(function, '095ea7b3'));
final params = [spender, amount];
return writeRaw(credentials, transaction, function, params);
}
/// Returns the amount of tokens owned by [account].
///
/// The optional [atBlock] parameter can be used to view historical data. When
/// set, the function will be evaluated in the specified block. By default, the
/// latest on-chain block will be used.
Future<BigInt> balanceOf(
web3.EthereumAddress account, {
web3.BlockNum? atBlock,
}) async {
final function = self.abi.functions[2];
assert(checkSignature(function, '70a08231'));
final params = [account];
final response = await read(function, params, atBlock);
return (response[0] as BigInt);
}
/// Returns the decimal precision of the token.
///
/// The optional [atBlock] parameter can be used to view historical data. When
/// set, the function will be evaluated in the specified block. By default, the
/// latest on-chain block will be used.
Future<BigInt> decimals({web3.BlockNum? atBlock}) async {
final function = self.abi.functions[3];
assert(checkSignature(function, '313ce567'));
final params = [];
final response = await read(function, params, atBlock);
return (response[0] as BigInt);
}
/// Returns the name of the token.
///
/// The optional [atBlock] parameter can be used to view historical data. When
/// set, the function will be evaluated in the specified block. By default, the
/// latest on-chain block will be used.
Future<String> name({web3.BlockNum? atBlock}) async {
final function = self.abi.functions[4];
assert(checkSignature(function, '06fdde03'));
final params = [];
final response = await read(function, params, atBlock);
return (response[0] as String);
}
/// Returns the symbol of the token.
///
/// The optional [atBlock] parameter can be used to view historical data. When
/// set, the function will be evaluated in the specified block. By default, the
/// latest on-chain block will be used.
Future<String> symbol({web3.BlockNum? atBlock}) async {
final function = self.abi.functions[5];
assert(checkSignature(function, '95d89b41'));
final params = [];
final response = await read(function, params, atBlock);
return (response[0] as String);
}
/// Returns the amount of tokens in existence.
///
/// The optional [atBlock] parameter can be used to view historical data. When
/// set, the function will be evaluated in the specified block. By default, the
/// latest on-chain block will be used.
Future<BigInt> totalSupply({web3.BlockNum? atBlock}) async {
final function = self.abi.functions[6];
assert(checkSignature(function, '18160ddd'));
final params = [];
final response = await read(function, params, atBlock);
return (response[0] as BigInt);
}
/// Moves [amount] tokens from the caller's account to [recipient]. Returns a boolean value indicating whether the operation succeeded. Emits a [Transfer] event.
///
/// The optional [transaction] parameter can be used to override parameters
/// like the gas price, nonce and max gas. The `data` and `to` fields will be
/// set by the contract.
Future<Uint8List> transfer(
web3.EthereumAddress recipient,
BigInt amount, {
required web3.Credentials credentials,
web3.Transaction? transaction,
}) async {
final function = self.abi.functions[7];
assert(checkSignature(function, 'a9059cbb'));
final params = [recipient, amount];
return writeRaw(credentials, transaction, function, params);
}
/// Moves [amount] tokens from [sender] to [recipient] using the allowance mechanism. [amount] is then deducted from the caller's allowance. Returns a boolean value indicating whether the operation succeeded. Emits a [Transfer] event.
///
/// The optional [transaction] parameter can be used to override parameters
/// like the gas price, nonce and max gas. The `data` and `to` fields will be
/// set by the contract.
Future<Uint8List> transferFrom(web3.EthereumAddress sender,
web3.EthereumAddress recipient, BigInt amount,
{required web3.Credentials credentials,
web3.Transaction? transaction}) async {
final function = self.abi.functions[8];
assert(checkSignature(function, '23b872dd'));
final params = [sender, recipient, amount];
return writeRaw(credentials, transaction, function, params);
}
/// Returns a live stream of all Approval events emitted by this contract.
Stream<Approval> approvalEvents(
{web3.BlockNum? fromBlock, web3.BlockNum? toBlock}) {
final event = self.event('Approval');
final filter = web3.FilterOptions.events(
contract: self, event: event, fromBlock: fromBlock, toBlock: toBlock);
return client.events(filter).map((web3.FilterEvent result) {
final decoded = event.decodeResults(result.topics!, result.data!);
return Approval._(decoded);
});
}
/// Returns a live stream of all Transfer events emitted by this contract.
Stream<Transfer> transferEvents(
{web3.BlockNum? fromBlock, web3.BlockNum? toBlock}) {
final event = self.event('Transfer');
final filter = web3.FilterOptions.events(
contract: self, event: event, fromBlock: fromBlock, toBlock: toBlock);
return client.events(filter).map((web3.FilterEvent result) {
final decoded = event.decodeResults(result.topics!, result.data!);
return Transfer._(decoded);
});
}
}
/// Emitted when the allowance of a [spender] for an [owner] is set by a call to [ERC20.approve]. [value] is the new allowance.
class Approval {
Approval._(List<dynamic> response)
: owner = (response[0] as web3.EthereumAddress),
spender = (response[1] as web3.EthereumAddress),
value = (response[2] as BigInt);
/// The owner address.
final web3.EthereumAddress owner;
/// The spender address.
final web3.EthereumAddress spender;
/// Value.
final BigInt value;
}
/// Emitted when [value] tokens are moved from one account ([from]) to another ([to]). Note that [value] may be zero.
class Transfer {
Transfer._(List<dynamic> response)
: from = (response[0] as web3.EthereumAddress),
to = (response[1] as web3.EthereumAddress),
value = (response[2] as BigInt);
/// From address.
final web3.EthereumAddress from;
/// To address.
final web3.EthereumAddress to;
/// Value.
final BigInt value;
}

View file

@ -2,21 +2,20 @@ import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:cw_core/node.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/node.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/pending_evm_chain_transaction.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
import 'package:cw_evm/pending_evm_chain_transaction.dart';
import 'package:cw_evm/.secrets.g.dart' as secrets;
import 'package:flutter/services.dart';
import 'package:http/http.dart';
import 'package:erc20/erc20.dart';
import 'package:web3dart/web3dart.dart';
import 'package:hex/hex.dart' as hex;
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';
import 'contract/erc20.dart';
abstract class EVMChainClient {
final httpClient = Client();
@ -82,7 +81,7 @@ abstract class EVMChainClient {
}
Future<PendingEVMChainTransaction> signTransaction({
required EthPrivateKey privateKey,
required Credentials privateKey,
required String toAddress,
required BigInt amount,
required int gas,
@ -96,8 +95,7 @@ abstract class EVMChainClient {
currency == CryptoCurrency.maticpoly ||
contractAddress != null);
bool isEVMCompatibleChain =
currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
bool isNativeToken = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
final price = _client!.getGasPrice();
@ -105,17 +103,16 @@ abstract class EVMChainClient {
from: privateKey.address,
to: EthereumAddress.fromHex(toAddress),
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
amount: isEVMCompatibleChain ? EtherAmount.inWei(amount) : EtherAmount.zero(),
amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(),
data: data != null ? hexToBytes(data) : null,
);
final signedTransaction =
await _client!.signTransaction(privateKey, transaction, chainId: chainId);
Uint8List signedTransaction;
final Function _sendTransaction;
if (isEVMCompatibleChain) {
_sendTransaction = () async => await sendTransaction(signedTransaction);
if (isNativeToken) {
signedTransaction = await _client!.signTransaction(privateKey, transaction, chainId: chainId);
} else {
final erc20 = ERC20(
client: _client!,
@ -123,16 +120,17 @@ abstract class EVMChainClient {
chainId: chainId,
);
_sendTransaction = () async {
await erc20.transfer(
signedTransaction = await erc20.transfer(
EthereumAddress.fromHex(toAddress),
amount,
credentials: privateKey,
transaction: transaction,
);
};
}
_sendTransaction = () async => await sendTransaction(signedTransaction);
return PendingEVMChainTransaction(
signedTransaction: signedTransaction,
amount: amount.toString(),
@ -158,8 +156,9 @@ abstract class EVMChainClient {
);
}
Future<String> sendTransaction(Uint8List signedTransaction) async =>
await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
Future<String> sendTransaction(Uint8List signedTransaction) async {
return await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
}
Future getTransactionDetails(String transactionHash) async {
// Wait for the transaction receipt to become available
@ -234,14 +233,17 @@ abstract class EVMChainClient {
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
final symbol = (decodedResponse['symbol'] ?? '') as String;
String filteredSymbol = symbol.replaceFirst(RegExp('^\\\$'), '');
final name = decodedResponse['name'] ?? '';
final symbol = decodedResponse['symbol'] ?? '';
final decimal = decodedResponse['decimals'] ?? '0';
final iconPath = decodedResponse['logo'] ?? '';
return Erc20Token(
name: name,
symbol: symbol,
symbol: filteredSymbol,
contractAddress: contractAddress,
decimal: int.tryParse(decimal) ?? 0,
iconPath: iconPath,

View file

@ -6,6 +6,8 @@ class EVMChainTransactionCreationException implements Exception {
EVMChainTransactionCreationException(CryptoCurrency currency)
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
EVMChainTransactionCreationException.fromMessage(this.exceptionMessage);
@override
String toString() => exceptionMessage;
}

View file

@ -0,0 +1,35 @@
import 'dart:async';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_ethereum/ledger_ethereum.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
class EVMChainHardwareWalletService {
EVMChainHardwareWalletService(this.ledger, this.device);
final Ledger ledger;
final LedgerDevice device;
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
final ethereumLedgerApp = EthereumLedgerApp(ledger);
final version = await ethereumLedgerApp.getVersion(device);
final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index);
for (final i in indexRange) {
final derivationPath = "m/44'/60'/$i'/0/0";
final address =
await ethereumLedgerApp.getAccounts(device, accountsDerivationPath: derivationPath);
accounts.add(HardwareAccountData(
address: address.first,
accountIndex: i,
derivationPath: derivationPath,
));
}
return accounts;
}
}

View file

@ -1,6 +1,7 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
class EVMChainTransactionCredentials {
EVMChainTransactionCredentials(

View file

@ -25,6 +25,7 @@ import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet_addresses.dart';
import 'package:cw_evm/evm_ledger_credentials.dart';
import 'package:cw_evm/file.dart';
import 'package:hex/hex.dart';
import 'package:hive/hive.dart';
@ -83,9 +84,9 @@ abstract class EVMChainWalletBase
late final Box<Erc20Token> evmChainErc20TokensBox;
late final EthPrivateKey _evmChainPrivateKey;
late final Credentials _evmChainPrivateKey;
EthPrivateKey get evmChainPrivateKey => _evmChainPrivateKey;
Credentials get evmChainPrivateKey => _evmChainPrivateKey;
late EVMChainClient _client;
@ -141,12 +142,18 @@ abstract class EVMChainWalletBase
await walletAddresses.init();
await transactionHistory.init();
if (walletInfo.isHardwareWallet) {
_evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address);
walletAddresses.address = walletInfo.address;
} else {
_evmChainPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
privateKey: _hexPrivateKey,
password: _password,
);
walletAddresses.address = _evmChainPrivateKey.address.hexEip55;
}
await save();
}
@ -283,6 +290,11 @@ abstract class EVMChainWalletBase
}
}
if (transactionCurrency is Erc20Token && isHardwareWallet) {
await (_evmChainPrivateKey as EvmLedgerCredentials)
.provideERC20Info(transactionCurrency.contractAddress, _client.chainId);
}
final pendingEVMChainTransaction = await _client.signTransaction(
privateKey: _evmChainPrivateKey,
toAddress: _credentials.outputs.first.isParsedAddress
@ -377,7 +389,9 @@ abstract class EVMChainWalletBase
String? get seed => _mnemonic;
@override
String get privateKey => HEX.encode(_evmChainPrivateKey.privateKey);
String? get privateKey => evmChainPrivateKey is EthPrivateKey
? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey)
: null;
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
@ -468,9 +482,15 @@ abstract class EVMChainWalletBase
await token.delete();
balance.remove(token);
await _removeTokenTransactionsInHistory(token);
_updateBalance();
}
Future<void> _removeTokenTransactionsInHistory(Erc20Token token) async {
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
await transactionHistory.save();
}
Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async =>
await _client.getErc20Token(contractAddress, chainName);
@ -529,8 +549,8 @@ abstract class EVMChainWalletBase
}
@override
String signMessage(String message, {String? address}) =>
bytesToHex(_evmChainPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
Future<String> signMessage(String message, {String? address}) async =>
bytesToHex(await _evmChainPrivateKey.signPersonalMessage(ascii.encode(message)));
Web3Client? getWeb3Client() => _client.getWeb3Client();
}

View file

@ -1,3 +1,4 @@
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
@ -27,3 +28,13 @@ class EVMChainRestoreWalletFromPrivateKey extends WalletCredentials {
final String privateKey;
}
class EVMChainRestoreWalletFromHardware extends WalletCredentials {
EVMChainRestoreWalletFromHardware({
required String name,
required this.hwAccountData,
WalletInfo? walletInfo,
}) : super(name: name, walletInfo: walletInfo);
final HardwareAccountData hwAccountData;
}

View file

@ -13,7 +13,8 @@ import 'package:hive/hive.dart';
abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletService<
EVMChainNewWalletCredentials,
EVMChainRestoreWalletFromSeedCredentials,
EVMChainRestoreWalletFromPrivateKey> {
EVMChainRestoreWalletFromPrivateKey,
EVMChainRestoreWalletFromHardware> {
EVMChainWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -24,6 +25,9 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
@override
Future<T> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet});
@override
Future<T> restoreFromHardwareWallet(EVMChainRestoreWalletFromHardware credentials);
@override
Future<T> openWallet(String name, String password);

View file

@ -0,0 +1,103 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:cw_core/hardware/device_not_connected_exception.dart';
import 'package:ledger_ethereum/ledger_ethereum.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
class EvmLedgerCredentials extends CredentialsWithKnownAddress {
final String _address;
Ledger? ledger;
LedgerDevice? ledgerDevice;
EthereumLedgerApp? ethereumLedgerApp;
EvmLedgerCredentials(this._address);
@override
EthereumAddress get address => EthereumAddress.fromHex(_address);
void setLedger(Ledger setLedger, [LedgerDevice? setLedgerDevice, String? derivationPath]) {
ledger = setLedger;
ledgerDevice = setLedgerDevice;
ethereumLedgerApp =
EthereumLedgerApp(ledger!, derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
}
@override
MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
@override
Future<MsgSignature> signToSignature(Uint8List payload,
{int? chainId, bool isEIP1559 = false}) async {
if (ledgerDevice == null && ledger?.devices.isNotEmpty != true) {
throw DeviceNotConnectedException();
}
final sig = await ethereumLedgerApp!.signTransaction(device, payload);
final v = sig[0].toInt();
final r = bytesToHex(sig.sublist(1, 1 + 32));
final s = bytesToHex(sig.sublist(1 + 32, 1 + 32 + 32));
var truncChainId = chainId ?? 1;
while (truncChainId.bitLength > 32) {
truncChainId >>= 8;
}
final truncTarget = truncChainId * 2 + 35;
int parity = v;
if (truncTarget & 0xff == v) {
parity = 0;
} else if ((truncTarget + 1) & 0xff == v) {
parity = 1;
}
// https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
int chainIdV;
if (isEIP1559) {
chainIdV = v;
} else {
chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
}
return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
}
@override
Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
if (isNotConnected) throw DeviceNotConnectedException();
final sig = await ethereumLedgerApp!.signMessage(device, payload);
final r = sig.sublist(1, 1 + 32);
final s = sig.sublist(1 + 32, 1 + 32 + 32);
final v = [sig[0]];
// https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L63
return Uint8List.fromList(r + s + v);
}
@override
Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
Future<void> provideERC20Info(String erc20ContractAddress, int chainId) async {
if (isNotConnected) throw DeviceNotConnectedException();
try {
await ethereumLedgerApp!.getAndProvideERC20TokenInformation(device,
erc20ContractAddress: erc20ContractAddress, chainId: chainId);
} on LedgerException catch (e) {
if (e.errorCode != -28672) rethrow;
}
}
bool get isNotConnected => (ledgerDevice ?? ledger?.devices.firstOrNull) == null;
LedgerDevice get device => ledgerDevice ?? ledger!.devices.first;
}

View file

@ -13,7 +13,6 @@ dependencies:
flutter:
sdk: flutter
web3dart: ^2.7.1
erc20: ^1.0.1
bip39: ^1.0.6
bip32: ^2.0.0
hex: ^0.2.0
@ -23,11 +22,26 @@ dependencies:
shared_preferences: ^2.0.15
cw_core:
path: ../cw_core
ledger_flutter: ^1.0.1
ledger_ethereum:
git:
url: https://github.com/cake-tech/ledger-ethereum.git
dependency_overrides:
web3dart:
git:
url: https://github.com/cake-tech/web3dart.git
ref: cake
ledger_flutter:
git:
url: https://github.com/cake-tech/ledger-flutter.git
ref: cake
watcher: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
build_runner: ^2.4.7
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
flutter_lints: ^2.0.0

View file

@ -56,7 +56,8 @@ class HavenRestoreWalletFromKeysCredentials extends WalletCredentials {
class HavenWalletService extends WalletService<
HavenNewWalletCredentials,
HavenRestoreWalletFromSeedCredentials,
HavenRestoreWalletFromKeysCredentials> {
HavenRestoreWalletFromKeysCredentials,
HavenNewWalletCredentials> {
HavenWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -172,6 +173,11 @@ class HavenWalletService extends WalletService<
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<HavenWallet> restoreFromHardwareWallet(HavenNewWalletCredentials credentials) {
throw UnimplementedError("Restoring a Haven wallet from a hardware wallet is not yet supported!");
}
@override
Future<HavenWallet> restoreFromKeys(
HavenRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {

View file

@ -69,10 +69,10 @@ packages:
dependency: transitive
description:
name: build_daemon
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "4.0.0"
build_resolvers:
dependency: "direct dev"
description:
@ -85,10 +85,10 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
@ -149,10 +149,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -338,6 +338,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.8.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
logging:
dependency: transitive
description:
@ -350,26 +374,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.11.0"
mime:
dependency: transitive
description:
@ -406,10 +430,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: "direct main"
description:
@ -563,26 +587,26 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@ -611,10 +635,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
timing:
dependency: transitive
description:
@ -639,14 +663,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
vm_service:
dependency: transitive
description:
name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "1.0.2"
version: "13.0.0"
watcher:
dependency: "direct overridden"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web_socket_channel:
dependency: transitive
description:
@ -680,5 +712,5 @@ packages:
source: hosted
version: "3.1.1"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0"

View file

@ -24,11 +24,14 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
build_runner: ^2.4.7
mobx_codegen: ^2.0.7
build_resolvers: ^2.0.9
hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -53,10 +53,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -201,6 +201,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.7"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints:
dependency: transitive
description:
@ -213,26 +237,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.11.0"
mobx:
dependency: transitive
description:
@ -245,10 +269,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: transitive
description:
@ -354,26 +378,26 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@ -394,10 +418,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
typed_data:
dependency: transitive
description:
@ -414,6 +438,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
win32:
dependency: transitive
description:
@ -431,5 +463,5 @@ packages:
source: hosted
version: "0.2.0+3"
sdks:
dart: ">=3.0.6 <4.0.0"
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0"

View file

@ -17,6 +17,9 @@ typedef restore_wallet_from_keys = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, P
typedef restore_wallet_from_spend_key = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
// typedef restore_wallet_from_device = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
// Int32, Int64, Pointer<Utf8>);
typedef is_wallet_exist = Int8 Function(Pointer<Utf8>);
typedef load_wallet = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Int8);

View file

@ -17,6 +17,9 @@ typedef RestoreWalletFromKeys = int Function(Pointer<Utf8>, Pointer<Utf8>,
typedef RestoreWalletFromSpendKey = int Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
typedef RestoreWalletFromDevice = int Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
int, int, Pointer<Utf8>);
typedef IsWalletExist = int Function(Pointer<Utf8>);
typedef LoadWallet = int Function(Pointer<Utf8>, Pointer<Utf8>, int);

View file

@ -31,6 +31,11 @@ final restoreWalletFromSpendKeyNative = moneroApi
'restore_wallet_from_spend_key')
.asFunction<RestoreWalletFromSpendKey>();
// final restoreWalletFromDeviceNative = moneroApi
// .lookup<NativeFunction<restore_wallet_from_device>>(
// 'restore_wallet_from_device')
// .asFunction<RestoreWalletFromDevice>();
final isWalletExistNative = moneroApi
.lookup<NativeFunction<is_wallet_exist>>('is_wallet_exist')
.asFunction<IsWalletExist>();
@ -185,6 +190,38 @@ void restoreWalletFromSpendKeySync(
}
}
// void restoreMoneroWalletFromDevice(
// {required String path,
// required String password,
// required String deviceName,
// int nettype = 0,
// int restoreHeight = 0}) {
//
// final pathPointer = path.toNativeUtf8();
// final passwordPointer = password.toNativeUtf8();
// final deviceNamePointer = deviceName.toNativeUtf8();
// final errorMessagePointer = ''.toNativeUtf8();
//
// final isWalletRestored = restoreWalletFromDeviceNative(
// pathPointer,
// passwordPointer,
// deviceNamePointer,
// nettype,
// restoreHeight,
// errorMessagePointer) != 0;
//
// calloc.free(pathPointer);
// calloc.free(passwordPointer);
//
// storeSync();
//
// if (!isWalletRestored) {
// throw WalletRestoreFromKeysException(
// message: convertUTF8ToString(pointer: errorMessagePointer));
// }
// }
void loadWallet({
required String path,
required String password,

View file

@ -686,7 +686,7 @@ abstract class MoneroWalletBase
void setExceptionHandler(void Function(FlutterErrorDetails) e) => onError = e;
@override
String signMessage(String message, {String? address}) {
Future<String> signMessage(String message, {String? address}) async {
final useAddress = address ?? "";
return monero_wallet.signMessage(message, address: useAddress);
}

View file

@ -55,7 +55,7 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
}
class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials> {
MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials, MoneroNewWalletCredentials> {
MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -227,6 +227,11 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
}
}
@override
Future<MoneroWallet> restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) {
throw UnimplementedError("Restoring a Monero wallet from a hardware wallet is not yet supported!");
}
@override
Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {

View file

@ -69,10 +69,10 @@ packages:
dependency: transitive
description:
name: build_daemon
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "4.0.0"
build_resolvers:
dependency: "direct dev"
description:
@ -85,10 +85,10 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
@ -149,10 +149,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -354,6 +354,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.8.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
logging:
dependency: transitive
description:
@ -366,26 +390,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.11.0"
mime:
dependency: transitive
description:
@ -422,10 +446,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: "direct main"
description:
@ -587,26 +611,26 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@ -635,10 +659,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
timing:
dependency: transitive
description:
@ -663,14 +687,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
vm_service:
dependency: transitive
description:
name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "1.0.2"
version: "13.0.0"
watcher:
dependency: "direct overridden"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web_socket_channel:
dependency: transitive
description:
@ -704,5 +736,5 @@ packages:
source: hosted
version: "3.1.1"
sdks:
dart: ">=3.0.6 <4.0.0"
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0"

View file

@ -26,11 +26,14 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
build_runner: ^2.4.7
build_resolvers: ^2.0.9
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -1,12 +1,28 @@
import 'package:cw_core/balance.dart';
import 'package:nanoutil/nanoutil.dart';
BigInt stringAmountToBigIntBanano(String amount) {
return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerBanano));
}
class BananoBalance extends Balance {
final BigInt currentBalance;
final BigInt receivableBalance;
BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
BananoBalance.fromFormattedString(
{required String formattedCurrentBalance, required String formattedReceivableBalance})
: currentBalance = stringAmountToBigIntBanano(formattedCurrentBalance),
receivableBalance = stringAmountToBigIntBanano(formattedReceivableBalance),
super(0, 0);
BananoBalance.fromRawString(
{required String currentBalance, required String receivableBalance})
: currentBalance = BigInt.parse(currentBalance),
receivableBalance = BigInt.parse(receivableBalance),
super(0, 0);
@override
String get formattedAvailableBalance {
return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano);

View file

@ -1,7 +1,7 @@
import 'package:cw_core/balance.dart';
import 'package:nanoutil/nanoutil.dart';
BigInt stringAmountToBigInt(String amount) {
BigInt stringAmountToBigIntNano(String amount) {
return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano));
}
@ -13,8 +13,8 @@ class NanoBalance extends Balance {
NanoBalance.fromFormattedString(
{required String formattedCurrentBalance, required String formattedReceivableBalance})
: currentBalance = stringAmountToBigInt(formattedCurrentBalance),
receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
: currentBalance = stringAmountToBigIntNano(formattedCurrentBalance),
receivableBalance = stringAmountToBigIntNano(formattedReceivableBalance),
super(0, 0);
NanoBalance.fromRawString(

View file

@ -10,6 +10,7 @@ import 'package:nanodart/nanodart.dart';
import 'package:cw_core/node.dart';
import 'package:nanoutil/nanoutil.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_nano/.secrets.g.dart' as secrets;
class NanoClient {
static const Map<String, String> CAKE_HEADERS = {
@ -52,10 +53,19 @@ class NanoClient {
}
}
Map<String, String> getHeaders() {
if (_node!.uri == "https://rpc.nano.to") {
return CAKE_HEADERS..addAll({
"key": secrets.nano2ApiKey,
});
}
return CAKE_HEADERS;
}
Future<NanoBalance> getBalance(String address) async {
final response = await http.post(
_node!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(),
body: jsonEncode(
{
"action": "account_balance",
@ -82,7 +92,7 @@ class NanoClient {
try {
final response = await http.post(
_node!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(),
body: jsonEncode(
{
"action": "account_info",
@ -94,7 +104,7 @@ class NanoClient {
final data = await jsonDecode(response.body);
return AccountInfoResponse.fromJson(data as Map<String, dynamic>);
} catch (e) {
print("error while getting account info");
print("error while getting account info $e");
return null;
}
}
@ -149,7 +159,7 @@ class NanoClient {
Future<String> requestWork(String hash) async {
final response = await http.post(
_powNode!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(),
body: json.encode(
{
"action": "work_generate",
@ -192,7 +202,7 @@ class NanoClient {
final processResponse = await http.post(
_node!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(),
body: processBody,
);
@ -351,7 +361,7 @@ class NanoClient {
});
final processResponse = await http.post(
_node!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(),
body: processBody,
);
@ -367,7 +377,7 @@ class NanoClient {
required String privateKey,
}) async {
final receivableResponse = await http.post(_node!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(),
body: jsonEncode({
"action": "receivable",
"account": destinationAddress,
@ -417,7 +427,7 @@ class NanoClient {
Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
try {
final response = await http.post(_node!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(),
body: jsonEncode({
"action": "account_history",
"account": address,

View file

@ -43,7 +43,7 @@ abstract class NanoWalletBase
}) : syncStatus = NotConnectedSyncStatus(),
_password = password,
_mnemonic = mnemonic,
_derivationType = walletInfo.derivationType!,
_derivationType = walletInfo.derivationInfo!.derivationType!,
_isTransactionUpdating = false,
_client = NanoClient(),
walletAddresses = NanoWalletAddresses(walletInfo),
@ -389,7 +389,10 @@ abstract class NanoWalletBase
derivationType = DerivationType.bip39;
}
walletInfo.derivationType = derivationType;
walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType);
if (walletInfo.derivationInfo!.derivationType == null) {
walletInfo.derivationInfo!.derivationType = derivationType;
}
return NanoWallet(
walletInfo: walletInfo,

View file

@ -2,8 +2,15 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class NanoNewWalletCredentials extends WalletCredentials {
NanoNewWalletCredentials({required String name, String? password})
: super(name: name, password: password);
NanoNewWalletCredentials({
required String name,
String? password,
DerivationType? derivationType,
}) : super(
name: name,
password: password,
derivationInfo: DerivationInfo(derivationType: derivationType),
);
}
class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
@ -11,11 +18,11 @@ class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
required String name,
required this.mnemonic,
String? password,
DerivationType? derivationType,
required DerivationType derivationType,
}) : super(
name: name,
password: password,
derivationType: derivationType,
derivationInfo: DerivationInfo(derivationType: derivationType),
);
final String mnemonic;
@ -30,12 +37,12 @@ class NanoRestoreWalletFromKeysCredentials extends WalletCredentials {
NanoRestoreWalletFromKeysCredentials({
required String name,
required String password,
required DerivationType derivationType,
required this.seedKey,
DerivationType? derivationType,
}) : super(
name: name,
password: password,
derivationType: derivationType,
derivationInfo: DerivationInfo(derivationType: derivationType),
);
final String seedKey;

View file

@ -14,7 +14,7 @@ import 'package:nanodart/nanodart.dart';
import 'package:nanoutil/nanoutil.dart';
class NanoWalletService extends WalletService<NanoNewWalletCredentials,
NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> {
NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials, NanoNewWalletCredentials> {
NanoWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -28,11 +28,11 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
@override
Future<WalletBase> create(NanoNewWalletCredentials credentials, {bool? isTestnet}) async {
// nano standard:
DerivationType derivationType = DerivationType.nano;
String seedKey = NanoSeeds.generateSeed();
String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
credentials.walletInfo!.derivationType = derivationType;
// ensure default if not present:
credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: DerivationType.nano);
final wallet = NanoWallet(
walletInfo: credentials.walletInfo!,
@ -88,9 +88,6 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
}
}
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
credentials.walletInfo!.derivationType = derivationType;
String? mnemonic;
// we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed
@ -112,6 +109,11 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
return wallet;
}
@override
Future<NanoWallet> restoreFromHardwareWallet(NanoNewWalletCredentials credentials) {
throw UnimplementedError("Restoring a Nano wallet from a hardware wallet is not yet supported!");
}
@override
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
if (credentials.mnemonic.contains(' ')) {
@ -128,9 +130,10 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
}
}
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
DerivationType derivationType =
credentials.walletInfo?.derivationInfo?.derivationType ?? DerivationType.nano;
credentials.walletInfo!.derivationType = derivationType;
credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: derivationType);
final wallet = await NanoWallet(
password: credentials.password!,

View file

@ -93,10 +93,10 @@ packages:
dependency: transitive
description:
name: build_daemon
sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169"
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "4.0.1"
build_resolvers:
dependency: transitive
description:
@ -109,10 +109,10 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
@ -173,10 +173,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -399,6 +399,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.8.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
libcrypto:
dependency: "direct main"
description:
@ -419,26 +443,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.11.0"
mime:
dependency: transitive
description:
@ -492,10 +516,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
version: "1.9.0"
path_provider:
dependency: transitive
description:
@ -713,26 +737,26 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
@ -761,10 +785,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.1"
timing:
dependency: transitive
description:
@ -789,14 +813,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
vm_service:
dependency: transitive
description:
name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "1.0.2"
version: "13.0.0"
watcher:
dependency: "direct overridden"
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web_socket_channel:
dependency: transitive
description:
@ -830,5 +862,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.7.0"

View file

@ -32,10 +32,13 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
build_runner: ^2.4.7
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -1,5 +1,6 @@
import 'package:bip39/bip39.dart' as bip39;
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
import 'package:cw_evm/evm_chain_wallet_service.dart';
@ -86,6 +87,29 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
return wallet;
}
@override
Future<PolygonWallet> restoreFromHardwareWallet(
EVMChainRestoreWalletFromHardware credentials) async {
credentials.walletInfo!.derivationInfo = DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0"
);
credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
credentials.walletInfo!.address = credentials.hwAccountData.address;
final wallet = PolygonWallet(
walletInfo: credentials.walletInfo!,
password: credentials.password!,
client: client,
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<PolygonWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {

View file

@ -23,12 +23,19 @@ dependencies:
bip39: ^1.0.6
collection: ^1.17.1
dependency_overrides:
web3dart:
git:
url: https://github.com/cake-tech/web3dart.git
ref: cake
watcher: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
build_runner: ^2.1.11
build_runner: ^2.4.7
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -35,9 +35,6 @@ class SolanaTransactionInfo extends TransactionInfo {
String amountFormatted() {
String stringBalance = solAmount.toString();
if (stringBalance.toString().length >= 6) {
stringBalance = stringBalance.substring(0, 6);
}
return '$stringBalance $tokenSymbol';
}

View file

@ -145,12 +145,17 @@ abstract class SolanaWalletBase
Future<Wallet> getWalletPair({String? mnemonic, String? privateKey}) async {
assert(mnemonic != null || privateKey != null);
if (privateKey != null) {
final privateKeyBytes = base58decode(privateKey);
return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes.take(32).toList());
if (mnemonic != null) {
return Wallet.fromMnemonic(mnemonic, account: 0, change: 0);
}
return Wallet.fromMnemonic(mnemonic!, account: 0, change: 0);
try {
final privateKeyBytes = base58decode(privateKey!);
return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes.take(32).toList());
} catch (_) {
final privateKeyBytes = HEX.decode(privateKey!);
return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes);
}
}
@override
@ -273,32 +278,12 @@ abstract class SolanaWalletBase
final transactions = await _client.fetchTransactions(address);
final Map<String, SolanaTransactionInfo> result = {};
for (var transactionModel in transactions) {
result[transactionModel.id] = SolanaTransactionInfo(
id: transactionModel.id,
to: transactionModel.to,
from: transactionModel.from,
blockTime: transactionModel.blockTime,
direction: transactionModel.isOutgoingTx
? TransactionDirection.outgoing
: TransactionDirection.incoming,
solAmount: transactionModel.amount,
isPending: false,
txFee: transactionModel.fee,
tokenSymbol: transactionModel.tokenSymbol,
);
}
transactionHistory.addMany(result);
await transactionHistory.save();
await _addTransactionsToTransactionHistory(transactions);
}
/// Fetches the SPL Tokens transactions linked to the token account Public Key
Future<void> _updateSPLTokenTransactions() async {
List<SolanaTransactionModel> splTokenTransactions = [];
// List<SolanaTransactionModel> splTokenTransactions = [];
// Make a copy of keys to avoid concurrent modification
var tokenKeys = List<CryptoCurrency>.from(balance.keys);
@ -312,13 +297,20 @@ abstract class SolanaWalletBase
_walletKeyPair!,
);
splTokenTransactions.addAll(tokenTxs);
// splTokenTransactions.addAll(tokenTxs);
await _addTransactionsToTransactionHistory(tokenTxs);
}
}
// await _addTransactionsToTransactionHistory(splTokenTransactions);
}
Future<void> _addTransactionsToTransactionHistory(
List<SolanaTransactionModel> transactions,
) async {
final Map<String, SolanaTransactionInfo> result = {};
for (var transactionModel in splTokenTransactions) {
for (var transactionModel in transactions) {
result[transactionModel.id] = SolanaTransactionInfo(
id: transactionModel.id,
to: transactionModel.to,
@ -373,7 +365,7 @@ abstract class SolanaWalletBase
String toJSON() => json.encode({
'mnemonic': _mnemonic,
'private_key': privateKey,
'private_key': _hexPrivateKey,
'balance': balance[currency]!.toJSON(),
});
@ -460,12 +452,23 @@ abstract class SolanaWalletBase
await token.delete();
balance.remove(token);
await _removeTokenTransactionsInHistory(token);
_updateBalance();
}
Future<void> _removeTokenTransactionsInHistory(SPLToken token) async {
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
await transactionHistory.save();
}
Future<SPLToken?> getSPLToken(String mintAddress) async {
// Convert SPL token mint address to public key
final mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress);
final Ed25519HDPublicKey mintPublicKey;
try {
mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress);
} catch (_) {
return null;
}
// Fetch token's metadata account
try {
@ -480,10 +483,12 @@ abstract class SolanaWalletBase
iconPath = await _client.getIconImageFromTokenUri(token.uri);
} catch (_) {}
String filteredTokenSymbol = token.symbol.replaceFirst(RegExp('^\\\$'), '');
return SPLToken.fromMetadata(
name: token.name,
mint: token.mint,
symbol: token.symbol,
symbol: filteredTokenSymbol,
mintAddress: mintAddress,
iconPath: iconPath,
);
@ -519,7 +524,7 @@ abstract class SolanaWalletBase
_transactionsUpdateTimer!.cancel();
}
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 20), (_) {
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) {
_updateBalance();
_updateNativeSOLTransactions();
_updateSPLTokenTransactions();

View file

@ -2,7 +2,10 @@ import 'dart:io';
import 'package:bip39/bip39.dart' as bip39;
import 'package:collection/collection.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
@ -13,7 +16,7 @@ import 'package:cw_solana/solana_wallet_creation_credentials.dart';
import 'package:hive/hive.dart';
class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
SolanaRestoreWalletFromSeedCredentials, SolanaRestoreWalletFromPrivateKey> {
SolanaRestoreWalletFromSeedCredentials, SolanaRestoreWalletFromPrivateKey, SolanaNewWalletCredentials> {
SolanaWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -134,4 +137,10 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(SolanaNewWalletCredentials credentials) {
// TODO: implement restoreFromHardwareWallet
throw UnimplementedError();
}
}

View file

@ -19,7 +19,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
@HiveField(3)
final int decimal;
@HiveField(4, defaultValue: false)
@HiveField(4, defaultValue: true)
bool _enabled;
@HiveField(5)
@ -39,7 +39,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
required this.mint,
this.iconPath,
this.tag = 'SOL',
bool enabled = false,
bool enabled = true,
}) : _enabled = enabled,
super(
name: mint.toLowerCase(),

View file

@ -26,10 +26,13 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
build_runner: ^2.1.11
build_runner: ^2.4.7
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
dependency_overrides:
watcher: ^1.1.0
flutter:
# assets:
# - images/a_dot_burr.jpeg

30
cw_tron/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

10
cw_tron/.metadata Normal file
View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
channel: stable
project_type: package

3
cw_tron/CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
cw_tron/LICENSE Normal file
View file

@ -0,0 +1 @@
TODO: Add your license here.

39
cw_tron/README.md Normal file
View file

@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.

View file

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

7
cw_tron/lib/cw_tron.dart Normal file
View file

@ -0,0 +1,7 @@
library cw_tron;
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}

View file

@ -0,0 +1,103 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_tron/tron_token.dart';
class DefaultTronTokens {
final List<TronToken> _defaultTokens = [
TronToken(
name: "Tether USD",
symbol: "USDT",
contractAddress: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
decimal: 6,
enabled: true,
),
TronToken(
name: "USD Coin",
symbol: "USDC",
contractAddress: "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8",
decimal: 6,
enabled: true,
),
TronToken(
name: "Bitcoin",
symbol: "BTC",
contractAddress: "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
decimal: 8,
enabled: false,
),
TronToken(
name: "Ethereum",
symbol: "ETH",
contractAddress: "TRFe3hT5oYhjSZ6f3ji5FJ7YCfrkWnHRvh",
decimal: 18,
enabled: false,
),
TronToken(
name: "Wrapped BTC",
symbol: "WBTC",
contractAddress: "TXpw8XeWYeTUd4quDskoUqeQPowRh4jY65",
decimal: 8,
enabled: true,
),
TronToken(
name: "Dogecoin",
symbol: "DOGE",
contractAddress: "THbVQp8kMjStKNnf2iCY6NEzThKMK5aBHg",
decimal: 8,
enabled: true,
),
TronToken(
name: "JUST Stablecoin",
symbol: "USDJ",
contractAddress: "TMwFHYXLJaRUPeW6421aqXL4ZEzPRFGkGT",
decimal: 18,
enabled: false,
),
TronToken(
name: "SUN",
symbol: "SUN",
contractAddress: "TSSMHYeV2uE9qYH95DqyoCuNCzEL1NvU3S",
decimal: 18,
enabled: false,
),
TronToken(
name: "Wrapped TRX",
symbol: "WTRX",
contractAddress: "TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR",
decimal: 6,
enabled: false,
),
TronToken(
name: "BitTorent",
symbol: "BTT",
contractAddress: "TAFjULxiVgT4qWk6UZwjqwZXTSaGaqnVp4",
decimal: 18,
enabled: false,
),
TronToken(
name: "BUSD Token",
symbol: "BUSD",
contractAddress: "TMz2SWatiAtZVVcH2ebpsbVtYwUPT9EdjH",
decimal: 18,
enabled: false,
),
TronToken(
name: "HTX",
symbol: "HTX",
contractAddress: "TUPM7K8REVzD2UdV4R5fe5M8XbnR2DdoJ6",
decimal: 18,
enabled: false,
),
];
List<TronToken> get initialTronTokens => _defaultTokens.map((token) {
String? iconPath;
try {
iconPath = CryptoCurrency.all
.firstWhere((element) =>
element.title.toUpperCase() == token.symbol.split(".").first.toUpperCase())
.iconPath;
} catch (_) {}
return TronToken.copyWith(token, iconPath, 'TRX');
}).toList();
}

39
cw_tron/lib/file.dart Normal file
View file

@ -0,0 +1,39 @@
import 'dart:io';
import 'package:cw_core/key.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
Future<void> write(
{required String path,
required String password,
required String data}) async {
final keys = extractKeys(password);
final key = encrypt.Key.fromBase64(keys.first);
final iv = encrypt.IV.fromBase64(keys.last);
final encrypted = await encode(key: key, iv: iv, data: data);
final f = File(path);
f.writeAsStringSync(encrypted);
}
Future<void> writeData(
{required String path,
required String password,
required String data}) async {
final keys = extractKeys(password);
final key = encrypt.Key.fromBase64(keys.first);
final iv = encrypt.IV.fromBase64(keys.last);
final encrypted = await encode(key: key, iv: iv, data: data);
final f = File(path);
f.writeAsStringSync(encrypted);
}
Future<String> read({required String path, required String password}) async {
final file = File(path);
if (!file.existsSync()) {
file.createSync();
}
final encrypted = file.readAsStringSync();
return decode(password: password, data: encrypted);
}

View file

@ -0,0 +1,33 @@
import 'package:cw_core/pending_transaction.dart';
import 'package:web3dart/crypto.dart';
class PendingTronTransaction with PendingTransaction {
final Function sendTransaction;
final List<int> signedTransaction;
final String fee;
final String amount;
PendingTronTransaction({
required this.sendTransaction,
required this.signedTransaction,
required this.fee,
required this.amount,
});
@override
String get amountFormatted => amount;
@override
Future<void> commit() async => await sendTransaction();
@override
String get feeFormatted => fee;
@override
String get hex => bytesToHex(signedTransaction);
@override
String get id => '';
}

436
cw_tron/lib/tron_abi.dart Normal file
View file

@ -0,0 +1,436 @@
final trc20Abi = [
{"inputs": [], "stateMutability": "nonpayable", "type": "constructor"},
{
"anonymous": false,
"inputs": [
{"indexed": true, "internalType": "address", "name": "owner", "type": "address"},
{"indexed": true, "internalType": "address", "name": "spender", "type": "address"},
{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{"indexed": false, "internalType": "uint256", "name": "total", "type": "uint256"},
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
{"indexed": true, "internalType": "address", "name": "buyer", "type": "address"},
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"},
{"indexed": false, "internalType": "address", "name": "contract_address", "type": "address"}
],
"name": "OrderPaid",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{"indexed": true, "internalType": "address", "name": "previousOwner", "type": "address"},
{"indexed": true, "internalType": "address", "name": "newOwner", "type": "address"}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{"indexed": false, "internalType": "address", "name": "token", "type": "address"},
{"indexed": false, "internalType": "bool", "name": "active", "type": "bool"}
],
"name": "TokenUpdate",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{"indexed": true, "internalType": "address", "name": "from", "type": "address"},
{"indexed": true, "internalType": "address", "name": "to", "type": "address"},
{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{"indexed": false, "internalType": "string", "name": "username", "type": "string"},
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"}
],
"name": "UserRegistred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
{"indexed": true, "internalType": "address", "name": "buyer", "type": "address"},
{"indexed": false, "internalType": "address", "name": "seller", "type": "address"}
],
"name": "WBuyer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"},
{"indexed": false, "internalType": "address", "name": "buyer", "type": "address"}
],
"name": "WSeller",
"type": "event"
},
{
"inputs": [],
"name": "CONTRACTPERCENTAGE",
"outputs": [
{"internalType": "uint8", "name": "", "type": "uint8"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
{"internalType": "uint256", "name": "order_total", "type": "uint256"},
{"internalType": "address", "name": "contractAddress", "type": "address"},
{"internalType": "address", "name": "seller", "type": "address"}
],
"name": "PayWithTokens",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "TOKENINCREAMENT",
"outputs": [
{"internalType": "uint16", "name": "", "type": "uint16"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "", "type": "address"}
],
"name": "_signer",
"outputs": [
{"internalType": "bool", "name": "", "type": "bool"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "", "type": "address"}
],
"name": "_tokens",
"outputs": [
{"internalType": "bool", "name": "active", "type": "bool"},
{"internalType": "uint16", "name": "token", "type": "uint16"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "", "type": "address"}
],
"name": "_users",
"outputs": [
{"internalType": "bool", "name": "active", "type": "bool"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "owner", "type": "address"},
{"internalType": "address", "name": "spender", "type": "address"}
],
"name": "allowance",
"outputs": [
{"internalType": "uint256", "name": "", "type": "uint256"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "spender", "type": "address"},
{"internalType": "uint256", "name": "amount", "type": "uint256"}
],
"name": "approve",
"outputs": [
{"internalType": "bool", "name": "", "type": "bool"}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "account", "type": "address"}
],
"name": "balanceOf",
"outputs": [
{"internalType": "uint256", "name": "", "type": "uint256"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "token", "type": "address"}
],
"name": "balanceOfContract",
"outputs": [
{"internalType": "uint256", "name": "", "type": "uint256"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "uint256", "name": "amount", "type": "uint256"}
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "account", "type": "address"},
{"internalType": "uint256", "name": "amount", "type": "uint256"}
],
"name": "burnFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "uint256", "name": "value", "type": "uint256"},
{"internalType": "address", "name": "_contractAddress", "type": "address"}
],
"name": "contractWithdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{"internalType": "uint8", "name": "", "type": "uint8"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "spender", "type": "address"},
{"internalType": "uint256", "name": "subtractedValue", "type": "uint256"}
],
"name": "decreaseAllowance",
"outputs": [
{"internalType": "bool", "name": "", "type": "bool"}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "spender", "type": "address"},
{"internalType": "uint256", "name": "addedValue", "type": "uint256"}
],
"name": "increaseAllowance",
"outputs": [
{"internalType": "bool", "name": "", "type": "bool"}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "to", "type": "address"},
{"internalType": "uint256", "name": "amount", "type": "uint256"}
],
"name": "mint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{"internalType": "string", "name": "", "type": "string"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{"internalType": "address", "name": "", "type": "address"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "token", "type": "address"},
{"internalType": "uint256", "name": "value", "type": "uint256"}
],
"name": "payToContract",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
{"internalType": "address", "name": "seller", "type": "address"}
],
"name": "payWithNativeToken",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{"internalType": "string", "name": "username", "type": "string"}
],
"name": "regiserUser",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "uint16", "name": "id", "type": "uint16"},
{"internalType": "address", "name": "buyer", "type": "address"},
{"internalType": "address", "name": "seller", "type": "address"}
],
"name": "selectOrder",
"outputs": [
{"internalType": "uint232", "name": "", "type": "uint232"},
{"internalType": "uint16", "name": "", "type": "uint16"},
{"internalType": "uint8", "name": "", "type": "uint8"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{"internalType": "string", "name": "", "type": "string"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "signer", "type": "address"}
],
"name": "toggleSigner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "tokenAddress", "type": "address"}
],
"name": "toggleToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{"internalType": "uint256", "name": "", "type": "uint256"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "to", "type": "address"},
{"internalType": "uint256", "name": "amount", "type": "uint256"}
],
"name": "transfer",
"outputs": [
{"internalType": "bool", "name": "", "type": "bool"}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "from", "type": "address"},
{"internalType": "address", "name": "to", "type": "address"},
{"internalType": "uint256", "name": "amount", "type": "uint256"}
],
"name": "transferFrom",
"outputs": [
{"internalType": "bool", "name": "", "type": "bool"}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "newOwner", "type": "address"}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "uint8", "name": "newPercentage", "type": "uint8"}
],
"name": "updateContractPercentage",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "address[]", "name": "buyer", "type": "address[]"},
{"internalType": "bytes[]", "name": "signature", "type": "bytes[]"},
{"internalType": "uint16[]", "name": "order_id", "type": "uint16[]"},
{"internalType": "address", "name": "contractAddress", "type": "address"}
],
"name": "widthrawForSellers",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "seller", "type": "address"},
{"internalType": "bytes", "name": "signature", "type": "bytes"},
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
{"internalType": "address", "name": "contractAddress", "type": "address"}
],
"name": "widthrowForBuyers",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];

View file

@ -0,0 +1,34 @@
import 'dart:convert';
import 'package:cw_core/balance.dart';
import 'package:on_chain/on_chain.dart';
class TronBalance extends Balance {
TronBalance(this.balance) : super(balance.toInt(), balance.toInt());
final BigInt balance;
@override
String get formattedAdditionalBalance => TronHelper.fromSun(balance);
@override
String get formattedAvailableBalance => TronHelper.fromSun(balance);
String toJSON() => json.encode({
'balance': balance.toString(),
});
static TronBalance? fromJSON(String? jsonSource) {
if (jsonSource == null) {
return null;
}
final decoded = json.decode(jsonSource) as Map;
try {
return TronBalance(BigInt.parse(decoded['balance']));
} catch (e) {
return TronBalance(BigInt.zero);
}
}
}

Some files were not shown because too many files have changed in this diff Show more