mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-537-Integrate-ThorChain-swaps
Conflicts: cw_bitcoin/lib/electrum_wallet.dart lib/bitcoin/cw_bitcoin.dart
This commit is contained in:
commit
5cfea38f22
224 changed files with 7765 additions and 1886 deletions
57
.github/workflows/pr_test_build.yml
vendored
57
.github/workflows/pr_test_build.yml
vendored
|
@ -6,9 +6,9 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
branch:
|
branch:
|
||||||
description: 'Branch name to build'
|
description: "Branch name to build"
|
||||||
required: true
|
required: true
|
||||||
default: 'main'
|
default: "main"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
PR_test_build:
|
PR_test_build:
|
||||||
|
@ -104,22 +104,14 @@ jobs:
|
||||||
- name: Build generated code
|
- name: Build generated code
|
||||||
run: |
|
run: |
|
||||||
cd /opt/android/cake_wallet
|
cd /opt/android/cake_wallet
|
||||||
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
./model_generator.sh
|
||||||
cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
|
||||||
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
|
||||||
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
|
||||||
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
|
||||||
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
|
||||||
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
|
||||||
cd cw_ethereum && flutter pub get && cd ..
|
|
||||||
cd cw_polygon && flutter pub get && cd ..
|
|
||||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
|
||||||
|
|
||||||
- name: Add secrets
|
- name: Add secrets
|
||||||
run: |
|
run: |
|
||||||
cd /opt/android/cake_wallet
|
cd /opt/android/cake_wallet
|
||||||
touch lib/.secrets.g.dart
|
touch lib/.secrets.g.dart
|
||||||
touch cw_evm/lib/.secrets.g.dart
|
touch cw_evm/lib/.secrets.g.dart
|
||||||
|
touch cw_solana/lib/.secrets.g.dart
|
||||||
echo "const salt = '${{ secrets.SALT }}';" > 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 keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
|
||||||
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
|
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
@ -154,45 +146,50 @@ jobs:
|
||||||
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
|
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
|
||||||
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
|
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 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
|
||||||
|
|
||||||
- name: Rename app
|
- name: Rename app
|
||||||
run: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
|
run: |
|
||||||
|
hash=`sha512sum <<<"${{ env.BRANCH_NAME }}"`
|
||||||
|
substring=${hash:0:15}
|
||||||
|
echo substring
|
||||||
|
echo -e "id=com.cakewallet.test_$(substring)\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cd /opt/android/cake_wallet
|
cd /opt/android/cake_wallet
|
||||||
flutter build apk --release
|
flutter build apk --release --split-per-abi
|
||||||
|
|
||||||
# - name: Push to App Center
|
# - name: Push to App Center
|
||||||
# run: |
|
# run: |
|
||||||
# echo 'Installing App Center CLI tools'
|
# echo 'Installing App Center CLI tools'
|
||||||
# npm install -g appcenter-cli
|
# npm install -g appcenter-cli
|
||||||
# echo "Publishing test to App Center"
|
# echo "Publishing test to App Center"
|
||||||
# appcenter distribute release \
|
# appcenter distribute release \
|
||||||
# --group "Testers" \
|
# --group "Testers" \
|
||||||
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
|
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
|
||||||
# --release-notes ${{ env.BRANCH_NAME }} \
|
# --release-notes ${{ env.BRANCH_NAME }} \
|
||||||
# --app Cake-Labs/Cake-Wallet \
|
# --app Cake-Labs/Cake-Wallet \
|
||||||
# --token ${{ secrets.APP_CENTER_TOKEN }} \
|
# --token ${{ secrets.APP_CENTER_TOKEN }} \
|
||||||
# --quiet
|
# --quiet
|
||||||
|
|
||||||
- name: Rename apk file
|
- name: Rename apk file
|
||||||
run: |
|
run: |
|
||||||
cd /opt/android/cake_wallet/build/app/outputs/apk/release
|
cd /opt/android/cake_wallet/build/app/outputs/flutter-apk
|
||||||
mkdir test-apk
|
mkdir test-apk
|
||||||
cp app-release.apk test-apk/${{env.BRANCH_NAME}}.apk
|
cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: kittaakos/upload-artifact-as-is@v0
|
uses: kittaakos/upload-artifact-as-is@v0
|
||||||
with:
|
with:
|
||||||
path: /opt/android/cake_wallet/build/app/outputs/apk/release/test-apk/
|
path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/
|
||||||
|
|
||||||
- name: Send Test APK
|
- name: Send Test APK
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: adrey/slack-file-upload-action@1.0.5
|
uses: adrey/slack-file-upload-action@1.0.5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.SLACK_APP_TOKEN }}
|
token: ${{ secrets.SLACK_APP_TOKEN }}
|
||||||
path: /opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk
|
path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/${{env.BRANCH_NAME}}.apk
|
||||||
channel: ${{ secrets.SLACK_APK_CHANNEL }}
|
channel: ${{ secrets.SLACK_APK_CHANNEL }}
|
||||||
title: "${{ env.BRANCH_NAME }}.apk"
|
title: "${{ env.BRANCH_NAME }}.apk"
|
||||||
filename: ${{ env.BRANCH_NAME }}.apk
|
filename: ${{ env.BRANCH_NAME }}.apk
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -86,14 +86,17 @@ cw_monero/cw_monero/android/.cxx/
|
||||||
**/*.g.dart
|
**/*.g.dart
|
||||||
|
|
||||||
android/key.properties
|
android/key.properties
|
||||||
|
android/app/key.jks
|
||||||
|
|
||||||
**/tool/.secrets-prod.json
|
**/tool/.secrets-prod.json
|
||||||
**/tool/.secrets-test.json
|
**/tool/.secrets-test.json
|
||||||
**/tool/.secrets-config.json
|
**/tool/.secrets-config.json
|
||||||
**/tool/.evm-secrets-config.json
|
**/tool/.evm-secrets-config.json
|
||||||
**/tool/.ethereum-secrets-config.json
|
**/tool/.ethereum-secrets-config.json
|
||||||
|
**/tool/.solana-secrets-config.json
|
||||||
**/lib/.secrets.g.dart
|
**/lib/.secrets.g.dart
|
||||||
**/cw_evm/lib/.secrets.g.dart
|
**/cw_evm/lib/.secrets.g.dart
|
||||||
|
**/cw_solana/lib/.secrets.g.dart
|
||||||
|
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
|
@ -128,6 +131,7 @@ lib/ethereum/ethereum.dart
|
||||||
lib/bitcoin_cash/bitcoin_cash.dart
|
lib/bitcoin_cash/bitcoin_cash.dart
|
||||||
lib/nano/nano.dart
|
lib/nano/nano.dart
|
||||||
lib/polygon/polygon.dart
|
lib/polygon/polygon.dart
|
||||||
|
lib/solana/solana.dart
|
||||||
|
|
||||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
||||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
||||||
|
|
|
@ -66,6 +66,7 @@
|
||||||
<data android:scheme="polygon" />
|
<data android:scheme="polygon" />
|
||||||
<data android:scheme="polygon-wallet" />
|
<data android:scheme="polygon-wallet" />
|
||||||
<data android:scheme="polygon_wallet" />
|
<data android:scheme="polygon_wallet" />
|
||||||
|
<data android:scheme="solana-wallet" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|
BIN
assets/images/avdo_icon.png
Normal file
BIN
assets/images/avdo_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
BIN
assets/images/bonk_icon.png
Normal file
BIN
assets/images/bonk_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
BIN
assets/images/digibyte.png
Normal file
BIN
assets/images/digibyte.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/images/gmt_icon.png
Normal file
BIN
assets/images/gmt_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
assets/images/hnt_icon.png
Normal file
BIN
assets/images/hnt_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
assets/images/ray_icon.png
Normal file
BIN
assets/images/ray_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
4
assets/solana_node_list.yml
Normal file
4
assets/solana_node_list.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-
|
||||||
|
uri: rpc.ankr.com
|
||||||
|
is_default: true
|
||||||
|
useSSL: true
|
|
@ -1,3 +1,2 @@
|
||||||
Improve wallet recovery and error tolerance
|
New themes
|
||||||
Enhance Background sync for Monero wallets
|
Bug fixes and enhancements
|
||||||
Bug fixes
|
|
|
@ -1,5 +1,6 @@
|
||||||
Bitcoin transactions fixes and enhancements
|
Add Solana wallet
|
||||||
EVM wallets enhancements (Ethereum and Polygon)
|
Support ALL Bitcoin address types (Legacy, Segwit (both variants), Taproot)
|
||||||
Improve wallet recovery and error tolerance
|
Enhance Sending/Receiving flow for Bitcoin
|
||||||
Enhance Background sync for Monero wallets
|
Improve fee calculations in Bitcoin
|
||||||
Bug fixes
|
New themes
|
||||||
|
Bug fixes and enhancements
|
|
@ -30,6 +30,7 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build
|
||||||
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_solana && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
cd cw_ethereum && flutter pub get && cd ..
|
cd cw_ethereum && flutter pub get && cd ..
|
||||||
cd cw_polygon && flutter pub get && cd ..
|
cd cw_polygon && flutter pub get && cd ..
|
||||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
import 'dart:typed_data';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
|
||||||
|
|
||||||
String addressFromOutput(Uint8List script, bitcoin.NetworkType networkType) {
|
String addressFromOutputScript(Script script, BasedUtxoNetwork network) {
|
||||||
try {
|
try {
|
||||||
return bitcoin.P2PKH(
|
switch (script.getAddressType()) {
|
||||||
data: PaymentData(output: script),
|
case P2pkhAddressType.p2pkh:
|
||||||
network: networkType)
|
return P2pkhAddress.fromScriptPubkey(script: script).toAddress(network);
|
||||||
.data
|
case P2shAddressType.p2pkInP2sh:
|
||||||
.address!;
|
return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
|
||||||
|
case SegwitAddresType.p2wpkh:
|
||||||
|
return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network);
|
||||||
|
case P2shAddressType.p2pkhInP2sh:
|
||||||
|
return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
|
||||||
|
case SegwitAddresType.p2wsh:
|
||||||
|
return P2wshAddress.fromScriptPubkey(script: script).toAddress(network);
|
||||||
|
case SegwitAddresType.p2tr:
|
||||||
|
return P2trAddress.fromScriptPubkey(script: script).toAddress(network);
|
||||||
|
default:
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
try {
|
|
||||||
return bitcoin.P2WPKH(
|
|
||||||
data: PaymentData(output: script),
|
|
||||||
network: networkType)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
} catch(_) {}
|
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,9 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin;
|
||||||
import 'package:bs58check/bs58check.dart' as bs58check;
|
|
||||||
import 'package:bitcoin_flutter/src/utils/constants/op.dart';
|
|
||||||
import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
|
|
||||||
import 'package:bitcoin_flutter/src/address.dart';
|
|
||||||
|
|
||||||
Uint8List p2shAddressToOutputScript(String address) {
|
List<int> addressToOutputScript(String address, bitcoin.BasedUtxoNetwork network) {
|
||||||
final decodeBase58 = bs58check.decode(address);
|
|
||||||
final hash = decodeBase58.sublist(1);
|
|
||||||
return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List addressToOutputScript(
|
|
||||||
String address, bitcoin.NetworkType networkType) {
|
|
||||||
try {
|
try {
|
||||||
// FIXME: improve validation for p2sh addresses
|
return bitcoin.addressToOutputScript(address: address, network: network);
|
||||||
// 3 for bitcoin
|
|
||||||
// m for litecoin
|
|
||||||
if (address.startsWith('3') || address.toLowerCase().startsWith('m')) {
|
|
||||||
return p2shAddressToOutputScript(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Address.addressToOutputScript(address, networkType);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
print(err);
|
print(err);
|
||||||
return Uint8List(0);
|
return Uint8List(0);
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||||
|
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:cw_bitcoin/script_hash.dart' as sh;
|
||||||
|
|
||||||
class BitcoinAddressRecord {
|
class BitcoinAddressRecord {
|
||||||
BitcoinAddressRecord(
|
BitcoinAddressRecord(
|
||||||
this.address, {
|
this.address, {
|
||||||
|
@ -10,23 +13,41 @@ class BitcoinAddressRecord {
|
||||||
int balance = 0,
|
int balance = 0,
|
||||||
String name = '',
|
String name = '',
|
||||||
bool isUsed = false,
|
bool isUsed = false,
|
||||||
|
required this.type,
|
||||||
|
String? scriptHash,
|
||||||
|
required this.network,
|
||||||
}) : _txCount = txCount,
|
}) : _txCount = txCount,
|
||||||
_balance = balance,
|
_balance = balance,
|
||||||
_name = name,
|
_name = name,
|
||||||
_isUsed = isUsed;
|
_isUsed = isUsed,
|
||||||
|
scriptHash =
|
||||||
|
scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null);
|
||||||
|
|
||||||
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork? network) {
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
return BitcoinAddressRecord(decoded['address'] as String,
|
return BitcoinAddressRecord(
|
||||||
index: decoded['index'] as int,
|
decoded['address'] as String,
|
||||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
index: decoded['index'] as int,
|
||||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||||
txCount: decoded['txCount'] as int? ?? 0,
|
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||||
name: decoded['name'] as String? ?? '',
|
txCount: decoded['txCount'] as int? ?? 0,
|
||||||
balance: decoded['balance'] as int? ?? 0);
|
name: decoded['name'] as String? ?? '',
|
||||||
|
balance: decoded['balance'] as int? ?? 0,
|
||||||
|
type: decoded['type'] != null && decoded['type'] != ''
|
||||||
|
? BitcoinAddressType.values
|
||||||
|
.firstWhere((type) => type.toString() == decoded['type'] as String)
|
||||||
|
: SegwitAddresType.p2wpkh,
|
||||||
|
scriptHash: decoded['scriptHash'] as String?,
|
||||||
|
network: (decoded['network'] as String?) == null
|
||||||
|
? network
|
||||||
|
: BasedUtxoNetwork.fromName(decoded['network'] as String),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
|
||||||
|
|
||||||
final String address;
|
final String address;
|
||||||
bool isHidden;
|
bool isHidden;
|
||||||
final int index;
|
final int index;
|
||||||
|
@ -34,6 +55,8 @@ class BitcoinAddressRecord {
|
||||||
int _balance;
|
int _balance;
|
||||||
String _name;
|
String _name;
|
||||||
bool _isUsed;
|
bool _isUsed;
|
||||||
|
String? scriptHash;
|
||||||
|
BasedUtxoNetwork? network;
|
||||||
|
|
||||||
int get txCount => _txCount;
|
int get txCount => _txCount;
|
||||||
|
|
||||||
|
@ -50,21 +73,28 @@ class BitcoinAddressRecord {
|
||||||
void setAsUsed() => _isUsed = true;
|
void setAsUsed() => _isUsed = true;
|
||||||
void setNewName(String label) => _name = label;
|
void setNewName(String label) => _name = label;
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => address.hashCode;
|
int get hashCode => address.hashCode;
|
||||||
|
|
||||||
String get cashAddr => bitbox.Address.toCashAddress(address);
|
String get cashAddr => bitbox.Address.toCashAddress(address);
|
||||||
|
|
||||||
|
BitcoinAddressType type;
|
||||||
|
|
||||||
|
String updateScriptHash(BasedUtxoNetwork network) {
|
||||||
|
scriptHash = sh.scriptHash(address, network: network);
|
||||||
|
return scriptHash!;
|
||||||
|
}
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'address': address,
|
'address': address,
|
||||||
'index': index,
|
'index': index,
|
||||||
'isHidden': isHidden,
|
'isHidden': isHidden,
|
||||||
|
'isUsed': isUsed,
|
||||||
'txCount': txCount,
|
'txCount': txCount,
|
||||||
'name': name,
|
'name': name,
|
||||||
'isUsed': isUsed,
|
|
||||||
'balance': balance,
|
'balance': balance,
|
||||||
|
'type': type.toString(),
|
||||||
|
'scriptHash': scriptHash,
|
||||||
|
'network': network?.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
42
cw_bitcoin/lib/bitcoin_receive_page_option.dart
Normal file
42
cw_bitcoin/lib/bitcoin_receive_page_option.dart
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:cw_core/receive_page_option.dart';
|
||||||
|
|
||||||
|
class BitcoinReceivePageOption implements ReceivePageOption {
|
||||||
|
static const p2wpkh = BitcoinReceivePageOption._('Segwit (P2WPKH) (Default)');
|
||||||
|
static const p2sh = BitcoinReceivePageOption._('Segwit-Compatible (P2SH)');
|
||||||
|
static const p2tr = BitcoinReceivePageOption._('Taproot (P2TR)');
|
||||||
|
static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
|
||||||
|
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
|
||||||
|
|
||||||
|
const BitcoinReceivePageOption._(this.value);
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const all = [
|
||||||
|
BitcoinReceivePageOption.p2wpkh,
|
||||||
|
BitcoinReceivePageOption.p2tr,
|
||||||
|
BitcoinReceivePageOption.p2wsh,
|
||||||
|
BitcoinReceivePageOption.p2sh,
|
||||||
|
BitcoinReceivePageOption.p2pkh
|
||||||
|
];
|
||||||
|
|
||||||
|
factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
|
||||||
|
switch (type) {
|
||||||
|
case SegwitAddresType.p2tr:
|
||||||
|
return BitcoinReceivePageOption.p2tr;
|
||||||
|
case SegwitAddresType.p2wsh:
|
||||||
|
return BitcoinReceivePageOption.p2wsh;
|
||||||
|
case P2pkhAddressType.p2pkh:
|
||||||
|
return BitcoinReceivePageOption.p2pkh;
|
||||||
|
case P2shAddressType.p2wpkhInP2sh:
|
||||||
|
return BitcoinReceivePageOption.p2sh;
|
||||||
|
case SegwitAddresType.p2wpkh:
|
||||||
|
default:
|
||||||
|
return BitcoinReceivePageOption.p2wpkh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,9 @@ class BitcoinUnspent extends Unspent {
|
||||||
: bitcoinAddressRecord = addressRecord,
|
: bitcoinAddressRecord = addressRecord,
|
||||||
super(addressRecord.address, hash, value, vout, null);
|
super(addressRecord.address, hash, value, vout, null);
|
||||||
|
|
||||||
factory BitcoinUnspent.fromJSON(
|
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) =>
|
||||||
BitcoinAddressRecord address, Map<String, dynamic> json) =>
|
BitcoinUnspent(
|
||||||
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
|
address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int);
|
||||||
json['tx_pos'] as int);
|
|
||||||
|
|
||||||
final BitcoinAddressRecord bitcoinAddressRecord;
|
final BitcoinAddressRecord bitcoinAddressRecord;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
@ -17,36 +18,42 @@ part 'bitcoin_wallet.g.dart';
|
||||||
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
||||||
|
|
||||||
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
BitcoinWalletBase(
|
BitcoinWalletBase({
|
||||||
{required String mnemonic,
|
required String mnemonic,
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required Uint8List seedBytes,
|
required Uint8List seedBytes,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
String? addressPageType,
|
||||||
ElectrumBalance? initialBalance,
|
BasedUtxoNetwork? networkParam,
|
||||||
int initialRegularAddressIndex = 0,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
int initialChangeAddressIndex = 0})
|
ElectrumBalance? initialBalance,
|
||||||
: super(
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
|
}) : super(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
networkType: bitcoin.bitcoin,
|
networkType: networkParam == null
|
||||||
|
? bitcoin.bitcoin
|
||||||
|
: networkParam == BitcoinNetwork.mainnet
|
||||||
|
? bitcoin.bitcoin
|
||||||
|
: bitcoin.testnet,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
currency: CryptoCurrency.btc) {
|
currency: CryptoCurrency.btc) {
|
||||||
walletAddresses = BitcoinWalletAddresses(
|
walletAddresses = BitcoinWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
electrumClient: electrumClient,
|
electrumClient: electrumClient,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
mainHd: hd,
|
mainHd: hd,
|
||||||
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
|
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
|
||||||
.derivePath("m/0'/1"),
|
network: networkParam ?? network,
|
||||||
networkType: networkType);
|
);
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||||
});
|
});
|
||||||
|
@ -57,21 +64,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
String? addressPageType,
|
||||||
|
BasedUtxoNetwork? network,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
int initialRegularAddressIndex = 0,
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
int initialChangeAddressIndex = 0
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
}) async {
|
}) async {
|
||||||
return BitcoinWallet(
|
return BitcoinWallet(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex);
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
addressPageType: addressPageType,
|
||||||
|
networkParam: network,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<BitcoinWallet> open({
|
static Future<BitcoinWallet> open({
|
||||||
|
@ -80,16 +92,21 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
}) async {
|
}) async {
|
||||||
final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
|
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password,
|
||||||
|
walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : null);
|
||||||
|
|
||||||
return BitcoinWallet(
|
return BitcoinWallet(
|
||||||
mnemonic: snp.mnemonic,
|
mnemonic: snp.mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses,
|
initialAddresses: snp.addresses,
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp.balance,
|
||||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
|
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex);
|
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||||
|
addressPageType: snp.addressPageType,
|
||||||
|
networkParam: snp.network,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
||||||
import 'package:cw_bitcoin/electrum.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -11,24 +10,31 @@ part 'bitcoin_wallet_addresses.g.dart';
|
||||||
class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
|
class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
|
||||||
|
|
||||||
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||||
BitcoinWalletAddressesBase(WalletInfo walletInfo,
|
BitcoinWalletAddressesBase(
|
||||||
{required bitcoin.HDWallet mainHd,
|
WalletInfo walletInfo, {
|
||||||
required bitcoin.HDWallet sideHd,
|
required super.mainHd,
|
||||||
required bitcoin.NetworkType networkType,
|
required super.sideHd,
|
||||||
required ElectrumClient electrumClient,
|
required super.network,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
required super.electrumClient,
|
||||||
int initialRegularAddressIndex = 0,
|
super.initialAddresses,
|
||||||
int initialChangeAddressIndex = 0})
|
super.initialRegularAddressIndex,
|
||||||
: super(walletInfo,
|
super.initialChangeAddressIndex,
|
||||||
initialAddresses: initialAddresses,
|
}) : super(walletInfo);
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
|
||||||
mainHd: mainHd,
|
|
||||||
sideHd: sideHd,
|
|
||||||
electrumClient: electrumClient,
|
|
||||||
networkType: networkType);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
|
String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
|
||||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
if (addressType == P2pkhAddressType.p2pkh)
|
||||||
|
return generateP2PKHAddress(hd: hd, index: index, network: network);
|
||||||
|
|
||||||
|
if (addressType == SegwitAddresType.p2tr)
|
||||||
|
return generateP2TRAddress(hd: hd, index: index, network: network);
|
||||||
|
|
||||||
|
if (addressType == SegwitAddresType.p2wsh)
|
||||||
|
return generateP2WSHAddress(hd: hd, index: index, network: network);
|
||||||
|
|
||||||
|
if (addressType == P2shAddressType.p2wpkhInP2sh)
|
||||||
|
return generateP2SHAddress(hd: hd, index: index, network: network);
|
||||||
|
|
||||||
|
return generateP2WPKHAddress(hd: hd, index: index, network: network);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
@ -23,12 +24,17 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
||||||
WalletType getType() => WalletType.bitcoin;
|
WalletType getType() => WalletType.bitcoin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
|
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
||||||
|
credentials.walletInfo?.network = network.value;
|
||||||
|
|
||||||
final wallet = await BitcoinWalletBase.create(
|
final wallet = await BitcoinWalletBase.create(
|
||||||
mnemonic: await generateMnemonic(),
|
mnemonic: await generateMnemonic(),
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
network: network,
|
||||||
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -92,20 +98,27 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
|
||||||
|
{bool? isTestnet}) async =>
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
||||||
|
{bool? isTestnet}) async {
|
||||||
if (!validateMnemonic(credentials.mnemonic)) {
|
if (!validateMnemonic(credentials.mnemonic)) {
|
||||||
throw BitcoinMnemonicIsIncorrectException();
|
throw BitcoinMnemonicIsIncorrectException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
||||||
|
credentials.walletInfo?.network = network.value;
|
||||||
|
|
||||||
final wallet = await BitcoinWalletBase.create(
|
final wallet = await BitcoinWalletBase.create(
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
mnemonic: credentials.mnemonic,
|
mnemonic: credentials.mnemonic,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
network: network,
|
||||||
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
|
|
|
@ -2,12 +2,12 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cw_bitcoin/script_hash.dart';
|
import 'package:cw_bitcoin/script_hash.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
String jsonrpcparams(List<Object> params) {
|
String jsonrpcparams(List<Object> params) {
|
||||||
final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
|
final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
|
||||||
|
@ -22,10 +22,7 @@ String jsonrpc(
|
||||||
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
|
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
|
||||||
|
|
||||||
class SocketTask {
|
class SocketTask {
|
||||||
SocketTask({
|
SocketTask({required this.isSubscription, this.completer, this.subject});
|
||||||
required this.isSubscription,
|
|
||||||
this.completer,
|
|
||||||
this.subject});
|
|
||||||
|
|
||||||
final Completer<dynamic>? completer;
|
final Completer<dynamic>? completer;
|
||||||
final BehaviorSubject<dynamic>? subject;
|
final BehaviorSubject<dynamic>? subject;
|
||||||
|
@ -51,8 +48,7 @@ class ElectrumClient {
|
||||||
Timer? _aliveTimer;
|
Timer? _aliveTimer;
|
||||||
String unterminatedString;
|
String unterminatedString;
|
||||||
|
|
||||||
Future<void> connectToUri(Uri uri) async =>
|
Future<void> connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port);
|
||||||
await connect(host: uri.host, port: uri.port);
|
|
||||||
|
|
||||||
Future<void> connect({required String host, required int port}) async {
|
Future<void> connect({required String host, required int port}) async {
|
||||||
try {
|
try {
|
||||||
|
@ -104,21 +100,20 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isJSONStringCorrect(unterminatedString)) {
|
if (isJSONStringCorrect(unterminatedString)) {
|
||||||
final response =
|
final response = json.decode(unterminatedString) as Map<String, dynamic>;
|
||||||
json.decode(unterminatedString) as Map<String, dynamic>;
|
|
||||||
_handleResponse(response);
|
_handleResponse(response);
|
||||||
unterminatedString = '';
|
unterminatedString = '';
|
||||||
}
|
}
|
||||||
} on TypeError catch (e) {
|
} on TypeError catch (e) {
|
||||||
if (!e.toString().contains('Map<String, Object>') && !e.toString().contains('Map<String, dynamic>')) {
|
if (!e.toString().contains('Map<String, Object>') &&
|
||||||
|
!e.toString().contains('Map<String, dynamic>')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
unterminatedString += message;
|
unterminatedString += message;
|
||||||
|
|
||||||
if (isJSONStringCorrect(unterminatedString)) {
|
if (isJSONStringCorrect(unterminatedString)) {
|
||||||
final response =
|
final response = json.decode(unterminatedString) as Map<String, dynamic>;
|
||||||
json.decode(unterminatedString) as Map<String, dynamic>;
|
|
||||||
_handleResponse(response);
|
_handleResponse(response);
|
||||||
// unterminatedString = null;
|
// unterminatedString = null;
|
||||||
unterminatedString = '';
|
unterminatedString = '';
|
||||||
|
@ -142,8 +137,7 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> version() =>
|
Future<List<String>> version() => call(method: 'server.version').then((dynamic result) {
|
||||||
call(method: 'server.version').then((dynamic result) {
|
|
||||||
if (result is List) {
|
if (result is List) {
|
||||||
return result.map((dynamic val) => val.toString()).toList();
|
return result.map((dynamic val) => val.toString()).toList();
|
||||||
}
|
}
|
||||||
|
@ -178,11 +172,10 @@ class ElectrumClient {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
|
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
|
||||||
String address, NetworkType networkType) =>
|
String address, BasedUtxoNetwork network) =>
|
||||||
call(
|
call(
|
||||||
method: 'blockchain.scripthash.listunspent',
|
method: 'blockchain.scripthash.listunspent',
|
||||||
params: [scriptHash(address, networkType: networkType)])
|
params: [scriptHash(address, network: network)]).then((dynamic result) {
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is List) {
|
if (result is List) {
|
||||||
return result.map((dynamic val) {
|
return result.map((dynamic val) {
|
||||||
if (val is Map<String, dynamic>) {
|
if (val is Map<String, dynamic>) {
|
||||||
|
@ -229,8 +222,7 @@ class ElectrumClient {
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getTransactionRaw(
|
Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async =>
|
||||||
{required String hash}) async =>
|
|
||||||
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
|
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
|
||||||
.then((dynamic result) {
|
.then((dynamic result) {
|
||||||
if (result is Map<String, dynamic>) {
|
if (result is Map<String, dynamic>) {
|
||||||
|
@ -240,8 +232,7 @@ class ElectrumClient {
|
||||||
return <String, dynamic>{};
|
return <String, dynamic>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<String> getTransactionHex(
|
Future<String> getTransactionHex({required String hash}) async =>
|
||||||
{required String hash}) async =>
|
|
||||||
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
|
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
|
||||||
.then((dynamic result) {
|
.then((dynamic result) {
|
||||||
if (result is String) {
|
if (result is String) {
|
||||||
|
@ -252,29 +243,40 @@ class ElectrumClient {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<String> broadcastTransaction(
|
Future<String> broadcastTransaction(
|
||||||
{required String transactionRaw}) async =>
|
{required String transactionRaw, BasedUtxoNetwork? network}) async {
|
||||||
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
|
if (network == BitcoinNetwork.testnet) {
|
||||||
.then((dynamic result) {
|
return http
|
||||||
if (result is String) {
|
.post(Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx'),
|
||||||
return result;
|
headers: <String, String>{'Content-Type': 'application/json; charset=utf-8'},
|
||||||
|
body: transactionRaw)
|
||||||
|
.then((http.Response response) {
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return response.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
throw Exception('Failed to broadcast transaction: ${response.body}');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getMerkle(
|
return call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
|
||||||
{required String hash, required int height}) async =>
|
.then((dynamic result) {
|
||||||
await call(
|
if (result is String) {
|
||||||
method: 'blockchain.transaction.get_merkle',
|
return result;
|
||||||
params: [hash, height]) as Map<String, dynamic>;
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getHeader({required int height}) async =>
|
return '';
|
||||||
await call(method: 'blockchain.block.get_header', params: [height])
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getMerkle({required String hash, required int height}) async =>
|
||||||
|
await call(method: 'blockchain.transaction.get_merkle', params: [hash, height])
|
||||||
as Map<String, dynamic>;
|
as Map<String, dynamic>;
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getHeader({required int height}) async =>
|
||||||
|
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
|
||||||
|
|
||||||
Future<double> estimatefee({required int p}) =>
|
Future<double> estimatefee({required int p}) =>
|
||||||
call(method: 'blockchain.estimatefee', params: [p])
|
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is double) {
|
if (result is double) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -314,20 +316,17 @@ class ElectrumClient {
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<List<int>> feeRates() async {
|
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
|
||||||
|
if (network == BitcoinNetwork.testnet) {
|
||||||
|
return [1, 1, 1];
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
final topDoubleString = await estimatefee(p: 1);
|
final topDoubleString = await estimatefee(p: 1);
|
||||||
final middleDoubleString = await estimatefee(p: 5);
|
final middleDoubleString = await estimatefee(p: 5);
|
||||||
final bottomDoubleString = await estimatefee(p: 100);
|
final bottomDoubleString = await estimatefee(p: 100);
|
||||||
final top =
|
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
|
||||||
(stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)
|
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
|
||||||
.round();
|
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
|
||||||
final middle =
|
|
||||||
(stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
|
|
||||||
.round();
|
|
||||||
final bottom =
|
|
||||||
(stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
|
|
||||||
.round();
|
|
||||||
|
|
||||||
return [bottom, middle, top];
|
return [bottom, middle, top];
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
@ -335,6 +334,21 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe
|
||||||
|
// example response:
|
||||||
|
// {
|
||||||
|
// "height": 520481,
|
||||||
|
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
||||||
|
// }
|
||||||
|
Future<int?> getCurrentBlockChainTip() =>
|
||||||
|
call(method: 'blockchain.headers.subscribe').then((result) {
|
||||||
|
if (result is Map<String, dynamic>) {
|
||||||
|
return result["height"] as int;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
|
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
|
||||||
_id += 1;
|
_id += 1;
|
||||||
return subscribe<Object>(
|
return subscribe<Object>(
|
||||||
|
@ -344,16 +358,14 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
BehaviorSubject<T>? subscribe<T>(
|
BehaviorSubject<T>? subscribe<T>(
|
||||||
{required String id,
|
{required String id, required String method, List<Object> params = const []}) {
|
||||||
required String method,
|
|
||||||
List<Object> params = const []}) {
|
|
||||||
try {
|
try {
|
||||||
final subscription = BehaviorSubject<T>();
|
final subscription = BehaviorSubject<T>();
|
||||||
_regisrySubscription(id, subscription);
|
_regisrySubscription(id, subscription);
|
||||||
socket!.write(jsonrpc(method: method, id: _id, params: params));
|
socket!.write(jsonrpc(method: method, id: _id, params: params));
|
||||||
|
|
||||||
return subscription;
|
return subscription;
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -370,9 +382,7 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> callWithTimeout(
|
Future<dynamic> callWithTimeout(
|
||||||
{required String method,
|
{required String method, List<Object> params = const [], int timeout = 4000}) async {
|
||||||
List<Object> params = const [],
|
|
||||||
int timeout = 4000}) async {
|
|
||||||
try {
|
try {
|
||||||
final completer = Completer<dynamic>();
|
final completer = Completer<dynamic>();
|
||||||
_id += 1;
|
_id += 1;
|
||||||
|
@ -386,7 +396,7 @@ class ElectrumClient {
|
||||||
});
|
});
|
||||||
|
|
||||||
return completer.future;
|
return completer.future;
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,8 +407,8 @@ class ElectrumClient {
|
||||||
onConnectionStatusChange = null;
|
onConnectionStatusChange = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _registryTask(int id, Completer<dynamic> completer) => _tasks[id.toString()] =
|
void _registryTask(int id, Completer<dynamic> completer) =>
|
||||||
SocketTask(completer: completer, isSubscription: false);
|
_tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false);
|
||||||
|
|
||||||
void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) =>
|
void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) =>
|
||||||
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
|
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
|
||||||
|
@ -419,8 +429,7 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _methodHandler(
|
void _methodHandler({required String method, required Map<String, dynamic> request}) {
|
||||||
{required String method, required Map<String, dynamic> request}) {
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'blockchain.scripthash.subscribe':
|
case 'blockchain.scripthash.subscribe':
|
||||||
final params = request['params'] as List<dynamic>;
|
final params = request['params'] as List<dynamic>;
|
||||||
|
@ -451,8 +460,8 @@ class ElectrumClient {
|
||||||
_methodHandler(method: method, request: response);
|
_methodHandler(method: method, request: response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id != null){
|
if (id != null) {
|
||||||
_finish(id, result);
|
_finish(id, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||||
import 'package:cw_bitcoin/address_from_output.dart';
|
import 'package:cw_bitcoin/address_from_output.dart';
|
||||||
|
@ -10,13 +11,12 @@ import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
class ElectrumTransactionBundle {
|
class ElectrumTransactionBundle {
|
||||||
ElectrumTransactionBundle(this.originalTransaction,
|
ElectrumTransactionBundle(this.originalTransaction,
|
||||||
{required this.ins,
|
{required this.ins, required this.confirmations, this.time, required this.height});
|
||||||
required this.confirmations,
|
final BtcTransaction originalTransaction;
|
||||||
this.time});
|
final List<BtcTransaction> ins;
|
||||||
final bitcoin.Transaction originalTransaction;
|
|
||||||
final List<bitcoin.Transaction> ins;
|
|
||||||
final int? time;
|
final int? time;
|
||||||
final int confirmations;
|
final int confirmations;
|
||||||
|
final int height;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElectrumTransactionInfo extends TransactionInfo {
|
class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
|
@ -39,8 +39,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
this.confirmations = confirmations;
|
this.confirmations = confirmations;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ElectrumTransactionInfo.fromElectrumVerbose(
|
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
|
||||||
Map<String, Object> obj, WalletType type,
|
|
||||||
{required List<BitcoinAddressRecord> addresses, required int height}) {
|
{required List<BitcoinAddressRecord> addresses, required int height}) {
|
||||||
final addressesSet = addresses.map((addr) => addr.address).toSet();
|
final addressesSet = addresses.map((addr) => addr.address).toSet();
|
||||||
final id = obj['txid'] as String;
|
final id = obj['txid'] as String;
|
||||||
|
@ -58,10 +57,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
for (dynamic vin in vins) {
|
for (dynamic vin in vins) {
|
||||||
final vout = vin['vout'] as int;
|
final vout = vin['vout'] as int;
|
||||||
final out = vin['tx']['vout'][vout] as Map;
|
final out = vin['tx']['vout'][vout] as Map;
|
||||||
final outAddresses =
|
final outAddresses = (out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
|
||||||
(out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
|
inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
|
||||||
inputsAmount +=
|
|
||||||
stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
|
|
||||||
|
|
||||||
if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) {
|
if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) {
|
||||||
direction = TransactionDirection.outgoing;
|
direction = TransactionDirection.outgoing;
|
||||||
|
@ -69,11 +66,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (dynamic out in vout) {
|
for (dynamic out in vout) {
|
||||||
final outAddresses =
|
final outAddresses = out['scriptPubKey']['addresses'] as List<Object>? ?? [];
|
||||||
out['scriptPubKey']['addresses'] as List<Object>? ?? [];
|
|
||||||
final ntrs = outAddresses.toSet().intersection(addressesSet);
|
final ntrs = outAddresses.toSet().intersection(addressesSet);
|
||||||
final value = stringDoubleToBitcoinAmount(
|
final value = stringDoubleToBitcoinAmount((out['value'] as double? ?? 0.0).toString());
|
||||||
(out['value'] as double? ?? 0.0).toString());
|
|
||||||
totalOutAmount += value;
|
totalOutAmount += value;
|
||||||
|
|
||||||
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
|
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
|
||||||
|
@ -96,44 +91,50 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ElectrumTransactionInfo.fromElectrumBundle(
|
factory ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
ElectrumTransactionBundle bundle,
|
ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network,
|
||||||
WalletType type,
|
{required Set<String> addresses, required int height}) {
|
||||||
bitcoin.NetworkType networkType,
|
|
||||||
{required Set<String> addresses,
|
|
||||||
required int height}) {
|
|
||||||
final date = bundle.time != null
|
final date = bundle.time != null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
|
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
|
||||||
: DateTime.now();
|
: DateTime.now();
|
||||||
var direction = TransactionDirection.incoming;
|
var direction = TransactionDirection.incoming;
|
||||||
var amount = 0;
|
var amount = 0;
|
||||||
var inputAmount = 0;
|
var inputAmount = 0;
|
||||||
var totalOutAmount = 0;
|
var totalOutAmount = 0;
|
||||||
|
|
||||||
for (var i = 0; i < bundle.originalTransaction.ins.length; i++) {
|
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
||||||
final input = bundle.originalTransaction.ins[i];
|
final input = bundle.originalTransaction.inputs[i];
|
||||||
final inputTransaction = bundle.ins[i];
|
final inputTransaction = bundle.ins[i];
|
||||||
final vout = input.index;
|
final outTransaction = inputTransaction.outputs[input.txIndex];
|
||||||
final outTransaction = inputTransaction.outs[vout!];
|
inputAmount += outTransaction.amount.toInt();
|
||||||
final address = addressFromOutput(outTransaction.script!, networkType);
|
if (addresses.contains(addressFromOutputScript(outTransaction.scriptPubKey, network))) {
|
||||||
inputAmount += outTransaction.value!;
|
|
||||||
if (addresses.contains(address)) {
|
|
||||||
direction = TransactionDirection.outgoing;
|
direction = TransactionDirection.outgoing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final out in bundle.originalTransaction.outs) {
|
final receivedAmounts = <int>[];
|
||||||
totalOutAmount += out.value!;
|
for (final out in bundle.originalTransaction.outputs) {
|
||||||
final address = addressFromOutput(out.script!, networkType);
|
totalOutAmount += out.amount.toInt();
|
||||||
final addressExists = addresses.contains(address);
|
final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network));
|
||||||
|
|
||||||
|
if (addressExists) {
|
||||||
|
receivedAmounts.add(out.amount.toInt());
|
||||||
|
}
|
||||||
|
|
||||||
if ((direction == TransactionDirection.incoming && addressExists) ||
|
if ((direction == TransactionDirection.incoming && addressExists) ||
|
||||||
(direction == TransactionDirection.outgoing && !addressExists)) {
|
(direction == TransactionDirection.outgoing && !addressExists)) {
|
||||||
amount += out.value!;
|
amount += out.amount.toInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (receivedAmounts.length == bundle.originalTransaction.outputs.length) {
|
||||||
|
// Self-send
|
||||||
|
direction = TransactionDirection.incoming;
|
||||||
|
amount = receivedAmounts.reduce((a, b) => a + b);
|
||||||
|
}
|
||||||
|
|
||||||
final fee = inputAmount - totalOutAmount;
|
final fee = inputAmount - totalOutAmount;
|
||||||
return ElectrumTransactionInfo(type,
|
return ElectrumTransactionInfo(type,
|
||||||
id: bundle.originalTransaction.getId(),
|
id: bundle.originalTransaction.txId(),
|
||||||
height: height,
|
height: height,
|
||||||
isPending: bundle.confirmations == 0,
|
isPending: bundle.confirmations == 0,
|
||||||
fee: fee,
|
fee: fee,
|
||||||
|
@ -152,8 +153,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
if (addresses != null) {
|
if (addresses != null) {
|
||||||
tx.outs.forEach((out) {
|
tx.outs.forEach((out) {
|
||||||
try {
|
try {
|
||||||
final p2pkh = bitcoin.P2PKH(
|
final p2pkh =
|
||||||
data: PaymentData(output: out.script), network: bitcoin.bitcoin);
|
bitcoin.P2PKH(data: PaymentData(output: out.script), network: bitcoin.bitcoin);
|
||||||
exist = addresses.contains(p2pkh.data.address);
|
exist = addresses.contains(p2pkh.data.address);
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
|
@ -163,9 +164,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final date = timestamp != null
|
final date =
|
||||||
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
|
timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now();
|
||||||
: DateTime.now();
|
|
||||||
|
|
||||||
return ElectrumTransactionInfo(type,
|
return ElectrumTransactionInfo(type,
|
||||||
id: tx.getId(),
|
id: tx.getId(),
|
||||||
|
@ -178,8 +178,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
confirmations: confirmations);
|
confirmations: confirmations);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ElectrumTransactionInfo.fromJson(
|
factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
|
||||||
Map<String, dynamic> data, WalletType type) {
|
|
||||||
return ElectrumTransactionInfo(type,
|
return ElectrumTransactionInfo(type,
|
||||||
id: data['id'] as String,
|
id: data['id'] as String,
|
||||||
height: data['height'] as int,
|
height: data['height'] as int,
|
||||||
|
|
|
@ -3,9 +3,10 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base;
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cw_bitcoin/address_to_output_script.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
|
||||||
|
@ -18,6 +19,7 @@ import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_history.dart';
|
import 'package:cw_bitcoin/electrum_transaction_history.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
|
import 'package:cw_bitcoin/litecoin_network.dart';
|
||||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||||
import 'package:cw_bitcoin/script_hash.dart';
|
import 'package:cw_bitcoin/script_hash.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
|
@ -33,10 +35,10 @@ import 'package:cw_core/utils/file.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hex/hex.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:rxdart/subjects.dart';
|
import 'package:rxdart/subjects.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
part 'electrum_wallet.g.dart';
|
part 'electrum_wallet.g.dart';
|
||||||
|
|
||||||
|
@ -73,6 +75,12 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||||
|
this.network = networkType == bitcoin.bitcoin
|
||||||
|
? BitcoinNetwork.mainnet
|
||||||
|
: networkType == litecoinNetwork
|
||||||
|
? LitecoinNetwork.mainnet
|
||||||
|
: BitcoinNetwork.testnet,
|
||||||
|
this.isTestnet = networkType == bitcoin.testnet,
|
||||||
super(walletInfo) {
|
super(walletInfo) {
|
||||||
this.electrumClient = electrumClient ?? ElectrumClient();
|
this.electrumClient = electrumClient ?? ElectrumClient();
|
||||||
this.walletInfo = walletInfo;
|
this.walletInfo = walletInfo;
|
||||||
|
@ -106,13 +114,13 @@ abstract class ElectrumWalletBase
|
||||||
@observable
|
@observable
|
||||||
SyncStatus syncStatus;
|
SyncStatus syncStatus;
|
||||||
|
|
||||||
List<String> get scriptHashes => walletAddresses.addresses
|
List<String> get scriptHashes => walletAddresses.addressesByReceiveType
|
||||||
.map((addr) => scriptHash(addr.address, networkType: networkType))
|
.map((addr) => scriptHash(addr.address, network: network))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
List<String> get publicScriptHashes => walletAddresses.addresses
|
List<String> get publicScriptHashes => walletAddresses.allAddresses
|
||||||
.where((addr) => !addr.isHidden)
|
.where((addr) => !addr.isHidden)
|
||||||
.map((addr) => scriptHash(addr.address, networkType: networkType))
|
.map((addr) => scriptHash(addr.address, network: network))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
String get xpub => hd.base58!;
|
String get xpub => hd.base58!;
|
||||||
|
@ -121,6 +129,10 @@ abstract class ElectrumWalletBase
|
||||||
String get seed => mnemonic;
|
String get seed => mnemonic;
|
||||||
|
|
||||||
bitcoin.NetworkType networkType;
|
bitcoin.NetworkType networkType;
|
||||||
|
BasedUtxoNetwork network;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool? isTestnet;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BitcoinWalletKeys get keys =>
|
BitcoinWalletKeys get keys =>
|
||||||
|
@ -145,12 +157,11 @@ abstract class ElectrumWalletBase
|
||||||
Future<void> startSync() async {
|
Future<void> startSync() async {
|
||||||
try {
|
try {
|
||||||
syncStatus = AttemptingSyncStatus();
|
syncStatus = AttemptingSyncStatus();
|
||||||
await walletAddresses.discoverAddresses();
|
|
||||||
await updateTransactions();
|
await updateTransactions();
|
||||||
_subscribeForUpdates();
|
_subscribeForUpdates();
|
||||||
await updateUnspent();
|
await updateUnspent();
|
||||||
await updateBalance();
|
await updateBalance();
|
||||||
_feeRates = await electrumClient.feeRates();
|
_feeRates = await electrumClient.feeRates(network: network);
|
||||||
|
|
||||||
Timer.periodic(
|
Timer.periodic(
|
||||||
const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
|
const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
|
||||||
|
@ -181,188 +192,211 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<EstimatedTxResult> _estimateTxFeeAndInputsToUse(
|
||||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
int credentialsAmount,
|
||||||
const minAmount = 546;
|
bool sendAll,
|
||||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
List<BitcoinBaseAddress> outputAddresses,
|
||||||
final inputs = <BitcoinUnspent>[];
|
List<BitcoinOutput> outputs,
|
||||||
final outputs = transactionCredentials.outputs;
|
BitcoinTransactionCredentials transactionCredentials,
|
||||||
final hasMultiDestination = outputs.length > 1;
|
{int? inputsCount}) async {
|
||||||
|
final utxos = <UtxoWithAddress>[];
|
||||||
|
List<ECPrivate> privateKeys = [];
|
||||||
|
|
||||||
|
var leftAmount = credentialsAmount;
|
||||||
var allInputsAmount = 0;
|
var allInputsAmount = 0;
|
||||||
|
|
||||||
final String? opReturnMemo = outputs.first.memo;
|
final String? opReturnMemo = outputs.first.memo;
|
||||||
|
|
||||||
|
for (int i = 0; i < unspentCoins.length; i++) {
|
||||||
|
final utx = unspentCoins[i];
|
||||||
|
|
||||||
if (unspentCoins.isEmpty) {
|
|
||||||
await updateUnspent();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final utx in unspentCoins) {
|
|
||||||
if (utx.isSending) {
|
if (utx.isSending) {
|
||||||
allInputsAmount += utx.value;
|
allInputsAmount += utx.value;
|
||||||
inputs.add(utx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputs.isEmpty) {
|
|
||||||
throw BitcoinTransactionNoInputsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final allAmountFee = transactionCredentials.feeRate != null
|
|
||||||
? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length)
|
|
||||||
: feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length);
|
|
||||||
|
|
||||||
final allAmount = allInputsAmount - allAmountFee;
|
|
||||||
|
|
||||||
var credentialsAmount = 0;
|
|
||||||
var amount = 0;
|
|
||||||
var fee = 0;
|
|
||||||
|
|
||||||
if (hasMultiDestination) {
|
|
||||||
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
|
|
||||||
|
|
||||||
if (allAmount - credentialsAmount < minAmount) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
amount = credentialsAmount;
|
|
||||||
|
|
||||||
if (transactionCredentials.feeRate != null) {
|
|
||||||
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount,
|
|
||||||
outputsCount: outputs.length + 1);
|
|
||||||
} else {
|
|
||||||
fee = calculateEstimatedFee(transactionCredentials.priority, amount,
|
|
||||||
outputsCount: outputs.length + 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final output = outputs.first;
|
|
||||||
credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
|
|
||||||
|
|
||||||
if (credentialsAmount > allAmount) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
amount = output.sendAll || allAmount - credentialsAmount < minAmount
|
|
||||||
? allAmount
|
|
||||||
: credentialsAmount;
|
|
||||||
|
|
||||||
if (output.sendAll || amount == allAmount) {
|
|
||||||
fee = allAmountFee;
|
|
||||||
} else if (transactionCredentials.feeRate != null) {
|
|
||||||
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount);
|
|
||||||
} else {
|
|
||||||
fee = calculateEstimatedFee(transactionCredentials.priority, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fee == 0) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
final totalAmount = amount + fee;
|
|
||||||
|
|
||||||
if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
final txb = bitcoin.TransactionBuilder(network: networkType);
|
|
||||||
final changeAddress = await walletAddresses.getChangeAddress();
|
|
||||||
var leftAmount = totalAmount;
|
|
||||||
var totalInputAmount = 0;
|
|
||||||
|
|
||||||
inputs.clear();
|
|
||||||
|
|
||||||
for (final utx in unspentCoins) {
|
|
||||||
if (utx.isSending) {
|
|
||||||
leftAmount = leftAmount - utx.value;
|
leftAmount = leftAmount - utx.value;
|
||||||
totalInputAmount += utx.value;
|
|
||||||
inputs.add(utx);
|
|
||||||
|
|
||||||
if (leftAmount <= 0) {
|
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);
|
||||||
|
|
||||||
|
utxos.add(
|
||||||
|
UtxoWithAddress(
|
||||||
|
utxo: BitcoinUtxo(
|
||||||
|
txHash: utx.hash,
|
||||||
|
value: BigInt.from(utx.value),
|
||||||
|
vout: utx.vout,
|
||||||
|
scriptType: _getScriptType(address),
|
||||||
|
),
|
||||||
|
ownerDetails:
|
||||||
|
UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: address),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
bool amountIsAcquired = !sendAll && leftAmount <= 0;
|
||||||
|
if ((inputsCount == null && amountIsAcquired) || inputsCount == i + 1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputs.isEmpty) {
|
if (utxos.isEmpty) {
|
||||||
throw BitcoinTransactionNoInputsException();
|
throw BitcoinTransactionNoInputsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amount <= 0 || totalInputAmount < totalAmount) {
|
var changeValue = allInputsAmount - credentialsAmount;
|
||||||
|
|
||||||
|
if (!sendAll) {
|
||||||
|
if (changeValue > 0) {
|
||||||
|
final changeAddress = await walletAddresses.getChangeAddress();
|
||||||
|
final address = _addressTypeFromStr(changeAddress, network);
|
||||||
|
outputAddresses.add(address);
|
||||||
|
outputs.add(BitcoinOutput(address: address, value: BigInt.from(changeValue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
|
||||||
|
utxos: utxos, outputs: outputs, network: network);
|
||||||
|
|
||||||
|
final fee = transactionCredentials.feeRate != null
|
||||||
|
? feeAmountWithFeeRate(transactionCredentials.feeRate!, 0, 0, size: estimatedSize)
|
||||||
|
: feeAmountForPriority(transactionCredentials.priority!, 0, 0, size: estimatedSize);
|
||||||
|
|
||||||
|
if (fee == 0) {
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
txb.setVersion(1);
|
var amount = credentialsAmount;
|
||||||
inputs.forEach((input) {
|
|
||||||
if (input.isP2wpkh) {
|
|
||||||
final p2wpkh = bitcoin
|
|
||||||
.P2WPKH(
|
|
||||||
data: generatePaymentData(
|
|
||||||
hd: input.bitcoinAddressRecord.isHidden
|
|
||||||
? walletAddresses.sideHd
|
|
||||||
: walletAddresses.mainHd,
|
|
||||||
index: input.bitcoinAddressRecord.index),
|
|
||||||
network: networkType)
|
|
||||||
.data;
|
|
||||||
|
|
||||||
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
|
final lastOutput = outputs.last;
|
||||||
} else {
|
if (!sendAll) {
|
||||||
txb.addInput(input.hash, input.vout);
|
if (changeValue > fee) {
|
||||||
|
// Here, lastOutput is change, deduct the fee from it
|
||||||
|
outputs[outputs.length - 1] =
|
||||||
|
BitcoinOutput(address: lastOutput.address, value: lastOutput.value - BigInt.from(fee));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
outputs.forEach((item) {
|
|
||||||
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
|
|
||||||
final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
|
|
||||||
txb.addOutput(addressToOutputScript(outputAddress, networkType), outputAmount!);
|
|
||||||
});
|
|
||||||
|
|
||||||
final estimatedSize = estimatedTransactionSize(inputs.length, outputs.length + 1);
|
|
||||||
var feeAmount = 0;
|
|
||||||
|
|
||||||
if (transactionCredentials.feeRate != null) {
|
|
||||||
feeAmount = transactionCredentials.feeRate! * estimatedSize;
|
|
||||||
} else {
|
} else {
|
||||||
feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize;
|
// Here, if sendAll, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount for change
|
||||||
|
amount = allInputsAmount - fee;
|
||||||
|
outputs[outputs.length - 1] =
|
||||||
|
BitcoinOutput(address: lastOutput.address, value: BigInt.from(amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
final changeValue = totalInputAmount - amount - feeAmount;
|
final totalAmount = amount + fee;
|
||||||
|
|
||||||
if (changeValue > minAmount) {
|
if (totalAmount > balance[currency]!.confirmed) {
|
||||||
txb.addOutput(changeAddress, changeValue);
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opReturnMemo != null) txb.addOutputData(opReturnMemo);
|
if (totalAmount > allInputsAmount) {
|
||||||
|
if (unspentCoins.where((utx) => utx.isSending).length == utxos.length) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
} else {
|
||||||
|
if (changeValue > fee) {
|
||||||
|
outputAddresses.removeLast();
|
||||||
|
outputs.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
return _estimateTxFeeAndInputsToUse(
|
||||||
final input = inputs[i];
|
credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials,
|
||||||
final keyPair = generateKeyPair(
|
inputsCount: utxos.length + 1);
|
||||||
hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
|
}
|
||||||
index: input.bitcoinAddressRecord.index,
|
|
||||||
network: networkType);
|
|
||||||
final witnessValue = input.isP2wpkh ? input.value : null;
|
|
||||||
|
|
||||||
txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return PendingBitcoinTransaction(txb.build(), type,
|
return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount);
|
||||||
electrumClient: electrumClient, amount: amount, fee: fee)
|
}
|
||||||
..addListener((transaction) async {
|
|
||||||
transactionHistory.addOne(transaction);
|
@override
|
||||||
await updateBalance();
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
|
try {
|
||||||
|
final outputs = <BitcoinOutput>[];
|
||||||
|
final outputAddresses = <BitcoinBaseAddress>[];
|
||||||
|
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||||
|
final hasMultiDestination = transactionCredentials.outputs.length > 1;
|
||||||
|
final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll;
|
||||||
|
|
||||||
|
var credentialsAmount = 0;
|
||||||
|
|
||||||
|
for (final out in transactionCredentials.outputs) {
|
||||||
|
final outputAddress = out.isParsedAddress ? out.extractedAddress! : out.address;
|
||||||
|
final address = _addressTypeFromStr(outputAddress, network);
|
||||||
|
|
||||||
|
outputAddresses.add(address);
|
||||||
|
|
||||||
|
if (hasMultiDestination) {
|
||||||
|
if (out.sendAll || out.formattedCryptoAmount! <= 0) {
|
||||||
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
final outputAmount = out.formattedCryptoAmount!;
|
||||||
|
credentialsAmount += outputAmount;
|
||||||
|
|
||||||
|
outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount)));
|
||||||
|
} else {
|
||||||
|
if (!sendAll) {
|
||||||
|
final outputAmount = out.formattedCryptoAmount!;
|
||||||
|
credentialsAmount += outputAmount;
|
||||||
|
outputs.add(BitcoinOutput(address: address, value: BigInt.from(outputAmount)));
|
||||||
|
} else {
|
||||||
|
// The value will be changed after estimating the Tx size and deducting the fee from the total
|
||||||
|
outputs.add(BitcoinOutput(address: address, value: BigInt.from(0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final estimatedTx = await _estimateTxFeeAndInputsToUse(
|
||||||
|
credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials);
|
||||||
|
|
||||||
|
final txb = BitcoinTransactionBuilder(
|
||||||
|
utxos: estimatedTx.utxos,
|
||||||
|
outputs: outputs,
|
||||||
|
fee: BigInt.from(estimatedTx.fee),
|
||||||
|
network: network);
|
||||||
|
|
||||||
|
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
|
||||||
|
final key = estimatedTx.privateKeys
|
||||||
|
.firstWhereOrNull((element) => element.getPublic().toHex() == publicKey);
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
throw Exception("Cannot find private key");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (utxo.utxo.isP2tr()) {
|
||||||
|
return key.signTapRoot(txDigest, sighash: sighash);
|
||||||
|
} else {
|
||||||
|
return key.signInput(txDigest, sigHash: sighash);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: fix
|
||||||
|
if (opReturnMemo != null) txb.addOutputData(opReturnMemo);
|
||||||
|
|
||||||
|
return PendingBitcoinTransaction(transaction, type,
|
||||||
|
electrumClient: electrumClient,
|
||||||
|
amount: estimatedTx.amount,
|
||||||
|
fee: estimatedTx.fee,
|
||||||
|
network: network)
|
||||||
|
..addListener((transaction) async {
|
||||||
|
transactionHistory.addOne(transaction);
|
||||||
|
await updateBalance();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'mnemonic': mnemonic,
|
'mnemonic': mnemonic,
|
||||||
'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
|
'account_index': walletAddresses.currentReceiveAddressIndexByType,
|
||||||
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
|
'change_address_index': walletAddresses.currentChangeAddressIndexByType,
|
||||||
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
|
'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
|
||||||
'balance': balance[currency]?.toJSON()
|
'address_page_type': walletInfo.addressPageType == null
|
||||||
|
? SegwitAddresType.p2wpkh.toString()
|
||||||
|
: walletInfo.addressPageType.toString(),
|
||||||
|
'balance': balance[currency]?.toJSON(),
|
||||||
|
'network_type': network == BitcoinNetwork.testnet ? 'testnet' : 'mainnet',
|
||||||
});
|
});
|
||||||
|
|
||||||
int feeRate(TransactionPriority priority) {
|
int feeRate(TransactionPriority priority) {
|
||||||
|
@ -377,24 +411,29 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int feeAmountForPriority(
|
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount,
|
||||||
BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
|
{int? size}) =>
|
||||||
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
|
feeRate(priority) * (size ?? estimatedTransactionSize(inputsCount, outputsCount));
|
||||||
|
|
||||||
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
|
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) =>
|
||||||
feeRate * estimatedTransactionSize(inputsCount, outputsCount);
|
feeRate * (size ?? estimatedTransactionSize(inputsCount, outputsCount));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) {
|
int calculateEstimatedFee(TransactionPriority? priority, int? amount,
|
||||||
|
{int? outputsCount, int? size}) {
|
||||||
if (priority is BitcoinTransactionPriority) {
|
if (priority is BitcoinTransactionPriority) {
|
||||||
return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
|
return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
|
||||||
outputsCount: outputsCount);
|
outputsCount: outputsCount, size: size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
|
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
|
||||||
|
if (size != null) {
|
||||||
|
return feeAmountWithFeeRate(feeRate, 0, 0, size: size);
|
||||||
|
}
|
||||||
|
|
||||||
int inputsCount = 0;
|
int inputsCount = 0;
|
||||||
|
|
||||||
if (amount != null) {
|
if (amount != null) {
|
||||||
|
@ -462,9 +501,6 @@ abstract class ElectrumWalletBase
|
||||||
await transactionHistory.changePassword(password);
|
await transactionHistory.changePassword(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
bitcoin.ECPair keyPairFor({required int index}) =>
|
|
||||||
generateKeyPair(hd: hd, index: index, network: networkType);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> rescan({required int height}) async => throw UnimplementedError();
|
Future<void> rescan({required int height}) async => throw UnimplementedError();
|
||||||
|
|
||||||
|
@ -478,20 +514,23 @@ abstract class ElectrumWalletBase
|
||||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
|
||||||
Future<void> updateUnspent() async {
|
Future<void> updateUnspent() async {
|
||||||
final unspent = await Future.wait(walletAddresses.addresses.map((address) => electrumClient
|
List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||||
.getListUnspentWithAddress(address.address, networkType)
|
|
||||||
.then((unspent) => unspent.map((unspent) {
|
final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
|
||||||
|
|
||||||
|
await Future.wait(walletAddresses.allAddresses.map((address) => electrumClient
|
||||||
|
.getListUnspentWithAddress(address.address, network)
|
||||||
|
.then((unspent) => Future.forEach<Map<String, dynamic>>(unspent, (unspent) async {
|
||||||
try {
|
try {
|
||||||
return BitcoinUnspent.fromJSON(address, unspent);
|
final coin = BitcoinUnspent.fromJSON(address, unspent);
|
||||||
} catch (_) {
|
final tx = await fetchTransactionInfo(
|
||||||
return null;
|
hash: coin.hash, height: 0, myAddresses: addressesSet);
|
||||||
}
|
coin.isChange = tx?.direction == TransactionDirection.outgoing;
|
||||||
}).whereNotNull())));
|
updatedUnspentCoins.add(coin);
|
||||||
unspentCoins = unspent.expand((e) => e).toList();
|
} catch (_) {}
|
||||||
unspentCoins.forEach((coin) async {
|
}))));
|
||||||
final tx = await fetchTransactionInfo(hash: coin.hash, height: 0);
|
|
||||||
coin.isChange = tx?.direction == TransactionDirection.outgoing;
|
unspentCoins = updatedUnspentCoins;
|
||||||
});
|
|
||||||
|
|
||||||
if (unspentCoinsInfo.isEmpty) {
|
if (unspentCoinsInfo.isEmpty) {
|
||||||
unspentCoins.forEach((coin) => _addCoinInfo(coin));
|
unspentCoins.forEach((coin) => _addCoinInfo(coin));
|
||||||
|
@ -500,8 +539,10 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
if (unspentCoins.isNotEmpty) {
|
if (unspentCoins.isNotEmpty) {
|
||||||
unspentCoins.forEach((coin) {
|
unspentCoins.forEach((coin) {
|
||||||
final coinInfoList = unspentCoinsInfo.values
|
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
||||||
.where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
|
element.walletId.contains(id) &&
|
||||||
|
element.hash.contains(coin.hash) &&
|
||||||
|
element.vout == coin.vout);
|
||||||
|
|
||||||
if (coinInfoList.isNotEmpty) {
|
if (coinInfoList.isNotEmpty) {
|
||||||
final coinInfo = coinInfoList.first;
|
final coinInfo = coinInfoList.first;
|
||||||
|
@ -542,7 +583,8 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
if (currentWalletUnspentCoins.isNotEmpty) {
|
if (currentWalletUnspentCoins.isNotEmpty) {
|
||||||
currentWalletUnspentCoins.forEach((element) {
|
currentWalletUnspentCoins.forEach((element) {
|
||||||
final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash));
|
final existUnspentCoins = unspentCoins
|
||||||
|
.where((coin) => element.hash.contains(coin.hash) && element.vout == coin.vout);
|
||||||
|
|
||||||
if (existUnspentCoins.isEmpty) {
|
if (existUnspentCoins.isEmpty) {
|
||||||
keys.add(element.key);
|
keys.add(element.key);
|
||||||
|
@ -560,92 +602,145 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
Future<ElectrumTransactionBundle> getTransactionExpanded(
|
Future<ElectrumTransactionBundle> getTransactionExpanded(
|
||||||
{required String hash, required int height}) async {
|
{required String hash, required int height}) async {
|
||||||
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
String transactionHex;
|
||||||
final transactionHex = verboseTransaction['hex'] as String;
|
int? time;
|
||||||
final original = bitcoin.Transaction.fromHex(transactionHex);
|
int confirmations = 0;
|
||||||
final ins = <bitcoin.Transaction>[];
|
if (network == BitcoinNetwork.testnet) {
|
||||||
final time = verboseTransaction['time'] as int?;
|
// Testnet public electrum server does not support verbose transaction fetching
|
||||||
final confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
transactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||||
|
|
||||||
for (final vin in original.ins) {
|
final status = json.decode(
|
||||||
final id = HEX.encode(vin.hash!.reversed.toList());
|
(await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body);
|
||||||
final txHex = await electrumClient.getTransactionHex(hash: id);
|
|
||||||
final tx = bitcoin.Transaction.fromHex(txHex);
|
time = status["block_time"] as int?;
|
||||||
|
final tip = await electrumClient.getCurrentBlockChainTip() ?? 0;
|
||||||
|
confirmations = tip - (status["block_height"] as int? ?? 0);
|
||||||
|
} else {
|
||||||
|
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
||||||
|
|
||||||
|
transactionHex = verboseTransaction['hex'] as String;
|
||||||
|
time = verboseTransaction['time'] as int?;
|
||||||
|
confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final original = bitcoin_base.BtcTransaction.fromRaw(transactionHex);
|
||||||
|
final ins = <bitcoin_base.BtcTransaction>[];
|
||||||
|
|
||||||
|
for (final vin in original.inputs) {
|
||||||
|
final txHex = await electrumClient.getTransactionHex(hash: vin.txId);
|
||||||
|
final tx = bitcoin_base.BtcTransaction.fromRaw(txHex);
|
||||||
ins.add(tx);
|
ins.add(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ElectrumTransactionBundle(original, ins: ins, time: time, confirmations: confirmations);
|
return ElectrumTransactionBundle(original,
|
||||||
|
ins: ins, time: time, confirmations: confirmations, height: height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
|
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
|
||||||
{required String hash, required int height}) async {
|
{required String hash,
|
||||||
|
required int height,
|
||||||
|
required Set<String> myAddresses,
|
||||||
|
bool? retryOnFailure}) async {
|
||||||
try {
|
try {
|
||||||
final tx = await getTransactionExpanded(hash: hash, height: height);
|
return ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
|
await getTransactionExpanded(hash: hash, height: height), walletInfo.type, network,
|
||||||
return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType,
|
addresses: myAddresses, height: height);
|
||||||
addresses: addresses, height: height);
|
} catch (e) {
|
||||||
} catch (_) {
|
if (e is FormatException && retryOnFailure == true) {
|
||||||
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
return fetchTransactionInfo(hash: hash, height: height, myAddresses: myAddresses);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||||
final addressHashes = <String, BitcoinAddressRecord>{};
|
|
||||||
final normalizedHistories = <Map<String, dynamic>>[];
|
|
||||||
final newTxCounts = <String, int>{};
|
|
||||||
|
|
||||||
walletAddresses.addresses.forEach((addressRecord) {
|
|
||||||
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
|
||||||
addressHashes[sh] = addressRecord;
|
|
||||||
newTxCounts[sh] = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final histories = addressHashes.keys.map((scriptHash) =>
|
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
||||||
electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
|
final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
|
||||||
final historyResults = await Future.wait(histories);
|
final currentHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
|
||||||
|
|
||||||
historyResults.forEach((history) {
|
await Future.wait(ADDRESS_TYPES.map((type) {
|
||||||
history.entries.forEach((historyItem) {
|
final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type);
|
||||||
if (historyItem.value.isNotEmpty) {
|
|
||||||
final address = addressHashes[historyItem.key];
|
return Future.wait(addressesByType.map((addressRecord) async {
|
||||||
address?.setAsUsed();
|
final history = await _fetchAddressHistory(addressRecord, addressesSet, currentHeight);
|
||||||
newTxCounts[historyItem.key] = historyItem.value.length;
|
|
||||||
normalizedHistories.addAll(historyItem.value);
|
if (history.isNotEmpty) {
|
||||||
|
addressRecord.txCount = history.length;
|
||||||
|
historiesWithDetails.addAll(history);
|
||||||
|
|
||||||
|
final matchedAddresses =
|
||||||
|
addressesByType.where((addr) => addr.isHidden == addressRecord.isHidden);
|
||||||
|
|
||||||
|
final isLastUsedAddress =
|
||||||
|
history.isNotEmpty && addressRecord.address == matchedAddresses.last.address;
|
||||||
|
|
||||||
|
if (isLastUsedAddress) {
|
||||||
|
await walletAddresses.discoverAddresses(
|
||||||
|
matchedAddresses.toList(),
|
||||||
|
addressRecord.isHidden,
|
||||||
|
(address, addressesSet) =>
|
||||||
|
_fetchAddressHistory(address, addressesSet, currentHeight)
|
||||||
|
.then((history) => history.isNotEmpty ? address.address : null),
|
||||||
|
type: type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
for (var sh in addressHashes.keys) {
|
|
||||||
var balanceData = await electrumClient.getBalance(sh);
|
|
||||||
var addressRecord = addressHashes[sh];
|
|
||||||
if (addressRecord != null) {
|
|
||||||
addressRecord.balance = balanceData['confirmed'] as int? ?? 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addressHashes.forEach((sh, addressRecord) {
|
|
||||||
addressRecord.txCount = newTxCounts[sh] ?? 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
|
|
||||||
try {
|
|
||||||
return fetchTransactionInfo(
|
|
||||||
hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
|
|
||||||
} catch (_) {
|
|
||||||
return Future.value(null);
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
|
return historiesWithDetails;
|
||||||
<String, ElectrumTransactionInfo>{}, (acc, tx) {
|
} catch (e) {
|
||||||
if (tx == null) {
|
print(e.toString());
|
||||||
return acc;
|
return {};
|
||||||
}
|
}
|
||||||
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
|
}
|
||||||
return acc;
|
|
||||||
});
|
Future<Map<String, ElectrumTransactionInfo>> _fetchAddressHistory(
|
||||||
|
BitcoinAddressRecord addressRecord, Set<String> addressesSet, int currentHeight) async {
|
||||||
|
try {
|
||||||
|
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
||||||
|
|
||||||
|
final history = await electrumClient
|
||||||
|
.getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network));
|
||||||
|
|
||||||
|
if (history.isNotEmpty) {
|
||||||
|
addressRecord.setAsUsed();
|
||||||
|
|
||||||
|
await Future.wait(history.map((transaction) async {
|
||||||
|
final txid = transaction['tx_hash'] as String;
|
||||||
|
final height = transaction['height'] as int;
|
||||||
|
final storedTx = transactionHistory.transactions[txid];
|
||||||
|
|
||||||
|
if (storedTx != null) {
|
||||||
|
if (height > 0) {
|
||||||
|
storedTx.height = height;
|
||||||
|
// the tx's block itself is the first confirmation so add 1
|
||||||
|
storedTx.confirmations = currentHeight - height + 1;
|
||||||
|
storedTx.isPending = storedTx.confirmations == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
historiesWithDetails[txid] = storedTx;
|
||||||
|
} else {
|
||||||
|
final tx = await fetchTransactionInfo(
|
||||||
|
hash: txid, height: height, myAddresses: addressesSet, retryOnFailure: true);
|
||||||
|
|
||||||
|
if (tx != null) {
|
||||||
|
historiesWithDetails[txid] = tx;
|
||||||
|
|
||||||
|
// Got a new transaction fetched, add it to the transaction history
|
||||||
|
// instead of waiting all to finish, and next time it will be faster
|
||||||
|
transactionHistory.addOne(tx);
|
||||||
|
await transactionHistory.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Future.value(null);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return historiesWithDetails;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
return {};
|
return {};
|
||||||
|
@ -659,10 +754,8 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
_isTransactionUpdating = true;
|
_isTransactionUpdating = true;
|
||||||
final transactions = await fetchTransactions();
|
await fetchTransactions();
|
||||||
transactionHistory.addMany(transactions);
|
|
||||||
walletAddresses.updateReceiveAddresses();
|
walletAddresses.updateReceiveAddresses();
|
||||||
await transactionHistory.save();
|
|
||||||
_isTransactionUpdating = false;
|
_isTransactionUpdating = false;
|
||||||
} catch (e, stacktrace) {
|
} catch (e, stacktrace) {
|
||||||
print(stacktrace);
|
print(stacktrace);
|
||||||
|
@ -693,11 +786,11 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ElectrumBalance> _fetchBalances() async {
|
Future<ElectrumBalance> _fetchBalances() async {
|
||||||
final addresses = walletAddresses.addresses.toList();
|
final addresses = walletAddresses.allAddresses.toList();
|
||||||
final balanceFutures = <Future<Map<String, dynamic>>>[];
|
final balanceFutures = <Future<Map<String, dynamic>>>[];
|
||||||
for (var i = 0; i < addresses.length; i++) {
|
for (var i = 0; i < addresses.length; i++) {
|
||||||
final addressRecord = addresses[i];
|
final addressRecord = addresses[i];
|
||||||
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
final sh = scriptHash(addressRecord.address, network: network);
|
||||||
final balanceFuture = electrumClient.getBalance(sh);
|
final balanceFuture = electrumClient.getBalance(sh);
|
||||||
balanceFutures.add(balanceFuture);
|
balanceFutures.add(balanceFuture);
|
||||||
}
|
}
|
||||||
|
@ -706,6 +799,7 @@ abstract class ElectrumWalletBase
|
||||||
unspentCoinsInfo.values.forEach((info) {
|
unspentCoinsInfo.values.forEach((info) {
|
||||||
unspentCoins.forEach((element) {
|
unspentCoins.forEach((element) {
|
||||||
if (element.hash == info.hash &&
|
if (element.hash == info.hash &&
|
||||||
|
element.vout == info.vout &&
|
||||||
info.isFrozen &&
|
info.isFrozen &&
|
||||||
element.bitcoinAddressRecord.address == info.address &&
|
element.bitcoinAddressRecord.address == info.address &&
|
||||||
element.value == info.value) {
|
element.value == info.value) {
|
||||||
|
@ -743,10 +837,10 @@ abstract class ElectrumWalletBase
|
||||||
String getChangeAddress() {
|
String getChangeAddress() {
|
||||||
const minCountOfHiddenAddresses = 5;
|
const minCountOfHiddenAddresses = 5;
|
||||||
final random = Random();
|
final random = Random();
|
||||||
var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList();
|
var addresses = walletAddresses.allAddresses.where((addr) => addr.isHidden).toList();
|
||||||
|
|
||||||
if (addresses.length < minCountOfHiddenAddresses) {
|
if (addresses.length < minCountOfHiddenAddresses) {
|
||||||
addresses = walletAddresses.addresses.toList();
|
addresses = walletAddresses.allAddresses.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return addresses[random.nextInt(addresses.length)].address;
|
return addresses[random.nextInt(addresses.length)].address;
|
||||||
|
@ -758,9 +852,62 @@ abstract class ElectrumWalletBase
|
||||||
@override
|
@override
|
||||||
String signMessage(String message, {String? address = null}) {
|
String signMessage(String message, {String? address = null}) {
|
||||||
final index = address != null
|
final index = address != null
|
||||||
? walletAddresses.addresses.firstWhere((element) => element.address == address).index
|
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
|
||||||
: null;
|
: null;
|
||||||
final HD = index == null ? hd : hd.derive(index);
|
final HD = index == null ? hd : hd.derive(index);
|
||||||
return base64Encode(HD.signMessage(message));
|
return base64Encode(HD.signMessage(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EstimateTxParams {
|
||||||
|
EstimateTxParams(
|
||||||
|
{required this.amount,
|
||||||
|
required this.feeRate,
|
||||||
|
required this.priority,
|
||||||
|
required this.outputsCount,
|
||||||
|
required this.size});
|
||||||
|
|
||||||
|
final int amount;
|
||||||
|
final int feeRate;
|
||||||
|
final TransactionPriority priority;
|
||||||
|
final int outputsCount;
|
||||||
|
final int size;
|
||||||
|
}
|
||||||
|
|
||||||
|
class EstimatedTxResult {
|
||||||
|
EstimatedTxResult(
|
||||||
|
{required this.utxos, required this.privateKeys, required this.fee, required this.amount});
|
||||||
|
|
||||||
|
final List<UtxoWithAddress> utxos;
|
||||||
|
final List<ECPrivate> privateKeys;
|
||||||
|
final int fee;
|
||||||
|
final int amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) {
|
||||||
|
if (P2pkhAddress.regex.hasMatch(address)) {
|
||||||
|
return P2pkhAddress.fromAddress(address: address, network: network);
|
||||||
|
} else if (P2shAddress.regex.hasMatch(address)) {
|
||||||
|
return P2shAddress.fromAddress(address: address, network: network);
|
||||||
|
} else if (P2wshAddress.regex.hasMatch(address)) {
|
||||||
|
return P2wshAddress.fromAddress(address: address, network: network);
|
||||||
|
} else if (P2trAddress.regex.hasMatch(address)) {
|
||||||
|
return P2trAddress.fromAddress(address: address, network: network);
|
||||||
|
} else {
|
||||||
|
return P2wpkhAddress.fromAddress(address: address, network: network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
|
||||||
|
if (type is P2pkhAddress) {
|
||||||
|
return P2pkhAddressType.p2pkh;
|
||||||
|
} else if (type is P2shAddress) {
|
||||||
|
return P2shAddressType.p2wpkhInP2sh;
|
||||||
|
} else if (type is P2wshAddress) {
|
||||||
|
return SegwitAddresType.p2wsh;
|
||||||
|
} else if (type is P2trAddress) {
|
||||||
|
return SegwitAddresType.p2tr;
|
||||||
|
} else {
|
||||||
|
return SegwitAddresType.p2wpkh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/electrum.dart';
|
import 'package:cw_bitcoin/electrum.dart';
|
||||||
import 'package:cw_bitcoin/script_hash.dart';
|
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
@ -12,25 +12,41 @@ part 'electrum_wallet_addresses.g.dart';
|
||||||
|
|
||||||
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
|
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
|
||||||
|
|
||||||
|
const List<BitcoinAddressType> ADDRESS_TYPES = [
|
||||||
|
SegwitAddresType.p2wpkh,
|
||||||
|
P2pkhAddressType.p2pkh,
|
||||||
|
SegwitAddresType.p2tr,
|
||||||
|
SegwitAddresType.p2wsh,
|
||||||
|
P2shAddressType.p2wpkhInP2sh,
|
||||||
|
];
|
||||||
|
|
||||||
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
ElectrumWalletAddressesBase(
|
||||||
{required this.mainHd,
|
WalletInfo walletInfo, {
|
||||||
required this.sideHd,
|
required this.mainHd,
|
||||||
required this.electrumClient,
|
required this.sideHd,
|
||||||
required this.networkType,
|
required this.electrumClient,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
required this.network,
|
||||||
int initialRegularAddressIndex = 0,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
int initialChangeAddressIndex = 0})
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
: addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
|
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||||
|
addressesByReceiveType =
|
||||||
|
ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
||||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
||||||
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
||||||
.toSet()),
|
.toSet()),
|
||||||
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
||||||
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
|
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
|
||||||
.toSet()),
|
.toSet()),
|
||||||
currentReceiveAddressIndex = initialRegularAddressIndex,
|
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
|
||||||
currentChangeAddressIndex = initialChangeAddressIndex,
|
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
|
||||||
super(walletInfo);
|
_addressPageType = walletInfo.addressPageType != null
|
||||||
|
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
|
||||||
|
: SegwitAddresType.p2wpkh,
|
||||||
|
super(walletInfo) {
|
||||||
|
updateAddressesByMatch();
|
||||||
|
}
|
||||||
|
|
||||||
static const defaultReceiveAddressesCount = 22;
|
static const defaultReceiveAddressesCount = 22;
|
||||||
static const defaultChangeAddressesCount = 17;
|
static const defaultChangeAddressesCount = 17;
|
||||||
|
@ -40,37 +56,48 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address);
|
static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address);
|
||||||
|
|
||||||
final ObservableList<BitcoinAddressRecord> addresses;
|
final ObservableList<BitcoinAddressRecord> _addresses;
|
||||||
|
// Matched by addressPageType
|
||||||
|
late ObservableList<BitcoinAddressRecord> addressesByReceiveType;
|
||||||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
||||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||||
final ElectrumClient electrumClient;
|
final ElectrumClient electrumClient;
|
||||||
final bitcoin.NetworkType networkType;
|
final BasedUtxoNetwork network;
|
||||||
final bitcoin.HDWallet mainHd;
|
final bitcoin.HDWallet mainHd;
|
||||||
final bitcoin.HDWallet sideHd;
|
final bitcoin.HDWallet sideHd;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
BitcoinAddressType get addressPageType => _addressPageType;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<BitcoinAddressRecord> get allAddresses => _addresses;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@computed
|
@computed
|
||||||
String get address {
|
String get address {
|
||||||
if (isEnabledAutoGenerateSubaddress) {
|
String receiveAddress;
|
||||||
if (receiveAddresses.isEmpty) {
|
|
||||||
final newAddress = generateNewAddress(hd: mainHd).address;
|
|
||||||
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress;
|
|
||||||
}
|
|
||||||
final receiveAddress = receiveAddresses.first.address;
|
|
||||||
|
|
||||||
return walletInfo.type == WalletType.bitcoinCash
|
final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch);
|
||||||
? toCashAddr(receiveAddress)
|
|
||||||
: receiveAddress;
|
if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) ||
|
||||||
|
typeMatchingReceiveAddresses.isEmpty) {
|
||||||
|
receiveAddress = generateNewAddress().address;
|
||||||
} else {
|
} else {
|
||||||
final receiveAddress = (receiveAddresses.first.address != addresses.first.address &&
|
final previousAddressMatchesType =
|
||||||
previousAddressRecord != null)
|
previousAddressRecord != null && previousAddressRecord!.type == addressPageType;
|
||||||
? previousAddressRecord!.address
|
|
||||||
: addresses.first.address;
|
|
||||||
|
|
||||||
return walletInfo.type == WalletType.bitcoinCash
|
if (previousAddressMatchesType &&
|
||||||
? toCashAddr(receiveAddress)
|
typeMatchingReceiveAddresses.first.address != addressesByReceiveType.first.address) {
|
||||||
: receiveAddress;
|
receiveAddress = previousAddressRecord!.address;
|
||||||
|
} else {
|
||||||
|
receiveAddress = typeMatchingReceiveAddresses.first.address;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
@ -81,7 +108,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
if (addr.startsWith('bitcoincash:')) {
|
if (addr.startsWith('bitcoincash:')) {
|
||||||
addr = toLegacy(addr);
|
addr = toLegacy(addr);
|
||||||
}
|
}
|
||||||
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
||||||
|
|
||||||
previousAddressRecord = addressRecord;
|
previousAddressRecord = addressRecord;
|
||||||
receiveAddresses.remove(addressRecord);
|
receiveAddresses.remove(addressRecord);
|
||||||
|
@ -89,16 +116,29 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get primaryAddress => getAddress(index: 0, hd: mainHd);
|
String get primaryAddress => getAddress(index: 0, hd: mainHd, addressType: addressPageType);
|
||||||
|
|
||||||
int currentReceiveAddressIndex;
|
Map<String, int> currentReceiveAddressIndexByType;
|
||||||
int currentChangeAddressIndex;
|
|
||||||
|
int get currentReceiveAddressIndex =>
|
||||||
|
currentReceiveAddressIndexByType[_addressPageType.toString()] ?? 0;
|
||||||
|
|
||||||
|
void set currentReceiveAddressIndex(int index) =>
|
||||||
|
currentReceiveAddressIndexByType[_addressPageType.toString()] = index;
|
||||||
|
|
||||||
|
Map<String, int> currentChangeAddressIndexByType;
|
||||||
|
|
||||||
|
int get currentChangeAddressIndex =>
|
||||||
|
currentChangeAddressIndexByType[_addressPageType.toString()] ?? 0;
|
||||||
|
|
||||||
|
void set currentChangeAddressIndex(int index) =>
|
||||||
|
currentChangeAddressIndexByType[_addressPageType.toString()] = index;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
BitcoinAddressRecord? previousAddressRecord;
|
BitcoinAddressRecord? previousAddressRecord;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) {
|
int get totalCountOfReceiveAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
|
||||||
if (!addressRecord.isHidden) {
|
if (!addressRecord.isHidden) {
|
||||||
return acc + 1;
|
return acc + 1;
|
||||||
}
|
}
|
||||||
|
@ -106,22 +146,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
});
|
});
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) {
|
int get totalCountOfChangeAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
|
||||||
if (addressRecord.isHidden) {
|
if (addressRecord.isHidden) {
|
||||||
return acc + 1;
|
return acc + 1;
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> discoverAddresses() async {
|
|
||||||
await _discoverAddresses(mainHd, false);
|
|
||||||
await _discoverAddresses(sideHd, true);
|
|
||||||
await updateAddressesInBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
await _generateInitialAddresses();
|
await _generateInitialAddresses();
|
||||||
|
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||||
|
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
|
||||||
|
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
|
||||||
|
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
|
||||||
|
updateAddressesByMatch();
|
||||||
updateReceiveAddresses();
|
updateReceiveAddresses();
|
||||||
updateChangeAddresses();
|
updateChangeAddresses();
|
||||||
await updateAddressesInBox();
|
await updateAddressesInBox();
|
||||||
|
@ -141,10 +180,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
if (changeAddresses.isEmpty) {
|
if (changeAddresses.isEmpty) {
|
||||||
final newAddresses = await _createNewAddresses(gap,
|
final newAddresses = await _createNewAddresses(gap,
|
||||||
hd: sideHd,
|
|
||||||
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
|
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
|
||||||
isHidden: true);
|
isHidden: true);
|
||||||
_addAddresses(newAddresses);
|
addAddresses(newAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentChangeAddressIndex >= changeAddresses.length) {
|
if (currentChangeAddressIndex >= changeAddresses.length) {
|
||||||
|
@ -157,19 +195,26 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, String? label}) {
|
BitcoinAddressRecord generateNewAddress({String label = ''}) {
|
||||||
final isHidden = hd == sideHd;
|
final newAddressIndex = addressesByReceiveType.fold(
|
||||||
|
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
|
||||||
|
|
||||||
final newAddressIndex = addresses.fold(
|
final address = BitcoinAddressRecord(
|
||||||
0, (int acc, addressRecord) => isHidden == addressRecord.isHidden ? acc + 1 : acc);
|
getAddress(index: newAddressIndex, hd: mainHd, addressType: addressPageType),
|
||||||
|
index: newAddressIndex,
|
||||||
final address = BitcoinAddressRecord(getAddress(index: newAddressIndex, hd: hd ?? sideHd),
|
isHidden: false,
|
||||||
index: newAddressIndex, isHidden: isHidden, name: label ?? '');
|
name: label,
|
||||||
addresses.add(address);
|
type: addressPageType,
|
||||||
|
network: network,
|
||||||
|
);
|
||||||
|
_addresses.add(address);
|
||||||
|
updateAddressesByMatch();
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getAddress({required int index, required bitcoin.HDWallet hd}) => '';
|
String getAddress(
|
||||||
|
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
||||||
|
'';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateAddressesInBox() async {
|
Future<void> updateAddressesInBox() async {
|
||||||
|
@ -187,126 +232,138 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
if (address.startsWith('bitcoincash:')) {
|
if (address.startsWith('bitcoincash:')) {
|
||||||
address = toLegacy(address);
|
address = toLegacy(address);
|
||||||
}
|
}
|
||||||
final addressRecord = addresses.firstWhere((addressRecord) => addressRecord.address == address);
|
final addressRecord =
|
||||||
|
_addresses.firstWhere((addressRecord) => addressRecord.address == address);
|
||||||
addressRecord.setNewName(label);
|
addressRecord.setNewName(label);
|
||||||
final index = addresses.indexOf(addressRecord);
|
final index = _addresses.indexOf(addressRecord);
|
||||||
addresses.remove(addressRecord);
|
_addresses.remove(addressRecord);
|
||||||
addresses.insert(index, addressRecord);
|
_addresses.insert(index, addressRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void updateAddressesByMatch() {
|
||||||
|
addressesByReceiveType.clear();
|
||||||
|
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void updateReceiveAddresses() {
|
void updateReceiveAddresses() {
|
||||||
receiveAddresses.removeRange(0, receiveAddresses.length);
|
receiveAddresses.removeRange(0, receiveAddresses.length);
|
||||||
final newAddresses =
|
final newAddresses =
|
||||||
addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
_addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
||||||
receiveAddresses.addAll(newAddresses);
|
receiveAddresses.addAll(newAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void updateChangeAddresses() {
|
void updateChangeAddresses() {
|
||||||
changeAddresses.removeRange(0, changeAddresses.length);
|
changeAddresses.removeRange(0, changeAddresses.length);
|
||||||
final newAddresses =
|
final newAddresses = _addresses.where((addressRecord) =>
|
||||||
addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
|
addressRecord.isHidden &&
|
||||||
|
!addressRecord.isUsed &&
|
||||||
|
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
|
||||||
|
addressRecord.type == SegwitAddresType.p2wpkh);
|
||||||
changeAddresses.addAll(newAddresses);
|
changeAddresses.addAll(newAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async {
|
@action
|
||||||
var hasAddrUse = true;
|
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
|
||||||
List<BitcoinAddressRecord> addrs;
|
Future<String?> Function(BitcoinAddressRecord, Set<String>) getAddressHistory,
|
||||||
|
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
|
||||||
if (addresses.isNotEmpty) {
|
if (!isHidden) {
|
||||||
|
_validateSideHdAddresses(addressList.toList());
|
||||||
|
|
||||||
if(!isHidden) {
|
|
||||||
final receiveAddressesList = addresses.where((addr) => !addr.isHidden).toList();
|
|
||||||
validateSideHdAddresses(receiveAddressesList);
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
|
|
||||||
} else {
|
|
||||||
addrs = await _createNewAddresses(
|
|
||||||
isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount,
|
|
||||||
startIndex: 0,
|
|
||||||
hd: hd,
|
|
||||||
isHidden: isHidden);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (hasAddrUse) {
|
final newAddresses = await _createNewAddresses(gap,
|
||||||
final addr = addrs.last.address;
|
startIndex: addressList.length, isHidden: isHidden, type: type);
|
||||||
hasAddrUse = await _hasAddressUsed(addr);
|
addAddresses(newAddresses);
|
||||||
|
|
||||||
if (!hasAddrUse) {
|
final addressesWithHistory = await Future.wait(newAddresses
|
||||||
break;
|
.map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
|
||||||
}
|
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
|
||||||
|
|
||||||
final start = addrs.length;
|
if (isLastAddressUsed) {
|
||||||
final count = start + gap;
|
discoverAddresses(addressList, isHidden, getAddressHistory, type: type);
|
||||||
final batch = await _createNewAddresses(count, startIndex: start, hd: hd, isHidden: isHidden);
|
|
||||||
addrs.addAll(batch);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addresses.length < addrs.length) {
|
|
||||||
_addAddresses(addrs);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _generateInitialAddresses() async {
|
Future<void> _generateInitialAddresses(
|
||||||
|
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
|
||||||
var countOfReceiveAddresses = 0;
|
var countOfReceiveAddresses = 0;
|
||||||
var countOfHiddenAddresses = 0;
|
var countOfHiddenAddresses = 0;
|
||||||
|
|
||||||
addresses.forEach((addr) {
|
_addresses.forEach((addr) {
|
||||||
if (addr.isHidden) {
|
if (addr.type == type) {
|
||||||
countOfHiddenAddresses += 1;
|
if (addr.isHidden) {
|
||||||
return;
|
countOfHiddenAddresses += 1;
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
countOfReceiveAddresses += 1;
|
countOfReceiveAddresses += 1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
|
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
|
||||||
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
|
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
|
||||||
final newAddresses = await _createNewAddresses(addressesCount,
|
final newAddresses = await _createNewAddresses(addressesCount,
|
||||||
startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false);
|
startIndex: countOfReceiveAddresses, isHidden: false, type: type);
|
||||||
addresses.addAll(newAddresses);
|
addAddresses(newAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
|
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
|
||||||
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
|
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
|
||||||
final newAddresses = await _createNewAddresses(addressesCount,
|
final newAddresses = await _createNewAddresses(addressesCount,
|
||||||
startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true);
|
startIndex: countOfHiddenAddresses, isHidden: true, type: type);
|
||||||
addresses.addAll(newAddresses);
|
addAddresses(newAddresses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
|
Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
|
||||||
{required bitcoin.HDWallet hd, int startIndex = 0, bool isHidden = false}) async {
|
{int startIndex = 0, bool isHidden = false, BitcoinAddressType? type}) async {
|
||||||
final list = <BitcoinAddressRecord>[];
|
final list = <BitcoinAddressRecord>[];
|
||||||
|
|
||||||
for (var i = startIndex; i < count + startIndex; i++) {
|
for (var i = startIndex; i < count + startIndex; i++) {
|
||||||
final address =
|
final address = BitcoinAddressRecord(
|
||||||
BitcoinAddressRecord(getAddress(index: i, hd: hd), index: i, isHidden: isHidden);
|
getAddress(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
|
||||||
|
index: i,
|
||||||
|
isHidden: isHidden,
|
||||||
|
type: type ?? addressPageType,
|
||||||
|
network: network,
|
||||||
|
);
|
||||||
list.add(address);
|
list.add(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
@action
|
||||||
final addressesSet = this.addresses.toSet();
|
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
||||||
|
final addressesSet = this._addresses.toSet();
|
||||||
addressesSet.addAll(addresses);
|
addressesSet.addAll(addresses);
|
||||||
this.addresses.removeRange(0, this.addresses.length);
|
this._addresses.clear();
|
||||||
this.addresses.addAll(addressesSet);
|
this._addresses.addAll(addressesSet);
|
||||||
|
updateAddressesByMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _hasAddressUsed(String address) async {
|
void _validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
|
||||||
final sh = scriptHash(address, networkType: networkType);
|
|
||||||
final transactionHistory = await electrumClient.getHistory(sh);
|
|
||||||
return transactionHistory.isNotEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
void validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
|
|
||||||
addrWithTransactions.forEach((element) {
|
addrWithTransactions.forEach((element) {
|
||||||
if (element.address != getAddress(index: element.index, hd: mainHd)) element.isHidden = true;
|
if (element.address !=
|
||||||
|
getAddress(index: element.index, hd: mainHd, addressType: element.type))
|
||||||
|
element.isHidden = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> setAddressType(BitcoinAddressType type) async {
|
||||||
|
_addressPageType = type;
|
||||||
|
updateAddressesByMatch();
|
||||||
|
walletInfo.addressPageType = addressPageType.toString();
|
||||||
|
await walletInfo.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isAddressPageTypeMatch(BitcoinAddressRecord addressRecord) {
|
||||||
|
return _isAddressByType(addressRecord, addressPageType);
|
||||||
|
}
|
||||||
|
|
||||||
|
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
||||||
|
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/utils/file.dart';
|
import 'package:cw_core/utils/file.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
class ElectrumWallletSnapshot {
|
class ElectrumWalletSnapshot {
|
||||||
ElectrumWallletSnapshot({
|
ElectrumWalletSnapshot({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.password,
|
required this.password,
|
||||||
|
@ -14,19 +15,24 @@ class ElectrumWallletSnapshot {
|
||||||
required this.addresses,
|
required this.addresses,
|
||||||
required this.balance,
|
required this.balance,
|
||||||
required this.regularAddressIndex,
|
required this.regularAddressIndex,
|
||||||
required this.changeAddressIndex});
|
required this.changeAddressIndex,
|
||||||
|
required this.addressPageType,
|
||||||
|
required this.network,
|
||||||
|
});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final String password;
|
final String password;
|
||||||
final WalletType type;
|
final WalletType type;
|
||||||
|
final String addressPageType;
|
||||||
|
final BasedUtxoNetwork network;
|
||||||
|
|
||||||
String mnemonic;
|
String mnemonic;
|
||||||
List<BitcoinAddressRecord> addresses;
|
List<BitcoinAddressRecord> addresses;
|
||||||
ElectrumBalance balance;
|
ElectrumBalance balance;
|
||||||
int regularAddressIndex;
|
Map<String, int> regularAddressIndex;
|
||||||
int changeAddressIndex;
|
Map<String, int> changeAddressIndex;
|
||||||
|
|
||||||
static Future<ElectrumWallletSnapshot> load(String name, WalletType type, String password) async {
|
static Future<ElectrumWalletSnapshot> load(String name, WalletType type, String password, BasedUtxoNetwork? network) async {
|
||||||
final path = await pathForWallet(name: name, type: type);
|
final path = await pathForWallet(name: name, type: type);
|
||||||
final jsonSource = await read(path: path, password: password);
|
final jsonSource = await read(path: path, password: password);
|
||||||
final data = json.decode(jsonSource) as Map;
|
final data = json.decode(jsonSource) as Map;
|
||||||
|
@ -34,26 +40,39 @@ class ElectrumWallletSnapshot {
|
||||||
final mnemonic = data['mnemonic'] as String;
|
final mnemonic = data['mnemonic'] as String;
|
||||||
final addresses = addressesTmp
|
final addresses = addressesTmp
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr))
|
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
|
||||||
.toList();
|
.toList();
|
||||||
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
|
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
|
||||||
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||||
var regularAddressIndex = 0;
|
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||||
var changeAddressIndex = 0;
|
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
regularAddressIndex = int.parse(data['account_index'] as String? ?? '0');
|
regularAddressIndexByType = {
|
||||||
changeAddressIndex = int.parse(data['change_address_index'] as String? ?? '0');
|
SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
|
||||||
} catch (_) {}
|
};
|
||||||
|
changeAddressIndexByType = {
|
||||||
|
SegwitAddresType.p2wpkh.toString():
|
||||||
|
int.parse(data['change_address_index'] as String? ?? '0')
|
||||||
|
};
|
||||||
|
} catch (_) {
|
||||||
|
try {
|
||||||
|
regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {};
|
||||||
|
changeAddressIndexByType = data["change_address_index"] as Map<String, int>? ?? {};
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
return ElectrumWallletSnapshot(
|
return ElectrumWalletSnapshot(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
password: password,
|
password: password,
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
addresses: addresses,
|
addresses: addresses,
|
||||||
balance: balance,
|
balance: balance,
|
||||||
regularAddressIndex: regularAddressIndex,
|
regularAddressIndex: regularAddressIndexByType,
|
||||||
changeAddressIndex: changeAddressIndex);
|
changeAddressIndex: changeAddressIndexByType,
|
||||||
|
addressPageType: data['address_page_type'] as String? ?? SegwitAddresType.p2wpkh.toString(),
|
||||||
|
network: data['network_type'] == 'testnet' ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
@ -20,17 +21,18 @@ part 'litecoin_wallet.g.dart';
|
||||||
class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
|
class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
|
||||||
|
|
||||||
abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
LitecoinWalletBase(
|
LitecoinWalletBase({
|
||||||
{required String mnemonic,
|
required String mnemonic,
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required Uint8List seedBytes,
|
required Uint8List seedBytes,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
String? addressPageType,
|
||||||
ElectrumBalance? initialBalance,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
int initialRegularAddressIndex = 0,
|
ElectrumBalance? initialBalance,
|
||||||
int initialChangeAddressIndex = 0})
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
: super(
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
|
}) : super(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
|
@ -41,41 +43,42 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
currency: CryptoCurrency.ltc) {
|
currency: CryptoCurrency.ltc) {
|
||||||
walletAddresses = LitecoinWalletAddresses(
|
walletAddresses = LitecoinWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
electrumClient: electrumClient,
|
electrumClient: electrumClient,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
mainHd: hd,
|
mainHd: hd,
|
||||||
sideHd: bitcoin.HDWallet
|
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
|
||||||
.fromSeed(seedBytes, network: networkType)
|
network: network,
|
||||||
.derivePath("m/0'/1"),
|
);
|
||||||
networkType: networkType,);
|
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<LitecoinWallet> create({
|
static Future<LitecoinWallet> create(
|
||||||
required String mnemonic,
|
{required String mnemonic,
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
String? addressPageType,
|
||||||
ElectrumBalance? initialBalance,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
int initialRegularAddressIndex = 0,
|
ElectrumBalance? initialBalance,
|
||||||
int initialChangeAddressIndex = 0
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
}) async {
|
Map<String, int>? initialChangeAddressIndex}) async {
|
||||||
return LitecoinWallet(
|
return LitecoinWallet(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex);
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
addressPageType: addressPageType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<LitecoinWallet> open({
|
static Future<LitecoinWallet> open({
|
||||||
|
@ -84,17 +87,20 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
}) async {
|
}) async {
|
||||||
final snp = await ElectrumWallletSnapshot.load (name, walletInfo.type, password);
|
final snp =
|
||||||
|
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
|
||||||
return LitecoinWallet(
|
return LitecoinWallet(
|
||||||
mnemonic: snp.mnemonic,
|
mnemonic: snp.mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses,
|
initialAddresses: snp.addresses,
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp.balance,
|
||||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
|
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex);
|
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||||
|
addressPageType: snp.addressPageType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,39 +1,28 @@
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:cw_bitcoin/electrum.dart';
|
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
part 'litecoin_wallet_addresses.g.dart';
|
part 'litecoin_wallet_addresses.g.dart';
|
||||||
|
|
||||||
class LitecoinWalletAddresses = LitecoinWalletAddressesBase
|
class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
|
||||||
with _$LitecoinWalletAddresses;
|
|
||||||
|
|
||||||
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||||
with Store {
|
|
||||||
LitecoinWalletAddressesBase(
|
LitecoinWalletAddressesBase(
|
||||||
WalletInfo walletInfo,
|
WalletInfo walletInfo, {
|
||||||
{required bitcoin.HDWallet mainHd,
|
required super.mainHd,
|
||||||
required bitcoin.HDWallet sideHd,
|
required super.sideHd,
|
||||||
required bitcoin.NetworkType networkType,
|
required super.network,
|
||||||
required ElectrumClient electrumClient,
|
required super.electrumClient,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
super.initialAddresses,
|
||||||
int initialRegularAddressIndex = 0,
|
super.initialRegularAddressIndex,
|
||||||
int initialChangeAddressIndex = 0})
|
super.initialChangeAddressIndex,
|
||||||
: super(
|
}) : super(walletInfo);
|
||||||
walletInfo,
|
|
||||||
initialAddresses: initialAddresses,
|
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
|
||||||
mainHd: mainHd,
|
|
||||||
sideHd: sideHd,
|
|
||||||
electrumClient: electrumClient,
|
|
||||||
networkType: networkType);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
|
String getAddress(
|
||||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
||||||
}
|
generateP2WPKHAddress(hd: hd, index: index, network: network);
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:io';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
import 'package:cw_bitcoin/litecoin_wallet.dart';
|
import 'package:cw_bitcoin/litecoin_wallet.dart';
|
||||||
import 'package:cw_core/wallet_service.dart';
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
@ -25,7 +25,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
WalletType getType() => WalletType.litecoin;
|
WalletType getType() => WalletType.litecoin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
final wallet = await LitecoinWalletBase.create(
|
final wallet = await LitecoinWalletBase.create(
|
||||||
mnemonic: await generateMnemonic(),
|
mnemonic: await generateMnemonic(),
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
|
@ -94,14 +94,14 @@ class LitecoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> restoreFromKeys(
|
Future<LitecoinWallet> restoreFromKeys(
|
||||||
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> restoreFromSeed(
|
Future<LitecoinWallet> restoreFromSeed(
|
||||||
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
|
||||||
if (!validateMnemonic(credentials.mnemonic)) {
|
if (!validateMnemonic(credentials.mnemonic)) {
|
||||||
throw BitcoinMnemonicIsIncorrectException();
|
throw LitecoinMnemonicIsIncorrectException();
|
||||||
}
|
}
|
||||||
|
|
||||||
final wallet = await LitecoinWalletBase.create(
|
final wallet = await LitecoinWalletBase.create(
|
||||||
|
|
|
@ -3,3 +3,9 @@ class BitcoinMnemonicIsIncorrectException implements Exception {
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LitecoinMnemonicIsIncorrectException implements Exception {
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'Litecoin mnemonic has incorrect format. Mnemonic should contain 24 words separated by space.';
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
|
import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_core/pending_transaction.dart';
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_bitcoin/electrum.dart';
|
import 'package:cw_bitcoin/electrum.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||||
|
@ -9,22 +9,21 @@ import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
class PendingBitcoinTransaction with PendingTransaction {
|
class PendingBitcoinTransaction with PendingTransaction {
|
||||||
PendingBitcoinTransaction(this._tx, this.type,
|
PendingBitcoinTransaction(this._tx, this.type,
|
||||||
{required this.electrumClient,
|
{required this.electrumClient, required this.amount, required this.fee, this.network})
|
||||||
required this.amount,
|
|
||||||
required this.fee})
|
|
||||||
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||||
|
|
||||||
final WalletType type;
|
final WalletType type;
|
||||||
final bitcoin.Transaction _tx;
|
final BtcTransaction _tx;
|
||||||
final ElectrumClient electrumClient;
|
final ElectrumClient electrumClient;
|
||||||
final int amount;
|
final int amount;
|
||||||
final int fee;
|
final int fee;
|
||||||
|
final BasedUtxoNetwork? network;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get id => _tx.getId();
|
String get id => _tx.txId();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get hex => _tx.toHex();
|
String get hex => _tx.serialize();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get amountFormatted => bitcoinAmountToString(amount: amount);
|
String get amountFormatted => bitcoinAmountToString(amount: amount);
|
||||||
|
@ -36,18 +35,16 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> commit() async {
|
Future<void> commit() async {
|
||||||
final result =
|
final result = await electrumClient.broadcastTransaction(transactionRaw: hex, network: network);
|
||||||
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
|
|
||||||
|
|
||||||
if (result.isEmpty) {
|
if (result.isEmpty) {
|
||||||
throw BitcoinCommitTransactionException();
|
throw BitcoinCommitTransactionException();
|
||||||
}
|
}
|
||||||
|
|
||||||
_listeners?.forEach((listener) => listener(transactionInfo()));
|
_listeners.forEach((listener) => listener(transactionInfo()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addListener(
|
void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
|
||||||
void Function(ElectrumTransactionInfo transaction) listener) =>
|
|
||||||
_listeners.add(listener);
|
_listeners.add(listener);
|
||||||
|
|
||||||
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
|
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
String scriptHash(String address, {required bitcoin.NetworkType networkType}) {
|
String scriptHash(String address, {required BasedUtxoNetwork network}) {
|
||||||
final outputScript =
|
final outputScript = addressToOutputScript(address: address, network: network);
|
||||||
bitcoin.Address.addressToOutputScript(address, networkType);
|
|
||||||
final parts = sha256.convert(outputScript).toString().split('');
|
final parts = sha256.convert(outputScript).toString().split('');
|
||||||
var res = '';
|
var res = '';
|
||||||
|
|
||||||
|
|
|
@ -1,55 +1,33 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||||
import 'package:hex/hex.dart';
|
import 'package:hex/hex.dart';
|
||||||
|
|
||||||
bitcoin.PaymentData generatePaymentData(
|
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, required int index}) =>
|
||||||
{required bitcoin.HDWallet hd, required int index}) =>
|
PaymentData(pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
|
||||||
PaymentData(
|
|
||||||
pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
|
|
||||||
|
|
||||||
bitcoin.ECPair generateKeyPair(
|
ECPrivate generateECPrivate(
|
||||||
{required bitcoin.HDWallet hd,
|
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||||
required int index,
|
ECPrivate.fromWif(hd.derive(index).wif!, netVersion: network.wifNetVer);
|
||||||
required bitcoin.NetworkType network}) =>
|
|
||||||
bitcoin.ECPair.fromWIF(hd.derive(index).wif!, network: network);
|
|
||||||
|
|
||||||
String generateP2WPKHAddress(
|
String generateP2WPKHAddress(
|
||||||
{required bitcoin.HDWallet hd,
|
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||||
required int index,
|
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhAddress().toAddress(network);
|
||||||
required bitcoin.NetworkType networkType}) =>
|
|
||||||
bitcoin
|
|
||||||
.P2WPKH(
|
|
||||||
data: PaymentData(
|
|
||||||
pubkey:
|
|
||||||
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))),
|
|
||||||
network: networkType)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
|
|
||||||
String generateP2WPKHAddressByPath(
|
String generateP2SHAddress(
|
||||||
{required bitcoin.HDWallet hd,
|
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||||
required String path,
|
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhInP2sh().toAddress(network);
|
||||||
required bitcoin.NetworkType networkType}) =>
|
|
||||||
bitcoin
|
String generateP2WSHAddress(
|
||||||
.P2WPKH(
|
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||||
data: PaymentData(
|
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wshAddress().toAddress(network);
|
||||||
pubkey:
|
|
||||||
Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey!))),
|
|
||||||
network: networkType)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
|
|
||||||
String generateP2PKHAddress(
|
String generateP2PKHAddress(
|
||||||
{required bitcoin.HDWallet hd,
|
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||||
required int index,
|
ECPublic.fromHex(hd.derive(index).pubKey!).toP2pkhAddress().toAddress(network);
|
||||||
required bitcoin.NetworkType networkType}) =>
|
|
||||||
bitcoin
|
String generateP2TRAddress(
|
||||||
.P2PKH(
|
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||||
data: PaymentData(
|
ECPublic.fromHex(hd.derive(index).pubKey!).toTaprootAddress().toAddress(network);
|
||||||
pubkey:
|
|
||||||
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))),
|
|
||||||
network: networkType)
|
|
||||||
.data
|
|
||||||
.address!;
|
|
||||||
|
|
|
@ -21,18 +21,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: args
|
name: args
|
||||||
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
|
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.4.2"
|
||||||
asn1lib:
|
asn1lib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: asn1lib
|
name: asn1lib
|
||||||
sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039
|
sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.5.2"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -75,6 +75,15 @@ packages:
|
||||||
url: "https://github.com/cake-tech/bitbox-flutter.git"
|
url: "https://github.com/cake-tech/bitbox-flutter.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
bitcoin_base:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: cake-update-v1
|
||||||
|
resolved-ref: "9611e9db77e92a8434e918cdfb620068f6fcb1aa"
|
||||||
|
url: "https://github.com/cake-tech/bitcoin_base.git"
|
||||||
|
source: git
|
||||||
|
version: "4.0.0"
|
||||||
bitcoin_flutter:
|
bitcoin_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -84,6 +93,14 @@ packages:
|
||||||
url: "https://github.com/cake-tech/bitcoin_flutter.git"
|
url: "https://github.com/cake-tech/bitcoin_flutter.git"
|
||||||
source: git
|
source: git
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
blockchain_utils:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: blockchain_utils
|
||||||
|
sha256: "9701dfaa74caad4daae1785f1ec4445cf7fb94e45620bc3a4aca1b9b281dc6c9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -104,10 +121,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build
|
name: build
|
||||||
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
|
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.4.1"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -120,10 +137,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_daemon
|
name: build_daemon
|
||||||
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
|
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "4.0.1"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
@ -136,18 +153,18 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
|
sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.3"
|
version: "2.4.8"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
|
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.2.7"
|
version: "7.2.10"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -160,10 +177,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: built_value
|
name: built_value
|
||||||
sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725"
|
sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.4.3"
|
version: "8.9.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -176,10 +193,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: checked_yaml
|
name: checked_yaml
|
||||||
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
|
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -192,10 +209,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_builder
|
name: code_builder
|
||||||
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
|
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.0"
|
version: "4.10.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -216,18 +233,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.3"
|
||||||
cryptography:
|
cryptography:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cryptography
|
name: cryptography
|
||||||
sha256: e0e37f79665cd5c86e8897f9abe1accfe813c0cc5299dab22256e22fddc1fef8
|
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.5.0"
|
||||||
cw_core:
|
cw_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -247,10 +264,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: encrypt
|
name: encrypt
|
||||||
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
|
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.1"
|
version: "5.0.3"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -263,10 +280,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
|
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.1.0"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -292,10 +309,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_mobx
|
name: flutter_mobx
|
||||||
sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
|
sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.6+5"
|
version: "2.2.0+2"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -313,18 +330,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: glob
|
name: glob
|
||||||
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
|
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: graphs
|
name: graphs
|
||||||
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
|
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.3.1"
|
||||||
hex:
|
hex:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -401,18 +418,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
|
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.8.1"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: logging
|
name: logging
|
||||||
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
|
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.2.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -449,18 +466,26 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: mobx
|
name: mobx
|
||||||
sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a
|
sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3+1"
|
version: "2.3.0+1"
|
||||||
mobx_codegen:
|
mobx_codegen:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: mobx_codegen
|
name: mobx_codegen
|
||||||
sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181"
|
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.3.0"
|
||||||
|
nested:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nested
|
||||||
|
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -481,26 +506,26 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.2"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -513,10 +538,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -529,26 +554,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
|
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.4"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.8"
|
||||||
pointycastle:
|
pointycastle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pointycastle
|
name: pointycastle
|
||||||
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
|
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.6.2"
|
version: "3.7.4"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -557,30 +582,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
process:
|
provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: process
|
name: provider
|
||||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.4"
|
version: "6.1.1"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pub_semver
|
name: pub_semver
|
||||||
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17"
|
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.4"
|
||||||
pubspec_parse:
|
pubspec_parse:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pubspec_parse
|
name: pubspec_parse
|
||||||
sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a"
|
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.3"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -593,18 +618,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf
|
name: shelf
|
||||||
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c
|
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
shelf_web_socket:
|
shelf_web_socket:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf_web_socket
|
name: shelf_web_socket
|
||||||
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8
|
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.4"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -702,10 +727,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: typed_data
|
name: typed_data
|
||||||
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.2"
|
||||||
unorm_dart:
|
unorm_dart:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -726,42 +751,42 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: watcher
|
name: watcher
|
||||||
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.1.0"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b
|
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.4.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
|
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "5.0.9"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
|
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0+3"
|
version: "1.0.4"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: yaml
|
name: yaml
|
||||||
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
|
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.0 <4.0.0"
|
dart: ">=3.0.0 <4.0.0"
|
||||||
flutter: ">=3.7.0"
|
flutter: ">=3.10.0"
|
||||||
|
|
|
@ -30,6 +30,11 @@ dependencies:
|
||||||
rxdart: ^0.27.5
|
rxdart: ^0.27.5
|
||||||
unorm_dart: ^0.2.0
|
unorm_dart: ^0.2.0
|
||||||
cryptography: ^2.0.5
|
cryptography: ^2.0.5
|
||||||
|
bitcoin_base:
|
||||||
|
git:
|
||||||
|
url: https://github.com/cake-tech/bitcoin_base.git
|
||||||
|
ref: cake-update-v1
|
||||||
|
blockchain_utils: ^1.6.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -28,17 +28,18 @@ part 'bitcoin_cash_wallet.g.dart';
|
||||||
class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet;
|
class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet;
|
||||||
|
|
||||||
abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
BitcoinCashWalletBase(
|
BitcoinCashWalletBase({
|
||||||
{required String mnemonic,
|
required String mnemonic,
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required Uint8List seedBytes,
|
required Uint8List seedBytes,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
String? addressPageType,
|
||||||
ElectrumBalance? initialBalance,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
int initialRegularAddressIndex = 0,
|
ElectrumBalance? initialBalance,
|
||||||
int initialChangeAddressIndex = 0})
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
: super(
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
|
}) : super(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
|
@ -48,40 +49,43 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
currency: CryptoCurrency.bch) {
|
currency: CryptoCurrency.bch) {
|
||||||
walletAddresses = BitcoinCashWalletAddresses(walletInfo,
|
walletAddresses = BitcoinCashWalletAddresses(
|
||||||
electrumClient: electrumClient,
|
walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
electrumClient: electrumClient,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialAddresses: initialAddresses,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
mainHd: hd,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
sideHd: bitcoin.HDWallet.fromSeed(seedBytes)
|
mainHd: hd,
|
||||||
.derivePath("m/44'/145'/0'/1"),
|
sideHd: bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/1"),
|
||||||
networkType: networkType);
|
network: network,
|
||||||
|
);
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Future<BitcoinCashWallet> create(
|
static Future<BitcoinCashWallet> create(
|
||||||
{required String mnemonic,
|
{required String mnemonic,
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
String? addressPageType,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
int initialRegularAddressIndex = 0,
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
int initialChangeAddressIndex = 0}) async {
|
Map<String, int>? initialChangeAddressIndex}) async {
|
||||||
return BitcoinCashWallet(
|
return BitcoinCashWallet(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: await Mnemonic.toSeed(mnemonic),
|
seedBytes: await Mnemonic.toSeed(mnemonic),
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex);
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
addressPageType: addressPageType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<BitcoinCashWallet> open({
|
static Future<BitcoinCashWallet> open({
|
||||||
|
@ -90,17 +94,20 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
}) async {
|
}) async {
|
||||||
final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
|
final snp = await ElectrumWalletSnapshot.load(
|
||||||
|
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
|
||||||
return BitcoinCashWallet(
|
return BitcoinCashWallet(
|
||||||
mnemonic: snp.mnemonic,
|
mnemonic: snp.mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses,
|
initialAddresses: snp.addresses,
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp.balance,
|
||||||
seedBytes: await Mnemonic.toSeed(snp.mnemonic),
|
seedBytes: await Mnemonic.toSeed(snp.mnemonic),
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex);
|
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||||
|
addressPageType: snp.addressPageType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -273,20 +280,18 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
electrumClient: electrumClient, amount: amount, fee: fee);
|
electrumClient: electrumClient, amount: amount, fee: fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
bitbox.ECPair generateKeyPair(
|
bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) =>
|
||||||
{required bitcoin.HDWallet hd,
|
|
||||||
required int index}) =>
|
|
||||||
bitbox.ECPair.fromWIF(hd.derive(index).wif!);
|
bitbox.ECPair.fromWIF(hd.derive(index).wif!);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int feeAmountForPriority(
|
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount,
|
||||||
BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
|
{int? size}) =>
|
||||||
feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
|
feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
|
||||||
|
|
||||||
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
|
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) =>
|
||||||
feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
|
feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
|
||||||
|
|
||||||
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
|
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
|
||||||
int inputsCount = 0;
|
int inputsCount = 0;
|
||||||
int totalValue = 0;
|
int totalValue = 0;
|
||||||
|
|
||||||
|
@ -326,9 +331,10 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
@override
|
@override
|
||||||
String signMessage(String message, {String? address = null}) {
|
String signMessage(String message, {String? address = null}) {
|
||||||
final index = address != null
|
final index = address != null
|
||||||
? walletAddresses.addresses
|
? walletAddresses.allAddresses
|
||||||
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
|
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
|
||||||
.index : null;
|
.index
|
||||||
|
: null;
|
||||||
final HD = index == null ? hd : hd.derive(index);
|
final HD = index == null ? hd : hd.derive(index);
|
||||||
return base64Encode(HD.signMessage(message));
|
return base64Encode(HD.signMessage(message));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -11,24 +10,19 @@ part 'bitcoin_cash_wallet_addresses.g.dart';
|
||||||
class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses;
|
class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses;
|
||||||
|
|
||||||
abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||||
BitcoinCashWalletAddressesBase(WalletInfo walletInfo,
|
BitcoinCashWalletAddressesBase(
|
||||||
{required bitcoin.HDWallet mainHd,
|
WalletInfo walletInfo, {
|
||||||
required bitcoin.HDWallet sideHd,
|
required super.mainHd,
|
||||||
required bitcoin.NetworkType networkType,
|
required super.sideHd,
|
||||||
required ElectrumClient electrumClient,
|
required super.network,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
required super.electrumClient,
|
||||||
int initialRegularAddressIndex = 0,
|
super.initialAddresses,
|
||||||
int initialChangeAddressIndex = 0})
|
super.initialRegularAddressIndex,
|
||||||
: super(walletInfo,
|
super.initialChangeAddressIndex,
|
||||||
initialAddresses: initialAddresses,
|
}) : super(walletInfo);
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
|
||||||
mainHd: mainHd,
|
|
||||||
sideHd: sideHd,
|
|
||||||
electrumClient: electrumClient,
|
|
||||||
networkType: networkType);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
|
String getAddress(
|
||||||
generateP2PKHAddress(hd: hd, index: index, networkType: networkType);
|
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
||||||
|
generateP2PKHAddress(hd: hd, index: index, network: network);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,7 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:bip39/bip39.dart';
|
import 'package:bip39/bip39.dart';
|
||||||
import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
|
import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
|
||||||
import 'package:cw_core/balance.dart';
|
|
||||||
import 'package:cw_core/pathForWallet.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/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -15,8 +12,7 @@ import 'package:collection/collection.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
|
class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
|
||||||
BitcoinCashRestoreWalletFromSeedCredentials,
|
BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials> {
|
||||||
BitcoinCashRestoreWalletFromWIFCredentials> {
|
|
||||||
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
|
@ -30,13 +26,9 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinCashWallet> create(
|
Future<BitcoinCashWallet> create(credentials, {bool? isTestnet}) async {
|
||||||
credentials) async {
|
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||||
final strength = (credentials.seedPhraseLength == 12)
|
|
||||||
? 128
|
|
||||||
: (credentials.seedPhraseLength == 24)
|
|
||||||
? 256
|
|
||||||
: 128;
|
|
||||||
final wallet = await BitcoinCashWalletBase.create(
|
final wallet = await BitcoinCashWalletBase.create(
|
||||||
mnemonic: await Mnemonic.generate(strength: strength),
|
mnemonic: await Mnemonic.generate(strength: strength),
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
|
@ -49,21 +41,25 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinCashWallet> openWallet(String name, String password) async {
|
Future<BitcoinCashWallet> openWallet(String name, String password) async {
|
||||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
final walletInfo = walletInfoSource.values
|
||||||
(info) => info.id == WalletBase.idFor(name, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final wallet = await BitcoinCashWalletBase.open(
|
final wallet = await BitcoinCashWalletBase.open(
|
||||||
password: password, name: name, walletInfo: walletInfo,
|
password: password,
|
||||||
|
name: name,
|
||||||
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
saveBackup(name);
|
saveBackup(name);
|
||||||
return wallet;
|
return wallet;
|
||||||
} catch(_) {
|
} catch (_) {
|
||||||
await restoreWalletFilesFromBackup(name);
|
await restoreWalletFilesFromBackup(name);
|
||||||
final wallet = await BitcoinCashWalletBase.open(
|
final wallet = await BitcoinCashWalletBase.open(
|
||||||
password: password, name: name, walletInfo: walletInfo,
|
password: password,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
name: name,
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
@ -71,17 +67,16 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> remove(String wallet) async {
|
Future<void> remove(String wallet) async {
|
||||||
File(await pathForWalletDir(name: wallet, type: getType()))
|
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||||
.delete(recursive: true);
|
final walletInfo = walletInfoSource.values
|
||||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||||
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
|
||||||
await walletInfoSource.delete(walletInfo.key);
|
await walletInfoSource.delete(walletInfo.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> rename(String currentName, String password, String newName) async {
|
Future<void> rename(String currentName, String password, String newName) async {
|
||||||
final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
|
final currentWalletInfo = walletInfoSource.values
|
||||||
(info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||||
final currentWallet = await BitcoinCashWalletBase.open(
|
final currentWallet = await BitcoinCashWalletBase.open(
|
||||||
password: password,
|
password: password,
|
||||||
name: currentName,
|
name: currentName,
|
||||||
|
@ -99,15 +94,14 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinCashWallet>
|
Future<BitcoinCashWallet> restoreFromKeys(credentials, {bool? isTestnet}) {
|
||||||
restoreFromKeys(credentials) {
|
|
||||||
// TODO: implement restoreFromKeys
|
// TODO: implement restoreFromKeys
|
||||||
throw UnimplementedError('restoreFromKeys() is not implemented');
|
throw UnimplementedError('restoreFromKeys() is not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinCashWallet> restoreFromSeed(
|
Future<BitcoinCashWallet> restoreFromSeed(BitcoinCashRestoreWalletFromSeedCredentials credentials,
|
||||||
BitcoinCashRestoreWalletFromSeedCredentials credentials) async {
|
{bool? isTestnet}) async {
|
||||||
if (!validateMnemonic(credentials.mnemonic)) {
|
if (!validateMnemonic(credentials.mnemonic)) {
|
||||||
throw BitcoinCashMnemonicIsIncorrectException();
|
throw BitcoinCashMnemonicIsIncorrectException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,10 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||||
ref: master
|
ref: master
|
||||||
bitcoin_base: ^3.0.1
|
bitcoin_base:
|
||||||
|
git:
|
||||||
|
url: https://github.com/cake-tech/bitcoin_base.git
|
||||||
|
ref: cake-update-v1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,9 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
required this.decimals,
|
required this.decimals,
|
||||||
this.fullName,
|
this.fullName,
|
||||||
this.iconPath,
|
this.iconPath,
|
||||||
this.tag})
|
this.tag,
|
||||||
|
this.enabled = false,
|
||||||
|
})
|
||||||
: super(title: title, raw: raw);
|
: super(title: title, raw: raw);
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
|
@ -17,6 +19,9 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
final String? fullName;
|
final String? fullName;
|
||||||
final String? iconPath;
|
final String? iconPath;
|
||||||
final int decimals;
|
final int decimals;
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
|
set enabled(bool value) => this.enabled = value;
|
||||||
|
|
||||||
static const all = [
|
static const all = [
|
||||||
CryptoCurrency.xmr,
|
CryptoCurrency.xmr,
|
||||||
|
@ -96,6 +101,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
CryptoCurrency.usdtPoly,
|
CryptoCurrency.usdtPoly,
|
||||||
CryptoCurrency.usdcEPoly,
|
CryptoCurrency.usdcEPoly,
|
||||||
CryptoCurrency.kaspa,
|
CryptoCurrency.kaspa,
|
||||||
|
CryptoCurrency.digibyte,
|
||||||
|
CryptoCurrency.usdtSol,
|
||||||
];
|
];
|
||||||
|
|
||||||
static const havenCurrencies = [
|
static const havenCurrencies = [
|
||||||
|
@ -207,7 +214,9 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
static const banano = CryptoCurrency(title: 'BAN', fullName: 'Banano', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png', decimals: 29);
|
static const banano = CryptoCurrency(title: 'BAN', fullName: 'Banano', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png', decimals: 29);
|
||||||
static const usdtPoly = CryptoCurrency(title: 'USDT', tag: 'POLY', fullName: 'Tether USD (PoS)', raw: 87, name: 'usdtpoly', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
static const usdtPoly = CryptoCurrency(title: 'USDT', tag: 'POLY', fullName: 'Tether USD (PoS)', raw: 87, name: 'usdtpoly', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||||
static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POLY', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POLY', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||||
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kaspa', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
|
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: 90, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||||
|
|
||||||
|
|
||||||
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
||||||
|
@ -238,7 +247,16 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
return CryptoCurrency._rawCurrencyMap[raw]!;
|
return CryptoCurrency._rawCurrencyMap[raw]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CryptoCurrency fromString(String name) {
|
// TODO: refactor this
|
||||||
|
static CryptoCurrency fromString(String name, {CryptoCurrency? walletCurrency}) {
|
||||||
|
try {
|
||||||
|
return CryptoCurrency.all.firstWhere((element) =>
|
||||||
|
element.title.toLowerCase() == name &&
|
||||||
|
(element.tag == null ||
|
||||||
|
element.tag == walletCurrency?.title ||
|
||||||
|
element.tag == walletCurrency?.tag));
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
if (CryptoCurrency._nameCurrencyMap[name.toLowerCase()] == null) {
|
if (CryptoCurrency._nameCurrencyMap[name.toLowerCase()] == null) {
|
||||||
final s = 'Unexpected token: $name for CryptoCurrency fromString';
|
final s = 'Unexpected token: $name for CryptoCurrency fromString';
|
||||||
throw ArgumentError.value(name, 'name', s);
|
throw ArgumentError.value(name, 'name', s);
|
||||||
|
|
|
@ -21,6 +21,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
|
||||||
return CryptoCurrency.banano;
|
return CryptoCurrency.banano;
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return CryptoCurrency.maticpoly;
|
return CryptoCurrency.maticpoly;
|
||||||
|
case WalletType.solana:
|
||||||
|
return CryptoCurrency.sol;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
||||||
}
|
}
|
||||||
|
|
13
cw_core/lib/enumerate.dart
Normal file
13
cw_core/lib/enumerate.dart
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
abstract class Enumerate {
|
||||||
|
String get value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
operator ==(other) {
|
||||||
|
if (identical(other, this)) return true;
|
||||||
|
if (other is! Enumerate) return false;
|
||||||
|
return other.runtimeType == runtimeType && value == other.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => value.hashCode;
|
||||||
|
}
|
|
@ -13,4 +13,5 @@ const ADDRESS_INFO_TYPE_ID = 11;
|
||||||
const ERC20_TOKEN_TYPE_ID = 12;
|
const ERC20_TOKEN_TYPE_ID = 12;
|
||||||
const NANO_ACCOUNT_TYPE_ID = 13;
|
const NANO_ACCOUNT_TYPE_ID = 13;
|
||||||
const POW_NODE_TYPE_ID = 14;
|
const POW_NODE_TYPE_ID = 14;
|
||||||
const DERIVATION_TYPE_TYPE_ID = 15;
|
const DERIVATION_TYPE_TYPE_ID = 15;
|
||||||
|
const SPL_TOKEN_TYPE_ID = 16;
|
||||||
|
|
|
@ -70,15 +70,10 @@ class Node extends HiveObject with Keyable {
|
||||||
Uri get uri {
|
Uri get uri {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return Uri.http(uriRaw, '');
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
return createUriFromElectrumAddress(uriRaw);
|
|
||||||
case WalletType.litecoin:
|
|
||||||
return createUriFromElectrumAddress(uriRaw);
|
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return Uri.http(uriRaw, '');
|
return Uri.http(uriRaw, '');
|
||||||
case WalletType.ethereum:
|
case WalletType.bitcoin:
|
||||||
return Uri.https(uriRaw, '');
|
case WalletType.litecoin:
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
return createUriFromElectrumAddress(uriRaw);
|
return createUriFromElectrumAddress(uriRaw);
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
|
@ -88,7 +83,9 @@ class Node extends HiveObject with Keyable {
|
||||||
} else {
|
} else {
|
||||||
return Uri.http(uriRaw, '');
|
return Uri.http(uriRaw, '');
|
||||||
}
|
}
|
||||||
|
case WalletType.ethereum:
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
|
case WalletType.solana:
|
||||||
return Uri.https(uriRaw, '');
|
return Uri.https(uriRaw, '');
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
||||||
|
@ -134,21 +131,17 @@ class Node extends HiveObject with Keyable {
|
||||||
try {
|
try {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return requestMoneroNode();
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
return requestElectrumServer();
|
|
||||||
case WalletType.litecoin:
|
|
||||||
return requestElectrumServer();
|
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return requestMoneroNode();
|
return requestMoneroNode();
|
||||||
case WalletType.ethereum:
|
|
||||||
return requestElectrumServer();
|
|
||||||
case WalletType.bitcoinCash:
|
|
||||||
return requestElectrumServer();
|
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
return requestNanoNode();
|
return requestNanoNode();
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
case WalletType.litecoin:
|
||||||
|
case WalletType.bitcoinCash:
|
||||||
|
case WalletType.ethereum:
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
|
case WalletType.solana:
|
||||||
return requestElectrumServer();
|
return requestElectrumServer();
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
Future<String> pathForWalletDir({required String name, required WalletType type}) async {
|
Future<String> pathForWalletDir({required String name, required WalletType type}) async {
|
||||||
|
|
21
cw_core/lib/receive_page_option.dart
Normal file
21
cw_core/lib/receive_page_option.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:cw_core/enumerate.dart';
|
||||||
|
|
||||||
|
class ReceivePageOption implements Enumerate {
|
||||||
|
static const mainnet = ReceivePageOption._('mainnet');
|
||||||
|
static const anonPayInvoice = ReceivePageOption._('anonPayInvoice');
|
||||||
|
static const anonPayDonationLink = ReceivePageOption._('anonPayDonationLink');
|
||||||
|
|
||||||
|
const ReceivePageOption._(this.value);
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReceivePageOptions = [
|
||||||
|
ReceivePageOption.mainnet,
|
||||||
|
ReceivePageOption.anonPayInvoice,
|
||||||
|
ReceivePageOption.anonPayDonationLink
|
||||||
|
];
|
|
@ -88,4 +88,6 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
||||||
Future<void> renameWalletFiles(String newWalletName);
|
Future<void> renameWalletFiles(String newWalletName);
|
||||||
|
|
||||||
String signMessage(String message, {String? address = null}) => throw UnimplementedError();
|
String signMessage(String message, {String? address = null}) => throw UnimplementedError();
|
||||||
|
|
||||||
|
bool? isTestnet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,12 @@ class WalletInfo extends HiveObject {
|
||||||
@HiveField(17)
|
@HiveField(17)
|
||||||
String? derivationPath;
|
String? derivationPath;
|
||||||
|
|
||||||
|
@HiveField(18)
|
||||||
|
String? addressPageType;
|
||||||
|
|
||||||
|
@HiveField(19)
|
||||||
|
String? network;
|
||||||
|
|
||||||
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
||||||
|
|
||||||
set yatLastUsedAddress(String address) {
|
set yatLastUsedAddress(String address) {
|
||||||
|
|
|
@ -9,11 +9,11 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
|
||||||
RFK extends WalletCredentials> {
|
RFK extends WalletCredentials> {
|
||||||
WalletType getType();
|
WalletType getType();
|
||||||
|
|
||||||
Future<WalletBase> create(N credentials);
|
Future<WalletBase> create(N credentials, {bool? isTestnet});
|
||||||
|
|
||||||
Future<WalletBase> restoreFromSeed(RFS credentials);
|
Future<WalletBase> restoreFromSeed(RFS credentials, {bool? isTestnet});
|
||||||
|
|
||||||
Future<WalletBase> restoreFromKeys(RFK credentials);
|
Future<WalletBase> restoreFromKeys(RFK credentials, {bool? isTestnet});
|
||||||
|
|
||||||
Future<WalletBase> openWallet(String name, String password);
|
Future<WalletBase> openWallet(String name, String password);
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ const walletTypes = [
|
||||||
WalletType.nano,
|
WalletType.nano,
|
||||||
WalletType.banano,
|
WalletType.banano,
|
||||||
WalletType.polygon,
|
WalletType.polygon,
|
||||||
|
WalletType.solana,
|
||||||
];
|
];
|
||||||
|
|
||||||
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
|
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
|
||||||
|
@ -46,7 +47,10 @@ enum WalletType {
|
||||||
bitcoinCash,
|
bitcoinCash,
|
||||||
|
|
||||||
@HiveField(9)
|
@HiveField(9)
|
||||||
polygon
|
polygon,
|
||||||
|
|
||||||
|
@HiveField(10)
|
||||||
|
solana
|
||||||
}
|
}
|
||||||
|
|
||||||
int serializeToInt(WalletType type) {
|
int serializeToInt(WalletType type) {
|
||||||
|
@ -69,6 +73,8 @@ int serializeToInt(WalletType type) {
|
||||||
return 7;
|
return 7;
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return 8;
|
return 8;
|
||||||
|
case WalletType.solana:
|
||||||
|
return 9;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -94,6 +100,8 @@ WalletType deserializeFromInt(int raw) {
|
||||||
return WalletType.bitcoinCash;
|
return WalletType.bitcoinCash;
|
||||||
case 8:
|
case 8:
|
||||||
return WalletType.polygon;
|
return WalletType.polygon;
|
||||||
|
case 9:
|
||||||
|
return WalletType.solana;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
|
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
|
||||||
}
|
}
|
||||||
|
@ -119,6 +127,8 @@ String walletTypeToString(WalletType type) {
|
||||||
return 'Banano';
|
return 'Banano';
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return 'Polygon';
|
return 'Polygon';
|
||||||
|
case WalletType.solana:
|
||||||
|
return 'Solana';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -144,6 +154,8 @@ String walletTypeToDisplayName(WalletType type) {
|
||||||
return 'Banano (BAN)';
|
return 'Banano (BAN)';
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return 'Polygon (MATIC)';
|
return 'Polygon (MATIC)';
|
||||||
|
case WalletType.solana:
|
||||||
|
return 'Solana (SOL)';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -169,6 +181,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
|
||||||
return CryptoCurrency.banano;
|
return CryptoCurrency.banano;
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return CryptoCurrency.maticpoly;
|
return CryptoCurrency.maticpoly;
|
||||||
|
case WalletType.solana:
|
||||||
|
return CryptoCurrency.sol;
|
||||||
default:
|
default:
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
||||||
|
|
|
@ -5,34 +5,34 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
|
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "47.0.0"
|
version: "64.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
|
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.0"
|
version: "6.2.0"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: args
|
name: args
|
||||||
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
|
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.4.2"
|
||||||
asn1lib:
|
asn1lib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: asn1lib
|
name: asn1lib
|
||||||
sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039
|
sha256: "21afe4333076c02877d14f4a89df111e658a6d466cbfc802eb705eb91bd5adfd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.5.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -53,10 +53,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build
|
name: build
|
||||||
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
|
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.4.1"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -69,34 +69,34 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_daemon
|
name: build_daemon
|
||||||
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
|
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "4.0.1"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
|
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.10"
|
version: "2.4.2"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
|
sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.3"
|
version: "2.4.8"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
|
sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.2.7"
|
version: "7.2.11"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -109,10 +109,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: built_value
|
name: built_value
|
||||||
sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725"
|
sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.4.3"
|
version: "8.8.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -125,10 +125,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: checked_yaml
|
name: checked_yaml
|
||||||
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
|
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -141,10 +141,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_builder
|
name: code_builder
|
||||||
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
|
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.0"
|
version: "4.10.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -165,26 +165,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.3"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
|
sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.4"
|
version: "2.3.4"
|
||||||
encrypt:
|
encrypt:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: encrypt
|
name: encrypt
|
||||||
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
|
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.1"
|
version: "5.0.3"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -197,10 +197,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
|
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.1.0"
|
||||||
file:
|
file:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -226,10 +226,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_mobx
|
name: flutter_mobx
|
||||||
sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
|
sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.6+5"
|
version: "2.2.0+2"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -247,18 +247,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: glob
|
name: glob
|
||||||
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
|
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: graphs
|
name: graphs
|
||||||
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
|
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.3.1"
|
||||||
hive:
|
hive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -327,18 +327,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
|
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.8.1"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: logging
|
name: logging
|
||||||
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
|
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.2.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -375,18 +375,26 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: mobx
|
name: mobx
|
||||||
sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a
|
sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3+1"
|
version: "2.3.0+1"
|
||||||
mobx_codegen:
|
mobx_codegen:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: mobx_codegen
|
name: mobx_codegen
|
||||||
sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181"
|
sha256: b26c7f9c20b38f0ea572c1ed3f29d8e027cb265538bbd1aed3ec198642cfca42
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.6.0+1"
|
||||||
|
nested:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nested
|
||||||
|
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -407,26 +415,26 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.2"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -439,10 +447,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -455,26 +463,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
|
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.4"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.8"
|
||||||
pointycastle:
|
pointycastle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pointycastle
|
name: pointycastle
|
||||||
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
|
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.6.2"
|
version: "3.7.4"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -483,46 +491,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
process:
|
provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: process
|
name: provider
|
||||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.4"
|
version: "6.1.1"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pub_semver
|
name: pub_semver
|
||||||
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17"
|
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.4"
|
||||||
pubspec_parse:
|
pubspec_parse:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pubspec_parse
|
name: pubspec_parse
|
||||||
sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a"
|
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.3"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf
|
name: shelf
|
||||||
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c
|
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
shelf_web_socket:
|
shelf_web_socket:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf_web_socket
|
name: shelf_web_socket
|
||||||
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8
|
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.4"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -540,18 +548,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
|
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.6"
|
version: "1.5.0"
|
||||||
source_helper:
|
source_helper:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_helper
|
name: source_helper
|
||||||
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
|
sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.4"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -620,10 +628,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: typed_data
|
name: typed_data
|
||||||
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.2"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -636,42 +644,42 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: watcher
|
name: watcher
|
||||||
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.1.0"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b
|
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.4.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
|
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "5.0.9"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
|
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0+3"
|
version: "1.0.4"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: yaml
|
name: yaml
|
||||||
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
|
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.0.0 <4.0.0"
|
dart: ">=3.0.0 <4.0.0"
|
||||||
flutter: ">=3.7.0"
|
flutter: ">=3.10.0"
|
||||||
|
|
|
@ -16,7 +16,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
WalletType getType() => WalletType.ethereum;
|
WalletType getType() => WalletType.ethereum;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<EthereumWallet> create(EVMChainNewWalletCredentials credentials) async {
|
Future<EthereumWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||||
|
|
||||||
final mnemonic = bip39.generateMnemonic(strength: strength);
|
final mnemonic = bip39.generateMnemonic(strength: strength);
|
||||||
|
@ -52,7 +52,6 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
saveBackup(name);
|
saveBackup(name);
|
||||||
return wallet;
|
return wallet;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
||||||
await restoreWalletFilesFromBackup(name);
|
await restoreWalletFilesFromBackup(name);
|
||||||
|
|
||||||
final wallet = await EthereumWallet.open(
|
final wallet = await EthereumWallet.open(
|
||||||
|
@ -84,7 +83,8 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async {
|
Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
|
||||||
|
{bool? isTestnet}) async {
|
||||||
final wallet = EthereumWallet(
|
final wallet = EthereumWallet(
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
privateKey: credentials.privateKey,
|
privateKey: credentials.privateKey,
|
||||||
|
@ -100,8 +100,8 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<EthereumWallet> restoreFromSeed(
|
Future<EthereumWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
|
||||||
EVMChainRestoreWalletFromSeedCredentials credentials) async {
|
{bool? isTestnet}) async {
|
||||||
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||||
throw EthereumMnemonicIsIncorrectException();
|
throw EthereumMnemonicIsIncorrectException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,7 +146,7 @@ abstract class EVMChainWalletBase
|
||||||
privateKey: _hexPrivateKey,
|
privateKey: _hexPrivateKey,
|
||||||
password: _password,
|
password: _password,
|
||||||
);
|
);
|
||||||
walletAddresses.address = _evmChainPrivateKey.address.toString();
|
walletAddresses.address = _evmChainPrivateKey.address.hexEip55;
|
||||||
await save();
|
await save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
|
||||||
super(walletInfo);
|
super(walletInfo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@observable
|
||||||
String address;
|
String address;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -22,7 +22,7 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
|
||||||
WalletType getType();
|
WalletType getType();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T> create(EVMChainNewWalletCredentials credentials);
|
Future<T> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T> openWallet(String name, String password);
|
Future<T> openWallet(String name, String password);
|
||||||
|
@ -31,10 +31,10 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
|
||||||
Future<void> rename(String currentName, String password, String newName);
|
Future<void> rename(String currentName, String password, String newName);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials);
|
Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials, {bool? isTestnet});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials);
|
Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials, {bool? isTestnet});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isWalletExit(String name) async =>
|
Future<bool> isWalletExit(String name) async =>
|
||||||
|
|
|
@ -68,7 +68,7 @@ class HavenWalletService extends WalletService<
|
||||||
WalletType getType() => WalletType.haven;
|
WalletType getType() => WalletType.haven;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<HavenWallet> create(HavenNewWalletCredentials credentials) async {
|
Future<HavenWallet> create(HavenNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
await haven_wallet_manager.createWallet(
|
await haven_wallet_manager.createWallet(
|
||||||
|
@ -174,7 +174,7 @@ class HavenWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<HavenWallet> restoreFromKeys(
|
Future<HavenWallet> restoreFromKeys(
|
||||||
HavenRestoreWalletFromKeysCredentials credentials) async {
|
HavenRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
await haven_wallet_manager.restoreFromKeys(
|
await haven_wallet_manager.restoreFromKeys(
|
||||||
|
@ -198,7 +198,7 @@ class HavenWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<HavenWallet> restoreFromSeed(
|
Future<HavenWallet> restoreFromSeed(
|
||||||
HavenRestoreWalletFromSeedCredentials credentials) async {
|
HavenRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
await haven_wallet_manager.restoreFromSeed(
|
await haven_wallet_manager.restoreFromSeed(
|
||||||
|
|
|
@ -576,15 +576,19 @@ abstract class MoneroWalletBase
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final height = _getHeightByDate(walletInfo.date);
|
int height = 0;
|
||||||
|
try {
|
||||||
if (height > MIN_RESTORE_HEIGHT) {
|
height = _getHeightByDate(walletInfo.date);
|
||||||
monero_wallet.setRecoveringFromSeed(isRecovery: true);
|
} catch (e, s) {
|
||||||
monero_wallet.setRefreshFromBlockHeight(height: height);
|
onError?.call(FlutterErrorDetails(
|
||||||
return;
|
exception: e,
|
||||||
|
stack: s,
|
||||||
|
library: this.runtimeType.toString(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Exception("height isn't > $MIN_RESTORE_HEIGHT!");
|
monero_wallet.setRecoveringFromSeed(isRecovery: true);
|
||||||
|
monero_wallet.setRefreshFromBlockHeight(height: height);
|
||||||
}
|
}
|
||||||
|
|
||||||
int _getHeightDistance(DateTime date) {
|
int _getHeightDistance(DateTime date) {
|
||||||
|
@ -600,7 +604,7 @@ abstract class MoneroWalletBase
|
||||||
final heightDistance = _getHeightDistance(date);
|
final heightDistance = _getHeightDistance(date);
|
||||||
|
|
||||||
if (nodeHeight <= 0) {
|
if (nodeHeight <= 0) {
|
||||||
// the node returned 0 (an error state), so lets just restore from cache:
|
// the node returned 0 (an error state)
|
||||||
throw Exception("nodeHeight is <= 0!");
|
throw Exception("nodeHeight is <= 0!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
|
||||||
WalletType getType() => WalletType.monero;
|
WalletType getType() => WalletType.monero;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
|
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
|
|
||||||
|
@ -203,7 +203,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<MoneroWallet> restoreFromKeys(MoneroRestoreWalletFromKeysCredentials credentials) async {
|
Future<MoneroWallet> restoreFromKeys(MoneroRestoreWalletFromKeysCredentials credentials,
|
||||||
|
{bool? isTestnet}) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
await monero_wallet_manager.restoreFromKeys(
|
await monero_wallet_manager.restoreFromKeys(
|
||||||
|
@ -227,7 +228,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials) async {
|
Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials,
|
||||||
|
{bool? isTestnet}) async {
|
||||||
// Restore from Polyseed
|
// Restore from Polyseed
|
||||||
if (Polyseed.isValidSeed(credentials.mnemonic)) {
|
if (Polyseed.isValidSeed(credentials.mnemonic)) {
|
||||||
return restoreFromPolyseed(credentials);
|
return restoreFromPolyseed(credentials);
|
||||||
|
|
|
@ -26,7 +26,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
||||||
WalletType getType() => WalletType.nano;
|
WalletType getType() => WalletType.nano;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<WalletBase> create(NanoNewWalletCredentials credentials) async {
|
Future<WalletBase> create(NanoNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
// nano standard:
|
// nano standard:
|
||||||
DerivationType derivationType = DerivationType.nano;
|
DerivationType derivationType = DerivationType.nano;
|
||||||
String seedKey = NanoSeeds.generateSeed();
|
String seedKey = NanoSeeds.generateSeed();
|
||||||
|
@ -79,7 +79,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async {
|
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
|
||||||
if (credentials.seedKey.contains(' ')) {
|
if (credentials.seedKey.contains(' ')) {
|
||||||
throw Exception("Invalid key!");
|
throw Exception("Invalid key!");
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,7 +113,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials) async {
|
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
|
||||||
if (credentials.mnemonic.contains(' ')) {
|
if (credentials.mnemonic.contains(' ')) {
|
||||||
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||||
throw nm.NanoMnemonicIsIncorrectException();
|
throw nm.NanoMnemonicIsIncorrectException();
|
||||||
|
|
|
@ -19,7 +19,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
||||||
WalletType getType() => WalletType.polygon;
|
WalletType getType() => WalletType.polygon;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PolygonWallet> create(EVMChainNewWalletCredentials credentials) async {
|
Future<PolygonWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||||
|
|
||||||
final mnemonic = bip39.generateMnemonic(strength: strength);
|
final mnemonic = bip39.generateMnemonic(strength: strength);
|
||||||
|
@ -62,7 +62,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
);
|
);
|
||||||
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -70,8 +70,8 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PolygonWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async {
|
Future<PolygonWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
|
||||||
|
{bool? isTestnet}) async {
|
||||||
final wallet = PolygonWallet(
|
final wallet = PolygonWallet(
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
privateKey: credentials.privateKey,
|
privateKey: credentials.privateKey,
|
||||||
|
@ -87,8 +87,8 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PolygonWallet> restoreFromSeed(
|
Future<PolygonWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
|
||||||
EVMChainRestoreWalletFromSeedCredentials credentials) async {
|
{bool? isTestnet}) async {
|
||||||
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||||
throw PolygonMnemonicIsIncorrectException();
|
throw PolygonMnemonicIsIncorrectException();
|
||||||
}
|
}
|
||||||
|
|
30
cw_solana/.gitignore
vendored
Normal file
30
cw_solana/.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_solana/.metadata
Normal file
10
cw_solana/.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_solana/CHANGELOG.md
Normal file
3
cw_solana/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 0.0.1
|
||||||
|
|
||||||
|
* TODO: Describe initial release.
|
1
cw_solana/LICENSE
Normal file
1
cw_solana/LICENSE
Normal file
|
@ -0,0 +1 @@
|
||||||
|
TODO: Add your license here.
|
39
cw_solana/README.md
Normal file
39
cw_solana/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_solana/analysis_options.yaml
Normal file
4
cw_solana/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_solana/lib/cw_solana.dart
Normal file
7
cw_solana/lib/cw_solana.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
library cw_solana;
|
||||||
|
|
||||||
|
/// A Calculator.
|
||||||
|
class Calculator {
|
||||||
|
/// Returns [value] plus 1.
|
||||||
|
int addOne(int value) => value + 1;
|
||||||
|
}
|
107
cw_solana/lib/default_spl_tokens.dart
Normal file
107
cw_solana/lib/default_spl_tokens.dart
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_solana/spl_token.dart';
|
||||||
|
|
||||||
|
class DefaultSPLTokens {
|
||||||
|
final List<SPLToken> _defaultTokens = [
|
||||||
|
SPLToken(
|
||||||
|
name: 'USDT Tether',
|
||||||
|
symbol: 'USDT',
|
||||||
|
mintAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
|
||||||
|
decimal: 6,
|
||||||
|
mint: 'usdtsol',
|
||||||
|
enabled: true,
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'USD Coin',
|
||||||
|
symbol: 'USDC',
|
||||||
|
mintAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||||
|
decimal: 6,
|
||||||
|
mint: 'usdcsol',
|
||||||
|
enabled: true,
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'Wrapped Ethereum (Sollet)',
|
||||||
|
symbol: 'soETH',
|
||||||
|
mintAddress: '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk',
|
||||||
|
decimal: 6,
|
||||||
|
mint: 'soEth',
|
||||||
|
iconPath: 'assets/images/eth_icon.png',
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'Wrapped SOL',
|
||||||
|
symbol: 'WSOL',
|
||||||
|
mintAddress: 'So11111111111111111111111111111111111111112',
|
||||||
|
decimal: 9,
|
||||||
|
mint: 'WSOL',
|
||||||
|
iconPath: 'assets/images/sol_icon.png',
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'Wrapped Bitcoin (Sollet)',
|
||||||
|
symbol: 'BTC',
|
||||||
|
mintAddress: '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E',
|
||||||
|
decimal: 6,
|
||||||
|
mint: 'btcsol',
|
||||||
|
iconPath: 'assets/images/btc.png',
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'Bonk',
|
||||||
|
symbol: 'Bonk',
|
||||||
|
mintAddress: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263',
|
||||||
|
decimal: 5,
|
||||||
|
mint: 'Bonk',
|
||||||
|
iconPath: 'assets/images/bonk_icon.png',
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'Helium Network Token',
|
||||||
|
symbol: 'HNT',
|
||||||
|
mintAddress: 'hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux',
|
||||||
|
decimal: 8,
|
||||||
|
mint: 'hnt',
|
||||||
|
iconPath: 'assets/images/hnt_icon.png',
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'Pyth Network',
|
||||||
|
symbol: 'PYTH',
|
||||||
|
mintAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3',
|
||||||
|
decimal: 6,
|
||||||
|
mint: 'pyth',
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'Raydium',
|
||||||
|
symbol: 'RAY',
|
||||||
|
mintAddress: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R',
|
||||||
|
decimal: 6,
|
||||||
|
mint: 'ray',
|
||||||
|
iconPath: 'assets/images/ray_icon.png',
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'GMT',
|
||||||
|
symbol: 'GMT',
|
||||||
|
mintAddress: '7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx',
|
||||||
|
decimal: 6,
|
||||||
|
mint: 'ray',
|
||||||
|
iconPath: 'assets/images/gmt_icon.png',
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'AvocadoCoin',
|
||||||
|
symbol: 'AVDO',
|
||||||
|
mintAddress: 'EE5L8cMU4itTsCSuor7NLK6RZx6JhsBe8GGV3oaAHm3P',
|
||||||
|
decimal: 8,
|
||||||
|
mint: 'avdo',
|
||||||
|
iconPath: 'assets/images/avdo_icon.png',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
List<SPLToken> get initialSPLTokens => _defaultTokens.map((token) {
|
||||||
|
String? iconPath;
|
||||||
|
if (token.iconPath != null) return token;
|
||||||
|
|
||||||
|
try {
|
||||||
|
iconPath = CryptoCurrency.all
|
||||||
|
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||||
|
.iconPath;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
return SPLToken.copyWith(token, iconPath, 'SOL');
|
||||||
|
}).toList();
|
||||||
|
}
|
39
cw_solana/lib/file.dart
Normal file
39
cw_solana/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);
|
||||||
|
}
|
43
cw_solana/lib/pending_solana_transaction.dart
Normal file
43
cw_solana/lib/pending_solana_transaction.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
import 'package:solana/encoder.dart';
|
||||||
|
|
||||||
|
class PendingSolanaTransaction with PendingTransaction {
|
||||||
|
final double amount;
|
||||||
|
final SignedTx signedTransaction;
|
||||||
|
final String destinationAddress;
|
||||||
|
final Function sendTransaction;
|
||||||
|
final double fee;
|
||||||
|
|
||||||
|
PendingSolanaTransaction({
|
||||||
|
required this.fee,
|
||||||
|
required this.amount,
|
||||||
|
required this.signedTransaction,
|
||||||
|
required this.destinationAddress,
|
||||||
|
required this.sendTransaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get amountFormatted {
|
||||||
|
String stringifiedAmount = amount.toString();
|
||||||
|
|
||||||
|
if (stringifiedAmount.toString().length >= 6) {
|
||||||
|
stringifiedAmount = stringifiedAmount.substring(0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringifiedAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> commit() async {
|
||||||
|
return await sendTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get feeFormatted => fee.toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hex => signedTransaction.encode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id => '';
|
||||||
|
}
|
39
cw_solana/lib/solana_balance.dart
Normal file
39
cw_solana/lib/solana_balance.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cw_core/balance.dart';
|
||||||
|
|
||||||
|
class SolanaBalance extends Balance {
|
||||||
|
SolanaBalance(this.balance) : super(balance.toInt(), balance.toInt());
|
||||||
|
|
||||||
|
final double balance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formattedAdditionalBalance => _balanceFormatted();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formattedAvailableBalance => _balanceFormatted();
|
||||||
|
|
||||||
|
String _balanceFormatted() {
|
||||||
|
String stringBalance = balance.toString();
|
||||||
|
if (stringBalance.toString().length >= 6) {
|
||||||
|
stringBalance = stringBalance.substring(0, 6);
|
||||||
|
}
|
||||||
|
return stringBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SolanaBalance? fromJSON(String? jsonSource) {
|
||||||
|
if (jsonSource == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return SolanaBalance(decoded['balance']);
|
||||||
|
} catch (e) {
|
||||||
|
return SolanaBalance(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJSON() => json.encode({'balance': balance.toString()});
|
||||||
|
}
|
478
cw_solana/lib/solana_client.dart
Normal file
478
cw_solana/lib/solana_client.dart
Normal file
|
@ -0,0 +1,478 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/node.dart';
|
||||||
|
import 'package:cw_solana/pending_solana_transaction.dart';
|
||||||
|
import 'package:cw_solana/solana_balance.dart';
|
||||||
|
import 'package:cw_solana/solana_transaction_model.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:solana/dto.dart';
|
||||||
|
import 'package:solana/encoder.dart';
|
||||||
|
import 'package:solana/solana.dart';
|
||||||
|
import '.secrets.g.dart' as secrets;
|
||||||
|
|
||||||
|
class SolanaWalletClient {
|
||||||
|
final httpClient = http.Client();
|
||||||
|
SolanaClient? _client;
|
||||||
|
|
||||||
|
bool connect(Node node) {
|
||||||
|
try {
|
||||||
|
Uri? rpcUri;
|
||||||
|
String webSocketUrl;
|
||||||
|
bool isModifiedNodeUri = false;
|
||||||
|
|
||||||
|
if (node.uriRaw == 'rpc.ankr.com') {
|
||||||
|
isModifiedNodeUri = true;
|
||||||
|
String ankrApiKey = secrets.ankrApiKey;
|
||||||
|
|
||||||
|
rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey');
|
||||||
|
webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
|
||||||
|
} else {
|
||||||
|
webSocketUrl = 'wss://${node.uriRaw}';
|
||||||
|
}
|
||||||
|
|
||||||
|
_client = SolanaClient(
|
||||||
|
rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri,
|
||||||
|
websocketUrl: Uri.parse(webSocketUrl),
|
||||||
|
timeout: const Duration(minutes: 2),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<double> getBalance(String address) async {
|
||||||
|
try {
|
||||||
|
final balance = await _client!.rpcClient.getBalance(address);
|
||||||
|
|
||||||
|
final solBalance = balance.value / lamportsPerSol;
|
||||||
|
|
||||||
|
return solBalance;
|
||||||
|
} catch (_) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ProgramAccountsResult?> getSPLTokenAccounts(String mintAddress, String publicKey) async {
|
||||||
|
try {
|
||||||
|
final tokenAccounts = await _client!.rpcClient.getTokenAccountsByOwner(
|
||||||
|
publicKey,
|
||||||
|
TokenAccountsFilter.byMint(mintAddress),
|
||||||
|
commitment: Commitment.confirmed,
|
||||||
|
encoding: Encoding.jsonParsed,
|
||||||
|
);
|
||||||
|
return tokenAccounts;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SolanaBalance?> getSplTokenBalance(String mintAddress, String publicKey) async {
|
||||||
|
// Fetch the token accounts (a token can have multiple accounts for various uses)
|
||||||
|
final tokenAccounts = await getSPLTokenAccounts(mintAddress, publicKey);
|
||||||
|
|
||||||
|
// Handle scenario where there is no token account
|
||||||
|
if (tokenAccounts == null || tokenAccounts.value.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum the balances of all accounts with the specified mint address
|
||||||
|
double totalBalance = 0.0;
|
||||||
|
|
||||||
|
for (var programAccount in tokenAccounts.value) {
|
||||||
|
final tokenAmountResult =
|
||||||
|
await _client!.rpcClient.getTokenAccountBalance(programAccount.pubkey);
|
||||||
|
|
||||||
|
final balance = tokenAmountResult.value.uiAmountString;
|
||||||
|
|
||||||
|
final balanceAsDouble = double.tryParse(balance ?? '0.0') ?? 0.0;
|
||||||
|
|
||||||
|
totalBalance += balanceAsDouble;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SolanaBalance(totalBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<double> getGasForMessage(String message) async {
|
||||||
|
try {
|
||||||
|
final gasPrice = await _client!.rpcClient.getFeeForMessage(message) ?? 0;
|
||||||
|
final fee = gasPrice / lamportsPerSol;
|
||||||
|
return fee;
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the Address's transactions into the account
|
||||||
|
Future<List<SolanaTransactionModel>> fetchTransactions(
|
||||||
|
Ed25519HDPublicKey publicKey, {
|
||||||
|
String? splTokenSymbol,
|
||||||
|
int? splTokenDecimal,
|
||||||
|
}) async {
|
||||||
|
List<SolanaTransactionModel> transactions = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _client!.rpcClient.getTransactionsList(
|
||||||
|
publicKey,
|
||||||
|
commitment: Commitment.confirmed,
|
||||||
|
limit: 1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (final tx in response) {
|
||||||
|
if (tx.transaction is ParsedTransaction) {
|
||||||
|
final parsedTx = (tx.transaction as ParsedTransaction);
|
||||||
|
final message = parsedTx.message;
|
||||||
|
|
||||||
|
final fee = (tx.meta?.fee ?? 0) / lamportsPerSol;
|
||||||
|
|
||||||
|
for (final instruction in message.instructions) {
|
||||||
|
if (instruction is ParsedInstruction) {
|
||||||
|
instruction.map(
|
||||||
|
system: (systemData) {
|
||||||
|
systemData.parsed.map(
|
||||||
|
transfer: (transferData) {
|
||||||
|
ParsedSystemTransferInformation transfer = transferData.info;
|
||||||
|
bool isOutgoingTx = transfer.source == publicKey.toBase58();
|
||||||
|
|
||||||
|
double amount = transfer.lamports.toDouble() / lamportsPerSol;
|
||||||
|
|
||||||
|
transactions.add(
|
||||||
|
SolanaTransactionModel(
|
||||||
|
id: parsedTx.signatures.first,
|
||||||
|
from: transfer.source,
|
||||||
|
to: transfer.destination,
|
||||||
|
amount: amount,
|
||||||
|
isOutgoingTx: isOutgoingTx,
|
||||||
|
blockTimeInInt: tx.blockTime!,
|
||||||
|
fee: fee,
|
||||||
|
programId: SystemProgram.programId,
|
||||||
|
tokenSymbol: 'SOL',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
transferChecked: (_) {},
|
||||||
|
unsupported: (_) {},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
splToken: (splTokenData) {
|
||||||
|
if (splTokenSymbol != null) {
|
||||||
|
splTokenData.parsed.map(
|
||||||
|
transfer: (transferData) {
|
||||||
|
SplTokenTransferInfo transfer = transferData.info;
|
||||||
|
bool isOutgoingTx = transfer.source == publicKey.toBase58();
|
||||||
|
|
||||||
|
double amount = (double.tryParse(transfer.amount) ?? 0.0) /
|
||||||
|
pow(10, splTokenDecimal ?? 9);
|
||||||
|
|
||||||
|
transactions.add(
|
||||||
|
SolanaTransactionModel(
|
||||||
|
id: parsedTx.signatures.first,
|
||||||
|
fee: fee,
|
||||||
|
from: transfer.source,
|
||||||
|
to: transfer.destination,
|
||||||
|
amount: amount,
|
||||||
|
isOutgoingTx: isOutgoingTx,
|
||||||
|
programId: TokenProgram.programId,
|
||||||
|
blockTimeInInt: tx.blockTime!,
|
||||||
|
tokenSymbol: splTokenSymbol,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
transferChecked: (transferCheckedData) {
|
||||||
|
SplTokenTransferCheckedInfo transfer = transferCheckedData.info;
|
||||||
|
bool isOutgoingTx = transfer.source == publicKey.toBase58();
|
||||||
|
double amount =
|
||||||
|
double.tryParse(transfer.tokenAmount.uiAmountString ?? '0.0') ?? 0.0;
|
||||||
|
|
||||||
|
transactions.add(
|
||||||
|
SolanaTransactionModel(
|
||||||
|
id: parsedTx.signatures.first,
|
||||||
|
fee: fee,
|
||||||
|
from: transfer.source,
|
||||||
|
to: transfer.destination,
|
||||||
|
amount: amount,
|
||||||
|
isOutgoingTx: isOutgoingTx,
|
||||||
|
programId: TokenProgram.programId,
|
||||||
|
blockTimeInInt: tx.blockTime!,
|
||||||
|
tokenSymbol: splTokenSymbol,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
generic: (genericData) {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
memo: (_) {},
|
||||||
|
unsupported: (a) {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
} catch (err) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<SolanaTransactionModel>> getSPLTokenTransfers(
|
||||||
|
String address,
|
||||||
|
String splTokenSymbol,
|
||||||
|
int splTokenDecimal,
|
||||||
|
Ed25519HDKeyPair ownerKeypair,
|
||||||
|
) async {
|
||||||
|
final tokenMint = Ed25519HDPublicKey.fromBase58(address);
|
||||||
|
|
||||||
|
ProgramAccount? associatedTokenAccount;
|
||||||
|
|
||||||
|
try {
|
||||||
|
associatedTokenAccount = await _client!.getAssociatedTokenAccount(
|
||||||
|
mint: tokenMint,
|
||||||
|
owner: ownerKeypair.publicKey,
|
||||||
|
commitment: Commitment.confirmed,
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (associatedTokenAccount == null) return [];
|
||||||
|
|
||||||
|
final accountPublicKey = Ed25519HDPublicKey.fromBase58(associatedTokenAccount.pubkey);
|
||||||
|
|
||||||
|
final tokenTransactions = await fetchTransactions(
|
||||||
|
accountPublicKey,
|
||||||
|
splTokenSymbol: splTokenSymbol,
|
||||||
|
splTokenDecimal: splTokenDecimal,
|
||||||
|
);
|
||||||
|
|
||||||
|
return tokenTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {}
|
||||||
|
|
||||||
|
SolanaClient? get getSolanaClient => _client;
|
||||||
|
|
||||||
|
Future<PendingSolanaTransaction> signSolanaTransaction({
|
||||||
|
required String tokenTitle,
|
||||||
|
required int tokenDecimals,
|
||||||
|
String? tokenMint,
|
||||||
|
required double inputAmount,
|
||||||
|
required String destinationAddress,
|
||||||
|
required Ed25519HDKeyPair ownerKeypair,
|
||||||
|
List<String> references = const [],
|
||||||
|
}) async {
|
||||||
|
const commitment = Commitment.confirmed;
|
||||||
|
|
||||||
|
final latestBlockhash =
|
||||||
|
await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value;
|
||||||
|
|
||||||
|
final recentBlockhash = RecentBlockhash(
|
||||||
|
blockhash: latestBlockhash.blockhash,
|
||||||
|
feeCalculator: const FeeCalculator(
|
||||||
|
lamportsPerSignature: 500,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tokenTitle == CryptoCurrency.sol.title) {
|
||||||
|
final pendingNativeTokenTransaction = await _signNativeTokenTransaction(
|
||||||
|
tokenTitle: tokenTitle,
|
||||||
|
tokenDecimals: tokenDecimals,
|
||||||
|
inputAmount: inputAmount,
|
||||||
|
destinationAddress: destinationAddress,
|
||||||
|
ownerKeypair: ownerKeypair,
|
||||||
|
recentBlockhash: recentBlockhash,
|
||||||
|
commitment: commitment,
|
||||||
|
);
|
||||||
|
return pendingNativeTokenTransaction;
|
||||||
|
} else {
|
||||||
|
final pendingSPLTokenTransaction = _signSPLTokenTransaction(
|
||||||
|
tokenTitle: tokenTitle,
|
||||||
|
tokenDecimals: tokenDecimals,
|
||||||
|
tokenMint: tokenMint!,
|
||||||
|
inputAmount: inputAmount,
|
||||||
|
destinationAddress: destinationAddress,
|
||||||
|
ownerKeypair: ownerKeypair,
|
||||||
|
recentBlockhash: recentBlockhash,
|
||||||
|
commitment: commitment,
|
||||||
|
);
|
||||||
|
return pendingSPLTokenTransaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PendingSolanaTransaction> _signNativeTokenTransaction({
|
||||||
|
required String tokenTitle,
|
||||||
|
required int tokenDecimals,
|
||||||
|
required double inputAmount,
|
||||||
|
required String destinationAddress,
|
||||||
|
required Ed25519HDKeyPair ownerKeypair,
|
||||||
|
required RecentBlockhash recentBlockhash,
|
||||||
|
required Commitment commitment,
|
||||||
|
}) async {
|
||||||
|
// Convert SOL to lamport
|
||||||
|
int lamports = (inputAmount * lamportsPerSol).toInt();
|
||||||
|
|
||||||
|
final instructions = [
|
||||||
|
SystemInstruction.transfer(
|
||||||
|
fundingAccount: ownerKeypair.publicKey,
|
||||||
|
recipientAccount: Ed25519HDPublicKey.fromBase58(destinationAddress),
|
||||||
|
lamports: lamports,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
final message = Message(instructions: instructions);
|
||||||
|
final signers = [ownerKeypair];
|
||||||
|
|
||||||
|
final signedTx = await _signTransactionInternal(
|
||||||
|
message: message,
|
||||||
|
signers: signers,
|
||||||
|
commitment: commitment,
|
||||||
|
recentBlockhash: recentBlockhash,
|
||||||
|
);
|
||||||
|
|
||||||
|
final fee = await _getFeeFromCompiledMessage(
|
||||||
|
message,
|
||||||
|
recentBlockhash,
|
||||||
|
signers.first.publicKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
sendTx() async => await sendTransaction(
|
||||||
|
signedTransaction: signedTx,
|
||||||
|
commitment: commitment,
|
||||||
|
);
|
||||||
|
|
||||||
|
final pendingTransaction = PendingSolanaTransaction(
|
||||||
|
amount: inputAmount,
|
||||||
|
signedTransaction: signedTx,
|
||||||
|
destinationAddress: destinationAddress,
|
||||||
|
sendTransaction: sendTx,
|
||||||
|
fee: fee,
|
||||||
|
);
|
||||||
|
|
||||||
|
return pendingTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PendingSolanaTransaction> _signSPLTokenTransaction({
|
||||||
|
required String tokenTitle,
|
||||||
|
required int tokenDecimals,
|
||||||
|
required String tokenMint,
|
||||||
|
required double inputAmount,
|
||||||
|
required String destinationAddress,
|
||||||
|
required Ed25519HDKeyPair ownerKeypair,
|
||||||
|
required RecentBlockhash recentBlockhash,
|
||||||
|
required Commitment commitment,
|
||||||
|
}) async {
|
||||||
|
final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress);
|
||||||
|
final mint = Ed25519HDPublicKey.fromBase58(tokenMint);
|
||||||
|
|
||||||
|
ProgramAccount? associatedRecipientAccount;
|
||||||
|
ProgramAccount? associatedSenderAccount;
|
||||||
|
|
||||||
|
associatedRecipientAccount = await _client!.getAssociatedTokenAccount(
|
||||||
|
mint: mint,
|
||||||
|
owner: destinationOwner,
|
||||||
|
commitment: commitment,
|
||||||
|
);
|
||||||
|
|
||||||
|
associatedSenderAccount = await _client!.getAssociatedTokenAccount(
|
||||||
|
owner: ownerKeypair.publicKey,
|
||||||
|
mint: mint,
|
||||||
|
commitment: commitment,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Throw an appropriate exception if the sender has no associated
|
||||||
|
// token account
|
||||||
|
if (associatedSenderAccount == null) {
|
||||||
|
throw NoAssociatedTokenAccountException(ownerKeypair.address, mint.toBase58());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
associatedRecipientAccount ??= await _client!.createAssociatedTokenAccount(
|
||||||
|
mint: mint,
|
||||||
|
owner: destinationOwner,
|
||||||
|
funder: ownerKeypair,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Insufficient lamports balance to complete this transaction');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input by the user
|
||||||
|
final amount = (inputAmount * pow(10, tokenDecimals)).toInt();
|
||||||
|
|
||||||
|
final instruction = TokenInstruction.transfer(
|
||||||
|
source: Ed25519HDPublicKey.fromBase58(associatedSenderAccount.pubkey),
|
||||||
|
destination: Ed25519HDPublicKey.fromBase58(associatedRecipientAccount.pubkey),
|
||||||
|
owner: ownerKeypair.publicKey,
|
||||||
|
amount: amount,
|
||||||
|
);
|
||||||
|
|
||||||
|
final message = Message(instructions: [instruction]);
|
||||||
|
final signers = [ownerKeypair];
|
||||||
|
|
||||||
|
final signedTx = await _signTransactionInternal(
|
||||||
|
message: message,
|
||||||
|
signers: signers,
|
||||||
|
commitment: commitment,
|
||||||
|
recentBlockhash: recentBlockhash,
|
||||||
|
);
|
||||||
|
|
||||||
|
final fee = await _getFeeFromCompiledMessage(
|
||||||
|
message,
|
||||||
|
recentBlockhash,
|
||||||
|
signers.first.publicKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
sendTx() async => await sendTransaction(
|
||||||
|
signedTransaction: signedTx,
|
||||||
|
commitment: commitment,
|
||||||
|
);
|
||||||
|
|
||||||
|
final pendingTransaction = PendingSolanaTransaction(
|
||||||
|
amount: inputAmount,
|
||||||
|
signedTransaction: signedTx,
|
||||||
|
destinationAddress: destinationAddress,
|
||||||
|
sendTransaction: sendTx,
|
||||||
|
fee: fee,
|
||||||
|
);
|
||||||
|
return pendingTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<double> _getFeeFromCompiledMessage(
|
||||||
|
Message message, RecentBlockhash recentBlockhash, Ed25519HDPublicKey feePayer) async {
|
||||||
|
final compile = message.compile(
|
||||||
|
recentBlockhash: recentBlockhash.blockhash,
|
||||||
|
feePayer: feePayer,
|
||||||
|
);
|
||||||
|
|
||||||
|
final base64Message = base64Encode(compile.toByteArray().toList());
|
||||||
|
|
||||||
|
final fee = await getGasForMessage(base64Message);
|
||||||
|
return fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SignedTx> _signTransactionInternal({
|
||||||
|
required Message message,
|
||||||
|
required List<Ed25519HDKeyPair> signers,
|
||||||
|
required Commitment commitment,
|
||||||
|
required RecentBlockhash recentBlockhash,
|
||||||
|
}) async {
|
||||||
|
final signedTx = await signTransaction(recentBlockhash, message, signers);
|
||||||
|
|
||||||
|
return signedTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> sendTransaction({
|
||||||
|
required SignedTx signedTransaction,
|
||||||
|
required Commitment commitment,
|
||||||
|
}) async {
|
||||||
|
final signature = await _client!.rpcClient.sendTransaction(
|
||||||
|
signedTransaction.encode(),
|
||||||
|
preflightCommitment: commitment,
|
||||||
|
);
|
||||||
|
|
||||||
|
_client!.waitForSignatureStatus(signature, status: commitment);
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
}
|
21
cw_solana/lib/solana_exceptions.dart
Normal file
21
cw_solana/lib/solana_exceptions.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
|
class SolanaTransactionCreationException implements Exception {
|
||||||
|
final String exceptionMessage;
|
||||||
|
|
||||||
|
SolanaTransactionCreationException(CryptoCurrency currency)
|
||||||
|
: exceptionMessage = 'Error creating ${currency.title} transaction.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => exceptionMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SolanaTransactionWrongBalanceException implements Exception {
|
||||||
|
final String exceptionMessage;
|
||||||
|
|
||||||
|
SolanaTransactionWrongBalanceException(CryptoCurrency currency)
|
||||||
|
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => exceptionMessage;
|
||||||
|
}
|
2058
cw_solana/lib/solana_mnemonics.dart
Normal file
2058
cw_solana/lib/solana_mnemonics.dart
Normal file
File diff suppressed because it is too large
Load diff
12
cw_solana/lib/solana_transaction_credentials.dart
Normal file
12
cw_solana/lib/solana_transaction_credentials.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/output_info.dart';
|
||||||
|
|
||||||
|
class SolanaTransactionCredentials {
|
||||||
|
SolanaTransactionCredentials(
|
||||||
|
this.outputs, {
|
||||||
|
required this.currency,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<OutputInfo> outputs;
|
||||||
|
final CryptoCurrency currency;
|
||||||
|
}
|
78
cw_solana/lib/solana_transaction_history.dart
Normal file
78
cw_solana/lib/solana_transaction_history.dart
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:core';
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_solana/file.dart';
|
||||||
|
import 'package:cw_solana/solana_transaction_info.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
|
||||||
|
part 'solana_transaction_history.g.dart';
|
||||||
|
|
||||||
|
const transactionsHistoryFileName = 'solana_transactions.json';
|
||||||
|
|
||||||
|
class SolanaTransactionHistory = SolanaTransactionHistoryBase with _$SolanaTransactionHistory;
|
||||||
|
|
||||||
|
abstract class SolanaTransactionHistoryBase extends TransactionHistoryBase<SolanaTransactionInfo>
|
||||||
|
with Store {
|
||||||
|
SolanaTransactionHistoryBase({required this.walletInfo, required String password})
|
||||||
|
: _password = password {
|
||||||
|
transactions = ObservableMap<String, SolanaTransactionInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletInfo walletInfo;
|
||||||
|
String _password;
|
||||||
|
|
||||||
|
Future<void> init() async => await _load();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> save() async {
|
||||||
|
try {
|
||||||
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
|
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) {
|
||||||
|
print('Error while saving solana transaction history: ${e.toString()}');
|
||||||
|
print(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addOne(SolanaTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addMany(Map<String, SolanaTransactionInfo> transactions) =>
|
||||||
|
this.transactions.addAll(transactions);
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _read() async {
|
||||||
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
|
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>? ?? {};
|
||||||
|
|
||||||
|
txs.entries.forEach((entry) {
|
||||||
|
final val = entry.value;
|
||||||
|
|
||||||
|
if (val is Map<String, dynamic>) {
|
||||||
|
final tx = SolanaTransactionInfo.fromJson(val);
|
||||||
|
_update(tx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _update(SolanaTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||||
|
}
|
78
cw_solana/lib/solana_transaction_info.dart
Normal file
78
cw_solana/lib/solana_transaction_info.dart
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import 'package:cw_core/format_amount.dart';
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
|
||||||
|
class SolanaTransactionInfo extends TransactionInfo {
|
||||||
|
SolanaTransactionInfo({
|
||||||
|
required this.id,
|
||||||
|
required this.blockTime,
|
||||||
|
required this.to,
|
||||||
|
required this.from,
|
||||||
|
required this.direction,
|
||||||
|
required this.solAmount,
|
||||||
|
this.tokenSymbol = "SOL",
|
||||||
|
required this.isPending,
|
||||||
|
required this.txFee,
|
||||||
|
}) : amount = solAmount.toInt();
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String? to;
|
||||||
|
final String? from;
|
||||||
|
final int amount;
|
||||||
|
final bool isPending;
|
||||||
|
final double solAmount;
|
||||||
|
final String tokenSymbol;
|
||||||
|
final DateTime blockTime;
|
||||||
|
final double txFee;
|
||||||
|
final TransactionDirection direction;
|
||||||
|
|
||||||
|
String? _fiatAmount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime get date => blockTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String amountFormatted() {
|
||||||
|
String stringBalance = solAmount.toString();
|
||||||
|
|
||||||
|
if (stringBalance.toString().length >= 6) {
|
||||||
|
stringBalance = stringBalance.substring(0, 6);
|
||||||
|
}
|
||||||
|
return '$stringBalance $tokenSymbol';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String fiatAmount() => _fiatAmount ?? '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String feeFormatted() => '${txFee.toString()} SOL';
|
||||||
|
|
||||||
|
factory SolanaTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||||
|
return SolanaTransactionInfo(
|
||||||
|
id: data['id'] as String,
|
||||||
|
solAmount: data['solAmount'],
|
||||||
|
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||||
|
blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int),
|
||||||
|
isPending: data['isPending'] as bool,
|
||||||
|
tokenSymbol: data['tokenSymbol'] as String,
|
||||||
|
to: data['to'],
|
||||||
|
from: data['from'],
|
||||||
|
txFee: data['txFee'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'solAmount': solAmount,
|
||||||
|
'direction': direction.index,
|
||||||
|
'blockTime': blockTime.millisecondsSinceEpoch,
|
||||||
|
'isPending': isPending,
|
||||||
|
'tokenSymbol': tokenSymbol,
|
||||||
|
'to': to,
|
||||||
|
'from': from,
|
||||||
|
'txFee': txFee,
|
||||||
|
};
|
||||||
|
}
|
47
cw_solana/lib/solana_transaction_model.dart
Normal file
47
cw_solana/lib/solana_transaction_model.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
class SolanaTransactionModel {
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
final String from;
|
||||||
|
|
||||||
|
final String to;
|
||||||
|
|
||||||
|
final double amount;
|
||||||
|
|
||||||
|
// If this is an outgoing transaction
|
||||||
|
final bool isOutgoingTx;
|
||||||
|
|
||||||
|
// The Program ID of this transaction, e.g, System Program, Token Program...
|
||||||
|
final String programId;
|
||||||
|
|
||||||
|
// The DateTime from the UNIX timestamp of the block where the transaction was included
|
||||||
|
final DateTime blockTime;
|
||||||
|
|
||||||
|
// The Transaction fee
|
||||||
|
final double fee;
|
||||||
|
|
||||||
|
// The token symbol
|
||||||
|
final String tokenSymbol;
|
||||||
|
|
||||||
|
SolanaTransactionModel({
|
||||||
|
required this.id,
|
||||||
|
required this.to,
|
||||||
|
required this.from,
|
||||||
|
required this.amount,
|
||||||
|
required this.programId,
|
||||||
|
required int blockTimeInInt,
|
||||||
|
this.isOutgoingTx = false,
|
||||||
|
required this.tokenSymbol,
|
||||||
|
required this.fee,
|
||||||
|
}) : blockTime = DateTime.fromMillisecondsSinceEpoch(blockTimeInInt * 1000);
|
||||||
|
|
||||||
|
factory SolanaTransactionModel.fromJson(Map<String, dynamic> json) => SolanaTransactionModel(
|
||||||
|
id: json['id'],
|
||||||
|
blockTimeInInt: int.parse(json["timeStamp"]) * 1000,
|
||||||
|
from: json["from"],
|
||||||
|
to: json["to"],
|
||||||
|
amount: double.parse(json["value"]),
|
||||||
|
programId: json["programId"],
|
||||||
|
fee: json['fee'],
|
||||||
|
tokenSymbol: json['tokenSymbol'],
|
||||||
|
);
|
||||||
|
}
|
510
cw_solana/lib/solana_wallet.dart
Normal file
510
cw_solana/lib/solana_wallet.dart
Normal file
|
@ -0,0 +1,510 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
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_solana/default_spl_tokens.dart';
|
||||||
|
import 'package:cw_solana/file.dart';
|
||||||
|
import 'package:cw_solana/solana_balance.dart';
|
||||||
|
import 'package:cw_solana/solana_client.dart';
|
||||||
|
import 'package:cw_solana/solana_exceptions.dart';
|
||||||
|
import 'package:cw_solana/solana_transaction_credentials.dart';
|
||||||
|
import 'package:cw_solana/solana_transaction_history.dart';
|
||||||
|
import 'package:cw_solana/solana_transaction_info.dart';
|
||||||
|
import 'package:cw_solana/solana_transaction_model.dart';
|
||||||
|
import 'package:cw_solana/solana_wallet_addresses.dart';
|
||||||
|
import 'package:cw_solana/spl_token.dart';
|
||||||
|
import 'package:hex/hex.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:solana/metaplex.dart' as metaplex;
|
||||||
|
import 'package:solana/solana.dart';
|
||||||
|
import 'package:web3dart/crypto.dart';
|
||||||
|
|
||||||
|
part 'solana_wallet.g.dart';
|
||||||
|
|
||||||
|
class SolanaWallet = SolanaWalletBase with _$SolanaWallet;
|
||||||
|
|
||||||
|
abstract class SolanaWalletBase
|
||||||
|
extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo> with Store {
|
||||||
|
SolanaWalletBase({
|
||||||
|
required WalletInfo walletInfo,
|
||||||
|
String? mnemonic,
|
||||||
|
String? privateKey,
|
||||||
|
required String password,
|
||||||
|
SolanaBalance? initialBalance,
|
||||||
|
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||||
|
_password = password,
|
||||||
|
_mnemonic = mnemonic,
|
||||||
|
_hexPrivateKey = privateKey,
|
||||||
|
_client = SolanaWalletClient(),
|
||||||
|
walletAddresses = SolanaWalletAddresses(walletInfo),
|
||||||
|
balance = ObservableMap<CryptoCurrency, SolanaBalance>.of(
|
||||||
|
{CryptoCurrency.sol: initialBalance ?? SolanaBalance(BigInt.zero.toDouble())}),
|
||||||
|
super(walletInfo) {
|
||||||
|
this.walletInfo = walletInfo;
|
||||||
|
transactionHistory = SolanaTransactionHistory(walletInfo: walletInfo, password: password);
|
||||||
|
|
||||||
|
if (!CakeHive.isAdapterRegistered(SPLToken.typeId)) {
|
||||||
|
CakeHive.registerAdapter(SPLTokenAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
|
_sharedPrefs.complete(SharedPreferences.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
final String _password;
|
||||||
|
final String? _mnemonic;
|
||||||
|
final String? _hexPrivateKey;
|
||||||
|
|
||||||
|
// The Solana WalletPair
|
||||||
|
Ed25519HDKeyPair? _walletKeyPair;
|
||||||
|
|
||||||
|
Ed25519HDKeyPair? get walletKeyPair => _walletKeyPair;
|
||||||
|
|
||||||
|
// To access the privateKey bytes.
|
||||||
|
Ed25519HDKeyPairData? _keyPairData;
|
||||||
|
|
||||||
|
late SolanaWalletClient _client;
|
||||||
|
|
||||||
|
Timer? _transactionsUpdateTimer;
|
||||||
|
|
||||||
|
late final Box<SPLToken> splTokensBox;
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletAddresses walletAddresses;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
SyncStatus syncStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
late ObservableMap<CryptoCurrency, SolanaBalance> balance;
|
||||||
|
|
||||||
|
Completer<SharedPreferences> _sharedPrefs = Completer();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Ed25519HDKeyPairData get keys {
|
||||||
|
if (_keyPairData == null) {
|
||||||
|
return Ed25519HDKeyPairData([], publicKey: const Ed25519HDPublicKey([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _keyPairData!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get seed => _mnemonic;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get privateKey => HEX.encode(_keyPairData!.bytes);
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
|
||||||
|
|
||||||
|
splTokensBox = await CakeHive.openBox<SPLToken>(boxName);
|
||||||
|
|
||||||
|
// Create WalletPair using either the mnemonic or the privateKey
|
||||||
|
_walletKeyPair = await getWalletPair(
|
||||||
|
mnemonic: _mnemonic,
|
||||||
|
privateKey: _hexPrivateKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extract the keyPairData containing both the privateKey bytes and the publicKey hex.
|
||||||
|
_keyPairData = await _walletKeyPair!.extract();
|
||||||
|
|
||||||
|
walletInfo.address = _walletKeyPair!.address;
|
||||||
|
|
||||||
|
await walletAddresses.init();
|
||||||
|
await transactionHistory.init();
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Wallet> getWalletPair({String? mnemonic, String? privateKey}) async {
|
||||||
|
assert(mnemonic != null || privateKey != null);
|
||||||
|
|
||||||
|
if (privateKey != null) {
|
||||||
|
final privateKeyBytes = hexToBytes(privateKey);
|
||||||
|
return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Wallet.fromMnemonic(mnemonic!, account: 0, change: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||||
|
|
||||||
|
@override
|
||||||
|
void close() {
|
||||||
|
_client.stop();
|
||||||
|
_transactionsUpdateTimer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> connectToNode({required Node node}) async {
|
||||||
|
try {
|
||||||
|
syncStatus = ConnectingSyncStatus();
|
||||||
|
|
||||||
|
final isConnected = _client.connect(node);
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
throw Exception("Solana Node connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Future.wait([
|
||||||
|
_updateBalance(),
|
||||||
|
_updateNativeSOLTransactions(),
|
||||||
|
_updateSPLTokenTransactions(),
|
||||||
|
]);
|
||||||
|
} catch (e) {
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
_setTransactionUpdateTimer();
|
||||||
|
|
||||||
|
syncStatus = ConnectedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
|
final solCredentials = credentials as SolanaTransactionCredentials;
|
||||||
|
|
||||||
|
final outputs = solCredentials.outputs;
|
||||||
|
|
||||||
|
final hasMultiDestination = outputs.length > 1;
|
||||||
|
|
||||||
|
await _updateBalance();
|
||||||
|
|
||||||
|
final CryptoCurrency transactionCurrency =
|
||||||
|
balance.keys.firstWhere((element) => element.title == solCredentials.currency.title);
|
||||||
|
|
||||||
|
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
|
||||||
|
|
||||||
|
double totalAmount = 0.0;
|
||||||
|
|
||||||
|
if (hasMultiDestination) {
|
||||||
|
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||||
|
throw SolanaTransactionWrongBalanceException(transactionCurrency);
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalAmountFromCredentials =
|
||||||
|
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||||
|
|
||||||
|
totalAmount = totalAmountFromCredentials.toDouble();
|
||||||
|
|
||||||
|
if (walletBalanceForCurrency < totalAmount) {
|
||||||
|
throw SolanaTransactionWrongBalanceException(transactionCurrency);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final output = outputs.first;
|
||||||
|
|
||||||
|
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
|
||||||
|
|
||||||
|
totalAmount = output.sendAll ? walletBalanceForCurrency : totalOriginalAmount;
|
||||||
|
|
||||||
|
if (walletBalanceForCurrency < totalAmount) {
|
||||||
|
throw SolanaTransactionWrongBalanceException(transactionCurrency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? tokenMint;
|
||||||
|
// Token Mint is only needed for transactions that are not native tokens(non-SOL transactions)
|
||||||
|
if (transactionCurrency.title != CryptoCurrency.sol.title) {
|
||||||
|
tokenMint = (transactionCurrency as SPLToken).mintAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
final pendingSolanaTransaction = await _client.signSolanaTransaction(
|
||||||
|
tokenMint: tokenMint,
|
||||||
|
tokenTitle: transactionCurrency.title,
|
||||||
|
inputAmount: totalAmount,
|
||||||
|
ownerKeypair: _walletKeyPair!,
|
||||||
|
tokenDecimals: transactionCurrency.decimals,
|
||||||
|
destinationAddress: solCredentials.outputs.first.isParsedAddress
|
||||||
|
? solCredentials.outputs.first.extractedAddress!
|
||||||
|
: solCredentials.outputs.first.address,
|
||||||
|
);
|
||||||
|
|
||||||
|
return pendingSolanaTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, SolanaTransactionInfo>> fetchTransactions() async => {};
|
||||||
|
|
||||||
|
/// Fetches the native SOL transactions linked to the wallet Public Key
|
||||||
|
Future<void> _updateNativeSOLTransactions() async {
|
||||||
|
final address = Ed25519HDPublicKey.fromBase58(_walletKeyPair!.address);
|
||||||
|
|
||||||
|
final transactions = await _client.fetchTransactions(address);
|
||||||
|
|
||||||
|
final Map<String, SolanaTransactionInfo> result = {};
|
||||||
|
|
||||||
|
for (var transactionModel in transactions) {
|
||||||
|
result[transactionModel.id] = SolanaTransactionInfo(
|
||||||
|
id: transactionModel.id,
|
||||||
|
to: transactionModel.to,
|
||||||
|
from: transactionModel.from,
|
||||||
|
blockTime: transactionModel.blockTime,
|
||||||
|
direction: transactionModel.isOutgoingTx
|
||||||
|
? TransactionDirection.outgoing
|
||||||
|
: TransactionDirection.incoming,
|
||||||
|
solAmount: transactionModel.amount,
|
||||||
|
isPending: false,
|
||||||
|
txFee: transactionModel.fee,
|
||||||
|
tokenSymbol: transactionModel.tokenSymbol,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionHistory.addMany(result);
|
||||||
|
|
||||||
|
await transactionHistory.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetches the SPL Tokens transactions linked to the token account Public Key
|
||||||
|
Future<void> _updateSPLTokenTransactions() async {
|
||||||
|
List<SolanaTransactionModel> splTokenTransactions = [];
|
||||||
|
|
||||||
|
for (var token in balance.keys) {
|
||||||
|
if (token is SPLToken) {
|
||||||
|
final tokenTxs = await _client.getSPLTokenTransfers(
|
||||||
|
token.mintAddress,
|
||||||
|
token.symbol,
|
||||||
|
token.decimal,
|
||||||
|
_walletKeyPair!,
|
||||||
|
);
|
||||||
|
|
||||||
|
splTokenTransactions.addAll(tokenTxs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, SolanaTransactionInfo> result = {};
|
||||||
|
|
||||||
|
for (var transactionModel in splTokenTransactions) {
|
||||||
|
result[transactionModel.id] = SolanaTransactionInfo(
|
||||||
|
id: transactionModel.id,
|
||||||
|
to: transactionModel.to,
|
||||||
|
from: transactionModel.from,
|
||||||
|
blockTime: transactionModel.blockTime,
|
||||||
|
direction: transactionModel.isOutgoingTx
|
||||||
|
? TransactionDirection.outgoing
|
||||||
|
: TransactionDirection.incoming,
|
||||||
|
solAmount: transactionModel.amount,
|
||||||
|
isPending: false,
|
||||||
|
txFee: transactionModel.fee,
|
||||||
|
tokenSymbol: transactionModel.tokenSymbol,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionHistory.addMany(result);
|
||||||
|
|
||||||
|
await transactionHistory.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> startSync() async {
|
||||||
|
try {
|
||||||
|
syncStatus = AttemptingSyncStatus();
|
||||||
|
|
||||||
|
await Future.wait([
|
||||||
|
_updateBalance(),
|
||||||
|
_updateNativeSOLTransactions(),
|
||||||
|
_updateSPLTokenTransactions(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
syncStatus = SyncedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
|
||||||
|
String toJSON() => json.encode({
|
||||||
|
'mnemonic': _mnemonic,
|
||||||
|
'private_key': privateKey,
|
||||||
|
'balance': balance[currency]!.toJSON(),
|
||||||
|
});
|
||||||
|
|
||||||
|
static Future<SolanaWallet> 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 = SolanaBalance.fromJSON(data['balance'] as String) ?? SolanaBalance(0.0);
|
||||||
|
|
||||||
|
return SolanaWallet(
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
password: password,
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
privateKey: privateKey,
|
||||||
|
initialBalance: balance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateBalance() async {
|
||||||
|
balance[currency] = await _fetchSOLBalance();
|
||||||
|
await _fetchSPLTokensBalances();
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SolanaBalance> _fetchSOLBalance() async {
|
||||||
|
final balance = await _client.getBalance(_walletKeyPair!.address);
|
||||||
|
|
||||||
|
return SolanaBalance(balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchSPLTokensBalances() async {
|
||||||
|
for (var token in splTokensBox.values) {
|
||||||
|
if (token.enabled) {
|
||||||
|
try {
|
||||||
|
final tokenBalance =
|
||||||
|
await _client.getSplTokenBalance(token.mintAddress, _walletKeyPair!.address) ??
|
||||||
|
balance[token] ??
|
||||||
|
SolanaBalance(0.0);
|
||||||
|
balance[token] = tokenBalance;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching spl token (${token.symbol}) balance ${e.toString()}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
balance.remove(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void>? updateBalance() async => await _updateBalance();
|
||||||
|
|
||||||
|
List<SPLToken> get splTokenCurrencies => splTokensBox.values.toList();
|
||||||
|
|
||||||
|
void addInitialTokens() {
|
||||||
|
final initialSPLTokens = DefaultSPLTokens().initialSPLTokens;
|
||||||
|
|
||||||
|
for (var token in initialSPLTokens) {
|
||||||
|
splTokensBox.put(token.mintAddress, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addSPLToken(SPLToken token) async {
|
||||||
|
await splTokensBox.put(token.mintAddress, token);
|
||||||
|
|
||||||
|
if (token.enabled) {
|
||||||
|
final tokenBalance =
|
||||||
|
await _client.getSplTokenBalance(token.mintAddress, _walletKeyPair!.address) ??
|
||||||
|
balance[token] ??
|
||||||
|
SolanaBalance(0.0);
|
||||||
|
|
||||||
|
balance[token] = tokenBalance;
|
||||||
|
} else {
|
||||||
|
balance.remove(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteSPLToken(SPLToken token) async {
|
||||||
|
await token.delete();
|
||||||
|
|
||||||
|
balance.remove(token);
|
||||||
|
_updateBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SPLToken?> getSPLToken(String mintAddress) async {
|
||||||
|
// Convert SPL token mint address to public key
|
||||||
|
final mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress);
|
||||||
|
|
||||||
|
// Fetch token's metadata account
|
||||||
|
final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey);
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SPLToken.fromMetadata(
|
||||||
|
name: token.name,
|
||||||
|
mint: token.mint,
|
||||||
|
symbol: token.symbol,
|
||||||
|
mintAddress: mintAddress,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> renameWalletFiles(String newWalletName) async {
|
||||||
|
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/$transactionsHistoryFileName');
|
||||||
|
|
||||||
|
// 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/$transactionsHistoryFileName');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: 20), (_) {
|
||||||
|
_updateSPLTokenTransactions();
|
||||||
|
_updateNativeSOLTransactions();
|
||||||
|
_updateBalance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> signSolanaMessage(String message) async {
|
||||||
|
// Convert the message to bytes
|
||||||
|
final messageBytes = utf8.encode(message);
|
||||||
|
|
||||||
|
// Sign the message bytes with the wallet's private key
|
||||||
|
final signature = await _walletKeyPair!.sign(messageBytes);
|
||||||
|
|
||||||
|
// Convert the signature to a hexadecimal string
|
||||||
|
final hex = bytesToHex(signature.bytes);
|
||||||
|
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
SolanaClient? get solanaClient => _client.getSolanaClient;
|
||||||
|
}
|
33
cw_solana/lib/solana_wallet_addresses.dart
Normal file
33
cw_solana/lib/solana_wallet_addresses.dart
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'solana_wallet_addresses.g.dart';
|
||||||
|
|
||||||
|
class SolanaWalletAddresses = SolanaWalletAddressesBase with _$SolanaWalletAddresses;
|
||||||
|
|
||||||
|
abstract class SolanaWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
SolanaWalletAddressesBase(WalletInfo walletInfo)
|
||||||
|
: address = '',
|
||||||
|
super(walletInfo);
|
||||||
|
|
||||||
|
@override
|
||||||
|
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) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
cw_solana/lib/solana_wallet_creation_credentials.dart
Normal file
29
cw_solana/lib/solana_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 SolanaNewWalletCredentials extends WalletCredentials {
|
||||||
|
SolanaNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
||||||
|
: super(name: name, walletInfo: walletInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SolanaRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||||
|
SolanaRestoreWalletFromSeedCredentials(
|
||||||
|
{required String name,
|
||||||
|
required String password,
|
||||||
|
required this.mnemonic,
|
||||||
|
WalletInfo? walletInfo})
|
||||||
|
: super(name: name, password: password, walletInfo: walletInfo);
|
||||||
|
|
||||||
|
final String mnemonic;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SolanaRestoreWalletFromPrivateKey extends WalletCredentials {
|
||||||
|
SolanaRestoreWalletFromPrivateKey(
|
||||||
|
{required String name,
|
||||||
|
required String password,
|
||||||
|
required this.privateKey,
|
||||||
|
WalletInfo? walletInfo})
|
||||||
|
: super(name: name, password: password, walletInfo: walletInfo);
|
||||||
|
|
||||||
|
final String privateKey;
|
||||||
|
}
|
120
cw_solana/lib/solana_wallet_service.dart
Normal file
120
cw_solana/lib/solana_wallet_service.dart
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:cw_core/pathForWallet.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_solana/solana_mnemonics.dart';
|
||||||
|
import 'package:cw_solana/solana_wallet.dart';
|
||||||
|
import 'package:cw_solana/solana_wallet_creation_credentials.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
|
||||||
|
SolanaRestoreWalletFromSeedCredentials, SolanaRestoreWalletFromPrivateKey> {
|
||||||
|
SolanaWalletService(this.walletInfoSource);
|
||||||
|
|
||||||
|
final Box<WalletInfo> walletInfoSource;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SolanaWallet> create(SolanaNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
|
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||||
|
|
||||||
|
final mnemonic = bip39.generateMnemonic(strength: strength);
|
||||||
|
|
||||||
|
final wallet = SolanaWallet(
|
||||||
|
walletInfo: credentials.walletInfo!,
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
password: credentials.password!,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
wallet.addInitialTokens();
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletType getType() => WalletType.solana;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isWalletExit(String name) async =>
|
||||||
|
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SolanaWallet> openWallet(String name, String password) async {
|
||||||
|
final walletInfo =
|
||||||
|
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||||
|
final wallet = await SolanaWalletBase.open(
|
||||||
|
name: name,
|
||||||
|
password: password,
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<SolanaWallet> restoreFromKeys(SolanaRestoreWalletFromPrivateKey credentials,
|
||||||
|
{bool? isTestnet}) async {
|
||||||
|
final wallet = SolanaWallet(
|
||||||
|
password: credentials.password!,
|
||||||
|
privateKey: credentials.privateKey,
|
||||||
|
walletInfo: credentials.walletInfo!,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
wallet.addInitialTokens();
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SolanaWallet> restoreFromSeed(SolanaRestoreWalletFromSeedCredentials credentials,
|
||||||
|
{bool? isTestnet}) async {
|
||||||
|
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||||
|
throw SolanaMnemonicIsIncorrectException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final wallet = SolanaWallet(
|
||||||
|
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 SolanaWalletBase.open(
|
||||||
|
password: password, name: currentName, walletInfo: currentWalletInfo);
|
||||||
|
|
||||||
|
await currentWallet.renameWalletFiles(newName);
|
||||||
|
|
||||||
|
final newWalletInfo = currentWalletInfo;
|
||||||
|
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||||
|
newWalletInfo.name = newName;
|
||||||
|
|
||||||
|
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||||
|
}
|
||||||
|
}
|
146
cw_solana/lib/spl_token.dart
Normal file
146
cw_solana/lib/spl_token.dart
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/hive_type_ids.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:solana/metaplex.dart';
|
||||||
|
|
||||||
|
part 'spl_token.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: SPLToken.typeId)
|
||||||
|
class SPLToken extends CryptoCurrency with HiveObjectMixin {
|
||||||
|
@HiveField(0)
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
|
final String symbol;
|
||||||
|
|
||||||
|
@HiveField(2)
|
||||||
|
final String mintAddress;
|
||||||
|
|
||||||
|
@HiveField(3)
|
||||||
|
final int decimal;
|
||||||
|
|
||||||
|
@HiveField(4, defaultValue: false)
|
||||||
|
bool _enabled;
|
||||||
|
|
||||||
|
@HiveField(5)
|
||||||
|
final String mint;
|
||||||
|
|
||||||
|
@HiveField(6)
|
||||||
|
final String? iconPath;
|
||||||
|
|
||||||
|
@HiveField(7)
|
||||||
|
final String? tag;
|
||||||
|
|
||||||
|
SPLToken({
|
||||||
|
required this.name,
|
||||||
|
required this.symbol,
|
||||||
|
required this.mintAddress,
|
||||||
|
required this.decimal,
|
||||||
|
required this.mint,
|
||||||
|
this.iconPath,
|
||||||
|
this.tag = 'SOL',
|
||||||
|
bool enabled = false,
|
||||||
|
}) : _enabled = enabled,
|
||||||
|
super(
|
||||||
|
name: mint.toLowerCase(),
|
||||||
|
title: symbol.toUpperCase(),
|
||||||
|
fullName: name,
|
||||||
|
tag: tag,
|
||||||
|
iconPath: iconPath,
|
||||||
|
decimals: decimal,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory SPLToken.fromMetadata({
|
||||||
|
required String name,
|
||||||
|
required String mint,
|
||||||
|
required String symbol,
|
||||||
|
required String mintAddress,
|
||||||
|
}) {
|
||||||
|
return SPLToken(
|
||||||
|
name: name,
|
||||||
|
symbol: symbol,
|
||||||
|
mintAddress: mintAddress,
|
||||||
|
decimal: 0,
|
||||||
|
mint: mint,
|
||||||
|
iconPath: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SPLToken.cryptoCurrency({
|
||||||
|
required String name,
|
||||||
|
required String symbol,
|
||||||
|
required int decimals,
|
||||||
|
required String iconPath,
|
||||||
|
required String mint,
|
||||||
|
}) {
|
||||||
|
return SPLToken(
|
||||||
|
name: name,
|
||||||
|
symbol: symbol,
|
||||||
|
decimal: decimals,
|
||||||
|
mint: mint,
|
||||||
|
iconPath: iconPath,
|
||||||
|
mintAddress: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get enabled => _enabled;
|
||||||
|
|
||||||
|
set enabled(bool value) => _enabled = value;
|
||||||
|
|
||||||
|
SPLToken.copyWith(SPLToken other, String? icon, String? tag)
|
||||||
|
: name = other.name,
|
||||||
|
symbol = other.symbol,
|
||||||
|
mintAddress = other.mintAddress,
|
||||||
|
decimal = other.decimal,
|
||||||
|
_enabled = other.enabled,
|
||||||
|
mint = other.mint,
|
||||||
|
tag = other.tag,
|
||||||
|
iconPath = icon,
|
||||||
|
super(
|
||||||
|
title: other.symbol.toUpperCase(),
|
||||||
|
name: other.symbol.toLowerCase(),
|
||||||
|
decimals: other.decimal,
|
||||||
|
fullName: other.name,
|
||||||
|
tag: other.tag,
|
||||||
|
iconPath: icon,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const typeId = SPL_TOKEN_TYPE_ID;
|
||||||
|
static const boxName = 'SPLTokens';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(other) =>
|
||||||
|
(other is SPLToken && other.mintAddress == mintAddress) ||
|
||||||
|
(other is CryptoCurrency && other.title == title);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => mintAddress.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NFT extends SPLToken {
|
||||||
|
final ImageInfo? imageInfo;
|
||||||
|
|
||||||
|
NFT(
|
||||||
|
String mint,
|
||||||
|
String name,
|
||||||
|
String symbol,
|
||||||
|
String mintAddress,
|
||||||
|
int decimal,
|
||||||
|
String iconPath,
|
||||||
|
this.imageInfo,
|
||||||
|
) : super(
|
||||||
|
name: name,
|
||||||
|
symbol: symbol,
|
||||||
|
mintAddress: mintAddress,
|
||||||
|
decimal: decimal,
|
||||||
|
mint: mint,
|
||||||
|
iconPath: iconPath,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageInfo {
|
||||||
|
final String uri;
|
||||||
|
final OffChainMetadata? data;
|
||||||
|
|
||||||
|
const ImageInfo(this.uri, this.data);
|
||||||
|
}
|
37
cw_solana/pubspec.yaml
Normal file
37
cw_solana/pubspec.yaml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
name: cw_solana
|
||||||
|
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
|
||||||
|
solana: ^0.30.1
|
||||||
|
cw_core:
|
||||||
|
path: ../cw_core
|
||||||
|
http: ^1.1.0
|
||||||
|
hive: ^2.2.3
|
||||||
|
bip39: ^1.0.6
|
||||||
|
mobx: ^2.3.0+1
|
||||||
|
shared_preferences: ^2.0.15
|
||||||
|
web3dart: ^2.7.1
|
||||||
|
bip32: ^2.0.0
|
||||||
|
hex: ^0.2.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_lints: ^2.0.0
|
||||||
|
build_runner: ^2.1.11
|
||||||
|
mobx_codegen: ^2.0.7
|
||||||
|
hive_generator: ^1.1.3
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
# assets:
|
||||||
|
# - images/a_dot_burr.jpeg
|
||||||
|
# - images/a_dot_ham.jpeg
|
12
cw_solana/test/cw_solana_test.dart
Normal file
12
cw_solana/test/cw_solana_test.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:cw_solana/cw_solana.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);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
platform :ios, '11.0'
|
platform :ios, '12.0'
|
||||||
source 'https://github.com/CocoaPods/Specs.git'
|
source 'https://github.com/CocoaPods/Specs.git'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
@ -44,7 +44,7 @@ post_install do |installer|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
|
||||||
target.build_configurations.each do |config|
|
target.build_configurations.each do |config|
|
||||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
|
||||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||||
'$(inherited)',
|
'$(inherited)',
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ PODS:
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
- CryptoSwift (1.7.1)
|
- CryptoSwift (1.8.1)
|
||||||
- cw_haven (0.0.1):
|
- cw_haven (0.0.1):
|
||||||
- cw_haven/Boost (= 0.0.1)
|
- cw_haven/Boost (= 0.0.1)
|
||||||
- cw_haven/Haven (= 0.0.1)
|
- cw_haven/Haven (= 0.0.1)
|
||||||
|
@ -132,9 +132,9 @@ PODS:
|
||||||
- permission_handler_apple (9.1.1):
|
- permission_handler_apple (9.1.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift (5.0.0)
|
- ReachabilitySwift (5.0.0)
|
||||||
- SDWebImage (5.16.0):
|
- SDWebImage (5.18.11):
|
||||||
- SDWebImage/Core (= 5.16.0)
|
- SDWebImage/Core (= 5.18.11)
|
||||||
- SDWebImage/Core (5.16.0)
|
- SDWebImage/Core (5.18.11)
|
||||||
- sensitive_clipboard (0.0.1):
|
- sensitive_clipboard (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
|
@ -142,9 +142,9 @@ PODS:
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- SwiftProtobuf (1.22.0)
|
- SwiftProtobuf (1.25.2)
|
||||||
- SwiftyGif (5.4.4)
|
- SwiftyGif (5.4.4)
|
||||||
- Toast (4.0.0)
|
- Toast (4.1.0)
|
||||||
- uni_links (0.0.1):
|
- uni_links (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- UnstoppableDomainsResolution (4.0.0):
|
- UnstoppableDomainsResolution (4.0.0):
|
||||||
|
@ -262,8 +262,8 @@ EXTERNAL SOURCES:
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
|
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
|
||||||
BigInt: f668a80089607f521586bbe29513d708491ef2f7
|
BigInt: f668a80089607f521586bbe29513d708491ef2f7
|
||||||
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
|
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||||
CryptoSwift: d3d18dc357932f7e6d580689e065cf1f176007c1
|
CryptoSwift: b9c701d6f5011df23794dbf7f2e480a77835d83d
|
||||||
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
|
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
|
||||||
cw_monero: 4cf3b96f2da8e95e2ef7d6703dd4d2c509127b7d
|
cw_monero: 4cf3b96f2da8e95e2ef7d6703dd4d2c509127b7d
|
||||||
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
|
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
|
||||||
|
@ -287,19 +287,19 @@ SPEC CHECKSUMS:
|
||||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
SDWebImage: 2aea163b50bfcb569a2726b6a754c54a4506fcf6
|
SDWebImage: a3ba0b8faac7228c3c8eadd1a55c9c9fe5e16457
|
||||||
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
|
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
|
||||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||||
SwiftProtobuf: 40bd808372cb8706108f22d28f8ab4a6b9bc6989
|
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
|
||||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
||||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||||
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
|
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
|
||||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||||
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
|
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
|
||||||
|
|
||||||
PODFILE CHECKSUM: 09df1114e7c360f55770d35a79356bf5446e0100
|
PODFILE CHECKSUM: fcb1b8418441a35b438585c9dd8374e722e6c6ca
|
||||||
|
|
||||||
COCOAPODS: 1.12.1
|
COCOAPODS: 1.12.1
|
||||||
|
|
|
@ -377,7 +377,7 @@
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -523,7 +523,7 @@
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -561,7 +561,7 @@
|
||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -607,9 +607,4 @@
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
SystemCapabilities = {
|
|
||||||
com.apple.BackgroundModes = {
|
|
||||||
enabled = 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,6 +180,16 @@
|
||||||
<string>polygon-wallet</string>
|
<string>polygon-wallet</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>solana-wallet</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>solana-wallet</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
|
|
@ -1,182 +1,209 @@
|
||||||
part of 'bitcoin.dart';
|
part of 'bitcoin.dart';
|
||||||
|
|
||||||
class CWBitcoin extends Bitcoin {
|
class CWBitcoin extends Bitcoin {
|
||||||
@override
|
@override
|
||||||
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
|
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
|
||||||
|
|
||||||
@override
|
|
||||||
WalletCredentials createBitcoinRestoreWalletFromSeedCredentials({
|
|
||||||
required String name,
|
|
||||||
required String mnemonic,
|
|
||||||
required String password})
|
|
||||||
=> BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
|
|
||||||
|
|
||||||
@override
|
|
||||||
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({
|
|
||||||
required String name,
|
|
||||||
required String password,
|
|
||||||
required String wif,
|
|
||||||
WalletInfo? walletInfo})
|
|
||||||
=> BitcoinRestoreWalletFromWIFCredentials(name: name, password: password, wif: wif, walletInfo: walletInfo);
|
|
||||||
|
|
||||||
@override
|
|
||||||
WalletCredentials createBitcoinNewWalletCredentials({
|
|
||||||
required String name,
|
|
||||||
WalletInfo? walletInfo})
|
|
||||||
=> BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<String> getWordList() => wordlist;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> getWalletKeys(Object wallet) {
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
|
||||||
final keys = bitcoinWallet.keys;
|
|
||||||
|
|
||||||
return <String, String>{
|
|
||||||
'wif': keys.wif,
|
|
||||||
'privateKey': keys.privateKey,
|
|
||||||
'publicKey': keys.publicKey
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<TransactionPriority> getTransactionPriorities()
|
|
||||||
=> BitcoinTransactionPriority.all;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<TransactionPriority> getLitecoinTransactionPriorities()
|
|
||||||
=> LitecoinTransactionPriority.all;
|
|
||||||
|
|
||||||
@override
|
|
||||||
TransactionPriority deserializeBitcoinTransactionPriority(int raw)
|
|
||||||
=> BitcoinTransactionPriority.deserialize(raw: raw);
|
|
||||||
|
|
||||||
@override
|
|
||||||
TransactionPriority deserializeLitecoinTransactionPriority(int raw)
|
|
||||||
=> LitecoinTransactionPriority.deserialize(raw: raw);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int getFeeRate(Object wallet, TransactionPriority priority) {
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
|
||||||
return bitcoinWallet.feeRate(priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> generateNewAddress(Object wallet, String label) async {
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
|
||||||
await bitcoinWallet.walletAddresses.generateNewAddress(label: label, hd: bitcoinWallet.hd);
|
|
||||||
await wallet.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> updateAddress(Object wallet,String address, String label) async {
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
|
||||||
bitcoinWallet.walletAddresses.updateAddress(address, label);
|
|
||||||
await wallet.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate})
|
|
||||||
=> BitcoinTransactionCredentials(
|
|
||||||
outputs.map((out) => OutputInfo(
|
|
||||||
fiatAmount: out.fiatAmount,
|
|
||||||
cryptoAmount: out.cryptoAmount,
|
|
||||||
address: out.address,
|
|
||||||
note: out.note,
|
|
||||||
sendAll: out.sendAll,
|
|
||||||
extractedAddress: out.extractedAddress,
|
|
||||||
isParsedAddress: out.isParsedAddress,
|
|
||||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
|
||||||
memo: out.memo))
|
|
||||||
.toList(),
|
|
||||||
priority: priority as BitcoinTransactionPriority,
|
|
||||||
feeRate: feeRate);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority? priority, required int feeRate})
|
|
||||||
=> BitcoinTransactionCredentials(
|
|
||||||
outputs,
|
|
||||||
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
|
||||||
feeRate: feeRate);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<String> getAddresses(Object wallet) {
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
|
||||||
return bitcoinWallet.walletAddresses.addresses
|
|
||||||
.map((BitcoinAddressRecord addr) => addr.address)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@computed
|
|
||||||
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
|
||||||
final electrumWallet = wallet as ElectrumWallet;
|
|
||||||
return electrumWallet.walletAddresses.addresses
|
|
||||||
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
|
|
||||||
id: addr.index,
|
|
||||||
name: addr.name,
|
|
||||||
address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address,
|
|
||||||
txCount: addr.txCount,
|
|
||||||
balance: addr.balance,
|
|
||||||
isChange: addr.isHidden))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String getAddress(Object wallet) {
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
|
||||||
return bitcoinWallet.walletAddresses.address;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String formatterBitcoinAmountToString({required int amount})
|
|
||||||
=> bitcoinAmountToString(amount: amount);
|
|
||||||
|
|
||||||
@override
|
|
||||||
double formatterBitcoinAmountToDouble({required int amount})
|
|
||||||
=> bitcoinAmountToDouble(amount: amount);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int formatterStringDoubleToBitcoinAmount(String amount)
|
|
||||||
=> stringDoubleToBitcoinAmount(amount);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate)
|
WalletCredentials createBitcoinRestoreWalletFromSeedCredentials(
|
||||||
=> (priority as BitcoinTransactionPriority).labelWithRate(rate);
|
{required String name, required String mnemonic, required String password}) =>
|
||||||
|
BitcoinRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
|
||||||
@override
|
|
||||||
List<BitcoinUnspent> getUnspents(Object wallet) {
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
|
||||||
return bitcoinWallet.unspentCoins;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateUnspents(Object wallet) async {
|
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
|
||||||
await bitcoinWallet.updateUnspent();
|
|
||||||
}
|
|
||||||
|
|
||||||
WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
|
|
||||||
return BitcoinWalletService(walletInfoSource, unspentCoinSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
|
|
||||||
return LitecoinWalletService(walletInfoSource, unspentCoinSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
TransactionPriority getBitcoinTransactionPriorityMedium()
|
|
||||||
=> BitcoinTransactionPriority.medium;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getLitecoinTransactionPriorityMedium()
|
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
|
||||||
=> LitecoinTransactionPriority.medium;
|
{required String name,
|
||||||
|
required String password,
|
||||||
|
required String wif,
|
||||||
|
WalletInfo? walletInfo}) =>
|
||||||
|
BitcoinRestoreWalletFromWIFCredentials(
|
||||||
|
name: name, password: password, wif: wif, walletInfo: walletInfo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getBitcoinTransactionPrioritySlow()
|
WalletCredentials createBitcoinNewWalletCredentials(
|
||||||
=> BitcoinTransactionPriority.slow;
|
{required String name, WalletInfo? walletInfo}) =>
|
||||||
|
BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getLitecoinTransactionPrioritySlow()
|
List<String> getWordList() => wordlist;
|
||||||
=> LitecoinTransactionPriority.slow;
|
|
||||||
}
|
@override
|
||||||
|
Map<String, String> getWalletKeys(Object wallet) {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
final keys = bitcoinWallet.keys;
|
||||||
|
|
||||||
|
return <String, String>{
|
||||||
|
'wif': keys.wif,
|
||||||
|
'privateKey': keys.privateKey,
|
||||||
|
'publicKey': keys.publicKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<TransactionPriority> getTransactionPriorities() => BitcoinTransactionPriority.all;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<TransactionPriority> getLitecoinTransactionPriorities() => LitecoinTransactionPriority.all;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority deserializeBitcoinTransactionPriority(int raw) =>
|
||||||
|
BitcoinTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority deserializeLitecoinTransactionPriority(int raw) =>
|
||||||
|
LitecoinTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int getFeeRate(Object wallet, TransactionPriority priority) {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
return bitcoinWallet.feeRate(priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> generateNewAddress(Object wallet, String label) async {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
await bitcoinWallet.walletAddresses.generateNewAddress(label: label);
|
||||||
|
await wallet.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateAddress(Object wallet, String address, String label) async {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
bitcoinWallet.walletAddresses.updateAddress(address, label);
|
||||||
|
await wallet.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object createBitcoinTransactionCredentials(List<Output> outputs,
|
||||||
|
{required TransactionPriority priority, int? feeRate}) =>
|
||||||
|
BitcoinTransactionCredentials(
|
||||||
|
outputs
|
||||||
|
.map((out) => OutputInfo(
|
||||||
|
fiatAmount: out.fiatAmount,
|
||||||
|
cryptoAmount: out.cryptoAmount,
|
||||||
|
address: out.address,
|
||||||
|
note: out.note,
|
||||||
|
sendAll: out.sendAll,
|
||||||
|
extractedAddress: out.extractedAddress,
|
||||||
|
isParsedAddress: out.isParsedAddress,
|
||||||
|
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||||
|
memo: out.memo))
|
||||||
|
.toList(),
|
||||||
|
priority: priority as BitcoinTransactionPriority,
|
||||||
|
feeRate: feeRate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs,
|
||||||
|
{TransactionPriority? priority, required int feeRate}) =>
|
||||||
|
BitcoinTransactionCredentials(outputs,
|
||||||
|
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
||||||
|
feeRate: feeRate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> getAddresses(Object wallet) {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
return bitcoinWallet.walletAddresses.addressesByReceiveType
|
||||||
|
.map((BitcoinAddressRecord addr) => addr.address)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@computed
|
||||||
|
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
||||||
|
final electrumWallet = wallet as ElectrumWallet;
|
||||||
|
return electrumWallet.walletAddresses.addressesByReceiveType
|
||||||
|
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
|
||||||
|
id: addr.index,
|
||||||
|
name: addr.name,
|
||||||
|
address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address,
|
||||||
|
txCount: addr.txCount,
|
||||||
|
balance: addr.balance,
|
||||||
|
isChange: addr.isHidden))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAddress(Object wallet) {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
return bitcoinWallet.walletAddresses.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String formatterBitcoinAmountToString({required int amount}) =>
|
||||||
|
bitcoinAmountToString(amount: amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
double formatterBitcoinAmountToDouble({required int amount}) =>
|
||||||
|
bitcoinAmountToDouble(amount: amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate) =>
|
||||||
|
(priority as BitcoinTransactionPriority).labelWithRate(rate);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<BitcoinUnspent> getUnspents(Object wallet) {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
return bitcoinWallet.unspentCoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateUnspents(Object wallet) async {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
await bitcoinWallet.updateUnspent();
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletService createBitcoinWalletService(
|
||||||
|
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
|
||||||
|
return BitcoinWalletService(walletInfoSource, unspentCoinSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletService createLitecoinWalletService(
|
||||||
|
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
|
||||||
|
return LitecoinWalletService(walletInfoSource, unspentCoinSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority getBitcoinTransactionPriorityMedium() => BitcoinTransactionPriority.medium;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority getLitecoinTransactionPriorityMedium() => LitecoinTransactionPriority.medium;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority getBitcoinTransactionPrioritySlow() => BitcoinTransactionPriority.slow;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority getLitecoinTransactionPrioritySlow() => LitecoinTransactionPriority.slow;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setAddressType(Object wallet, dynamic option) async {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
await bitcoinWallet.walletAddresses.setAddressType(option as BitcoinAddressType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReceivePageOption getSelectedAddressType(Object wallet) {
|
||||||
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
|
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option) {
|
||||||
|
switch (option) {
|
||||||
|
case BitcoinReceivePageOption.p2pkh:
|
||||||
|
return P2pkhAddressType.p2pkh;
|
||||||
|
case BitcoinReceivePageOption.p2sh:
|
||||||
|
return P2shAddressType.p2wpkhInP2sh;
|
||||||
|
case BitcoinReceivePageOption.p2tr:
|
||||||
|
return SegwitAddresType.p2tr;
|
||||||
|
case BitcoinReceivePageOption.p2wsh:
|
||||||
|
return SegwitAddresType.p2wsh;
|
||||||
|
case BitcoinReceivePageOption.p2wpkh:
|
||||||
|
default:
|
||||||
|
return SegwitAddresType.p2wpkh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
part of 'bitcoin_cash.dart';
|
part of 'bitcoin_cash.dart';
|
||||||
|
|
||||||
class CWBitcoinCash extends BitcoinCash {
|
class CWBitcoinCash extends BitcoinCash {
|
||||||
@override
|
|
||||||
String getMnemonic(int? strength) => Mnemonic.generate();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Uint8List getSeedFromMnemonic(String seed) => Mnemonic.toSeed(seed);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getCashAddrFormat(String address) => AddressUtils.getCashAddrFormat(address);
|
String getCashAddrFormat(String address) => AddressUtils.getCashAddrFormat(address);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/erc20_token.dart';
|
import 'package:cw_core/erc20_token.dart';
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ class AddressValidator extends TextValidator {
|
||||||
: super(
|
: super(
|
||||||
errorMessage: S.current.error_text_address,
|
errorMessage: S.current.error_text_address,
|
||||||
useAdditionalValidation: type == CryptoCurrency.btc
|
useAdditionalValidation: type == CryptoCurrency.btc
|
||||||
? bitcoin.Address.validateAddress
|
? (String txt) => validateAddress(address: txt, network: BitcoinNetwork.mainnet)
|
||||||
: null,
|
: null,
|
||||||
pattern: getPattern(type),
|
pattern: getPattern(type),
|
||||||
length: getLength(type));
|
length: getLength(type));
|
||||||
|
@ -25,7 +26,7 @@ class AddressValidator extends TextValidator {
|
||||||
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
|
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
|
||||||
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
|
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
|
||||||
case CryptoCurrency.btc:
|
case CryptoCurrency.btc:
|
||||||
return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
|
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$';
|
||||||
case CryptoCurrency.nano:
|
case CryptoCurrency.nano:
|
||||||
return '[0-9a-zA-Z_]';
|
return '[0-9a-zA-Z_]';
|
||||||
case CryptoCurrency.banano:
|
case CryptoCurrency.banano:
|
||||||
|
@ -90,7 +91,7 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.dai:
|
case CryptoCurrency.dai:
|
||||||
case CryptoCurrency.dash:
|
case CryptoCurrency.dash:
|
||||||
case CryptoCurrency.eos:
|
case CryptoCurrency.eos:
|
||||||
return '[0-9a-zA-Z]';
|
return '[0-9a-zA-Z]';
|
||||||
case CryptoCurrency.bch:
|
case CryptoCurrency.bch:
|
||||||
return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{42}\$|^bitcoincash:q|p[0-9a-zA-Z]{41}\$|^bitcoincash:q|p[0-9a-zA-Z]{42}\$';
|
return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{42}\$|^bitcoincash:q|p[0-9a-zA-Z]{41}\$|^bitcoincash:q|p[0-9a-zA-Z]{42}\$';
|
||||||
case CryptoCurrency.bnb:
|
case CryptoCurrency.bnb:
|
||||||
|
@ -130,6 +131,12 @@ class AddressValidator extends TextValidator {
|
||||||
if (type is Erc20Token) {
|
if (type is Erc20Token) {
|
||||||
return [42];
|
return [42];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (solana != null) {
|
||||||
|
final length = solana!.getValidationLength(type);
|
||||||
|
if (length != null) return length;
|
||||||
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CryptoCurrency.xmr:
|
case CryptoCurrency.xmr:
|
||||||
return null;
|
return null;
|
||||||
|
@ -192,11 +199,11 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.sc:
|
case CryptoCurrency.sc:
|
||||||
return [76];
|
return [76];
|
||||||
case CryptoCurrency.sol:
|
case CryptoCurrency.sol:
|
||||||
|
case CryptoCurrency.usdtSol:
|
||||||
|
case CryptoCurrency.usdcsol:
|
||||||
return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44];
|
return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44];
|
||||||
case CryptoCurrency.trx:
|
case CryptoCurrency.trx:
|
||||||
return [34];
|
return [34];
|
||||||
case CryptoCurrency.usdcsol:
|
|
||||||
return [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44];
|
|
||||||
case CryptoCurrency.usdt:
|
case CryptoCurrency.usdt:
|
||||||
return [34];
|
return [34];
|
||||||
case CryptoCurrency.usdttrc20:
|
case CryptoCurrency.usdttrc20:
|
||||||
|
@ -250,9 +257,9 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.near:
|
case CryptoCurrency.near:
|
||||||
return [64];
|
return [64];
|
||||||
case CryptoCurrency.btcln:
|
case CryptoCurrency.btcln:
|
||||||
return null;
|
case CryptoCurrency.kaspa:
|
||||||
default:
|
default:
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,12 +270,11 @@ class AddressValidator extends TextValidator {
|
||||||
'|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
||||||
'|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
|
'|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
|
||||||
case CryptoCurrency.btc:
|
case CryptoCurrency.btc:
|
||||||
return '([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
|
return '([^0-9a-zA-Z]|^)([1mn][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2pkhAddress type
|
||||||
'|([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)([23][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2shAddress type
|
||||||
'|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{25,39})([^0-9a-zA-Z]|\$)' //P2wpkhAddress type
|
||||||
'|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type
|
||||||
'|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{39}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)'; //P2trAddress type
|
||||||
'|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{59}([^0-9a-zA-Z]|\$)';
|
|
||||||
case CryptoCurrency.ltc:
|
case CryptoCurrency.ltc:
|
||||||
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||||
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||||
|
@ -286,8 +292,20 @@ class AddressValidator extends TextValidator {
|
||||||
'|bitcoincash:q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)'
|
'|bitcoincash:q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)'
|
||||||
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
|
||||||
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)';
|
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)';
|
||||||
|
case CryptoCurrency.sol:
|
||||||
|
return '([^0-9a-zA-Z]|^)[1-9A-HJ-NP-Za-km-z]{43,44}([^0-9a-zA-Z]|\$)';
|
||||||
default:
|
default:
|
||||||
|
if (type.tag == CryptoCurrency.eth.title) {
|
||||||
|
return '0x[0-9a-zA-Z]{42}';
|
||||||
|
}
|
||||||
|
if (type.tag == CryptoCurrency.maticpoly.tag) {
|
||||||
|
return '0x[0-9a-zA-Z]{42}';
|
||||||
|
}
|
||||||
|
if (type.tag == CryptoCurrency.sol.title) {
|
||||||
|
return '([^0-9a-zA-Z]|^)[1-9A-HJ-NP-Za-km-z]{43,44}([^0-9a-zA-Z]|\$)';
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -464,8 +464,6 @@ class BackupService {
|
||||||
PreferencesKey.disableSellKey: _sharedPreferences.getBool(PreferencesKey.disableSellKey),
|
PreferencesKey.disableSellKey: _sharedPreferences.getBool(PreferencesKey.disableSellKey),
|
||||||
PreferencesKey.defaultBuyProvider:
|
PreferencesKey.defaultBuyProvider:
|
||||||
_sharedPreferences.getInt(PreferencesKey.defaultBuyProvider),
|
_sharedPreferences.getInt(PreferencesKey.defaultBuyProvider),
|
||||||
PreferencesKey.isDarkThemeLegacy:
|
|
||||||
_sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy),
|
|
||||||
PreferencesKey.currentPinLength: _sharedPreferences.getInt(PreferencesKey.currentPinLength),
|
PreferencesKey.currentPinLength: _sharedPreferences.getInt(PreferencesKey.currentPinLength),
|
||||||
PreferencesKey.currentTransactionPriorityKeyLegacy:
|
PreferencesKey.currentTransactionPriorityKeyLegacy:
|
||||||
_sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy),
|
_sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:cake_wallet/haven/haven.dart';
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/nano/nano.dart';
|
import 'package:cake_wallet/nano/nano.dart';
|
||||||
|
@ -37,6 +38,8 @@ class SeedValidator extends Validator<MnemonicItem> {
|
||||||
return nano!.getNanoWordList(language);
|
return nano!.getNanoWordList(language);
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.getPolygonWordList(language);
|
return polygon!.getPolygonWordList(language);
|
||||||
|
case WalletType.solana:
|
||||||
|
return solana!.getSolanaWordList(language);
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:cake_wallet/core/wallet_connect/evm_chain_service.dart';
|
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_service.dart';
|
||||||
|
|
||||||
enum EVMChainId {
|
enum EVMChainId {
|
||||||
ethereum,
|
ethereum,
|
|
@ -3,7 +3,7 @@ import 'dart:developer';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart';
|
import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
|
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_id.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||||
|
@ -20,8 +20,8 @@ import 'package:eth_sig_util/util/utils.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||||
import 'package:web3dart/web3dart.dart';
|
import 'package:web3dart/web3dart.dart';
|
||||||
import 'chain_service.dart';
|
import '../chain_service.dart';
|
||||||
import 'wallet_connect_key_service.dart';
|
import '../../wallet_connect_key_service.dart';
|
||||||
|
|
||||||
class EvmChainServiceImpl implements ChainService {
|
class EvmChainServiceImpl implements ChainService {
|
||||||
final AppStore appStore;
|
final AppStore appStore;
|
|
@ -0,0 +1,28 @@
|
||||||
|
class SolanaSignMessage {
|
||||||
|
final String pubkey;
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
SolanaSignMessage({
|
||||||
|
required this.pubkey,
|
||||||
|
required this.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SolanaSignMessage.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SolanaSignMessage(
|
||||||
|
pubkey: json['pubkey'] as String,
|
||||||
|
message: json['message'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'pubkey': pubkey,
|
||||||
|
'message': message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SolanaSignMessage(pubkey: $pubkey, message: $message)';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
class SolanaSignTransaction {
|
||||||
|
final String? feePayer;
|
||||||
|
final String? recentBlockhash;
|
||||||
|
final String transaction;
|
||||||
|
final List<SolanaInstruction>? instructions;
|
||||||
|
|
||||||
|
SolanaSignTransaction({
|
||||||
|
required this.feePayer,
|
||||||
|
required this.recentBlockhash,
|
||||||
|
required this.instructions,
|
||||||
|
required this.transaction,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SolanaSignTransaction.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SolanaSignTransaction(
|
||||||
|
feePayer:json['feePayer'] !=null ? json['feePayer'] as String: null,
|
||||||
|
recentBlockhash: json['recentBlockhash']!=null? json['recentBlockhash'] as String: null,
|
||||||
|
instructions:json['instructions']!=null? (json['instructions'] as List<dynamic>)
|
||||||
|
.map((e) => SolanaInstruction.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(): null,
|
||||||
|
transaction: json['transaction'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'feePayer': feePayer,
|
||||||
|
'recentBlockhash': recentBlockhash,
|
||||||
|
'instructions': instructions,
|
||||||
|
'transaction': transaction,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SolanaSignTransaction(feePayer: $feePayer, recentBlockhash: $recentBlockhash, instructions: $instructions, transaction: $transaction)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SolanaInstruction {
|
||||||
|
final String programId;
|
||||||
|
final List<SolanaKeyMetadata> keys;
|
||||||
|
final List<int> data;
|
||||||
|
|
||||||
|
SolanaInstruction({
|
||||||
|
required this.programId,
|
||||||
|
required this.keys,
|
||||||
|
required this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SolanaInstruction.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SolanaInstruction(
|
||||||
|
programId: json['programId'] as String,
|
||||||
|
keys: (json['keys'] as List<dynamic>)
|
||||||
|
.map((e) => SolanaKeyMetadata.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
data: (json['data'] as List<dynamic>).map((e) => e as int).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'programId': programId,
|
||||||
|
'keys': keys,
|
||||||
|
'data': data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SolanaInstruction(programId: $programId, keys: $keys, data: $data)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SolanaKeyMetadata {
|
||||||
|
final String pubkey;
|
||||||
|
final bool isSigner;
|
||||||
|
final bool isWritable;
|
||||||
|
|
||||||
|
SolanaKeyMetadata({
|
||||||
|
required this.pubkey,
|
||||||
|
required this.isSigner,
|
||||||
|
required this.isWritable,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SolanaKeyMetadata.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SolanaKeyMetadata(
|
||||||
|
pubkey: json['pubkey'] as String,
|
||||||
|
isSigner: json['isSigner'] as bool,
|
||||||
|
isWritable: json['isWritable'] as bool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'pubkey': pubkey,
|
||||||
|
'isSigner': isSigner,
|
||||||
|
'isWritable': isWritable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SolanaKeyMetadata(pubkey: $pubkey, isSigner: $isSigner, isWritable: $isWritable)';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import 'solana_chain_service.dart';
|
||||||
|
|
||||||
|
enum SolanaChainId {
|
||||||
|
mainnet,
|
||||||
|
testnet,
|
||||||
|
devnet,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SolanaChainIdX on SolanaChainId {
|
||||||
|
String chain() {
|
||||||
|
String name = '';
|
||||||
|
|
||||||
|
switch (this) {
|
||||||
|
case SolanaChainId.mainnet:
|
||||||
|
name = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ';
|
||||||
|
break;
|
||||||
|
case SolanaChainId.testnet:
|
||||||
|
name = '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K';
|
||||||
|
break;
|
||||||
|
case SolanaChainId.devnet:
|
||||||
|
name = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '${SolanaChainServiceImpl.namespace}:$name';
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue