mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 11:39:22 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into bip39-minor-enhancements
This commit is contained in:
commit
8ffde50953
249 changed files with 8737 additions and 1652 deletions
5
.github/workflows/pr_test_build.yml
vendored
5
.github/workflows/pr_test_build.yml
vendored
|
@ -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,8 @@ 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 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
5
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
ext.kotlin_version = '1.8.21'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
|
5
assets/banano_node_list.yml
Normal file
5
assets/banano_node_list.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
-
|
||||
uri: kaliumapi.appditto.com
|
||||
path: /api
|
||||
useSSL: true
|
||||
is_default: true
|
BIN
assets/images/bluetooth.png
Normal file
BIN
assets/images/bluetooth.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/images/ledger_nano.png
Normal file
BIN
assets/images/ledger_nano.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/usb.png
Normal file
BIN
assets/images/usb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,2 +1 @@
|
|||
UI enhancements
|
||||
Bug fixes
|
||||
Generic bug fixes and enhancements
|
|
@ -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
|
||||
Bug fixes
|
||||
Hardware wallets support for Bitcoin, Ethereum and Polygon
|
||||
Security enhancements
|
||||
Bug fixes and generic enhancements
|
8
assets/tron_node_list.yml
Normal file
8
assets/tron_node_list.yml
Normal 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
|
43
cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
Normal file
43
cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,13 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonic.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:ledger_bitcoin/ledger_bitcoin.dart';
|
||||
import 'package:ledger_flutter/ledger_flutter.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
|
@ -20,11 +25,12 @@ 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,
|
||||
|
@ -33,32 +39,34 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
Map<String, int>? initialChangeAddressIndex,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: networkParam == null
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
xpub: xpub,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: networkParam == null
|
||||
? bitcoin.bitcoin
|
||||
: networkParam == BitcoinNetwork.mainnet
|
||||
? bitcoin.bitcoin
|
||||
: networkParam == BitcoinNetwork.mainnet
|
||||
? bitcoin.bitcoin
|
||||
: bitcoin.testnet,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
currency: CryptoCurrency.btc,
|
||||
) {
|
||||
String derivationPath = walletInfo.derivationInfo!.derivationPath! + "/0";
|
||||
String sideDerivationPath = walletInfo.derivationInfo!.derivationPath! + "/1";
|
||||
final hd2 = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
||||
: bitcoin.testnet,
|
||||
initialAddresses: initialAddresses,
|
||||
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,
|
||||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
mainHd: hd2.derivePath(derivationPath),
|
||||
sideHd: hd2.derivePath(sideDerivationPath),
|
||||
mainHd: hd,
|
||||
sideHd: accountHD.derive(1),
|
||||
network: networkParam ?? network,
|
||||
);
|
||||
autorun((_) {
|
||||
|
@ -126,25 +134,29 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
);
|
||||
|
||||
// set the default if not present:
|
||||
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'";
|
||||
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? "m/0'/0";
|
||||
walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
|
||||
|
||||
late Uint8List seedBytes;
|
||||
Uint8List? seedBytes = 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;
|
||||
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,
|
||||
|
@ -158,4 +170,49 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -36,9 +37,22 @@ class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -14,8 +14,11 @@ 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;
|
||||
|
@ -99,9 +102,28 @@ 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 =>
|
||||
{bool? isTestnet}) async =>
|
||||
throw UnimplementedError();
|
||||
|
||||
@override
|
||||
|
|
|
@ -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,17 +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(walletInfo.derivationInfo?.derivationPath ?? "m/0'"),
|
||||
: accountHD =
|
||||
getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo),
|
||||
syncStatus = NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_feeRates = <int>[],
|
||||
|
@ -80,20 +79,44 @@ 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
|
||||
|
@ -123,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;
|
||||
|
@ -203,7 +226,9 @@ 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;
|
||||
|
@ -217,12 +242,22 @@ abstract class ElectrumWalletBase
|
|||
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!;
|
||||
|
||||
privateKeys.add(privkey);
|
||||
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(
|
||||
|
@ -233,7 +268,7 @@ abstract class ElectrumWalletBase
|
|||
scriptType: _getScriptType(address),
|
||||
),
|
||||
ownerDetails: UtxoAddressDetails(
|
||||
publicKey: privkey.getPublic().toHex(),
|
||||
publicKey: pubKeyHex,
|
||||
address: address,
|
||||
),
|
||||
),
|
||||
|
@ -294,6 +329,7 @@ abstract class ElectrumWalletBase
|
|||
return EstimatedTxResult(
|
||||
utxos: utxos,
|
||||
privateKeys: privateKeys,
|
||||
publicKeys: publicKeys,
|
||||
fee: fee,
|
||||
amount: amount,
|
||||
isSendAll: true,
|
||||
|
@ -312,7 +348,9 @@ abstract class ElectrumWalletBase
|
|||
bool? useUnconfirmed,
|
||||
}) async {
|
||||
final utxos = <UtxoWithAddress>[];
|
||||
List<ECPrivate> privateKeys = [];
|
||||
final privateKeys = <ECPrivate>[];
|
||||
final publicKeys = <String, PublicKeyWithDerivationPath>{};
|
||||
|
||||
int allInputsAmount = 0;
|
||||
bool spendsUnconfirmedTX = false;
|
||||
|
||||
|
@ -332,12 +370,23 @@ abstract class ElectrumWalletBase
|
|||
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);
|
||||
|
||||
privateKeys.add(privkey);
|
||||
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(
|
||||
|
@ -348,7 +397,7 @@ abstract class ElectrumWalletBase
|
|||
scriptType: _getScriptType(address),
|
||||
),
|
||||
ownerDetails: UtxoAddressDetails(
|
||||
publicKey: privkey.getPublic().toHex(),
|
||||
publicKey: pubKeyHex,
|
||||
address: address,
|
||||
),
|
||||
),
|
||||
|
@ -490,6 +539,7 @@ abstract class ElectrumWalletBase
|
|||
return EstimatedTxResult(
|
||||
utxos: utxos,
|
||||
privateKeys: privateKeys,
|
||||
publicKeys: publicKeys,
|
||||
fee: fee,
|
||||
amount: amount,
|
||||
hasChange: true,
|
||||
|
@ -557,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(
|
||||
|
@ -618,8 +697,22 @@ 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,
|
||||
|
@ -1263,7 +1356,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;
|
||||
|
@ -1286,6 +1379,9 @@ abstract class ElectrumWalletBase
|
|||
|
||||
return BitcoinNetwork.mainnet;
|
||||
}
|
||||
|
||||
static String _hardenedDerivationPath(String derivationPath) =>
|
||||
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
|
||||
}
|
||||
|
||||
class EstimateTxParams {
|
||||
|
@ -1307,6 +1403,7 @@ class EstimatedTxResult {
|
|||
EstimatedTxResult({
|
||||
required this.utxos,
|
||||
required this.privateKeys,
|
||||
required this.publicKeys,
|
||||
required this.fee,
|
||||
required this.amount,
|
||||
required this.hasChange,
|
||||
|
@ -1317,6 +1414,7 @@ class EstimatedTxResult {
|
|||
|
||||
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;
|
||||
|
@ -1325,6 +1423,13 @@ class EstimatedTxResult {
|
|||
final bool spendsUnconfirmedTX;
|
||||
}
|
||||
|
||||
class PublicKeyWithDerivationPath {
|
||||
const PublicKeyWithDerivationPath(this.publicKey, this.derivationPath);
|
||||
|
||||
final String derivationPath;
|
||||
final String publicKey;
|
||||
}
|
||||
|
||||
BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {
|
||||
if (network is BitcoinCashNetwork) {
|
||||
if (!address.startsWith("bitcoincash:") &&
|
||||
|
|
|
@ -13,6 +13,7 @@ class ElectrumWalletSnapshot {
|
|||
required this.type,
|
||||
required this.password,
|
||||
required this.mnemonic,
|
||||
required this.xpub,
|
||||
required this.addresses,
|
||||
required this.balance,
|
||||
required this.regularAddressIndex,
|
||||
|
@ -28,7 +29,8 @@ class ElectrumWalletSnapshot {
|
|||
final WalletType type;
|
||||
final String? addressPageType;
|
||||
|
||||
String mnemonic;
|
||||
String? mnemonic;
|
||||
String? xpub;
|
||||
List<BitcoinAddressRecord> addresses;
|
||||
ElectrumBalance balance;
|
||||
Map<String, int> regularAddressIndex;
|
||||
|
@ -43,7 +45,8 @@ 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>()
|
||||
|
@ -79,6 +82,7 @@ class ElectrumWalletSnapshot {
|
|||
password: password,
|
||||
passphrase: passphrase,
|
||||
mnemonic: mnemonic,
|
||||
xpub: xpub,
|
||||
addresses: addresses,
|
||||
balance: balance,
|
||||
regularAddressIndex: regularAddressIndexByType,
|
||||
|
|
|
@ -15,6 +15,7 @@ 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';
|
||||
|
||||
|
@ -52,8 +53,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
mainHd: hd2.derivePath(derivationPath),
|
||||
sideHd: hd2.derivePath(sideDerivationPath),
|
||||
mainHd: hd,
|
||||
sideHd: accountHD.derive(1),
|
||||
network: network,
|
||||
);
|
||||
autorun((_) {
|
||||
|
@ -66,11 +67,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,
|
||||
|
@ -78,7 +94,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,
|
||||
|
@ -94,13 +110,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,
|
||||
|
|
|
@ -16,7 +16,7 @@ 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;
|
||||
|
@ -30,6 +30,7 @@ class LitecoinWalletService extends WalletService<
|
|||
final wallet = await LitecoinWalletBase.create(
|
||||
mnemonic: await generateElectrumMnemonic(),
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
|
@ -93,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 =>
|
||||
|
@ -107,6 +113,7 @@ class LitecoinWalletService extends WalletService<
|
|||
|
||||
final wallet = await LitecoinWalletBase.create(
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
|
|
96
cw_bitcoin/lib/psbt_transaction_builder.dart
Normal file
96
cw_bitcoin/lib/psbt_transaction_builder.dart
Normal 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,
|
||||
});
|
||||
}
|
|
@ -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:
|
||||
|
@ -313,6 +321,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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
|
||||
|
@ -326,6 +342,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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,31 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.1"
|
||||
ledger_bitcoin:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: b6ed573cbeb57d5f0d39dfe4254bf9d15b620ab6
|
||||
url: "https://github.com/cake-tech/ledger-bitcoin.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
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:
|
||||
|
@ -582,6 +631,14 @@ 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:
|
||||
|
@ -606,6 +663,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:
|
||||
|
@ -788,5 +861,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
dart: ">=3.0.6 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
|
|
|
@ -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.git
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()]!;
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
28
cw_core/lib/hardware/device_connection_type.dart
Normal file
28
cw_core/lib/hardware/device_connection_type.dart
Normal 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';
|
||||
}
|
||||
}
|
||||
}
|
7
cw_core/lib/hardware/device_not_connected_exception.dart
Normal file
7
cw_core/lib/hardware/device_not_connected_exception.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
class DeviceNotConnectedException implements Exception {
|
||||
final String message;
|
||||
|
||||
DeviceNotConnectedException({
|
||||
this.message = '',
|
||||
});
|
||||
}
|
19
cw_core/lib/hardware/hardware_account_data.dart
Normal file
19
cw_core/lib/hardware/hardware_account_data.dart
Normal 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;
|
||||
}
|
|
@ -16,3 +16,5 @@ 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;
|
||||
|
|
|
@ -94,6 +94,7 @@ class Node extends HiveObject with Keyable {
|
|||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ abstract class WalletCredentials {
|
|||
this.password,
|
||||
this.passphrase,
|
||||
this.derivationInfo,
|
||||
this.hardwareWalletType,
|
||||
}) {
|
||||
if (this.walletInfo != null && derivationInfo != null) {
|
||||
this.walletInfo!.derivationInfo = derivationInfo;
|
||||
|
@ -22,4 +23,5 @@ abstract class WalletCredentials {
|
|||
String? passphrase;
|
||||
WalletInfo? walletInfo;
|
||||
DerivationInfo? derivationInfo;
|
||||
HardwareWalletType? hardwareWalletType;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -20,6 +21,12 @@ enum DerivationType {
|
|||
electrum,
|
||||
}
|
||||
|
||||
@HiveType(typeId: HARDWARE_WALLET_TYPE_TYPE_ID)
|
||||
enum HardwareWalletType {
|
||||
@HiveField(0)
|
||||
ledger,
|
||||
}
|
||||
|
||||
@HiveType(typeId: DerivationInfo.typeId)
|
||||
class DerivationInfo extends HiveObject {
|
||||
DerivationInfo({
|
||||
|
@ -40,7 +47,7 @@ class DerivationInfo extends HiveObject {
|
|||
@HiveField(1, defaultValue: '')
|
||||
String balance;
|
||||
|
||||
@HiveField(2)
|
||||
@HiveField(2, defaultValue: 0)
|
||||
int transactionsCount;
|
||||
|
||||
@HiveField(3)
|
||||
|
@ -71,8 +78,9 @@ class WalletInfo extends HiveObject {
|
|||
this.yatEid,
|
||||
this.yatLastUsedAddressRaw,
|
||||
this.showIntroCakePayCard,
|
||||
this.derivationInfo)
|
||||
: _yatLastUsedAddressController = StreamController<String>.broadcast();
|
||||
this.derivationInfo,
|
||||
this.hardwareWalletType,
|
||||
): _yatLastUsedAddressController = StreamController<String>.broadcast();
|
||||
|
||||
factory WalletInfo.external({
|
||||
required String id,
|
||||
|
@ -88,6 +96,7 @@ class WalletInfo extends HiveObject {
|
|||
String yatEid = '',
|
||||
String yatLastUsedAddressRaw = '',
|
||||
DerivationInfo? derivationInfo,
|
||||
HardwareWalletType? hardwareWalletType,
|
||||
}) {
|
||||
return WalletInfo(
|
||||
id,
|
||||
|
@ -103,6 +112,7 @@ class WalletInfo extends HiveObject {
|
|||
yatLastUsedAddressRaw,
|
||||
showIntroCakePayCard,
|
||||
derivationInfo,
|
||||
hardwareWalletType,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -154,9 +164,11 @@ class WalletInfo extends HiveObject {
|
|||
@HiveField(15)
|
||||
List<String>? usedAddresses;
|
||||
|
||||
@deprecated
|
||||
@HiveField(16)
|
||||
DerivationType? derivationType; // no longer used
|
||||
|
||||
@deprecated
|
||||
@HiveField(17)
|
||||
String? derivationPath; // no longer used
|
||||
|
||||
|
@ -169,6 +181,9 @@ class WalletInfo extends HiveObject {
|
|||
@HiveField(20)
|
||||
DerivationInfo? derivationInfo;
|
||||
|
||||
@HiveField(21)
|
||||
HardwareWalletType? hardwareWalletType;
|
||||
|
||||
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
||||
|
||||
set yatLastUsedAddress(String address) {
|
||||
|
@ -185,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;
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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');
|
||||
|
|
22
cw_core/lib/window_size.dart
Normal file
22
cw_core/lib/window_size.dart
Normal 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}'.");
|
||||
}
|
||||
}
|
|
@ -28,11 +28,13 @@ 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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
209
cw_evm/lib/contract/erc20.dart
Normal file
209
cw_evm/lib/contract/erc20.dart
Normal 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;
|
||||
}
|
|
@ -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(
|
||||
EthereumAddress.fromHex(toAddress),
|
||||
amount,
|
||||
credentials: privateKey,
|
||||
transaction: transaction,
|
||||
);
|
||||
};
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
35
cw_evm/lib/evm_chain_hardware_wallet_service.dart
Normal file
35
cw_evm/lib/evm_chain_hardware_wallet_service.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
_evmChainPrivateKey = await getPrivateKey(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: _hexPrivateKey,
|
||||
password: _password,
|
||||
);
|
||||
walletAddresses.address = _evmChainPrivateKey.address.hexEip55;
|
||||
|
||||
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);
|
||||
|
||||
|
@ -535,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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
103
cw_evm/lib/evm_ledger_credentials.dart
Normal file
103
cw_evm/lib/evm_ledger_credentials.dart
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
@ -109,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(' ')) {
|
||||
|
|
|
@ -173,10 +173,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.2"
|
||||
version: "1.17.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -419,18 +419,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16"
|
||||
version: "0.12.15"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "0.2.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -713,10 +713,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
version: "1.9.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -761,10 +761,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
version: "0.5.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -790,21 +790,13 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.4"
|
||||
watcher:
|
||||
dependency: "direct overridden"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4-beta"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -838,5 +830,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -360,7 +365,7 @@ abstract class SolanaWalletBase
|
|||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
'private_key': privateKey,
|
||||
'private_key': _hexPrivateKey,
|
||||
'balance': balance[currency]!.toJSON(),
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
30
cw_tron/.gitignore
vendored
Normal 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
10
cw_tron/.metadata
Normal 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
3
cw_tron/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
1
cw_tron/LICENSE
Normal file
1
cw_tron/LICENSE
Normal file
|
@ -0,0 +1 @@
|
|||
TODO: Add your license here.
|
39
cw_tron/README.md
Normal file
39
cw_tron/README.md
Normal 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.
|
4
cw_tron/analysis_options.yaml
Normal file
4
cw_tron/analysis_options.yaml
Normal 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
7
cw_tron/lib/cw_tron.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
library cw_tron;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
103
cw_tron/lib/default_tron_tokens.dart
Normal file
103
cw_tron/lib/default_tron_tokens.dart
Normal 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
39
cw_tron/lib/file.dart
Normal 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);
|
||||
}
|
33
cw_tron/lib/pending_tron_transaction.dart
Normal file
33
cw_tron/lib/pending_tron_transaction.dart
Normal 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
436
cw_tron/lib/tron_abi.dart
Normal 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"
|
||||
}
|
||||
];
|
34
cw_tron/lib/tron_balance.dart
Normal file
34
cw_tron/lib/tron_balance.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
563
cw_tron/lib/tron_client.dart
Normal file
563
cw_tron/lib/tron_client.dart
Normal file
|
@ -0,0 +1,563 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_tron/pending_tron_transaction.dart';
|
||||
import 'package:cw_tron/tron_abi.dart';
|
||||
import 'package:cw_tron/tron_balance.dart';
|
||||
import 'package:cw_tron/tron_http_provider.dart';
|
||||
import 'package:cw_tron/tron_token.dart';
|
||||
import 'package:cw_tron/tron_transaction_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart';
|
||||
import '.secrets.g.dart' as secrets;
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
class TronClient {
|
||||
final httpClient = Client();
|
||||
TronProvider? _provider;
|
||||
// This is an internal tracker, so we don't have to "refetch".
|
||||
int _nativeTxEstimatedFee = 0;
|
||||
|
||||
int get chainId => 1000;
|
||||
|
||||
Future<List<TronTransactionModel>> fetchTransactions(String address,
|
||||
{String? contractAddress}) async {
|
||||
try {
|
||||
final response = await httpClient.get(
|
||||
Uri.https(
|
||||
"api.trongrid.io",
|
||||
"/v1/accounts/$address/transactions",
|
||||
{
|
||||
"only_confirmed": "true",
|
||||
"limit": "200",
|
||||
},
|
||||
),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
response.statusCode < 300 &&
|
||||
jsonResponse['status'] != false) {
|
||||
return (jsonResponse['data'] as List).map((e) {
|
||||
return TronTransactionModel.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e, s) {
|
||||
log('Error getting tx: ${e.toString()}\n ${s.toString()}');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<TronTRC20TransactionModel>> fetchTrc20ExcludedTransactions(String address) async {
|
||||
try {
|
||||
final response = await httpClient.get(
|
||||
Uri.https(
|
||||
"api.trongrid.io",
|
||||
"/v1/accounts/$address/transactions/trc20",
|
||||
{
|
||||
"only_confirmed": "true",
|
||||
"limit": "200",
|
||||
},
|
||||
),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
response.statusCode < 300 &&
|
||||
jsonResponse['status'] != false) {
|
||||
return (jsonResponse['data'] as List).map((e) {
|
||||
return TronTRC20TransactionModel.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e, s) {
|
||||
log('Error getting trc20 tx: ${e.toString()}\n ${s.toString()}');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
bool connect(Node node) {
|
||||
try {
|
||||
final formattedUrl = '${node.isSSL ? 'https' : 'http'}://${node.uriRaw}';
|
||||
_provider = TronProvider(TronHTTPProvider(url: formattedUrl));
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<BigInt> getBalance(TronAddress address) async {
|
||||
try {
|
||||
final accountDetails = await _provider!.request(TronRequestGetAccount(address: address));
|
||||
|
||||
return accountDetails?.balance ?? BigInt.zero;
|
||||
} catch (_) {
|
||||
return BigInt.zero;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getFeeLimit(
|
||||
TransactionRaw rawTransaction,
|
||||
TronAddress address,
|
||||
TronAddress receiverAddress, {
|
||||
int energyUsed = 0,
|
||||
bool isEstimatedFeeFlow = false,
|
||||
}) async {
|
||||
try {
|
||||
// Get the tron chain parameters.
|
||||
final chainParams = await _provider!.request(TronRequestGetChainParameters());
|
||||
|
||||
final bandWidthInSun = chainParams.getTransactionFee!;
|
||||
log('BandWidth In Sun: $bandWidthInSun');
|
||||
|
||||
final energyInSun = chainParams.getEnergyFee!;
|
||||
log('Energy In Sun: $energyInSun');
|
||||
|
||||
final fakeTransaction = Transaction(
|
||||
rawData: rawTransaction,
|
||||
signature: [Uint8List(65)],
|
||||
);
|
||||
|
||||
// Calculate the total size of the fake transaction, considering the required network overhead.
|
||||
final transactionSize = fakeTransaction.length + 64;
|
||||
|
||||
// Assign the calculated size to the variable representing the required bandwidth.
|
||||
int neededBandWidth = transactionSize;
|
||||
log('Initial Needed Bandwidth: $neededBandWidth');
|
||||
|
||||
int neededEnergy = energyUsed;
|
||||
log('Initial Needed Energy: $neededEnergy');
|
||||
|
||||
// Fetch account resources to assess the available bandwidth and energy
|
||||
final accountResource =
|
||||
await _provider!.request(TronRequestGetAccountResource(address: address));
|
||||
|
||||
neededEnergy -= accountResource.howManyEnergy.toInt();
|
||||
log('Account resource energy: ${accountResource.howManyEnergy.toInt()}');
|
||||
log('Needed Energy after deducting from account resource energy: $neededEnergy');
|
||||
|
||||
// Deduct the bandwidth from the account's available bandwidth.
|
||||
final BigInt accountBandWidth = accountResource.howManyBandwIth;
|
||||
log('Account resource bandwidth: ${accountResource.howManyBandwIth.toInt()}');
|
||||
|
||||
if (accountBandWidth >= BigInt.from(neededBandWidth) && !isEstimatedFeeFlow) {
|
||||
log('Account has more bandwidth than required');
|
||||
neededBandWidth = 0;
|
||||
}
|
||||
|
||||
if (neededEnergy < 0) {
|
||||
neededEnergy = 0;
|
||||
}
|
||||
|
||||
final energyBurn = neededEnergy * energyInSun.toInt();
|
||||
log('Energy Burn: $energyBurn');
|
||||
|
||||
final bandWidthBurn = neededBandWidth * bandWidthInSun;
|
||||
log('Bandwidth Burn: $bandWidthBurn');
|
||||
|
||||
int totalBurn = energyBurn + bandWidthBurn;
|
||||
log('Total Burn: $totalBurn');
|
||||
|
||||
/// If there is a note (memo), calculate the memo fee.
|
||||
if (rawTransaction.data != null) {
|
||||
totalBurn += chainParams.getMemoFee!;
|
||||
}
|
||||
|
||||
log('Final total burn: $totalBurn');
|
||||
|
||||
return totalBurn;
|
||||
} catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getEstimatedFee(TronAddress ownerAddress) async {
|
||||
const constantAmount = '1000';
|
||||
// Fetch the latest Tron block
|
||||
final block = await _provider!.request(TronRequestGetNowBlock());
|
||||
|
||||
// Create the transfer contract
|
||||
final contract = TransferContract(
|
||||
amount: TronHelper.toSun(constantAmount),
|
||||
ownerAddress: ownerAddress,
|
||||
toAddress: ownerAddress,
|
||||
);
|
||||
|
||||
// Prepare the contract parameter for the transaction.
|
||||
final parameter = Any(typeUrl: contract.typeURL, value: contract);
|
||||
|
||||
// Create a TransactionContract object with the contract type and parameter.
|
||||
final transactionContract =
|
||||
TransactionContract(type: contract.contractType, parameter: parameter);
|
||||
|
||||
// Set the transaction expiration time (maximum 24 hours)
|
||||
final expireTime = DateTime.now().add(const Duration(hours: 24));
|
||||
|
||||
// Create a raw transaction
|
||||
TransactionRaw rawTransaction = TransactionRaw(
|
||||
refBlockBytes: block.blockHeader.rawData.refBlockBytes,
|
||||
refBlockHash: block.blockHeader.rawData.refBlockHash,
|
||||
expiration: BigInt.from(expireTime.millisecondsSinceEpoch),
|
||||
contract: [transactionContract],
|
||||
timestamp: block.blockHeader.rawData.timestamp,
|
||||
);
|
||||
|
||||
final estimatedFee = await getFeeLimit(
|
||||
rawTransaction,
|
||||
ownerAddress,
|
||||
ownerAddress,
|
||||
isEstimatedFeeFlow: true,
|
||||
);
|
||||
|
||||
_nativeTxEstimatedFee = estimatedFee;
|
||||
|
||||
return estimatedFee;
|
||||
}
|
||||
|
||||
Future<int> getTRCEstimatedFee(TronAddress ownerAddress) async {
|
||||
String contractAddress = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
|
||||
String constantAmount =
|
||||
'0'; // We're using 0 as the base amount here as we get an error when balance is zero i.e for new wallets.
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("transfer");
|
||||
|
||||
/// address /// amount
|
||||
final transferparams = [
|
||||
ownerAddress,
|
||||
TronHelper.toSun(constantAmount),
|
||||
];
|
||||
|
||||
final contractAddr = TronAddress(contractAddress);
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: contractAddr,
|
||||
data: function.encodeHex(transferparams),
|
||||
),
|
||||
);
|
||||
|
||||
if (!request.isSuccess) {
|
||||
log("Tron TRC20 error: ${request.error} \n ${request.respose}");
|
||||
}
|
||||
|
||||
final feeLimit = await getFeeLimit(
|
||||
request.transactionRaw!,
|
||||
ownerAddress,
|
||||
ownerAddress,
|
||||
energyUsed: request.energyUsed ?? 0,
|
||||
isEstimatedFeeFlow: true,
|
||||
);
|
||||
return feeLimit;
|
||||
}
|
||||
|
||||
Future<PendingTronTransaction> signTransaction({
|
||||
required TronPrivateKey ownerPrivKey,
|
||||
required String toAddress,
|
||||
required String amount,
|
||||
required CryptoCurrency currency,
|
||||
required BigInt tronBalance,
|
||||
required bool sendAll,
|
||||
}) async {
|
||||
// Get the owner tron address from the key
|
||||
final ownerAddress = ownerPrivKey.publicKey().toAddress();
|
||||
|
||||
// Define the receiving Tron address for the transaction.
|
||||
final receiverAddress = TronAddress(toAddress);
|
||||
|
||||
bool isNativeTransaction = currency == CryptoCurrency.trx;
|
||||
|
||||
String totalAmount;
|
||||
TransactionRaw rawTransaction;
|
||||
if (isNativeTransaction) {
|
||||
if (sendAll) {
|
||||
final accountResource =
|
||||
await _provider!.request(TronRequestGetAccountResource(address: ownerAddress));
|
||||
|
||||
final availableBandWidth = accountResource.howManyBandwIth.toInt();
|
||||
|
||||
// 269 is the current middle ground for bandwidth per transaction
|
||||
if (availableBandWidth >= 269) {
|
||||
totalAmount = amount;
|
||||
} else {
|
||||
final amountInSun = TronHelper.toSun(amount).toInt();
|
||||
|
||||
// 5000 added here is a buffer since we're working with "estimated" value of the fee.
|
||||
final result = amountInSun - (_nativeTxEstimatedFee + 5000);
|
||||
|
||||
totalAmount = TronHelper.fromSun(BigInt.from(result));
|
||||
}
|
||||
} else {
|
||||
totalAmount = amount;
|
||||
}
|
||||
rawTransaction = await _signNativeTransaction(
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
totalAmount,
|
||||
tronBalance,
|
||||
sendAll,
|
||||
);
|
||||
} else {
|
||||
final tokenAddress = (currency as TronToken).contractAddress;
|
||||
totalAmount = amount;
|
||||
rawTransaction = await _signTrcTokenTransaction(
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
totalAmount,
|
||||
tokenAddress,
|
||||
tronBalance,
|
||||
);
|
||||
}
|
||||
|
||||
final signature = ownerPrivKey.sign(rawTransaction.toBuffer());
|
||||
|
||||
sendTx() async => await sendTransaction(
|
||||
rawTransaction: rawTransaction,
|
||||
signature: signature,
|
||||
);
|
||||
|
||||
return PendingTronTransaction(
|
||||
signedTransaction: signature,
|
||||
amount: totalAmount,
|
||||
fee: TronHelper.fromSun(rawTransaction.feeLimit ?? BigInt.zero),
|
||||
sendTransaction: sendTx,
|
||||
);
|
||||
}
|
||||
|
||||
Future<TransactionRaw> _signNativeTransaction(
|
||||
TronAddress ownerAddress,
|
||||
TronAddress receiverAddress,
|
||||
String amount,
|
||||
BigInt tronBalance,
|
||||
bool sendAll,
|
||||
) async {
|
||||
// This is introduce to server as a limit in cases where feeLimit is 0
|
||||
// The transaction signing will fail if the feeLimit is explicitly 0.
|
||||
int defaultFeeLimit = 269000;
|
||||
|
||||
final block = await _provider!.request(TronRequestGetNowBlock());
|
||||
// Create the transfer contract
|
||||
final contract = TransferContract(
|
||||
amount: TronHelper.toSun(amount),
|
||||
ownerAddress: ownerAddress,
|
||||
toAddress: receiverAddress,
|
||||
);
|
||||
|
||||
// Prepare the contract parameter for the transaction.
|
||||
final parameter = Any(typeUrl: contract.typeURL, value: contract);
|
||||
|
||||
// Create a TransactionContract object with the contract type and parameter.
|
||||
final transactionContract =
|
||||
TransactionContract(type: contract.contractType, parameter: parameter);
|
||||
|
||||
// Set the transaction expiration time (maximum 24 hours)
|
||||
final expireTime = DateTime.now().add(const Duration(hours: 24));
|
||||
|
||||
// Create a raw transaction
|
||||
TransactionRaw rawTransaction = TransactionRaw(
|
||||
refBlockBytes: block.blockHeader.rawData.refBlockBytes,
|
||||
refBlockHash: block.blockHeader.rawData.refBlockHash,
|
||||
expiration: BigInt.from(expireTime.millisecondsSinceEpoch),
|
||||
contract: [transactionContract],
|
||||
timestamp: block.blockHeader.rawData.timestamp,
|
||||
);
|
||||
|
||||
final feeLimit = await getFeeLimit(rawTransaction, ownerAddress, receiverAddress);
|
||||
final feeLimitToUse = feeLimit != 0 ? feeLimit : defaultFeeLimit;
|
||||
final tronBalanceInt = tronBalance.toInt();
|
||||
|
||||
if (feeLimit > tronBalanceInt) {
|
||||
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
|
||||
throw Exception(
|
||||
'You don\'t have enough TRX to cover the transaction fee for this transaction. Please top up.\nTransaction fee: $feeInTrx TRX',
|
||||
);
|
||||
}
|
||||
|
||||
rawTransaction = rawTransaction.copyWith(
|
||||
feeLimit: BigInt.from(feeLimitToUse),
|
||||
);
|
||||
|
||||
return rawTransaction;
|
||||
}
|
||||
|
||||
Future<TransactionRaw> _signTrcTokenTransaction(
|
||||
TronAddress ownerAddress,
|
||||
TronAddress receiverAddress,
|
||||
String amount,
|
||||
String contractAddress,
|
||||
BigInt tronBalance,
|
||||
) async {
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("transfer");
|
||||
|
||||
/// address /// amount
|
||||
final transferparams = [
|
||||
receiverAddress,
|
||||
TronHelper.toSun(amount),
|
||||
];
|
||||
|
||||
final contractAddr = TronAddress(contractAddress);
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: contractAddr,
|
||||
data: function.encodeHex(transferparams),
|
||||
),
|
||||
);
|
||||
|
||||
if (!request.isSuccess) {
|
||||
log("Tron TRC20 error: ${request.error} \n ${request.respose}");
|
||||
throw Exception(
|
||||
'An error occured while creating the transfer request. Please try again.',
|
||||
);
|
||||
}
|
||||
|
||||
final feeLimit = await getFeeLimit(
|
||||
request.transactionRaw!,
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
energyUsed: request.energyUsed ?? 0,
|
||||
);
|
||||
|
||||
final tronBalanceInt = tronBalance.toInt();
|
||||
|
||||
if (feeLimit > tronBalanceInt) {
|
||||
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
|
||||
throw Exception(
|
||||
'You don\'t have enough TRX to cover the transaction fee for this transaction. Please top up. Transaction fee: $feeInTrx TRX',
|
||||
);
|
||||
}
|
||||
|
||||
final rawTransaction = request.transactionRaw!.copyWith(
|
||||
feeLimit: BigInt.from(feeLimit),
|
||||
);
|
||||
|
||||
return rawTransaction;
|
||||
}
|
||||
|
||||
Future<String> sendTransaction({
|
||||
required TransactionRaw rawTransaction,
|
||||
required List<int> signature,
|
||||
}) async {
|
||||
try {
|
||||
final transaction = Transaction(rawData: rawTransaction, signature: [signature]);
|
||||
|
||||
final raw = BytesUtils.toHexString(transaction.toBuffer());
|
||||
|
||||
final txBroadcastResult = await _provider!.request(TronRequestBroadcastHex(transaction: raw));
|
||||
|
||||
if (txBroadcastResult.isSuccess) {
|
||||
return txBroadcastResult.txId!;
|
||||
} else {
|
||||
throw Exception(txBroadcastResult.error);
|
||||
}
|
||||
} catch (e) {
|
||||
log('Send block Exception: ${e.toString()}');
|
||||
throw Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TronBalance> fetchTronTokenBalances(String userAddress, String contractAddress) async {
|
||||
try {
|
||||
final ownerAddress = TronAddress(userAddress);
|
||||
|
||||
final tokenAddress = TronAddress(contractAddress);
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("balanceOf");
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract.fromMethod(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: tokenAddress,
|
||||
function: function,
|
||||
params: [ownerAddress],
|
||||
),
|
||||
);
|
||||
|
||||
final outputResult = request.outputResult?.first ?? BigInt.zero;
|
||||
|
||||
return TronBalance(outputResult);
|
||||
} catch (_) {
|
||||
return TronBalance(BigInt.zero);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TronToken?> getTronToken(String contractAddress, String userAddress) async {
|
||||
try {
|
||||
final tokenAddress = TronAddress(contractAddress);
|
||||
|
||||
final ownerAddress = TronAddress(userAddress);
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final name =
|
||||
(await getTokenDetail(contract, "name", ownerAddress, tokenAddress) as String?) ?? '';
|
||||
|
||||
final symbol =
|
||||
(await getTokenDetail(contract, "symbol", ownerAddress, tokenAddress) as String?) ?? '';
|
||||
|
||||
final decimal =
|
||||
(await getTokenDetail(contract, "decimals", ownerAddress, tokenAddress) as BigInt?) ??
|
||||
BigInt.zero;
|
||||
|
||||
return TronToken(
|
||||
name: name,
|
||||
symbol: symbol,
|
||||
contractAddress: contractAddress,
|
||||
decimal: decimal.toInt(),
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> getTokenDetail(
|
||||
ContractABI contract,
|
||||
String functionName,
|
||||
TronAddress ownerAddress,
|
||||
TronAddress tokenAddress,
|
||||
) async {
|
||||
final function = contract.functionFromName(functionName);
|
||||
|
||||
try {
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract.fromMethod(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: tokenAddress,
|
||||
function: function,
|
||||
params: [],
|
||||
),
|
||||
);
|
||||
|
||||
final outputResult = request.outputResult?.first;
|
||||
|
||||
return outputResult;
|
||||
} catch (_) {
|
||||
log('Erorr fetching detail: ${_.toString()}');
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
16
cw_tron/lib/tron_exception.dart
Normal file
16
cw_tron/lib/tron_exception.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class TronMnemonicIsIncorrectException implements Exception {
|
||||
@override
|
||||
String toString() =>
|
||||
'Tron mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
||||
}
|
||||
class TronTransactionCreationException implements Exception {
|
||||
final String exceptionMessage;
|
||||
|
||||
TronTransactionCreationException(CryptoCurrency currency)
|
||||
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||
|
||||
@override
|
||||
String toString() => exceptionMessage;
|
||||
}
|
41
cw_tron/lib/tron_http_provider.dart
Normal file
41
cw_tron/lib/tron_http_provider.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:on_chain/tron/tron.dart';
|
||||
import '.secrets.g.dart' as secrets;
|
||||
|
||||
class TronHTTPProvider implements TronServiceProvider {
|
||||
TronHTTPProvider(
|
||||
{required this.url,
|
||||
http.Client? client,
|
||||
this.defaultRequestTimeout = const Duration(seconds: 30)})
|
||||
: client = client ?? http.Client();
|
||||
@override
|
||||
final String url;
|
||||
final http.Client client;
|
||||
final Duration defaultRequestTimeout;
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> get(TronRequestDetails params, [Duration? timeout]) async {
|
||||
final response = await client.get(Uri.parse(params.url(url)), headers: {
|
||||
'Content-Type': 'application/json',
|
||||
if (url.contains("trongrid")) 'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
}).timeout(timeout ?? defaultRequestTimeout);
|
||||
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> post(TronRequestDetails params, [Duration? timeout]) async {
|
||||
final response = await client
|
||||
.post(Uri.parse(params.url(url)),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
if (url.contains("trongrid")) 'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
body: params.toRequestBody())
|
||||
.timeout(timeout ?? defaultRequestTimeout);
|
||||
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||
return data;
|
||||
}
|
||||
}
|
80
cw_tron/lib/tron_token.dart
Normal file
80
cw_tron/lib/tron_token.dart
Normal file
|
@ -0,0 +1,80 @@
|
|||
// ignore_for_file: annotate_overrides, overridden_fields
|
||||
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'tron_token.g.dart';
|
||||
|
||||
@HiveType(typeId: TronToken.typeId)
|
||||
class TronToken extends CryptoCurrency with HiveObjectMixin {
|
||||
@HiveField(0)
|
||||
final String name;
|
||||
|
||||
@HiveField(1)
|
||||
final String symbol;
|
||||
|
||||
@HiveField(2)
|
||||
final String contractAddress;
|
||||
|
||||
@HiveField(3)
|
||||
final int decimal;
|
||||
|
||||
@HiveField(4, defaultValue: true)
|
||||
bool _enabled;
|
||||
|
||||
@HiveField(5)
|
||||
final String? iconPath;
|
||||
|
||||
@HiveField(6)
|
||||
final String? tag;
|
||||
|
||||
bool get enabled => _enabled;
|
||||
|
||||
set enabled(bool value) => _enabled = value;
|
||||
|
||||
TronToken({
|
||||
required this.name,
|
||||
required this.symbol,
|
||||
required this.contractAddress,
|
||||
required this.decimal,
|
||||
bool enabled = true,
|
||||
this.iconPath,
|
||||
this.tag = 'TRX',
|
||||
}) : _enabled = enabled,
|
||||
super(
|
||||
name: symbol.toLowerCase(),
|
||||
title: symbol.toUpperCase(),
|
||||
fullName: name,
|
||||
tag: tag,
|
||||
iconPath: iconPath,
|
||||
decimals: decimal);
|
||||
|
||||
TronToken.copyWith(TronToken other, String? icon, String? tag)
|
||||
: name = other.name,
|
||||
symbol = other.symbol,
|
||||
contractAddress = other.contractAddress,
|
||||
decimal = other.decimal,
|
||||
_enabled = other.enabled,
|
||||
tag = tag ?? other.tag,
|
||||
iconPath = icon ?? other.iconPath,
|
||||
super(
|
||||
name: other.name,
|
||||
title: other.symbol.toUpperCase(),
|
||||
fullName: other.name,
|
||||
tag: tag ?? other.tag,
|
||||
iconPath: icon ?? other.iconPath,
|
||||
decimals: other.decimal,
|
||||
);
|
||||
|
||||
static const typeId = TRON_TOKEN_TYPE_ID;
|
||||
static const boxName = 'TronTokens';
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
(other is TronToken && other.contractAddress == contractAddress) ||
|
||||
(other is CryptoCurrency && other.title == title);
|
||||
|
||||
@override
|
||||
int get hashCode => contractAddress.hashCode;
|
||||
}
|
12
cw_tron/lib/tron_transaction_credentials.dart
Normal file
12
cw_tron/lib/tron_transaction_credentials.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class TronTransactionCredentials {
|
||||
TronTransactionCredentials(
|
||||
this.outputs, {
|
||||
required this.currency,
|
||||
});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final CryptoCurrency currency;
|
||||
}
|
80
cw_tron/lib/tron_transaction_history.dart
Normal file
80
cw_tron/lib/tron_transaction_history.dart
Normal file
|
@ -0,0 +1,80 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:core';
|
||||
import 'dart:developer';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_evm/file.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
||||
part 'tron_transaction_history.g.dart';
|
||||
|
||||
class TronTransactionHistory = TronTransactionHistoryBase with _$TronTransactionHistory;
|
||||
|
||||
abstract class TronTransactionHistoryBase extends TransactionHistoryBase<TronTransactionInfo>
|
||||
with Store {
|
||||
TronTransactionHistoryBase({required this.walletInfo, required String password})
|
||||
: _password = password {
|
||||
transactions = ObservableMap<String, TronTransactionInfo>();
|
||||
}
|
||||
|
||||
String _password;
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
|
||||
Future<void> init() async => await _load();
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
String transactionsHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
try {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||
final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
|
||||
final data = json.encode({'transactions': transactionMaps});
|
||||
await writeData(path: path, password: _password, data: data);
|
||||
} catch (e, s) {
|
||||
log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}');
|
||||
log(s.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(TronTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, TronTransactionInfo> transactions) =>
|
||||
this.transactions.addAll(transactions);
|
||||
|
||||
Future<Map<String, dynamic>> _read() async {
|
||||
String transactionsHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||
final content = await read(path: path, password: _password);
|
||||
if (content.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
return json.decode(content) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final content = await _read();
|
||||
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
for (var entry in txs.entries) {
|
||||
final val = entry.value;
|
||||
|
||||
if (val is Map<String, dynamic>) {
|
||||
final tx = TronTransactionInfo.fromJson(val);
|
||||
_update(tx);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _update(TronTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
}
|
93
cw_tron/lib/tron_transaction_info.dart
Normal file
93
cw_tron/lib/tron_transaction_info.dart
Normal file
|
@ -0,0 +1,93 @@
|
|||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:on_chain/on_chain.dart' as onchain;
|
||||
import 'package:on_chain/tron/tron.dart';
|
||||
|
||||
class TronTransactionInfo extends TransactionInfo {
|
||||
TronTransactionInfo({
|
||||
required this.id,
|
||||
required this.tronAmount,
|
||||
required this.txFee,
|
||||
required this.direction,
|
||||
required this.blockTime,
|
||||
required this.to,
|
||||
required this.from,
|
||||
required this.isPending,
|
||||
this.tokenSymbol = 'TRX',
|
||||
}) : amount = tronAmount.toInt();
|
||||
|
||||
final String id;
|
||||
final String? to;
|
||||
final String? from;
|
||||
final int amount;
|
||||
final BigInt tronAmount;
|
||||
final String tokenSymbol;
|
||||
final DateTime blockTime;
|
||||
final bool isPending;
|
||||
final int? txFee;
|
||||
final TransactionDirection direction;
|
||||
|
||||
factory TronTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||
return TronTransactionInfo(
|
||||
id: data['id'] as String,
|
||||
tronAmount: BigInt.parse(data['tronAmount']),
|
||||
txFee: data['txFee'],
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int),
|
||||
tokenSymbol: data['tokenSymbol'] as String,
|
||||
to: data['to'],
|
||||
from: data['from'],
|
||||
isPending: data['isPending'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'tronAmount': tronAmount.toString(),
|
||||
'txFee': txFee,
|
||||
'direction': direction.index,
|
||||
'blockTime': blockTime.millisecondsSinceEpoch,
|
||||
'tokenSymbol': tokenSymbol,
|
||||
'to': to,
|
||||
'from': from,
|
||||
'isPending': isPending,
|
||||
};
|
||||
|
||||
@override
|
||||
DateTime get date => blockTime;
|
||||
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
String amountFormatted() {
|
||||
String formattedAmount = _rawAmountAsString(tronAmount);
|
||||
|
||||
return '$formattedAmount $tokenSymbol';
|
||||
}
|
||||
|
||||
@override
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
|
||||
@override
|
||||
String feeFormatted() {
|
||||
final formattedFee = onchain.TronHelper.fromSun(BigInt.from(txFee ?? 0));
|
||||
|
||||
return '$formattedFee TRX';
|
||||
}
|
||||
|
||||
String _rawAmountAsString(BigInt amount) {
|
||||
String formattedAmount = TronHelper.fromSun(amount);
|
||||
|
||||
if (formattedAmount.length >= 8) {
|
||||
formattedAmount = formattedAmount.substring(0, 8);
|
||||
}
|
||||
|
||||
return formattedAmount;
|
||||
}
|
||||
|
||||
String rawTronAmount() => _rawAmountAsString(tronAmount);
|
||||
}
|
205
cw_tron/lib/tron_transaction_model.dart
Normal file
205
cw_tron/lib/tron_transaction_model.dart
Normal file
|
@ -0,0 +1,205 @@
|
|||
import 'package:blockchain_utils/hex/hex.dart';
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
class TronTRC20TransactionModel extends TronTransactionModel {
|
||||
String? transactionId;
|
||||
|
||||
String? tokenSymbol;
|
||||
|
||||
int? timestamp;
|
||||
|
||||
@override
|
||||
String? from;
|
||||
|
||||
@override
|
||||
String? to;
|
||||
|
||||
String? value;
|
||||
|
||||
@override
|
||||
String get hash => transactionId!;
|
||||
|
||||
@override
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp ?? 0);
|
||||
|
||||
@override
|
||||
BigInt? get amount => BigInt.parse(value ?? '0');
|
||||
|
||||
@override
|
||||
int? get fee => 0;
|
||||
|
||||
TronTRC20TransactionModel({
|
||||
this.transactionId,
|
||||
this.tokenSymbol,
|
||||
this.timestamp,
|
||||
this.from,
|
||||
this.to,
|
||||
this.value,
|
||||
});
|
||||
|
||||
TronTRC20TransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
transactionId = json['transaction_id'];
|
||||
tokenSymbol = json['token_info'] != null ? json['token_info']['symbol'] : null;
|
||||
timestamp = json['block_timestamp'];
|
||||
from = json['from'];
|
||||
to = json['to'];
|
||||
value = json['value'];
|
||||
}
|
||||
}
|
||||
|
||||
class TronTransactionModel {
|
||||
List<Ret>? ret;
|
||||
String? txID;
|
||||
int? blockTimestamp;
|
||||
List<Contract>? contracts;
|
||||
|
||||
/// Getters to extract out the needed/useful information directly from the model params
|
||||
/// Without having to go through extra steps in the methods that use this model.
|
||||
bool get isError {
|
||||
if (ret?.first.contractRet == null) return true;
|
||||
|
||||
return ret?.first.contractRet != "SUCCESS";
|
||||
}
|
||||
|
||||
String get hash => txID!;
|
||||
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(blockTimestamp ?? 0);
|
||||
|
||||
String? get from => contracts?.first.parameter?.value?.ownerAddress;
|
||||
|
||||
String? get to => contracts?.first.parameter?.value?.receiverAddress;
|
||||
|
||||
BigInt? get amount => contracts?.first.parameter?.value?.txAmount;
|
||||
|
||||
int? get fee => ret?.first.fee;
|
||||
|
||||
String? get contractAddress => contracts?.first.parameter?.value?.contractAddress;
|
||||
|
||||
TronTransactionModel({
|
||||
this.ret,
|
||||
this.txID,
|
||||
this.blockTimestamp,
|
||||
this.contracts,
|
||||
});
|
||||
|
||||
TronTransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
if (json['ret'] != null) {
|
||||
ret = <Ret>[];
|
||||
json['ret'].forEach((v) {
|
||||
ret!.add(Ret.fromJson(v));
|
||||
});
|
||||
}
|
||||
txID = json['txID'];
|
||||
blockTimestamp = json['block_timestamp'];
|
||||
contracts = json['raw_data'] != null
|
||||
? (json['raw_data']['contract'] as List)
|
||||
.map((e) => Contract.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class Ret {
|
||||
String? contractRet;
|
||||
int? fee;
|
||||
|
||||
Ret({this.contractRet, this.fee});
|
||||
|
||||
Ret.fromJson(Map<String, dynamic> json) {
|
||||
contractRet = json['contractRet'];
|
||||
fee = json['fee'];
|
||||
}
|
||||
}
|
||||
|
||||
class Contract {
|
||||
Parameter? parameter;
|
||||
String? type;
|
||||
|
||||
Contract({this.parameter, this.type});
|
||||
|
||||
Contract.fromJson(Map<String, dynamic> json) {
|
||||
parameter = json['parameter'] != null ? Parameter.fromJson(json['parameter']) : null;
|
||||
type = json['type'];
|
||||
}
|
||||
}
|
||||
|
||||
class Parameter {
|
||||
Value? value;
|
||||
String? typeUrl;
|
||||
|
||||
Parameter({this.value, this.typeUrl});
|
||||
|
||||
Parameter.fromJson(Map<String, dynamic> json) {
|
||||
value = json['value'] != null ? Value.fromJson(json['value']) : null;
|
||||
typeUrl = json['type_url'];
|
||||
}
|
||||
}
|
||||
|
||||
class Value {
|
||||
String? data;
|
||||
String? ownerAddress;
|
||||
String? contractAddress;
|
||||
int? amount;
|
||||
String? toAddress;
|
||||
String? assetName;
|
||||
|
||||
//Getters to extract address for tron transactions
|
||||
/// If the contract address is null, it returns the toAddress
|
||||
/// If it's not null, it decodes the data field and gets the receiver address.
|
||||
String? get receiverAddress {
|
||||
if (contractAddress == null) return toAddress;
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
return _decodeAddressFromEncodedDataField(data!);
|
||||
}
|
||||
|
||||
//Getters to extract amount for tron transactions
|
||||
/// If the contract address is null, it returns the amount
|
||||
/// If it's not null, it decodes the data field and gets the tx amount.
|
||||
BigInt? get txAmount {
|
||||
if (contractAddress == null) return BigInt.from(amount ?? 0);
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
return _decodeAmountInvolvedFromEncodedDataField(data!);
|
||||
}
|
||||
|
||||
Value(
|
||||
{this.data,
|
||||
this.ownerAddress,
|
||||
this.contractAddress,
|
||||
this.amount,
|
||||
this.toAddress,
|
||||
this.assetName});
|
||||
|
||||
Value.fromJson(Map<String, dynamic> json) {
|
||||
data = json['data'];
|
||||
ownerAddress = json['owner_address'];
|
||||
contractAddress = json['contract_address'];
|
||||
amount = json['amount'];
|
||||
toAddress = json['to_address'];
|
||||
assetName = json['asset_name'];
|
||||
}
|
||||
|
||||
/// To get the address from the encoded data field
|
||||
String _decodeAddressFromEncodedDataField(String output) {
|
||||
// To get the receiver address from the encoded params
|
||||
output = output.replaceFirst('0x', '').substring(8);
|
||||
final abiCoder = ABICoder.fromType('address');
|
||||
final decoded = abiCoder.decode(AbiParameter.bytes, hex.decode(output));
|
||||
final tronAddress = TronAddress.fromEthAddress((decoded.result as ETHAddress).toBytes());
|
||||
|
||||
return tronAddress.toString();
|
||||
}
|
||||
|
||||
/// To get the amount from the encoded data field
|
||||
BigInt _decodeAmountInvolvedFromEncodedDataField(String output) {
|
||||
output = output.replaceFirst('0x', '').substring(72);
|
||||
final amountAbiCoder = ABICoder.fromType('uint256');
|
||||
final decodedA = amountAbiCoder.decode(AbiParameter.uint256, hex.decode(output));
|
||||
final amount = decodedA.result as BigInt;
|
||||
|
||||
return amount;
|
||||
}
|
||||
}
|
566
cw_tron/lib/tron_wallet.dart
Normal file
566
cw_tron/lib/tron_wallet.dart
Normal file
|
@ -0,0 +1,566 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_tron/default_tron_tokens.dart';
|
||||
import 'package:cw_tron/file.dart';
|
||||
import 'package:cw_tron/tron_abi.dart';
|
||||
import 'package:cw_tron/tron_balance.dart';
|
||||
import 'package:cw_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_exception.dart';
|
||||
import 'package:cw_tron/tron_token.dart';
|
||||
import 'package:cw_tron/tron_transaction_credentials.dart';
|
||||
import 'package:cw_tron/tron_transaction_history.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:cw_tron/tron_wallet_addresses.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
part 'tron_wallet.g.dart';
|
||||
|
||||
class TronWallet = TronWalletBase with _$TronWallet;
|
||||
|
||||
abstract class TronWalletBase
|
||||
extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo> with Store {
|
||||
TronWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
TronBalance? initialBalance,
|
||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
_hexPrivateKey = privateKey,
|
||||
_client = TronClient(),
|
||||
walletAddresses = TronWalletAddresses(walletInfo),
|
||||
balance = ObservableMap<CryptoCurrency, TronBalance>.of(
|
||||
{CryptoCurrency.trx: initialBalance ?? TronBalance(BigInt.zero)},
|
||||
),
|
||||
super(walletInfo) {
|
||||
this.walletInfo = walletInfo;
|
||||
transactionHistory = TronTransactionHistory(walletInfo: walletInfo, password: password);
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(TronToken.typeId)) {
|
||||
CakeHive.registerAdapter(TronTokenAdapter());
|
||||
}
|
||||
}
|
||||
|
||||
final String? _mnemonic;
|
||||
final String? _hexPrivateKey;
|
||||
final String _password;
|
||||
|
||||
late final Box<TronToken> tronTokensBox;
|
||||
|
||||
late final TronPrivateKey _tronPrivateKey;
|
||||
|
||||
late final TronPublicKey _tronPublicKey;
|
||||
|
||||
TronPublicKey get tronPublicKey => _tronPublicKey;
|
||||
|
||||
TronPrivateKey get tronPrivateKey => _tronPrivateKey;
|
||||
|
||||
late String _tronAddress;
|
||||
|
||||
late final TronClient _client;
|
||||
|
||||
Timer? _transactionsUpdateTimer;
|
||||
|
||||
@override
|
||||
WalletAddresses walletAddresses;
|
||||
|
||||
@observable
|
||||
String? nativeTxEstimatedFee;
|
||||
|
||||
@observable
|
||||
String? trc20EstimatedFee;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
late ObservableMap<CryptoCurrency, TronBalance> balance;
|
||||
|
||||
Future<void> init() async {
|
||||
await initTronTokensBox();
|
||||
|
||||
await walletAddresses.init();
|
||||
await transactionHistory.init();
|
||||
_tronPrivateKey = await getPrivateKey(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: _hexPrivateKey,
|
||||
password: _password,
|
||||
);
|
||||
|
||||
_tronPublicKey = _tronPrivateKey.publicKey();
|
||||
|
||||
_tronAddress = _tronPublicKey.toAddress().toString();
|
||||
|
||||
walletAddresses.address = _tronAddress;
|
||||
|
||||
await save();
|
||||
}
|
||||
|
||||
static Future<TronWallet> open({
|
||||
required String name,
|
||||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
}) async {
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final balance = TronBalance.fromJSON(data['balance'] as String) ?? TronBalance(BigInt.zero);
|
||||
|
||||
return TronWallet(
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
privateKey: privateKey,
|
||||
initialBalance: balance,
|
||||
);
|
||||
}
|
||||
|
||||
void addInitialTokens() {
|
||||
final initialTronTokens = DefaultTronTokens().initialTronTokens;
|
||||
|
||||
for (var token in initialTronTokens) {
|
||||
tronTokensBox.put(token.contractAddress, token);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initTronTokensBox() async {
|
||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${TronToken.boxName}";
|
||||
|
||||
tronTokensBox = await CakeHive.openBox<TronToken>(boxName);
|
||||
}
|
||||
|
||||
String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name';
|
||||
|
||||
Future<TronPrivateKey> getPrivateKey({
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
}) async {
|
||||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (privateKey != null) {
|
||||
return TronPrivateKey(privateKey);
|
||||
}
|
||||
|
||||
final seed = bip39.mnemonicToSeed(mnemonic!);
|
||||
|
||||
// Derive a TRON private key from the seed
|
||||
final bip44 = Bip44.fromSeed(seed, Bip44Coins.tron);
|
||||
|
||||
final childKey = bip44.deriveDefaultPath;
|
||||
|
||||
return TronPrivateKey.fromBytes(childKey.privateKey.raw);
|
||||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) {
|
||||
throw UnimplementedError("changePassword");
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
|
||||
final isConnected = _client.connect(node);
|
||||
|
||||
if (!isConnected) {
|
||||
throw Exception("${walletInfo.type.name.toUpperCase()} Node connection failed");
|
||||
}
|
||||
|
||||
_getEstimatedFees();
|
||||
_setTransactionUpdateTimer();
|
||||
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getEstimatedFees() async {
|
||||
final nativeFee = await _getNativeTxFee();
|
||||
nativeTxEstimatedFee = TronHelper.fromSun(BigInt.from(nativeFee));
|
||||
|
||||
final trc20Fee = await _getTrc20TxFee();
|
||||
trc20EstimatedFee = TronHelper.fromSun(BigInt.from(trc20Fee));
|
||||
|
||||
log('Native Estimated Fee: $nativeTxEstimatedFee');
|
||||
log('TRC20 Estimated Fee: $trc20EstimatedFee');
|
||||
}
|
||||
|
||||
Future<int> _getNativeTxFee() async {
|
||||
try {
|
||||
final fee = await _client.getEstimatedFee(_tronPublicKey.toAddress());
|
||||
return fee;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _getTrc20TxFee() async {
|
||||
try {
|
||||
final trc20fee = await _client.getTRCEstimatedFee(_tronPublicKey.toAddress());
|
||||
return trc20fee;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
await _updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
final tronCredentials = credentials as TronTransactionCredentials;
|
||||
|
||||
final outputs = tronCredentials.outputs;
|
||||
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
|
||||
final CryptoCurrency transactionCurrency =
|
||||
balance.keys.firstWhere((element) => element.title == tronCredentials.currency.title);
|
||||
|
||||
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
|
||||
|
||||
BigInt totalAmount = BigInt.zero;
|
||||
bool shouldSendAll = false;
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
|
||||
final totalAmountFromCredentials =
|
||||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||
|
||||
totalAmount = BigInt.from(totalAmountFromCredentials);
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
} else {
|
||||
final output = outputs.first;
|
||||
|
||||
shouldSendAll = output.sendAll;
|
||||
|
||||
if (shouldSendAll) {
|
||||
totalAmount = walletBalanceForCurrency;
|
||||
} else {
|
||||
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
|
||||
totalAmount = TronHelper.toSun(totalOriginalAmount.toString());
|
||||
}
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount || totalAmount < BigInt.zero) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
final tronBalance = balance[CryptoCurrency.trx]?.balance ?? BigInt.zero;
|
||||
|
||||
final pendingTransaction = await _client.signTransaction(
|
||||
ownerPrivKey: _tronPrivateKey,
|
||||
toAddress: tronCredentials.outputs.first.isParsedAddress
|
||||
? tronCredentials.outputs.first.extractedAddress!
|
||||
: tronCredentials.outputs.first.address,
|
||||
amount: TronHelper.fromSun(totalAmount),
|
||||
currency: transactionCurrency,
|
||||
tronBalance: tronBalance,
|
||||
sendAll: shouldSendAll,
|
||||
);
|
||||
|
||||
return pendingTransaction;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, TronTransactionInfo>> fetchTransactions() async {
|
||||
final address = _tronAddress;
|
||||
|
||||
final transactions = await _client.fetchTransactions(address);
|
||||
|
||||
final Map<String, TronTransactionInfo> result = {};
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final ownerAddress = TronAddress(_tronAddress);
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
if (transactionModel.isError) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String? tokenSymbol;
|
||||
if (transactionModel.contractAddress != null) {
|
||||
final tokenAddress = TronAddress(transactionModel.contractAddress!);
|
||||
|
||||
tokenSymbol = (await _client.getTokenDetail(
|
||||
contract,
|
||||
"symbol",
|
||||
ownerAddress,
|
||||
tokenAddress,
|
||||
) as String?) ??
|
||||
'';
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
tronAmount: transactionModel.amount ?? BigInt.zero,
|
||||
direction: TronAddress(transactionModel.from!, visible: false).toAddress() == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: tokenSymbol ?? "TRX",
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
|
||||
return transactionHistory.transactions;
|
||||
}
|
||||
|
||||
Future<void> fetchTrc20ExcludedTransactions() async {
|
||||
final address = _tronAddress;
|
||||
|
||||
final transactions = await _client.fetchTrc20ExcludedTransactions(address);
|
||||
|
||||
final Map<String, TronTransactionInfo> result = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
if (transactionHistory.transactions.containsKey(transactionModel.hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
tronAmount: transactionModel.amount ?? BigInt.zero,
|
||||
direction: transactionModel.from! == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: transactionModel.tokenSymbol ?? "TRX",
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Object get keys => throw UnimplementedError("keys");
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) {
|
||||
throw UnimplementedError("rescan");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await write(path: path, password: _password, data: toJSON());
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
String? get seed => _mnemonic;
|
||||
|
||||
@override
|
||||
String get privateKey => _tronPrivateKey.toHex();
|
||||
|
||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
'private_key': privateKey,
|
||||
'balance': balance[currency]!.toJSON(),
|
||||
});
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
balance[currency] = await _fetchTronBalance();
|
||||
|
||||
await _fetchTronTokenBalances();
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<TronBalance> _fetchTronBalance() async {
|
||||
final balance = await _client.getBalance(_tronPublicKey.toAddress());
|
||||
return TronBalance(balance);
|
||||
}
|
||||
|
||||
Future<void> _fetchTronTokenBalances() async {
|
||||
for (var token in tronTokensBox.values) {
|
||||
try {
|
||||
if (token.enabled) {
|
||||
balance[token] = await _client.fetchTronTokenBalances(
|
||||
_tronAddress,
|
||||
token.contractAddress,
|
||||
);
|
||||
} else {
|
||||
balance.remove(token);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void>? updateBalance() async => await _updateBalance();
|
||||
|
||||
List<TronToken> get tronTokenCurrencies => tronTokensBox.values.toList();
|
||||
|
||||
Future<void> addTronToken(TronToken token) async {
|
||||
String? iconPath;
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
|
||||
final newToken = TronToken(
|
||||
name: token.name,
|
||||
symbol: token.symbol,
|
||||
contractAddress: token.contractAddress,
|
||||
decimal: token.decimal,
|
||||
enabled: token.enabled,
|
||||
tag: token.tag ?? "TRX",
|
||||
iconPath: iconPath,
|
||||
);
|
||||
|
||||
await tronTokensBox.put(newToken.contractAddress, newToken);
|
||||
|
||||
if (newToken.enabled) {
|
||||
balance[newToken] = await _client.fetchTronTokenBalances(
|
||||
_tronAddress,
|
||||
newToken.contractAddress,
|
||||
);
|
||||
} else {
|
||||
balance.remove(newToken);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteTronToken(TronToken token) async {
|
||||
await token.delete();
|
||||
|
||||
balance.remove(token);
|
||||
await _removeTokenTransactionsInHistory(token);
|
||||
_updateBalance();
|
||||
}
|
||||
|
||||
Future<void> _removeTokenTransactionsInHistory(TronToken token) async {
|
||||
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
Future<TronToken?> getTronToken(String contractAddress) async =>
|
||||
await _client.getTronToken(contractAddress, _tronAddress);
|
||||
|
||||
@override
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
String transactionHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
|
||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||
final currentWalletFile = File(currentWalletPath);
|
||||
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
final currentTransactionsFile = File('$currentDirPath/$transactionHistoryFileNameForWallet');
|
||||
|
||||
// Copies current wallet files into new wallet name's dir and files
|
||||
if (currentWalletFile.existsSync()) {
|
||||
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||
await currentWalletFile.copy(newWalletPath);
|
||||
}
|
||||
if (currentTransactionsFile.existsSync()) {
|
||||
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
await currentTransactionsFile.copy('$newDirPath/$transactionHistoryFileNameForWallet');
|
||||
}
|
||||
|
||||
// Delete old name's dir and files
|
||||
await Directory(currentDirPath).delete(recursive: true);
|
||||
}
|
||||
|
||||
void _setTransactionUpdateTimer() {
|
||||
if (_transactionsUpdateTimer?.isActive ?? false) {
|
||||
_transactionsUpdateTimer!.cancel();
|
||||
}
|
||||
|
||||
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) async {
|
||||
_updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address}) async =>
|
||||
_tronPrivateKey.signPersonalMessage(ascii.encode(message));
|
||||
|
||||
String getTronBase58AddressFromHex(String hexAddress) {
|
||||
return TronAddress(hexAddress).toAddress();
|
||||
}
|
||||
|
||||
void updateScanProviderUsageState(bool isEnabled) {
|
||||
if (isEnabled) {
|
||||
fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
_setTransactionUpdateTimer();
|
||||
} else {
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
}
|
||||
}
|
36
cw_tron/lib/tron_wallet_addresses.dart
Normal file
36
cw_tron/lib/tron_wallet_addresses.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'tron_wallet_addresses.g.dart';
|
||||
|
||||
class TronWalletAddresses = TronWalletAddressesBase with _$TronWalletAddresses;
|
||||
|
||||
abstract class TronWalletAddressesBase extends WalletAddresses with Store {
|
||||
TronWalletAddressesBase(WalletInfo walletInfo)
|
||||
: address = '',
|
||||
super(walletInfo);
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
address = walletInfo.address;
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
29
cw_tron/lib/tron_wallet_creation_credentials.dart
Normal file
29
cw_tron/lib/tron_wallet_creation_credentials.dart
Normal file
|
@ -0,0 +1,29 @@
|
|||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class TronNewWalletCredentials extends WalletCredentials {
|
||||
TronNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
||||
: super(name: name, walletInfo: walletInfo);
|
||||
}
|
||||
|
||||
class TronRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
TronRestoreWalletFromSeedCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class TronRestoreWalletFromPrivateKey extends WalletCredentials {
|
||||
TronRestoreWalletFromPrivateKey(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.privateKey,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String privateKey;
|
||||
}
|
160
cw_tron/lib/tron_wallet_service.dart
Normal file
160
cw_tron/lib/tron_wallet_service.dart
Normal file
|
@ -0,0 +1,160 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
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';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_exception.dart';
|
||||
import 'package:cw_tron/tron_wallet.dart';
|
||||
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class TronWalletService extends WalletService<
|
||||
TronNewWalletCredentials,
|
||||
TronRestoreWalletFromSeedCredentials,
|
||||
TronRestoreWalletFromPrivateKey,
|
||||
TronNewWalletCredentials> {
|
||||
TronWalletService(this.walletInfoSource, {required this.client});
|
||||
|
||||
late TronClient client;
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.tron;
|
||||
|
||||
@override
|
||||
Future<TronWallet> create(
|
||||
TronNewWalletCredentials credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
final mnemonic = bip39.generateMnemonic(strength: strength);
|
||||
|
||||
final wallet = TronWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> openWallet(String name, String password) async {
|
||||
final walletInfo =
|
||||
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
|
||||
try {
|
||||
final wallet = await TronWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
|
||||
final wallet = await TronWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> restoreFromKeys(
|
||||
TronRestoreWalletFromPrivateKey credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
final wallet = TronWallet(
|
||||
password: credentials.password!,
|
||||
privateKey: credentials.privateKey,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> restoreFromSeed(
|
||||
TronRestoreWalletFromSeedCredentials credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw TronMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
final wallet = TronWallet(
|
||||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = walletInfoSource.values
|
||||
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||
final currentWallet = await TronWalletBase.open(
|
||||
password: password, name: currentName, walletInfo: currentWalletInfo);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
|
||||
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||
await walletInfoSource.delete(walletInfo.key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
|
||||
// TODO: implement restoreFromHardwareWallet
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
33
cw_tron/pubspec.yaml
Normal file
33
cw_tron/pubspec.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: cw_tron
|
||||
description: A new Flutter package project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
homepage: https://cakewallet.com
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.6 <4.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
cw_evm:
|
||||
path: ../cw_evm
|
||||
on_chain: ^3.0.1
|
||||
blockchain_utils: ^2.1.1
|
||||
mobx: ^2.3.0+1
|
||||
bip39: ^1.0.6
|
||||
hive: ^2.2.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
build_runner: ^2.3.3
|
||||
mobx_codegen: ^2.1.1
|
||||
hive_generator: ^1.1.3
|
||||
flutter:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
12
cw_tron/test/cw_tron_test.dart
Normal file
12
cw_tron/test/cw_tron_test.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:cw_tron/cw_tron.dart';
|
||||
|
||||
void main() {
|
||||
test('adds one to input values', () {
|
||||
final calculator = Calculator();
|
||||
expect(calculator.addOne(2), 3);
|
||||
expect(calculator.addOne(-7), -6);
|
||||
expect(calculator.addOne(0), 1);
|
||||
});
|
||||
}
|
301
how_to_add_new_wallet_type.md
Normal file
301
how_to_add_new_wallet_type.md
Normal file
|
@ -0,0 +1,301 @@
|
|||
# Guide to adding a new wallet type in Cake Wallet
|
||||
|
||||
## Wallet Integration
|
||||
|
||||
**N:B** Throughout this guide, `walletx` refers to the specific wallet type you want to add. If you're adding `BNB` to CakeWallet, then `walletx` for you here is `bnb`.
|
||||
|
||||
**Core Folder/Files Setup**
|
||||
- Idenitify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
|
||||
- Add a new entry to `WalletType` class in `cw_core/wallet_type.dart`.
|
||||
- Fill out the necessary information int he various functions in the files, concerning the wallet name, the native currency type, symbol etc.
|
||||
- Go to `cw_core/lib/currency_for_wallet_type.dart`, in the `currencyForWalletType` function, add a case for `walletx`, returning the native cryptocurrency for `walletx`.
|
||||
- If the cryptocurrency for walletx is not available among the default cryptocurrencies, add a new cryptocurrency entry in `cw_core/lib/cryptocurrency.dart`.
|
||||
- Add the newly created cryptocurrency name to the list named `all` in this file.
|
||||
- Create a package for the wallet specific integration, name it. `cw_walletx`
|
||||
- Add the following initial common files and replicate to fit the wallet
|
||||
- walletx_transaction_history.dart
|
||||
- walletx_transaction_info.dart
|
||||
- walletx_mnemonics_exception.dart
|
||||
- walletx_tokens.dart
|
||||
- walletx_wallet_service.dart:
|
||||
- walletx_wallet.dart
|
||||
- etc.
|
||||
|
||||
- Add the code to run the code generation needed for the files in the `cw_walletx` package to the `model_generator.sh` script
|
||||
|
||||
cd cw_walletx && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
|
||||
- Add the relevant dev_dependencies for generating the files also
|
||||
- build_runner
|
||||
- mobx_codegen
|
||||
- hive_generator
|
||||
|
||||
**WalletX Proxy Setup**
|
||||
|
||||
A `Proxy` class is used to communicate with the specific wallet package we have. Instead of directly making use of methods and parameters in `cw_walletx` within the `lib` directory, we use a proxy to access these data. All important functions, calls and interactions we want to make with our `cw_walletx` package would be defined and done through the proxy class. The class would define the import
|
||||
|
||||
- Create a proxy folder titled `walletx` to handle the wallet operations. It would contain 2 files: `cw_walletx.dart` and `walletx.dart`.
|
||||
- `cw_walletx.dart` file would hold an implementation class containing major operations to be done in the lib directory. It serves as the link between the cw_walletx package and the rest of the codebase(lib directory files and folders).
|
||||
- `walletx.dart` would contain the abstract class highlighting the methods that would bring the functionalities and features in the `cw_walletx` package to the rest of the `lib` directory.
|
||||
- Add `walletx.dart` to `.gitignore` as we won’t be pushing it: `lib/tron/tron.dart`.
|
||||
- `walletx.dart` would always be generated based on the configure files we would be setting up in the next step.
|
||||
|
||||
**Configuration Files Setup**
|
||||
- Before we populate the field, head over to `tool/configure.dart` to setup the necessary configurations for the `walletx` proxy.
|
||||
- Define the output path, it’ll follow the format `lib/walletx/walletx.dart`.
|
||||
- Add the variable to check if `walletx` is to be activated
|
||||
- Define the function that would generate the abstract class for the proxy.(We will flesh out this function in the next steps).
|
||||
- Add the defined variable in step 2 to the `generatePubspec` and `generateWalletTypes`.
|
||||
- Next, modify the following functions:
|
||||
- generatePubspec function
|
||||
1. Add the parameters to the method params (i.e required bool hasWalletX)
|
||||
2. Define a variable to hold the entry for the pubspec.yaml file
|
||||
|
||||
const cwWalletX = """
|
||||
cw_tron:
|
||||
path: ./cw_walletx
|
||||
""";
|
||||
|
||||
3. Add an if block that takes in the passed parameter and adds the defined variable(inn the previous step) to the list of outputs
|
||||
|
||||
if (hasWalletX) {
|
||||
output += '\n$cwWalletX’;
|
||||
}
|
||||
|
||||
- generateWalletTypes function
|
||||
1. Add the parameters to the method params (i.e required bool hasWalletX)
|
||||
2. Add an if block to add the wallet type to the list of outputs this function generates
|
||||
|
||||
if (hasWalletX) {
|
||||
outputContent += '\tWalletType.walletx,\n’;
|
||||
}
|
||||
|
||||
- Head over to `scripts/android/pubspec.sh` script, and modify the `CONFIG_ARGS` under `$CAKEWALLET`. Add `"—walletx”` to the end of the passed in params.
|
||||
- Repeat this in `scripts/ios/app_config.sh` and `scripts/macos/app_config.sh`
|
||||
- Open a terminal and cd into `scripts/android/`. Run the following commands to run setup configuration scripts(proxy class, add walletx to list of wallet types and add cw_walletx to pubspec).
|
||||
|
||||
source ./app_env.sh cakewallet
|
||||
|
||||
./app_config.sh
|
||||
|
||||
cd cw_walletx && flutter pub get && flutter packages pub run build_runner build
|
||||
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
Moving forward, our interactions with the cw_walletx package would be through the proxy class and its methods.
|
||||
|
||||
**Pre-Wallet Creation for WalletX**
|
||||
- Go to `di.dart` and locate the block to `registerWalletService`. In this, add the case to handle creating the WalletXWalletService
|
||||
|
||||
case WalletType.walletx:
|
||||
return walletx!.createWalletXWalletService(_walletInfoSource);
|
||||
|
||||
- Go to `lib/view_model/wallet_new_vm.dart`, in the getCredentials method, which gets the new wallet credentials for walletX add the case for the new wallet
|
||||
|
||||
case WalletType.walletx:
|
||||
return walletx!.createWalletXNewWalletCredentials(name: name);
|
||||
|
||||
**Node Setup**
|
||||
- Before we can be able to successfully create a new wallet of wallet type walletx we need to setup the node that the wallet would use:
|
||||
- In the assets directory, create a new file and name it `walletx_node_list.yml`. This yml file would contain the details for nodes to be used for walletX. An example structure for each node entry
|
||||
|
||||
uri: "api.nodeurl.io"
|
||||
is_default: true
|
||||
useSSL: true
|
||||
|
||||
You can add as many node entries as desired.
|
||||
|
||||
- Add the path to the yml file created to the `pubspec_base.yaml` file (`“assets/walletx_node_list.yml”`)
|
||||
- Go to `lib/entities/node_list.dart`, add a function to load the node entries we made in `walletx_node_list.yml` for walletx.
|
||||
- Name your function `loadDefaultWalletXNodes()`. The function would handle loading the yml file as a string and parsing it into a Node Object to be used within the app. Here’s a template for the function.
|
||||
|
||||
Future<List<Node>> loadDefaultWalletXNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/tron_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.tron;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
- Inside the `resetToDefault` function, call the function you created and add the result to the nodes result variable.
|
||||
- Go to `lib/entities/default_settings_migration.dart` file, we’ll be adding the following to the file.
|
||||
- At the top of the file, after the imports, define the default nodeUrl for wallet-name.
|
||||
- Next, write a function to fetch the node for this default uri you added above.
|
||||
|
||||
Node? getWalletXDefaultNode({required Box<Node> nodes}) {
|
||||
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == walletXDefaultNodeUri) ??
|
||||
nodes.values.firstWhereOrNull((node) => node.type == WalletType.walletx);
|
||||
}
|
||||
|
||||
- Next, write a function that will add the list of nodes we declared in the `walletx_node_list.yml` file to the Nodes Box, to be used in the app. Here’s the format for this function
|
||||
|
||||
Future<void> addWalletXNodeList({required Box<Node> nodes}) async {
|
||||
final nodeList = await loadDefaultWalletXNodes();
|
||||
for (var node in nodeList) {
|
||||
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||
await nodes.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- Next, we’ll write the function to change walletX current node to default. An handy function we would make use of later on. Add a new preference key in `lib/entities/preference_key.dart` with the format `PreferencesKey.currentWalletXNodeIdKey`, we’ll use it to identify the current node id.
|
||||
|
||||
Future<void> changeWalletXCurrentNodeToDefault(
|
||||
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
|
||||
final node = getWalletXDefaultNode(nodes: nodes);
|
||||
final nodeId = node?.key as int? ?? 0;
|
||||
await sharedPreferences.setInt(PreferencesKey.currentWalletXNodeIdKey, nodeId);
|
||||
}
|
||||
|
||||
- Next, in the `defaultSettingsMigration` function at the top of the file, add a new case to handle both `addWalletXNodeList` and `changeWalletXCurrentNodeToDefault`
|
||||
|
||||
case “next-number-increment”:
|
||||
await addWalletXNodeList(nodes: nodes);
|
||||
await changeWalletXCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
|
||||
- Next, increase the `initialMigrationVersion` number in `main.dart` to be the new case entry number you entered in the step above for the `defaultSettingsMigration` function.
|
||||
- Next, go to `lib/view_model/node_list/node_list_view_model.dart`
|
||||
- In the `reset` function, add a case for walletX:
|
||||
|
||||
case WalletType.tron:
|
||||
node = getTronDefaultNode(nodes: _nodeSource)!;
|
||||
break;
|
||||
|
||||
- Lastly, go to `cw_core/lib/node.dart`,
|
||||
- In the uri getter, add a case to handle the uri setup for walletX. If the node uses http, return `Uri.http`, if not, return `Uri.https`
|
||||
|
||||
case WalletType.walletX:
|
||||
return Uri.https(uriRaw, ‘’);
|
||||
|
||||
- Also, in the `requestNode` method, add a case for `WalletType.walletx`
|
||||
- Next is the modifications to `lib/store/settings_store.dart` file:
|
||||
- In the `load` function, create a variable to fetch the currentWalletxNodeId using the `PreferencesKey.currentWalletXNodeIdKey` we created earlier.
|
||||
- Create another variable `walletXNode` which gets the walletx node using the nodeId variable assigned in the step above.
|
||||
- Add a check to see if walletXNode is not null, if it’s not null, assign the created tronNode variable to the nodeMap with a type of walletX
|
||||
|
||||
final walletXNode = nodeSource.get(walletXNodeId);
|
||||
final walletXNodeId = sharedPreferences.getInt(PreferencesKey.currentWalletXNodeIdKey);
|
||||
if (walletXNode != null) {
|
||||
nodes[WalletType.walletx] = walletXNode;
|
||||
}
|
||||
|
||||
- Repeat the steps above in the `reload` function
|
||||
- Next, add a case for walletX in the `_saveCurrentNode` function.
|
||||
|
||||
- Run the following commands after to generate modified files in cw_core and lib
|
||||
|
||||
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
- Lastly, before we run the app to test what we’ve done so far,
|
||||
- Go to `lib/src/dashboard/widgets/menu_widget.dart` and add an icon for walletX to be used within the app.
|
||||
- Go to `lib/src/screens/wallet_list/wallet_list_page.dart` and add an icon for walletx, add a case for walletx also in the `imageFor` method.
|
||||
- Do the same thing in `lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart`
|
||||
|
||||
- One last thing before we can create a wallet for walletx, go to `lib/view_model/wallet_new_vm.dart`
|
||||
- Modify the `seedPhraseWordsLength` getter by adding a case for `WalletType.walletx`
|
||||
|
||||
Now you can run the codebase and successfully create a wallet for type walletX successfully.
|
||||
|
||||
**Display Seeds/Keys**
|
||||
- Next, we want to set up our wallet to display the seeds and/or keys in the security page of the app.
|
||||
- Go to `lib/view_model/wallet_keys_view_model.dart`
|
||||
- Modify the `populateItems` function by adding a case for `WalletType.walletx` in it.
|
||||
- Now your seeds and/or keys should display when you go to Security and Backup -> Show seed/keys page within the app.
|
||||
|
||||
**Restore Wallet**
|
||||
- Go to `lib/core/seed_validator.dart`
|
||||
- In the `getWordList` method, add a case to handle `WalletType.walletx` which would return the word list to be used to validate the passed in seeds.
|
||||
- Next, go to `lib/restore_view_model.dart`
|
||||
- Modify the `hasRestoreFromPrivateKey` to reflect if walletx supports restore from Key
|
||||
- Add a switch case to handle the various restore modes that walletX supports
|
||||
- Modify the `getCredential` method to handle the restore flows for `WalletType.walletx`
|
||||
- Run the build_runner code generation command
|
||||
|
||||
**Receive**
|
||||
- Go to `lib/view_model/wallet_address_list/wallet_address_list_view_model.dart`
|
||||
- Create an implementation of `PaymentUri` for type WalletX.
|
||||
- In the uri getter, add a case for `WalletType.walletx` returning the implementation class for `PaymentUri`
|
||||
- Modify the `addressList` getter to return the address/addresses for walletx
|
||||
|
||||
**Balance Screen**
|
||||
- Go to `lib/view_model/dashboard/balance_view_model.dart`
|
||||
- Modify the function to adjust the way the balance is being display on the app: `isHomeScreenSettingsEnabled`
|
||||
- Add a case to the `availableBalanceLabel` getter to modify the text being displayed (Available or confirmed)
|
||||
- Same for `additionalBalanceLabel`
|
||||
- Next, go to `lib/reactions/fiat_rate_update.dart`
|
||||
- Modify the `startFiatRateUpdate` function and add a check for `WalletType.walletx` to return all the token currencies
|
||||
- Next, go to `lib/reactions/on_current_wallet_change.dart`
|
||||
- Modify the `startCurrentWalletChangeReaction` function and add a check for `WalletType.walletx` to return all the token currencies
|
||||
- Lastly, go to `lib/view_model/dashboard/transaction_list_item.dart`
|
||||
- In the `formattedFiatAmount` getter, add a case to handle the fiat amount conversion for `WalletType.walletx`
|
||||
|
||||
**Send ViewModel**
|
||||
- Go to `lib/view_model/send/send_view_model.dart`
|
||||
- Modify the `_credentials` function to reflect `WalletType.walletx`
|
||||
- Modify `hasMultipleTokens` to reflect wallets
|
||||
|
||||
**Exchange**
|
||||
- Go to lib/view_model/exchange/exchange_view_model.dart
|
||||
- First, add a case for WalletType.walletx in the `initialPairBasedOnWallet` method.
|
||||
- If WalletX supports tokens, go to `lib/view_model/exchange/exchange_trade_view_model.dart`
|
||||
- Modify the `_checkIfCanSend` method by creating a `_isWalletXToken` that checks if the from currency is WalletX and if its tag is for walletx
|
||||
- Add `_isWalletXToken` to the return logic for the method.
|
||||
|
||||
**Secrets**
|
||||
- Create a json file named `wallet-secrets-config.json` and put an empty curly bracket “{}” in it
|
||||
- Add a new entry to `tool/utils/secret_key.dart` for walletx
|
||||
- Modify the `tool/generate_secrets_config.dart` file for walletx, don’t forget to call `secrets.clear()` before adding a new set of generation logic
|
||||
- Modify the `tool/import_secrets_config.dart` file for walletx
|
||||
- In the `.gitignore` file, add `**/tool/.walletx-secrets-config.json` and `**/cw_walletx/lib/.secrets.g.dart`
|
||||
|
||||
**HomeSettings: WalletX Tokens Display and Management**
|
||||
- Go to `lib/view_model/dashboard/home_settings_view_model.dart`
|
||||
- Modify the `_updateTokensList` method to add all walletx tokens if the wallet type is `WalletType.walletx`.
|
||||
- Modify the `getTokenAddressBasedOnWallet` method to include a case to fetch the address for a WalletX token.
|
||||
- Modify the `getToken` method to return a specific walletx token
|
||||
- Modify the `addToken`, `deleteToken` and `changeTokenAvailability` methods to handle cases where the walletType is walletx
|
||||
|
||||
**Buy and Sell WalletX**
|
||||
- Go to `lib/entities/provider_types.dart`
|
||||
- Add a case for `WalletType.walletx` in the `getAvailableBuyProviderTypes` method. Return a list of providers that support buying WalletX.
|
||||
- Add a case for `WalletType.walletx` in the `getAvailableSellProviderTypes` method. Return a list of providers that support selling WalletX.
|
||||
|
||||
**Restore QR setup**
|
||||
- Go to `lib/view_model/restore/wallet_restore_from_qr_code.dart`
|
||||
- Add the scheme for walletx in `_walletTypeMap`
|
||||
- Also modify `_determineWalletRestoreMode` to include a case for walletx
|
||||
- Go to `lib/view_model/restore/restore_from_qr_vm.dart`
|
||||
- Modify `getCredentialsFromRestoredWallet` method
|
||||
- Go to `lib/core/address_validator.dart`
|
||||
- Modify the `getAddressFromStringPattern` method to add a case for `WalletType.walletx`
|
||||
- and if it has tokens (ex. erc20, trc20, spl tokens) then add them to the switch case as well
|
||||
- Add the scheme for walletx for both Android in `AndroidManifestBase.xml` and iOS in `InfoBase.plist`
|
||||
|
||||
**Transaction History**
|
||||
- Go to `lib/view_model/transaction_details_view_model.dart`
|
||||
- Add a case for `WalletType.walletx` to add the items to be displayed on the detailed view
|
||||
- Modify the `_explorerUrl` method to add the blockchain explorer link for WalletX in order to view the more info on a transaction
|
||||
- Modify the `_explorerDescription` to display the name of the explorer
|
||||
|
||||
|
||||
|
||||
|
||||
# Points to note when adding the new wallet type
|
||||
|
||||
1. if it has tokens (ex. ERC20, SPL, etc...) make sure to add that to this function `_checkIfCanSend` in `exchange_trade_view_model.dart`
|
||||
1. if it has tokens (ex. ERC20, SPL, etc...) make sure to add a check for the tags as well in the
|
||||
2. Check On/Off ramp providers that support the new wallet currency and add them accordingly in `provider_types.dart`
|
||||
3. Add support for wallet uri scheme to restore from QR for both Android in `AndroidManifestBase.xml` and iOS in `InfoBase.plist`
|
||||
4. Make sure no imports are using the wallet internal package files directly, instead use the proxy layers that is created in the main lib `lib/cw_ethereum.dart` for example. (i.e try building Monero.com if you get compilation errors, then you probably missed something)
|
||||
|
||||
|
||||
Copyright (C) 2018-2023 Cake Labs LLC
|
|
@ -21,6 +21,6 @@
|
|||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>11.0</string>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -109,6 +109,8 @@ PODS:
|
|||
- flutter_inappwebview_ios/Core (0.0.1):
|
||||
- Flutter
|
||||
- OrderedSet (~> 5.0)
|
||||
- flutter_local_authentication (1.2.0):
|
||||
- Flutter
|
||||
- flutter_mailer (0.0.1):
|
||||
- Flutter
|
||||
- flutter_secure_storage (6.0.0):
|
||||
|
@ -118,8 +120,6 @@ PODS:
|
|||
- Toast
|
||||
- in_app_review (0.2.0):
|
||||
- Flutter
|
||||
- local_auth_ios (0.0.1):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner (5.0.11)
|
||||
- OrderedSet (5.0.0)
|
||||
- package_info (0.0.1):
|
||||
|
@ -131,7 +131,12 @@ PODS:
|
|||
- FlutterMacOS
|
||||
- permission_handler_apple (9.1.1):
|
||||
- Flutter
|
||||
- Protobuf (3.25.3)
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- reactive_ble_mobile (0.0.1):
|
||||
- Flutter
|
||||
- Protobuf (~> 3.5)
|
||||
- SwiftProtobuf (~> 1.0)
|
||||
- SDWebImage (5.18.11):
|
||||
- SDWebImage/Core (= 5.18.11)
|
||||
- SDWebImage/Core (5.18.11)
|
||||
|
@ -170,15 +175,16 @@ DEPENDENCIES:
|
|||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||
- flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`)
|
||||
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- reactive_ble_mobile (from `.symlinks/plugins/reactive_ble_mobile/ios`)
|
||||
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
|
@ -196,6 +202,7 @@ SPEC REPOS:
|
|||
- DKPhotoGallery
|
||||
- MTBBarcodeScanner
|
||||
- OrderedSet
|
||||
- Protobuf
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
- SwiftProtobuf
|
||||
|
@ -226,6 +233,8 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter
|
||||
flutter_inappwebview_ios:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||
flutter_local_authentication:
|
||||
:path: ".symlinks/plugins/flutter_local_authentication/ios"
|
||||
flutter_mailer:
|
||||
:path: ".symlinks/plugins/flutter_mailer/ios"
|
||||
flutter_secure_storage:
|
||||
|
@ -234,8 +243,6 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
in_app_review:
|
||||
:path: ".symlinks/plugins/in_app_review/ios"
|
||||
local_auth_ios:
|
||||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
||||
package_info:
|
||||
:path: ".symlinks/plugins/package_info/ios"
|
||||
package_info_plus:
|
||||
|
@ -244,6 +251,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
reactive_ble_mobile:
|
||||
:path: ".symlinks/plugins/reactive_ble_mobile/ios"
|
||||
sensitive_clipboard:
|
||||
:path: ".symlinks/plugins/sensitive_clipboard/ios"
|
||||
share_plus:
|
||||
|
@ -273,30 +282,32 @@ SPEC CHECKSUMS:
|
|||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
|
||||
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
|
||||
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
fluttertoast: 48c57db1b71b0ce9e6bba9f31c940ff4b001293c
|
||||
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
||||
local_auth_ios: 1ba1475238daa33a6ffa2a29242558437be435ac
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||
Protobuf: 8e9074797a13c484a79959fdb819ef4ae6da7dbe
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c
|
||||
SDWebImage: a3ba0b8faac7228c3c8eadd1a55c9c9fe5e16457
|
||||
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
|
||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
|
||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
9F46EE5E2BC11178009318F5 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 9F46EE5D2BC11178009318F5 /* PrivacyInfo.xcprivacy */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -40,6 +41,7 @@
|
|||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9F46EE5D2BC11178009318F5 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
AD0937B0140D5A4C24E73BEA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -123,6 +125,7 @@
|
|||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
9F46EE5D2BC11178009318F5 /* PrivacyInfo.xcprivacy */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
|
@ -196,6 +199,7 @@
|
|||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
9F46EE5E2BC11178009318F5 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue