mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +00:00
CW-555-Add-Solana-Wallet (#1272)
* chore: Create cw_solana package and clean up files * feat: Add Solana Wallet - Create, Restore form seed, restore from Key, Restore from QR, Send, Receive, transaction history, spl tokens * fix: Make transactions file specific to solana only for solana transactions * chore: Revert inject app details script * fix: Fix issue with node and switch current node to main beta instead of testnet * fix: Fix merge conflicts and adjust migration version * fix: Fetch spl token error Signed-off-by: Blazebrain <davidadegoke16@gmail.com> * fix: Diplay and activate spl tokens bug * fix: Review and fixes * fix: reverted formatting for cryptocurrency class * fix: Review comments, split sending flow into signing and sending separately, fix issues * fix: Revert throwing unimplenented error * chore: Fix comment * chore: Fix comment * fix: Errors in flow * Update provider_types.dart [skip ci] * fix: Issues with solana wallet * Update solana_wallet.dart [skip ci] * fix: Review comments * fix: Date time config * fix: Revert bash script for app details * fix: Error with balance, displaying fees, fixing sent or received identifier bug, displaying token symbol with token transaction item in transactions list * fix: Issues with address validation when sending spl tokens and walletconnect initial setup * fix: Issues with sending, fetching transactions history, almost wrapping up walletconnect * fix: Adjust imports that would affect monerocom building successfully * fix: Refine transaction direction and continue work on walletconnect * feat: Display SPL token transfers in the transaction history and finally settle the transaction direction * fix: Delay in transactions history dispaly, show native token transactions first, then process spl token transactions * feat: Switch node and revert solana chain id to previous id * fix: Remove print statement * fix: Remove await for transactions, fetch all transaction histories instantly and adjust solana send success message * chore: Code refactoring and streamlined wallet type check for solana send success message * fix: Make timeout error for node silent and add spl token images --------- Signed-off-by: Blazebrain <davidadegoke16@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
parent
5a7ea87543
commit
109bba4301
133 changed files with 5356 additions and 353 deletions
31
.github/workflows/pr_test_build.yml
vendored
31
.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:
|
||||||
|
@ -111,6 +111,7 @@ jobs:
|
||||||
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_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_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_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
|
||||||
|
@ -120,6 +121,7 @@ jobs:
|
||||||
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,6 +156,7 @@ 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: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
|
||||||
|
@ -163,18 +166,18 @@ jobs:
|
||||||
cd /opt/android/cake_wallet
|
cd /opt/android/cake_wallet
|
||||||
flutter build apk --release
|
flutter build apk --release
|
||||||
|
|
||||||
# - 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: |
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -92,8 +92,10 @@ android/key.properties
|
||||||
**/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 +130,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/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
|
|
@ -9,7 +9,8 @@ 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 +18,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,
|
||||||
|
@ -208,6 +212,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
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: 'kaspa', iconPath: 'assets/images/kaspa_icon.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 =
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,3 +14,4 @@ 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 {
|
||||||
|
|
|
@ -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');
|
||||||
|
|
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;
|
||||||
|
}
|
109
cw_solana/lib/default_spl_tokens.dart
Normal file
109
cw_solana/lib/default_spl_tokens.dart
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
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',
|
||||||
|
enabled: true,
|
||||||
|
iconPath: 'assets/images/eth_icon.png',
|
||||||
|
),
|
||||||
|
SPLToken(
|
||||||
|
name: 'Wrapped SOL',
|
||||||
|
symbol: 'WSOL',
|
||||||
|
mintAddress: 'So11111111111111111111111111111111111111112',
|
||||||
|
decimal: 9,
|
||||||
|
mint: 'WSOL',
|
||||||
|
enabled: true,
|
||||||
|
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()});
|
||||||
|
}
|
477
cw_solana/lib/solana_client.dart
Normal file
477
cw_solana/lib/solana_client.dart
Normal file
|
@ -0,0 +1,477 @@
|
||||||
|
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.finalized;
|
||||||
|
|
||||||
|
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(
|
||||||
|
'Error while creating an associated token account for the recipient: ${e.toString()}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
_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;
|
||||||
|
}
|
118
cw_solana/lib/solana_wallet_service.dart
Normal file
118
cw_solana/lib/solana_wallet_service.dart
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
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) 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) 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) 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);
|
||||||
|
});
|
||||||
|
}
|
|
@ -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,6 +1,7 @@
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
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';
|
||||||
|
|
||||||
|
@ -8,9 +9,7 @@ class AddressValidator extends TextValidator {
|
||||||
AddressValidator({required CryptoCurrency type})
|
AddressValidator({required CryptoCurrency type})
|
||||||
: 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 : null,
|
||||||
? bitcoin.Address.validateAddress
|
|
||||||
: null,
|
|
||||||
pattern: getPattern(type),
|
pattern: getPattern(type),
|
||||||
length: getLength(type));
|
length: getLength(type));
|
||||||
|
|
||||||
|
@ -130,6 +129,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 +197,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:
|
||||||
|
@ -286,6 +291,8 @@ 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:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/core/wallet_connect/chain_service/solana/entities/solana_sign_message.dart';
|
||||||
|
import 'package:cake_wallet/core/wallet_connect/chain_service/solana/solana_chain_id.dart';
|
||||||
|
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
|
||||||
|
import 'package:cake_wallet/core/wallet_connect/models/connection_model.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
||||||
|
import 'package:solana/base58.dart';
|
||||||
|
import 'package:solana/solana.dart';
|
||||||
|
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||||
|
import '../chain_service.dart';
|
||||||
|
import '../../wallet_connect_key_service.dart';
|
||||||
|
import 'entities/solana_sign_transaction.dart';
|
||||||
|
|
||||||
|
class SolanaChainServiceImpl implements ChainService {
|
||||||
|
final BottomSheetService bottomSheetService;
|
||||||
|
final Web3Wallet wallet;
|
||||||
|
final WalletConnectKeyService wcKeyService;
|
||||||
|
|
||||||
|
static const namespace = 'solana';
|
||||||
|
static const solSignTransaction = 'solana_signTransaction';
|
||||||
|
static const solSignMessage = 'solana_signMessage';
|
||||||
|
|
||||||
|
final SolanaChainId reference;
|
||||||
|
|
||||||
|
final SolanaClient solanaClient;
|
||||||
|
|
||||||
|
final Ed25519HDKeyPair? ownerKeyPair;
|
||||||
|
|
||||||
|
SolanaChainServiceImpl({
|
||||||
|
required this.reference,
|
||||||
|
required this.wcKeyService,
|
||||||
|
required this.bottomSheetService,
|
||||||
|
required this.wallet,
|
||||||
|
required this.ownerKeyPair,
|
||||||
|
required String webSocketUrl,
|
||||||
|
required Uri rpcUrl,
|
||||||
|
SolanaClient? solanaClient,
|
||||||
|
}) : solanaClient = solanaClient ??
|
||||||
|
SolanaClient(
|
||||||
|
rpcUrl: rpcUrl,
|
||||||
|
websocketUrl: Uri.parse(webSocketUrl),
|
||||||
|
timeout: const Duration(minutes: 2),
|
||||||
|
) {
|
||||||
|
for (final String event in getEvents()) {
|
||||||
|
wallet.registerEventEmitter(chainId: getChainId(), event: event);
|
||||||
|
}
|
||||||
|
wallet.registerRequestHandler(
|
||||||
|
chainId: getChainId(),
|
||||||
|
method: solSignTransaction,
|
||||||
|
handler: solanaSignTransaction,
|
||||||
|
);
|
||||||
|
wallet.registerRequestHandler(
|
||||||
|
chainId: getChainId(),
|
||||||
|
method: solSignMessage,
|
||||||
|
handler: solanaSignMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getNamespace() {
|
||||||
|
return namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getChainId() {
|
||||||
|
return reference.chain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> getEvents() {
|
||||||
|
return [''];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> requestAuthorization(String? text) async {
|
||||||
|
// Show the bottom sheet
|
||||||
|
final bool? isApproved = await bottomSheetService.queueBottomSheet(
|
||||||
|
widget: Web3RequestModal(
|
||||||
|
child: ConnectionWidget(
|
||||||
|
title: S.current.signTransaction,
|
||||||
|
info: [
|
||||||
|
ConnectionModel(
|
||||||
|
text: text,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) as bool?;
|
||||||
|
|
||||||
|
if (isApproved != null && isApproved == false) {
|
||||||
|
return 'User rejected signature';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> solanaSignTransaction(String topic, dynamic parameters) async {
|
||||||
|
log('received solana sign transaction request $parameters');
|
||||||
|
|
||||||
|
final solanaSignTx =
|
||||||
|
SolanaSignTransaction.fromJson(parameters as Map<String, dynamic>);
|
||||||
|
|
||||||
|
final String? authError = await requestAuthorization('Confirm request to sign transaction?');
|
||||||
|
|
||||||
|
if (authError != null) {
|
||||||
|
return authError;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final message =
|
||||||
|
await solanaClient.rpcClient.getMessageFromEncodedTx(solanaSignTx.transaction);
|
||||||
|
|
||||||
|
final sign = await ownerKeyPair?.signMessage(
|
||||||
|
message: message,
|
||||||
|
recentBlockhash: solanaSignTx.recentBlockhash ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sign == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
String signature = sign.signatures.first.toBase58();
|
||||||
|
|
||||||
|
print(signature);
|
||||||
|
print(signature.runtimeType);
|
||||||
|
|
||||||
|
bottomSheetService.queueBottomSheet(
|
||||||
|
isModalDismissible: true,
|
||||||
|
widget: BottomSheetMessageDisplayWidget(
|
||||||
|
message: S.current.awaitDAppProcessing,
|
||||||
|
isError: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
} catch (e) {
|
||||||
|
log('An error has occurred while signing transaction: ${e.toString()}');
|
||||||
|
bottomSheetService.queueBottomSheet(
|
||||||
|
isModalDismissible: true,
|
||||||
|
widget: BottomSheetMessageDisplayWidget(
|
||||||
|
message: '${S.current.errorSigningTransaction}: ${e.toString()}',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return 'Failed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> solanaSignMessage(String topic, dynamic parameters) async {
|
||||||
|
log('received solana sign message request: $parameters');
|
||||||
|
|
||||||
|
final solanaSignMessage = SolanaSignMessage.fromJson(parameters as Map<String, dynamic>);
|
||||||
|
|
||||||
|
final String? authError = await requestAuthorization('Confirm request to sign message?');
|
||||||
|
|
||||||
|
if (authError != null) {
|
||||||
|
return authError;
|
||||||
|
}
|
||||||
|
Signature? sign;
|
||||||
|
|
||||||
|
try {
|
||||||
|
sign = await ownerKeyPair?.sign(base58decode(solanaSignMessage.message));
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sign == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
String signature = sign.toBase58();
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
|
@ -13,7 +14,6 @@ abstract class WalletConnectKeyService {
|
||||||
/// If the chain is not found, returns an empty list.
|
/// If the chain is not found, returns an empty list.
|
||||||
/// - [chain]: The chain to get the keys for.
|
/// - [chain]: The chain to get the keys for.
|
||||||
List<ChainKeyModel> getKeysForChain(WalletBase wallet);
|
List<ChainKeyModel> getKeysForChain(WalletBase wallet);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class KeyServiceImpl implements WalletConnectKeyService {
|
class KeyServiceImpl implements WalletConnectKeyService {
|
||||||
|
@ -23,6 +23,8 @@ class KeyServiceImpl implements WalletConnectKeyService {
|
||||||
return ethereum!.getPrivateKey(wallet);
|
return ethereum!.getPrivateKey(wallet);
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.getPrivateKey(wallet);
|
return polygon!.getPrivateKey(wallet);
|
||||||
|
case WalletType.solana:
|
||||||
|
return solana!.getPrivateKey(wallet);
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -34,6 +36,8 @@ class KeyServiceImpl implements WalletConnectKeyService {
|
||||||
return ethereum!.getPublicKey(wallet);
|
return ethereum!.getPublicKey(wallet);
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.getPublicKey(wallet);
|
return polygon!.getPublicKey(wallet);
|
||||||
|
case WalletType.solana:
|
||||||
|
return solana!.getPublicKey(wallet);
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -53,6 +57,14 @@ class KeyServiceImpl implements WalletConnectKeyService {
|
||||||
privateKey: _getPrivateKeyForWallet(wallet),
|
privateKey: _getPrivateKeyForWallet(wallet),
|
||||||
publicKey: _getPublicKeyForWallet(wallet),
|
publicKey: _getPublicKeyForWallet(wallet),
|
||||||
),
|
),
|
||||||
|
ChainKeyModel(
|
||||||
|
chains: [
|
||||||
|
'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ', // main-net
|
||||||
|
'solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K', // test-net
|
||||||
|
],
|
||||||
|
privateKey: _getPrivateKeyForWallet(wallet),
|
||||||
|
publicKey: _getPublicKeyForWallet(wallet),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,23 +2,27 @@ import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
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/evm_chain_service.dart';
|
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_service.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
|
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart';
|
import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart';
|
import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart';
|
||||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart';
|
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart';
|
||||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
|
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
|
||||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:eth_sig_util/eth_sig_util.dart';
|
import 'package:eth_sig_util/eth_sig_util.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||||
|
|
||||||
|
import 'chain_service/solana/solana_chain_id.dart';
|
||||||
|
import 'chain_service/solana/solana_chain_service.dart';
|
||||||
import 'wc_bottom_sheet_service.dart';
|
import 'wc_bottom_sheet_service.dart';
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
|
||||||
|
@ -114,14 +118,34 @@ abstract class Web3WalletServiceBase with Store {
|
||||||
final newAuthRequests = _web3Wallet.completeRequests.getAll();
|
final newAuthRequests = _web3Wallet.completeRequests.getAll();
|
||||||
auth.addAll(newAuthRequests);
|
auth.addAll(newAuthRequests);
|
||||||
|
|
||||||
for (final cId in EVMChainId.values) {
|
if (isEVMCompatibleChain(appStore.wallet!.type)) {
|
||||||
EvmChainServiceImpl(
|
for (final cId in EVMChainId.values) {
|
||||||
reference: cId,
|
EvmChainServiceImpl(
|
||||||
appStore: appStore,
|
reference: cId,
|
||||||
wcKeyService: walletKeyService,
|
appStore: appStore,
|
||||||
bottomSheetService: _bottomSheetHandler,
|
wcKeyService: walletKeyService,
|
||||||
wallet: _web3Wallet,
|
bottomSheetService: _bottomSheetHandler,
|
||||||
);
|
wallet: _web3Wallet,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appStore.wallet!.type == WalletType.solana) {
|
||||||
|
for (final cId in SolanaChainId.values) {
|
||||||
|
final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type);
|
||||||
|
final rpcUri = node.uri;
|
||||||
|
final webSocketUri = 'wss://${node.uriRaw}/ws${node.uri.path}';
|
||||||
|
|
||||||
|
SolanaChainServiceImpl(
|
||||||
|
reference: cId,
|
||||||
|
rpcUrl: rpcUri,
|
||||||
|
webSocketUrl: webSocketUri,
|
||||||
|
wcKeyService: walletKeyService,
|
||||||
|
bottomSheetService: _bottomSheetHandler,
|
||||||
|
wallet: _web3Wallet,
|
||||||
|
ownerKeyPair: solana!.getWalletKeyPair(appStore.wallet!),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import 'package:cake_wallet/ionia/ionia_gift_card.dart';
|
||||||
import 'package:cake_wallet/ionia/ionia_tip.dart';
|
import 'package:cake_wallet/ionia/ionia_tip.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
|
import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/buy/buy_options_page.dart';
|
import 'package:cake_wallet/src/screens/buy/buy_options_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/buy/webview_page.dart';
|
import 'package:cake_wallet/src/screens/buy/webview_page.dart';
|
||||||
|
@ -863,6 +864,8 @@ Future<void> setup({
|
||||||
return nano!.createNanoWalletService(_walletInfoSource);
|
return nano!.createNanoWalletService(_walletInfoSource);
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.createPolygonWalletService(_walletInfoSource);
|
return polygon!.createPolygonWalletService(_walletInfoSource);
|
||||||
|
case WalletType.solana:
|
||||||
|
return solana!.createSolanaWalletService(_walletInfoSource);
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
||||||
}
|
}
|
||||||
|
@ -1174,7 +1177,7 @@ Future<void> setup({
|
||||||
getIt.registerFactoryParam<EditTokenPage, HomeSettingsViewModel, Map<String, dynamic>>(
|
getIt.registerFactoryParam<EditTokenPage, HomeSettingsViewModel, Map<String, dynamic>>(
|
||||||
(homeSettingsViewModel, arguments) => EditTokenPage(
|
(homeSettingsViewModel, arguments) => EditTokenPage(
|
||||||
homeSettingsViewModel: homeSettingsViewModel,
|
homeSettingsViewModel: homeSettingsViewModel,
|
||||||
erc20token: arguments['token'] as Erc20Token?,
|
token: arguments['token'] as CryptoCurrency?,
|
||||||
initialContractAddress: arguments['contractAddress'] as String?,
|
initialContractAddress: arguments['contractAddress'] as String?,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -30,6 +30,7 @@ const polygonDefaultNodeUri = 'polygon-bor.publicnode.com';
|
||||||
const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
|
const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
|
||||||
const nanoDefaultNodeUri = 'rpc.nano.to';
|
const nanoDefaultNodeUri = 'rpc.nano.to';
|
||||||
const nanoDefaultPowNodeUri = 'rpc.nano.to';
|
const nanoDefaultPowNodeUri = 'rpc.nano.to';
|
||||||
|
const solanaDefaultNodeUri = 'rpc.ankr.com';
|
||||||
|
|
||||||
Future<void> defaultSettingsMigration(
|
Future<void> defaultSettingsMigration(
|
||||||
{required int version,
|
{required int version,
|
||||||
|
@ -186,10 +187,15 @@ Future<void> defaultSettingsMigration(
|
||||||
await rewriteSecureStoragePin(secureStorage: secureStorage);
|
await rewriteSecureStoragePin(secureStorage: secureStorage);
|
||||||
break;
|
break;
|
||||||
case 26:
|
case 26:
|
||||||
/// commented out as it was a probable cause for some users to have white screen issues
|
|
||||||
/// maybe due to multiple access on Secure Storage at once
|
/// commented out as it was a probable cause for some users to have white screen issues
|
||||||
/// or long await time on start of the app
|
/// maybe due to multiple access on Secure Storage at once
|
||||||
// await insecureStorageMigration(secureStorage: secureStorage, sharedPreferences: sharedPreferences);
|
/// or long await time on start of the app
|
||||||
|
// await insecureStorageMigration(secureStorage: secureStorage, sharedPreferences: sharedPreferences);
|
||||||
|
case 27:
|
||||||
|
await addSolanaNodeList(nodes: nodes);
|
||||||
|
await changeSolanaCurrentNodeToDefault(
|
||||||
|
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -384,6 +390,11 @@ Node getMoneroDefaultNode({required Box<Node> nodes}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node? getSolanaDefaultNode({required Box<Node> nodes}) {
|
||||||
|
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == solanaDefaultNodeUri) ??
|
||||||
|
nodes.values.firstWhereOrNull((node) => node.type == WalletType.solana);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> insecureStorageMigration({
|
Future<void> insecureStorageMigration({
|
||||||
required SharedPreferences sharedPreferences,
|
required SharedPreferences sharedPreferences,
|
||||||
required FlutterSecureStorage secureStorage,
|
required FlutterSecureStorage secureStorage,
|
||||||
|
@ -673,6 +684,7 @@ Future<void> checkCurrentNodes(
|
||||||
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
|
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
|
||||||
final currentBitcoinCashNodeId =
|
final currentBitcoinCashNodeId =
|
||||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
|
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
|
||||||
|
final currentSolanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
|
||||||
final currentMoneroNode =
|
final currentMoneroNode =
|
||||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
|
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
|
||||||
final currentBitcoinElectrumServer =
|
final currentBitcoinElectrumServer =
|
||||||
|
@ -691,6 +703,8 @@ Future<void> checkCurrentNodes(
|
||||||
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
|
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
|
||||||
final currentBitcoinCashNodeServer =
|
final currentBitcoinCashNodeServer =
|
||||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinCashNodeId);
|
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinCashNodeId);
|
||||||
|
final currentSolanaNodeServer =
|
||||||
|
nodeSource.values.firstWhereOrNull((node) => node.key == currentSolanaNodeId);
|
||||||
if (currentMoneroNode == null) {
|
if (currentMoneroNode == null) {
|
||||||
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
|
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
|
||||||
await nodeSource.add(newCakeWalletNode);
|
await nodeSource.add(newCakeWalletNode);
|
||||||
|
@ -750,6 +764,12 @@ Future<void> checkCurrentNodes(
|
||||||
await nodeSource.add(node);
|
await nodeSource.add(node);
|
||||||
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
|
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentSolanaNodeServer == null) {
|
||||||
|
final node = Node(uri: solanaDefaultNodeUri, type: WalletType.solana);
|
||||||
|
await nodeSource.add(node);
|
||||||
|
await sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resetBitcoinElectrumServer(
|
Future<void> resetBitcoinElectrumServer(
|
||||||
|
@ -861,3 +881,20 @@ Future<void> changePolygonCurrentNodeToDefault(
|
||||||
|
|
||||||
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, nodeId);
|
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> addSolanaNodeList({required Box<Node> nodes}) async {
|
||||||
|
final nodeList = await loadDefaultSolanaNodes();
|
||||||
|
for (var node in nodeList) {
|
||||||
|
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||||
|
await nodes.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> changeSolanaCurrentNodeToDefault(
|
||||||
|
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
|
||||||
|
final node = getSolanaDefaultNode(nodes: nodes);
|
||||||
|
final nodeId = node?.key as int? ?? 0;
|
||||||
|
|
||||||
|
await sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, nodeId);
|
||||||
|
}
|
||||||
|
|
|
@ -149,6 +149,23 @@ Future<List<Node>> loadDefaultPolygonNodes() async {
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Node>> loadDefaultSolanaNodes() async {
|
||||||
|
final nodesRaw = await rootBundle.loadString('assets/solana_node_list.yml');
|
||||||
|
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||||
|
final nodes = <Node>[];
|
||||||
|
|
||||||
|
for (final raw in loadedNodes) {
|
||||||
|
if (raw is Map) {
|
||||||
|
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||||
|
|
||||||
|
node.type = WalletType.solana;
|
||||||
|
nodes.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> resetToDefault(Box<Node> nodeSource) async {
|
Future<void> resetToDefault(Box<Node> nodeSource) async {
|
||||||
final moneroNodes = await loadDefaultNodes();
|
final moneroNodes = await loadDefaultNodes();
|
||||||
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
||||||
|
@ -158,6 +175,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
|
||||||
final ethereumNodes = await loadDefaultEthereumNodes();
|
final ethereumNodes = await loadDefaultEthereumNodes();
|
||||||
final nanoNodes = await loadDefaultNanoNodes();
|
final nanoNodes = await loadDefaultNanoNodes();
|
||||||
final polygonNodes = await loadDefaultPolygonNodes();
|
final polygonNodes = await loadDefaultPolygonNodes();
|
||||||
|
final solanaNodes = await loadDefaultSolanaNodes();
|
||||||
|
|
||||||
final nodes = moneroNodes +
|
final nodes = moneroNodes +
|
||||||
bitcoinElectrumServerList +
|
bitcoinElectrumServerList +
|
||||||
|
@ -166,7 +184,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
|
||||||
ethereumNodes +
|
ethereumNodes +
|
||||||
bitcoinCashElectrumServerList +
|
bitcoinCashElectrumServerList +
|
||||||
nanoNodes +
|
nanoNodes +
|
||||||
polygonNodes;
|
polygonNodes +
|
||||||
|
solanaNodes;
|
||||||
|
|
||||||
await nodeSource.clear();
|
await nodeSource.clear();
|
||||||
await nodeSource.addAll(nodes);
|
await nodeSource.addAll(nodes);
|
||||||
|
|
|
@ -13,6 +13,7 @@ class PreferencesKey {
|
||||||
static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
|
static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
|
||||||
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
||||||
static const currentBitcoinCashNodeIdKey = 'current_node_id_bch';
|
static const currentBitcoinCashNodeIdKey = 'current_node_id_bch';
|
||||||
|
static const currentSolanaNodeIdKey = 'current_node_id_sol';
|
||||||
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
|
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
|
||||||
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
||||||
static const shouldSaveRecipientAddressKey = 'save_recipient_address';
|
static const shouldSaveRecipientAddressKey = 'save_recipient_address';
|
||||||
|
|
|
@ -21,12 +21,13 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
|
||||||
return ethereum!.getTransactionPriorities();
|
return ethereum!.getTransactionPriorities();
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
return bitcoinCash!.getTransactionPriorities();
|
return bitcoinCash!.getTransactionPriorities();
|
||||||
// no such thing for nano/banano:
|
|
||||||
case WalletType.nano:
|
|
||||||
case WalletType.banano:
|
|
||||||
return [];
|
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.getTransactionPriorities();
|
return polygon!.getTransactionPriorities();
|
||||||
|
// no such thing for nano/banano/solana:
|
||||||
|
case WalletType.nano:
|
||||||
|
case WalletType.banano:
|
||||||
|
case WalletType.solana:
|
||||||
|
return [];
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,8 @@ class ProvidersHelper {
|
||||||
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood];
|
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood];
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return [ProviderType.askEachTime, ProviderType.dfx];
|
return [ProviderType.askEachTime, ProviderType.dfx];
|
||||||
|
case WalletType.solana:
|
||||||
|
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood];
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return [];
|
return [];
|
||||||
|
@ -88,6 +90,13 @@ class ProvidersHelper {
|
||||||
return [ProviderType.askEachTime, ProviderType.moonpaySell];
|
return [ProviderType.askEachTime, ProviderType.moonpaySell];
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return [ProviderType.askEachTime, ProviderType.dfx];
|
return [ProviderType.askEachTime, ProviderType.dfx];
|
||||||
|
case WalletType.solana:
|
||||||
|
return [
|
||||||
|
ProviderType.askEachTime,
|
||||||
|
ProviderType.onramper,
|
||||||
|
ProviderType.robinhood,
|
||||||
|
ProviderType.moonpaySell,
|
||||||
|
];
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
|
|
|
@ -119,12 +119,13 @@ class CWEthereum extends Ethereum {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addErc20Token(WalletBase wallet, Erc20Token token) async =>
|
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) async {
|
||||||
await (wallet as EthereumWallet).addErc20Token(token);
|
await (wallet as EthereumWallet).addErc20Token(token as Erc20Token);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token) async =>
|
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) async =>
|
||||||
await (wallet as EthereumWallet).deleteErc20Token(token);
|
await (wallet as EthereumWallet).deleteErc20Token(token as Erc20Token);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
|
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
|
||||||
|
@ -153,4 +154,6 @@ class CWEthereum extends Ethereum {
|
||||||
Web3Client? getWeb3Client(WalletBase wallet) {
|
Web3Client? getWeb3Client(WalletBase wallet) {
|
||||||
return (wallet as EthereumWallet).getWeb3Client();
|
return (wallet as EthereumWallet).getWeb3Client();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,7 +163,7 @@ Future<void> initializeAppConfigs() async {
|
||||||
transactionDescriptions: transactionDescriptions,
|
transactionDescriptions: transactionDescriptions,
|
||||||
secureStorage: secureStorage,
|
secureStorage: secureStorage,
|
||||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||||
initialMigrationVersion: 26);
|
initialMigrationVersion: 27);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initialSetup(
|
Future<void> initialSetup(
|
||||||
|
|
|
@ -119,12 +119,12 @@ class CWPolygon extends Polygon {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addErc20Token(WalletBase wallet, Erc20Token token) async =>
|
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) async =>
|
||||||
await (wallet as PolygonWallet).addErc20Token(token);
|
await (wallet as PolygonWallet).addErc20Token(token as Erc20Token);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token) async =>
|
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) async =>
|
||||||
await (wallet as PolygonWallet).deleteErc20Token(token);
|
await (wallet as PolygonWallet).deleteErc20Token(token as Erc20Token);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
|
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
|
||||||
|
@ -153,4 +153,6 @@ class CWPolygon extends Polygon {
|
||||||
Web3Client? getWeb3Client(WalletBase wallet) {
|
Web3Client? getWeb3Client(WalletBase wallet) {
|
||||||
return (wallet as PolygonWallet).getWeb3Client();
|
return (wallet as PolygonWallet).getWeb3Client();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/erc20_token.dart';
|
import 'package:cw_core/erc20_token.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -35,7 +37,7 @@ Future<void> startFiatRateUpdate(
|
||||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Erc20Token>? currencies;
|
Iterable<CryptoCurrency>? currencies;
|
||||||
if (appStore.wallet!.type == WalletType.ethereum) {
|
if (appStore.wallet!.type == WalletType.ethereum) {
|
||||||
currencies =
|
currencies =
|
||||||
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
|
@ -46,6 +48,12 @@ Future<void> startFiatRateUpdate(
|
||||||
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appStore.wallet!.type == WalletType.solana) {
|
||||||
|
currencies =
|
||||||
|
solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (currencies != null) {
|
if (currencies != null) {
|
||||||
for (final currency in currencies) {
|
for (final currency in currencies) {
|
||||||
() async {
|
() async {
|
||||||
|
|
|
@ -3,6 +3,8 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.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/crypto_currency.dart';
|
||||||
import 'package:cw_core/erc20_token.dart';
|
import 'package:cw_core/erc20_token.dart';
|
||||||
import 'package:cw_core/transaction_history.dart';
|
import 'package:cw_core/transaction_history.dart';
|
||||||
import 'package:cw_core/balance.dart';
|
import 'package:cw_core/balance.dart';
|
||||||
|
@ -109,7 +111,7 @@ void startCurrentWalletChangeReaction(
|
||||||
fiat: settingsStore.fiatCurrency,
|
fiat: settingsStore.fiatCurrency,
|
||||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
|
|
||||||
Iterable<Erc20Token>? currencies;
|
Iterable<CryptoCurrency>? currencies;
|
||||||
if (wallet.type == WalletType.ethereum) {
|
if (wallet.type == WalletType.ethereum) {
|
||||||
currencies =
|
currencies =
|
||||||
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
|
@ -118,6 +120,10 @@ void startCurrentWalletChangeReaction(
|
||||||
currencies =
|
currencies =
|
||||||
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
}
|
}
|
||||||
|
if (wallet.type == WalletType.solana) {
|
||||||
|
currencies =
|
||||||
|
solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
if (currencies != null) {
|
if (currencies != null) {
|
||||||
for (final currency in currencies) {
|
for (final currency in currencies) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
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/chain_service/solana/solana_chain_id.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
bool isEVMCompatibleChain(WalletType walletType) {
|
bool isEVMCompatibleChain(WalletType walletType) {
|
||||||
|
@ -11,12 +12,24 @@ bool isEVMCompatibleChain(WalletType walletType) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isWalletConnectCompatibleChain(WalletType walletType) {
|
||||||
|
switch (walletType) {
|
||||||
|
case WalletType.polygon:
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
|
String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
|
||||||
switch (walletType) {
|
switch (walletType) {
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
return EVMChainId.ethereum.chain();
|
return EVMChainId.ethereum.chain();
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return EVMChainId.polygon.chain();
|
return EVMChainId.polygon.chain();
|
||||||
|
case WalletType.solana:
|
||||||
|
return SolanaChainId.mainnet.chain();
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -40,6 +53,8 @@ String getChainNameBasedOnWalletType(WalletType walletType) {
|
||||||
return 'eth';
|
return 'eth';
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return 'polygon';
|
return 'polygon';
|
||||||
|
case WalletType.solana:
|
||||||
|
return 'solana';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -51,6 +66,8 @@ String getTokenNameBasedOnWalletType(WalletType walletType) {
|
||||||
return 'ETH';
|
return 'ETH';
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return 'MATIC';
|
return 'MATIC';
|
||||||
|
case WalletType.solana:
|
||||||
|
return 'SOL';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
118
lib/solana/cw_solana.dart
Normal file
118
lib/solana/cw_solana.dart
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
part of 'solana.dart';
|
||||||
|
|
||||||
|
class CWSolana extends Solana {
|
||||||
|
@override
|
||||||
|
List<String> getSolanaWordList(String language) => SolanaMnemonics.englishWordlist;
|
||||||
|
|
||||||
|
WalletService createSolanaWalletService(Box<WalletInfo> walletInfoSource) =>
|
||||||
|
SolanaWalletService(walletInfoSource);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletCredentials createSolanaNewWalletCredentials({
|
||||||
|
required String name,
|
||||||
|
WalletInfo? walletInfo,
|
||||||
|
}) =>
|
||||||
|
SolanaNewWalletCredentials(name: name, walletInfo: walletInfo);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletCredentials createSolanaRestoreWalletFromSeedCredentials({
|
||||||
|
required String name,
|
||||||
|
required String mnemonic,
|
||||||
|
required String password,
|
||||||
|
}) =>
|
||||||
|
SolanaRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletCredentials createSolanaRestoreWalletFromPrivateKey({
|
||||||
|
required String name,
|
||||||
|
required String privateKey,
|
||||||
|
required String password,
|
||||||
|
}) =>
|
||||||
|
SolanaRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAddress(WalletBase wallet) => (wallet as SolanaWallet).walletAddresses.address;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getPrivateKey(WalletBase wallet) => (wallet as SolanaWallet).privateKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getPublicKey(WalletBase wallet) => (wallet as SolanaWallet).keys.publicKey.toBase58();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Ed25519HDKeyPair? getWalletKeyPair(WalletBase wallet) => (wallet as SolanaWallet).walletKeyPair;
|
||||||
|
|
||||||
|
Object createSolanaTransactionCredentials(
|
||||||
|
List<Output> outputs, {
|
||||||
|
required CryptoCurrency currency,
|
||||||
|
}) =>
|
||||||
|
SolanaTransactionCredentials(
|
||||||
|
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))
|
||||||
|
.toList(),
|
||||||
|
currency: currency,
|
||||||
|
);
|
||||||
|
|
||||||
|
Object createSolanaTransactionCredentialsRaw(
|
||||||
|
List<OutputInfo> outputs, {
|
||||||
|
required CryptoCurrency currency,
|
||||||
|
}) =>
|
||||||
|
SolanaTransactionCredentials(outputs, currency: currency);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SPLToken> getSPLTokenCurrencies(WalletBase wallet) {
|
||||||
|
final solanaWallet = wallet as SolanaWallet;
|
||||||
|
return solanaWallet.splTokenCurrencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> addSPLToken(WalletBase wallet, CryptoCurrency token) async =>
|
||||||
|
await (wallet as SolanaWallet).addSPLToken(token as SPLToken);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deleteSPLToken(WalletBase wallet, CryptoCurrency token) async =>
|
||||||
|
await (wallet as SolanaWallet).deleteSPLToken(token as SPLToken);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SPLToken?> getSPLToken(WalletBase wallet, String mintAddress) async {
|
||||||
|
final solanaWallet = wallet as SolanaWallet;
|
||||||
|
return await solanaWallet.getSPLToken(mintAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
|
||||||
|
transaction as SolanaTransactionInfo;
|
||||||
|
if (transaction.tokenSymbol == CryptoCurrency.sol.title) {
|
||||||
|
return CryptoCurrency.sol;
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet as SolanaWallet;
|
||||||
|
return wallet.splTokenCurrencies
|
||||||
|
.firstWhere((element) => transaction.tokenSymbol == element.symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double getTransactionAmountRaw(TransactionInfo transactionInfo) {
|
||||||
|
return (transactionInfo as SolanaTransactionInfo).solAmount.toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getTokenAddress(CryptoCurrency asset) => (asset as SPLToken).mintAddress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<int>? getValidationLength(CryptoCurrency type) {
|
||||||
|
if (type is SPLToken) {
|
||||||
|
return [44];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
|
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
|
||||||
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
||||||
final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
||||||
|
final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24);
|
||||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||||
|
|
||||||
Image _newWalletImage(BuildContext context) => Image.asset(
|
Image _newWalletImage(BuildContext context) => Image.asset(
|
||||||
|
@ -153,6 +154,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
return bananoIcon;
|
return bananoIcon;
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygonIcon;
|
return polygonIcon;
|
||||||
|
case WalletType.solana:
|
||||||
|
return solanaIcon;
|
||||||
default:
|
default:
|
||||||
return nonWalletTypeIcon;
|
return nonWalletTypeIcon;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/erc20_token.dart';
|
import 'package:cw_core/erc20_token.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -16,12 +17,12 @@ class EditTokenPage extends BasePage {
|
||||||
EditTokenPage({
|
EditTokenPage({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.homeSettingsViewModel,
|
required this.homeSettingsViewModel,
|
||||||
this.erc20token,
|
this.token,
|
||||||
this.initialContractAddress,
|
this.initialContractAddress,
|
||||||
}) : assert(erc20token == null || initialContractAddress == null);
|
}) : assert(token == null || initialContractAddress == null);
|
||||||
|
|
||||||
final HomeSettingsViewModel homeSettingsViewModel;
|
final HomeSettingsViewModel homeSettingsViewModel;
|
||||||
final Erc20Token? erc20token;
|
final CryptoCurrency? token;
|
||||||
final String? initialContractAddress;
|
final String? initialContractAddress;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -31,7 +32,7 @@ class EditTokenPage extends BasePage {
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
return EditTokenPageBody(
|
return EditTokenPageBody(
|
||||||
homeSettingsViewModel: homeSettingsViewModel,
|
homeSettingsViewModel: homeSettingsViewModel,
|
||||||
erc20token: erc20token,
|
token: token,
|
||||||
initialContractAddress: initialContractAddress,
|
initialContractAddress: initialContractAddress,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -41,12 +42,12 @@ class EditTokenPageBody extends StatefulWidget {
|
||||||
const EditTokenPageBody({
|
const EditTokenPageBody({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.homeSettingsViewModel,
|
required this.homeSettingsViewModel,
|
||||||
this.erc20token,
|
this.token,
|
||||||
this.initialContractAddress,
|
this.initialContractAddress,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final HomeSettingsViewModel homeSettingsViewModel;
|
final HomeSettingsViewModel homeSettingsViewModel;
|
||||||
final Erc20Token? erc20token;
|
final CryptoCurrency? token;
|
||||||
final String? initialContractAddress;
|
final String? initialContractAddress;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -73,11 +74,15 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
if (widget.erc20token != null) {
|
String? address;
|
||||||
_contractAddressController.text = widget.erc20token!.contractAddress;
|
|
||||||
_tokenNameController.text = widget.erc20token!.name;
|
if (widget.token != null) {
|
||||||
_tokenSymbolController.text = widget.erc20token!.symbol;
|
address = widget.homeSettingsViewModel.getTokenAddressBasedOnWallet(widget.token!);
|
||||||
_tokenDecimalController.text = widget.erc20token!.decimal.toString();
|
|
||||||
|
_contractAddressController.text = address ?? '';
|
||||||
|
_tokenNameController.text = widget.token!.name;
|
||||||
|
_tokenSymbolController.text = widget.token!.title;
|
||||||
|
_tokenDecimalController.text = widget.token!.decimals.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.initialContractAddress != null) {
|
if (widget.initialContractAddress != null) {
|
||||||
|
@ -91,7 +96,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final contractAddress = _contractAddressController.text;
|
final contractAddress = _contractAddressController.text;
|
||||||
if (contractAddress.isNotEmpty && contractAddress != widget.erc20token?.contractAddress) {
|
if (contractAddress.isNotEmpty && contractAddress != address) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_showDisclaimer = true;
|
_showDisclaimer = true;
|
||||||
});
|
});
|
||||||
|
@ -139,7 +144,9 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
color: Theme.of(context)
|
||||||
|
.extension<TransactionTradeTheme>()!
|
||||||
|
.detailsTitlesColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -172,12 +179,12 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (widget.erc20token != null) {
|
if (widget.token != null) {
|
||||||
await widget.homeSettingsViewModel.deleteErc20Token(widget.erc20token!);
|
await widget.homeSettingsViewModel.deleteToken(widget.token!);
|
||||||
}
|
}
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
text: widget.erc20token != null ? S.of(context).delete : S.of(context).cancel,
|
text: widget.token != null ? S.of(context).delete : S.of(context).cancel,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
),
|
),
|
||||||
|
@ -188,7 +195,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (_formKey.currentState!.validate() &&
|
if (_formKey.currentState!.validate() &&
|
||||||
(!_showDisclaimer || _disclaimerChecked)) {
|
(!_showDisclaimer || _disclaimerChecked)) {
|
||||||
await widget.homeSettingsViewModel.addErc20Token(Erc20Token(
|
await widget.homeSettingsViewModel.addToken(Erc20Token(
|
||||||
name: _tokenNameController.text,
|
name: _tokenNameController.text,
|
||||||
symbol: _tokenSymbolController.text,
|
symbol: _tokenSymbolController.text,
|
||||||
contractAddress: _contractAddressController.text,
|
contractAddress: _contractAddressController.text,
|
||||||
|
@ -214,14 +221,13 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||||
|
|
||||||
void _getTokenInfo() async {
|
void _getTokenInfo() async {
|
||||||
if (_contractAddressController.text.isNotEmpty) {
|
if (_contractAddressController.text.isNotEmpty) {
|
||||||
final token =
|
final token = await widget.homeSettingsViewModel.getToken(_contractAddressController.text);
|
||||||
await widget.homeSettingsViewModel.getErc20Token(_contractAddressController.text);
|
|
||||||
|
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name;
|
if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name;
|
||||||
if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.symbol;
|
if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.title;
|
||||||
if (_tokenDecimalController.text.isEmpty)
|
if (_tokenDecimalController.text.isEmpty)
|
||||||
_tokenDecimalController.text = token.decimal.toString();
|
_tokenDecimalController.text = token.decimals.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
|
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/address_theme.dart';
|
import 'package:cake_wallet/themes/extensions/address_theme.dart';
|
||||||
|
@ -117,7 +118,7 @@ class HomeSettingsPage extends BasePage {
|
||||||
|
|
||||||
return SettingsSwitcherCell(
|
return SettingsSwitcherCell(
|
||||||
title: "${token.name} "
|
title: "${token.name} "
|
||||||
"(${token.symbol})",
|
"(${token.title})",
|
||||||
value: token.enabled,
|
value: token.enabled,
|
||||||
onValueChange: (_, bool value) {
|
onValueChange: (_, bool value) {
|
||||||
_homeSettingsViewModel.changeTokenAvailability(token, value);
|
_homeSettingsViewModel.changeTokenAvailability(token, value);
|
||||||
|
@ -128,20 +129,16 @@ class HomeSettingsPage extends BasePage {
|
||||||
'token': token,
|
'token': token,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
leading: token.iconPath != null
|
leading: CakeImageWidget(
|
||||||
? Container(
|
imageUrl: token.iconPath,
|
||||||
child: Image.asset(
|
height: 40,
|
||||||
token.iconPath!,
|
width: 40,
|
||||||
height: 30.0,
|
displayOnError: Container(
|
||||||
width: 30.0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Container(
|
|
||||||
height: 30.0,
|
height: 30.0,
|
||||||
width: 30.0,
|
width: 30.0,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
token.symbol.substring(0, min(token.symbol.length, 2)),
|
token.title.substring(0, min(token.title.length, 2)),
|
||||||
style: TextStyle(fontSize: 11),
|
style: TextStyle(fontSize: 11),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -149,7 +146,8 @@ class HomeSettingsPage extends BasePage {
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: Colors.grey.shade400,
|
color: Colors.grey.shade400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).cardColor,
|
color: Theme.of(context).cardColor,
|
||||||
borderRadius: BorderRadius.circular(30),
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
|
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
||||||
import 'package:cake_wallet/src/widgets/introducing_card.dart';
|
import 'package:cake_wallet/src/widgets/introducing_card.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
@ -333,15 +334,11 @@ class BalanceRowWidget extends StatelessWidget {
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
currency.iconPath != null
|
CakeImageWidget(
|
||||||
? Container(
|
imageUrl: currency.iconPath,
|
||||||
child: Image.asset(
|
height: 40,
|
||||||
currency.iconPath!,
|
width: 40,
|
||||||
height: 40.0,
|
displayOnError: Container(
|
||||||
width: 40.0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Container(
|
|
||||||
height: 30.0,
|
height: 30.0,
|
||||||
width: 30.0,
|
width: 30.0,
|
||||||
child: Center(
|
child: Center(
|
||||||
|
@ -355,6 +352,7 @@ class BalanceRowWidget extends StatelessWidget {
|
||||||
color: Colors.grey.shade400,
|
color: Colors.grey.shade400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
currency.title,
|
currency.title,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:cake_wallet/entities/wallet_nft_response.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/nft_image_tile_widget.dart';
|
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||||
|
@ -94,7 +94,7 @@ class NFTDetailsPage extends BasePage {
|
||||||
.syncedBackgroundColor,
|
.syncedBackgroundColor,
|
||||||
|
|
||||||
),
|
),
|
||||||
child: NFTImageWidget(
|
child: CakeImageWidget(
|
||||||
imageUrl: nftAsset.normalizedMetadata?.imageUrl,
|
imageUrl: nftAsset.normalizedMetadata?.imageUrl,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -18,23 +18,23 @@ class MenuWidget extends StatefulWidget {
|
||||||
|
|
||||||
class MenuWidgetState extends State<MenuWidget> {
|
class MenuWidgetState extends State<MenuWidget> {
|
||||||
MenuWidgetState()
|
MenuWidgetState()
|
||||||
: this.menuWidth = 0,
|
: this.menuWidth = 0,
|
||||||
this.screenWidth = 0,
|
this.screenWidth = 0,
|
||||||
this.screenHeight = 0,
|
this.screenHeight = 0,
|
||||||
this.headerHeight = 120,
|
this.headerHeight = 120,
|
||||||
this.tileHeight = 60,
|
this.tileHeight = 60,
|
||||||
this.fromTopEdge = 50,
|
this.fromTopEdge = 50,
|
||||||
this.fromBottomEdge = 25,
|
this.fromBottomEdge = 25,
|
||||||
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
|
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
|
||||||
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
|
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
|
||||||
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
|
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
|
||||||
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
|
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
|
||||||
this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
|
this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
|
||||||
this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
|
this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
|
||||||
this.bananoIcon = Image.asset('assets/images/nano_icon.png'),
|
this.bananoIcon = Image.asset('assets/images/nano_icon.png'),
|
||||||
this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'),
|
this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'),
|
||||||
this.polygonIcon = Image.asset('assets/images/matic_icon.png');
|
this.polygonIcon = Image.asset('assets/images/matic_icon.png'),
|
||||||
|
this.solanaIcon = Image.asset('assets/images/sol_icon.png');
|
||||||
|
|
||||||
final largeScreen = 731;
|
final largeScreen = 731;
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
Image nanoIcon;
|
Image nanoIcon;
|
||||||
Image bananoIcon;
|
Image bananoIcon;
|
||||||
Image polygonIcon;
|
Image polygonIcon;
|
||||||
|
Image solanaIcon;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -224,6 +224,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
||||||
return bananoIcon;
|
return bananoIcon;
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygonIcon;
|
return polygonIcon;
|
||||||
|
case WalletType.solana:
|
||||||
|
return solanaIcon;
|
||||||
default:
|
default:
|
||||||
throw Exception('No icon for ${type.toString()}');
|
throw Exception('No icon for ${type.toString()}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import 'package:cake_wallet/entities/wallet_nft_response.dart';
|
import 'package:cake_wallet/entities/wallet_nft_response.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/nft_image_tile_widget.dart';
|
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class NFTTileWidget extends StatelessWidget {
|
class NFTTileWidget extends StatelessWidget {
|
||||||
|
@ -38,7 +37,7 @@ class NFTTileWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||||
),
|
),
|
||||||
child: NFTImageWidget(
|
child: CakeImageWidget(
|
||||||
imageUrl: nftAsset.normalizedMetadata?.imageUrl,
|
imageUrl: nftAsset.normalizedMetadata?.imageUrl,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -14,6 +14,7 @@ import 'package:cake_wallet/utils/payment_request.dart';
|
||||||
import 'package:cake_wallet/utils/request_review_handler.dart';
|
import 'package:cake_wallet/utils/request_review_handler.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
import 'package:cake_wallet/view_model/send/output.dart';
|
import 'package:cake_wallet/view_model/send/output.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -439,10 +440,17 @@ class SendPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is TransactionCommitted) {
|
if (state is TransactionCommitted) {
|
||||||
|
String alertContent;
|
||||||
|
if (sendViewModel.walletType == WalletType.solana) {
|
||||||
|
alertContent =
|
||||||
|
'${S.of(_dialogContext).send_success(sendViewModel.selectedCryptoCurrency.toString())}. ${S.of(_dialogContext).waitFewSecondForTxUpdate}';
|
||||||
|
} else {
|
||||||
|
alertContent = S.of(_dialogContext).send_success(
|
||||||
|
sendViewModel.selectedCryptoCurrency.toString());
|
||||||
|
}
|
||||||
return AlertWithOneAction(
|
return AlertWithOneAction(
|
||||||
alertTitle: '',
|
alertTitle: '',
|
||||||
alertContent: S.of(_dialogContext).send_success(
|
alertContent: alertContent,
|
||||||
sendViewModel.selectedCryptoCurrency.toString()),
|
|
||||||
buttonText: S.of(_dialogContext).ok,
|
buttonText: S.of(_dialogContext).ok,
|
||||||
buttonAction: () {
|
buttonAction: () {
|
||||||
Navigator.of(_dialogContext).pop();
|
Navigator.of(_dialogContext).pop();
|
||||||
|
|
|
@ -321,7 +321,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
? sendViewModel.allAmountValidator
|
? sendViewModel.allAmountValidator
|
||||||
: sendViewModel.amountValidator,
|
: sendViewModel.amountValidator,
|
||||||
),
|
),
|
||||||
if (!sendViewModel.isBatchSending)
|
if (!sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 2,
|
top: 2,
|
||||||
right: 0,
|
right: 0,
|
||||||
|
|
|
@ -107,7 +107,7 @@ class ConnectionSyncPage extends BasePage {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (isEVMCompatibleChain(dashboardViewModel.wallet.type)) ...[
|
if (isWalletConnectCompatibleChain(dashboardViewModel.wallet.type)) ...[
|
||||||
WalletConnectTile(
|
WalletConnectTile(
|
||||||
onTap: () => Navigator.of(context).pushNamed(Routes.walletConnectConnectionsListing),
|
onTap: () => Navigator.of(context).pushNamed(Routes.walletConnectConnectionsListing),
|
||||||
),
|
),
|
||||||
|
|
|
@ -26,7 +26,7 @@ class OtherSettingsPage extends BasePage {
|
||||||
padding: EdgeInsets.only(top: 10),
|
padding: EdgeInsets.only(top: 10),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (!_otherSettingsViewModel.changeRepresentativeEnabled)
|
if (_otherSettingsViewModel.displayTransactionPriority)
|
||||||
SettingsPickerCell(
|
SettingsPickerCell(
|
||||||
title: S.current.settings_fee_priority,
|
title: S.current.settings_fee_priority,
|
||||||
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -26,12 +27,11 @@ class PairingItemWidget extends StatelessWidget {
|
||||||
'$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
'$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: CircleAvatar(
|
leading: CakeImageWidget(
|
||||||
backgroundImage: (metadata.icons.isNotEmpty
|
imageUrl: metadata.icons.isNotEmpty ? metadata.icons[0]: null,
|
||||||
? NetworkImage(metadata.icons[0])
|
displayOnError: CircleAvatar(
|
||||||
: const AssetImage(
|
backgroundImage: AssetImage('assets/images/default_icon.png'),
|
||||||
'assets/images/default_icon.png',
|
),
|
||||||
)) as ImageProvider<Object>,
|
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
metadata.name,
|
metadata.name,
|
||||||
|
|
|
@ -103,6 +103,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
|
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
|
||||||
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
||||||
final polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24);
|
final polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24);
|
||||||
|
final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24);
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
final double tileHeight = 60;
|
final double tileHeight = 60;
|
||||||
Flushbar<void>? _progressBar;
|
Flushbar<void>? _progressBar;
|
||||||
|
@ -313,6 +314,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
return nanoIcon;
|
return nanoIcon;
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygonIcon;
|
return polygonIcon;
|
||||||
|
case WalletType.solana:
|
||||||
|
return solanaIcon;
|
||||||
default:
|
default:
|
||||||
return nonWalletTypeIcon;
|
return nonWalletTypeIcon;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,45 @@ import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
class NFTImageWidget extends StatelessWidget {
|
class CakeImageWidget extends StatelessWidget {
|
||||||
const NFTImageWidget({
|
CakeImageWidget({
|
||||||
required this.imageUrl,
|
required this.imageUrl,
|
||||||
});
|
Widget? displayOnError,
|
||||||
|
this.height,
|
||||||
|
this.width,
|
||||||
|
}) : _displayOnError = displayOnError ?? Icon(Icons.error);
|
||||||
|
|
||||||
final String? imageUrl;
|
final String? imageUrl;
|
||||||
|
final double? height;
|
||||||
|
final double? width;
|
||||||
|
final Widget? _displayOnError;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
try {
|
try {
|
||||||
if (imageUrl == null) return Icon(Icons.error);
|
if (imageUrl == null) return _displayOnError!;
|
||||||
|
|
||||||
|
if (imageUrl!.contains('assets/images')) {
|
||||||
|
return Image.asset(
|
||||||
|
imageUrl!,
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (imageUrl!.contains('.svg')) {
|
if (imageUrl!.contains('.svg')) {
|
||||||
return SvgPicture.network(imageUrl!);
|
return SvgPicture.network(
|
||||||
|
imageUrl!,
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Image.network(
|
return Image.network(
|
||||||
imageUrl!,
|
imageUrl!,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
loadingBuilder: (BuildContext _, Widget child, ImageChunkEvent? loadingProgress) {
|
loadingBuilder: (BuildContext _, Widget child, ImageChunkEvent? loadingProgress) {
|
||||||
if (loadingProgress == null) {
|
if (loadingProgress == null) {
|
||||||
return child;
|
return child;
|
||||||
|
@ -31,7 +51,7 @@ class NFTImageWidget extends StatelessWidget {
|
||||||
errorBuilder: (_, __, ___) => Icon(Icons.error),
|
errorBuilder: (_, __, ___) => Icon(Icons.error),
|
||||||
);
|
);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return Icon(Icons.error);
|
return _displayOnError!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -41,7 +41,7 @@ abstract class AppStoreBase with Store {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.wallet!.setExceptionHandler(ExceptionHandler.onError);
|
this.wallet!.setExceptionHandler(ExceptionHandler.onError);
|
||||||
|
|
||||||
if (isEVMCompatibleChain(wallet.type)) {
|
if (isWalletConnectCompatibleChain(wallet.type)) {
|
||||||
await getIt.get<Web3WalletService>().onDispose();
|
await getIt.get<Web3WalletService>().onDispose();
|
||||||
getIt.get<Web3WalletService>().create();
|
getIt.get<Web3WalletService>().create();
|
||||||
await getIt.get<Web3WalletService>().init();
|
await getIt.get<Web3WalletService>().init();
|
||||||
|
|
|
@ -838,6 +838,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
|
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
|
||||||
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
|
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
|
||||||
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
|
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
|
||||||
|
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
|
||||||
final moneroNode = nodeSource.get(nodeId);
|
final moneroNode = nodeSource.get(nodeId);
|
||||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||||
|
@ -847,6 +848,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
|
final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
|
||||||
final nanoNode = nodeSource.get(nanoNodeId);
|
final nanoNode = nodeSource.get(nanoNodeId);
|
||||||
final nanoPowNode = powNodeSource.get(nanoPowNodeId);
|
final nanoPowNode = powNodeSource.get(nanoPowNodeId);
|
||||||
|
final solanaNode = nodeSource.get(solanaNodeId);
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
final deviceName = await _getDeviceName() ?? '';
|
final deviceName = await _getDeviceName() ?? '';
|
||||||
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
||||||
|
@ -903,6 +905,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
powNodes[WalletType.nano] = nanoPowNode;
|
powNodes[WalletType.nano] = nanoPowNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (solanaNode != null) {
|
||||||
|
nodes[WalletType.solana] = solanaNode;
|
||||||
|
}
|
||||||
|
|
||||||
final savedSyncMode = SyncMode.all.firstWhere((element) {
|
final savedSyncMode = SyncMode.all.firstWhere((element) {
|
||||||
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0);
|
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0);
|
||||||
});
|
});
|
||||||
|
@ -1190,6 +1196,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
|
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
|
||||||
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
|
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
|
||||||
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
|
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
|
||||||
|
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
|
||||||
final moneroNode = nodeSource.get(nodeId);
|
final moneroNode = nodeSource.get(nodeId);
|
||||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||||
|
@ -1198,7 +1205,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
final polygonNode = nodeSource.get(polygonNodeId);
|
final polygonNode = nodeSource.get(polygonNodeId);
|
||||||
final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
|
final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
|
||||||
final nanoNode = nodeSource.get(nanoNodeId);
|
final nanoNode = nodeSource.get(nanoNodeId);
|
||||||
|
final solanaNode = nodeSource.get(solanaNodeId);
|
||||||
if (moneroNode != null) {
|
if (moneroNode != null) {
|
||||||
nodes[WalletType.monero] = moneroNode;
|
nodes[WalletType.monero] = moneroNode;
|
||||||
}
|
}
|
||||||
|
@ -1231,6 +1238,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
nodes[WalletType.nano] = nanoNode;
|
nodes[WalletType.nano] = nanoNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (solanaNode != null) {
|
||||||
|
nodes[WalletType.solana] = solanaNode;
|
||||||
|
}
|
||||||
|
|
||||||
// MIGRATED:
|
// MIGRATED:
|
||||||
|
|
||||||
useTOTP2FA = await SecureKey.getBool(
|
useTOTP2FA = await SecureKey.getBool(
|
||||||
|
@ -1358,6 +1369,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
|
await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.solana:
|
||||||
|
await _sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,10 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
|
|
||||||
bool get hasSeedPhraseLengthOption =>
|
bool get hasSeedPhraseLengthOption =>
|
||||||
type == WalletType.bitcoinCash || type == WalletType.ethereum;
|
type == WalletType.bitcoinCash ||
|
||||||
|
type == WalletType.ethereum ||
|
||||||
|
type == WalletType.polygon ||
|
||||||
|
type == WalletType.solana;
|
||||||
|
|
||||||
bool get hasSeedTypeOption => type == WalletType.monero;
|
bool get hasSeedTypeOption => type == WalletType.monero;
|
||||||
|
|
||||||
|
|
|
@ -18,15 +18,15 @@ import 'package:mobx/mobx.dart';
|
||||||
part 'balance_view_model.g.dart';
|
part 'balance_view_model.g.dart';
|
||||||
|
|
||||||
class BalanceRecord {
|
class BalanceRecord {
|
||||||
const BalanceRecord({
|
const BalanceRecord(
|
||||||
required this.availableBalance,
|
{required this.availableBalance,
|
||||||
required this.additionalBalance,
|
required this.additionalBalance,
|
||||||
required this.frozenBalance,
|
required this.frozenBalance,
|
||||||
required this.fiatAvailableBalance,
|
required this.fiatAvailableBalance,
|
||||||
required this.fiatAdditionalBalance,
|
required this.fiatAdditionalBalance,
|
||||||
required this.fiatFrozenBalance,
|
required this.fiatFrozenBalance,
|
||||||
required this.asset,
|
required this.asset,
|
||||||
required this.formattedAssetTitle});
|
required this.formattedAssetTitle});
|
||||||
final String fiatAdditionalBalance;
|
final String fiatAdditionalBalance;
|
||||||
final String fiatAvailableBalance;
|
final String fiatAvailableBalance;
|
||||||
final String fiatFrozenBalance;
|
final String fiatFrozenBalance;
|
||||||
|
@ -41,12 +41,10 @@ class BalanceViewModel = BalanceViewModelBase with _$BalanceViewModel;
|
||||||
|
|
||||||
abstract class BalanceViewModelBase with Store {
|
abstract class BalanceViewModelBase with Store {
|
||||||
BalanceViewModelBase(
|
BalanceViewModelBase(
|
||||||
{required this.appStore,
|
{required this.appStore, required this.settingsStore, required this.fiatConvertationStore})
|
||||||
required this.settingsStore,
|
: isReversing = false,
|
||||||
required this.fiatConvertationStore})
|
isShowCard = appStore.wallet!.walletInfo.isShowIntroCakePayCard,
|
||||||
: isReversing = false,
|
wallet = appStore.wallet! {
|
||||||
isShowCard = appStore.wallet!.walletInfo.isShowIntroCakePayCard,
|
|
||||||
wallet = appStore.wallet! {
|
|
||||||
reaction((_) => appStore.wallet, _onWalletChange);
|
reaction((_) => appStore.wallet, _onWalletChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,8 +58,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
bool isReversing;
|
bool isReversing;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||||
wallet;
|
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
double get price {
|
double get price {
|
||||||
|
@ -82,7 +79,8 @@ abstract class BalanceViewModelBase with Store {
|
||||||
bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
|
bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get isHomeScreenSettingsEnabled => isEVMCompatibleChain(wallet.type);
|
bool get isHomeScreenSettingsEnabled =>
|
||||||
|
isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get hasAccounts => wallet.type == WalletType.monero;
|
bool get hasAccounts => wallet.type == WalletType.monero;
|
||||||
|
@ -97,7 +95,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
String get asset {
|
String get asset {
|
||||||
final typeFormatted = walletTypeToString(appStore.wallet!.type);
|
final typeFormatted = walletTypeToString(appStore.wallet!.type);
|
||||||
|
|
||||||
switch(wallet.type) {
|
switch (wallet.type) {
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return '$typeFormatted Assets';
|
return '$typeFormatted Assets';
|
||||||
default:
|
default:
|
||||||
|
@ -120,13 +118,14 @@ abstract class BalanceViewModelBase with Store {
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
String get availableBalanceLabel {
|
String get availableBalanceLabel {
|
||||||
switch(wallet.type) {
|
switch (wallet.type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
|
case WalletType.solana:
|
||||||
return S.current.xmr_available_balance;
|
return S.current.xmr_available_balance;
|
||||||
default:
|
default:
|
||||||
return S.current.confirmed;
|
return S.current.confirmed;
|
||||||
|
@ -135,11 +134,12 @@ abstract class BalanceViewModelBase with Store {
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
String get additionalBalanceLabel {
|
String get additionalBalanceLabel {
|
||||||
switch(wallet.type) {
|
switch (wallet.type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
|
case WalletType.solana:
|
||||||
return S.current.xmr_full_balance;
|
return S.current.xmr_full_balance;
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
|
@ -228,15 +228,17 @@ abstract class BalanceViewModelBase with Store {
|
||||||
Map<CryptoCurrency, BalanceRecord> get balances {
|
Map<CryptoCurrency, BalanceRecord> get balances {
|
||||||
return wallet.balance.map((key, value) {
|
return wallet.balance.map((key, value) {
|
||||||
if (displayMode == BalanceDisplayMode.hiddenBalance) {
|
if (displayMode == BalanceDisplayMode.hiddenBalance) {
|
||||||
return MapEntry(key, BalanceRecord(
|
return MapEntry(
|
||||||
availableBalance: '---',
|
key,
|
||||||
additionalBalance: '---',
|
BalanceRecord(
|
||||||
frozenBalance: '---',
|
availableBalance: '---',
|
||||||
fiatAdditionalBalance: isFiatDisabled ? '' : '---',
|
additionalBalance: '---',
|
||||||
fiatAvailableBalance: isFiatDisabled ? '' : '---',
|
frozenBalance: '---',
|
||||||
fiatFrozenBalance: isFiatDisabled ? '' : '---',
|
fiatAdditionalBalance: isFiatDisabled ? '' : '---',
|
||||||
asset: key,
|
fiatAvailableBalance: isFiatDisabled ? '' : '---',
|
||||||
formattedAssetTitle: _formatterAsset(key)));
|
fiatFrozenBalance: isFiatDisabled ? '' : '---',
|
||||||
|
asset: key,
|
||||||
|
formattedAssetTitle: _formatterAsset(key)));
|
||||||
}
|
}
|
||||||
final fiatCurrency = settingsStore.fiatCurrency;
|
final fiatCurrency = settingsStore.fiatCurrency;
|
||||||
final price = fiatConvertationStore.prices[key] ?? 0;
|
final price = fiatConvertationStore.prices[key] ?? 0;
|
||||||
|
@ -245,25 +247,23 @@ abstract class BalanceViewModelBase with Store {
|
||||||
// throw Exception('Price is null for: $key');
|
// throw Exception('Price is null for: $key');
|
||||||
// }
|
// }
|
||||||
|
|
||||||
final additionalFiatBalance = isFiatDisabled ? '' : (fiatCurrency.toString()
|
final additionalFiatBalance = isFiatDisabled
|
||||||
+ ' '
|
? ''
|
||||||
+ _getFiatBalance(
|
: (fiatCurrency.toString() +
|
||||||
price: price,
|
' ' +
|
||||||
cryptoAmount: value.formattedAdditionalBalance));
|
_getFiatBalance(price: price, cryptoAmount: value.formattedAdditionalBalance));
|
||||||
|
|
||||||
final availableFiatBalance = isFiatDisabled ? '' : (fiatCurrency.toString()
|
final availableFiatBalance = isFiatDisabled
|
||||||
+ ' '
|
? ''
|
||||||
+ _getFiatBalance(
|
: (fiatCurrency.toString() +
|
||||||
price: price,
|
' ' +
|
||||||
cryptoAmount: value.formattedAvailableBalance));
|
_getFiatBalance(price: price, cryptoAmount: value.formattedAvailableBalance));
|
||||||
|
|
||||||
|
|
||||||
final frozenFiatBalance = isFiatDisabled ? '' : (fiatCurrency.toString()
|
|
||||||
+ ' '
|
|
||||||
+ _getFiatBalance(
|
|
||||||
price: price,
|
|
||||||
cryptoAmount: getFormattedFrozenBalance(value)));
|
|
||||||
|
|
||||||
|
final frozenFiatBalance = isFiatDisabled
|
||||||
|
? ''
|
||||||
|
: (fiatCurrency.toString() +
|
||||||
|
' ' +
|
||||||
|
_getFiatBalance(price: price, cryptoAmount: getFormattedFrozenBalance(value)));
|
||||||
|
|
||||||
return MapEntry(
|
return MapEntry(
|
||||||
key,
|
key,
|
||||||
|
@ -276,12 +276,22 @@ abstract class BalanceViewModelBase with Store {
|
||||||
fiatFrozenBalance: frozenFiatBalance,
|
fiatFrozenBalance: frozenFiatBalance,
|
||||||
asset: key,
|
asset: key,
|
||||||
formattedAssetTitle: _formatterAsset(key)));
|
formattedAssetTitle: _formatterAsset(key)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get hasAdditionalBalance => !isEVMCompatibleChain(wallet.type);
|
bool get hasAdditionalBalance => _hasAdditionBalanceForWalletType(wallet.type);
|
||||||
|
|
||||||
|
bool _hasAdditionBalanceForWalletType(WalletType type) {
|
||||||
|
switch (type) {
|
||||||
|
case WalletType.ethereum:
|
||||||
|
case WalletType.polygon:
|
||||||
|
case WalletType.solana:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<BalanceRecord> get formattedBalances {
|
List<BalanceRecord> get formattedBalances {
|
||||||
|
@ -358,9 +368,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _onWalletChange(
|
void _onWalletChange(
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet) {
|
||||||
TransactionInfo>?
|
|
||||||
wallet) {
|
|
||||||
if (wallet == null) {
|
if (wallet == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -371,7 +379,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> disableIntroCakePayCard () async {
|
Future<void> disableIntroCakePayCard() async {
|
||||||
const cardDisplayStatus = false;
|
const cardDisplayStatus = false;
|
||||||
wallet.walletInfo.showIntroCakePayCard = cardDisplayStatus;
|
wallet.walletInfo.showIntroCakePayCard = cardDisplayStatus;
|
||||||
await wallet.walletInfo.save();
|
await wallet.walletInfo.save();
|
||||||
|
@ -401,6 +409,6 @@ abstract class BalanceViewModelBase with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getFormattedFrozenBalance(Balance walletBalance) => walletBalance.formattedUnAvailableBalance;
|
String getFormattedFrozenBalance(Balance walletBalance) =>
|
||||||
|
walletBalance.formattedUnAvailableBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
import 'package:cake_wallet/entities/sort_balance_types.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
@ -16,14 +17,14 @@ class HomeSettingsViewModel = HomeSettingsViewModelBase with _$HomeSettingsViewM
|
||||||
|
|
||||||
abstract class HomeSettingsViewModelBase with Store {
|
abstract class HomeSettingsViewModelBase with Store {
|
||||||
HomeSettingsViewModelBase(this._settingsStore, this._balanceViewModel)
|
HomeSettingsViewModelBase(this._settingsStore, this._balanceViewModel)
|
||||||
: tokens = ObservableSet<Erc20Token>() {
|
: tokens = ObservableSet<CryptoCurrency>() {
|
||||||
_updateTokensList();
|
_updateTokensList();
|
||||||
}
|
}
|
||||||
|
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
final BalanceViewModel _balanceViewModel;
|
final BalanceViewModel _balanceViewModel;
|
||||||
|
|
||||||
final ObservableSet<Erc20Token> tokens;
|
final ObservableSet<CryptoCurrency> tokens;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
String searchText = '';
|
String searchText = '';
|
||||||
|
@ -43,7 +44,7 @@ abstract class HomeSettingsViewModelBase with Store {
|
||||||
@action
|
@action
|
||||||
void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
|
void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
|
||||||
|
|
||||||
Future<void> addErc20Token(Erc20Token token) async {
|
Future<void> addToken(CryptoCurrency token) async {
|
||||||
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
|
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
|
||||||
await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
|
await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
|
||||||
}
|
}
|
||||||
|
@ -52,23 +53,31 @@ abstract class HomeSettingsViewModelBase with Store {
|
||||||
await polygon!.addErc20Token(_balanceViewModel.wallet, token);
|
await polygon!.addErc20Token(_balanceViewModel.wallet, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_balanceViewModel.wallet.type == WalletType.solana) {
|
||||||
|
await solana!.addSPLToken(_balanceViewModel.wallet, token);
|
||||||
|
}
|
||||||
|
|
||||||
_updateTokensList();
|
_updateTokensList();
|
||||||
_updateFiatPrices(token);
|
_updateFiatPrices(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteErc20Token(Erc20Token token) async {
|
Future<void> deleteToken(CryptoCurrency token) async {
|
||||||
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
|
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
|
||||||
await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token);
|
await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_balanceViewModel.wallet.type == WalletType.polygon) {
|
if (_balanceViewModel.wallet.type == WalletType.polygon) {
|
||||||
await polygon!.deleteErc20Token(_balanceViewModel.wallet, token);
|
await polygon!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_balanceViewModel.wallet.type == WalletType.solana) {
|
||||||
|
await solana!.deleteSPLToken(_balanceViewModel.wallet, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateTokensList();
|
_updateTokensList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Erc20Token?> getErc20Token(String contractAddress) async {
|
Future<CryptoCurrency?> getToken(String contractAddress) async {
|
||||||
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
|
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
|
||||||
return await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
|
return await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
|
||||||
}
|
}
|
||||||
|
@ -77,12 +86,16 @@ abstract class HomeSettingsViewModelBase with Store {
|
||||||
return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress);
|
return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_balanceViewModel.wallet.type == WalletType.solana) {
|
||||||
|
return await solana!.getSPLToken(_balanceViewModel.wallet, contractAddress);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
|
CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
|
||||||
|
|
||||||
void _updateFiatPrices(Erc20Token token) async {
|
void _updateFiatPrices(CryptoCurrency token) async {
|
||||||
try {
|
try {
|
||||||
_balanceViewModel.fiatConvertationStore.prices[token] =
|
_balanceViewModel.fiatConvertationStore.prices[token] =
|
||||||
await FiatConversionService.fetchPrice(
|
await FiatConversionService.fetchPrice(
|
||||||
|
@ -92,20 +105,27 @@ abstract class HomeSettingsViewModelBase with Store {
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeTokenAvailability(Erc20Token token, bool value) async {
|
void changeTokenAvailability(CryptoCurrency token, bool value) async {
|
||||||
token.enabled = value;
|
token.enabled = value;
|
||||||
|
|
||||||
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
|
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
|
||||||
ethereum!.addErc20Token(_balanceViewModel.wallet, token);
|
ethereum!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_balanceViewModel.wallet.type == WalletType.polygon) {
|
if (_balanceViewModel.wallet.type == WalletType.polygon) {
|
||||||
polygon!.addErc20Token(_balanceViewModel.wallet, token);
|
polygon!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_balanceViewModel.wallet.type == WalletType.solana) {
|
||||||
|
solana!.addSPLToken(_balanceViewModel.wallet, token);
|
||||||
|
}
|
||||||
|
|
||||||
_refreshTokensList();
|
_refreshTokensList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _updateTokensList() {
|
void _updateTokensList() {
|
||||||
int _sortFunc(Erc20Token e1, Erc20Token e2) {
|
int _sortFunc(CryptoCurrency e1, CryptoCurrency e2) {
|
||||||
int index1 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e1);
|
int index1 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e1);
|
||||||
int index2 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e2);
|
int index2 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e2);
|
||||||
|
|
||||||
|
@ -138,6 +158,14 @@ abstract class HomeSettingsViewModelBase with Store {
|
||||||
.toList()
|
.toList()
|
||||||
..sort(_sortFunc));
|
..sort(_sortFunc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_balanceViewModel.wallet.type == WalletType.solana) {
|
||||||
|
tokens.addAll(solana!
|
||||||
|
.getSPLTokenCurrencies(_balanceViewModel.wallet)
|
||||||
|
.where((element) => _matchesSearchText(element))
|
||||||
|
.toList()
|
||||||
|
..sort(_sortFunc));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -153,10 +181,32 @@ abstract class HomeSettingsViewModelBase with Store {
|
||||||
_updateTokensList();
|
_updateTokensList();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _matchesSearchText(Erc20Token asset) {
|
bool _matchesSearchText(CryptoCurrency asset) {
|
||||||
|
final address = getTokenAddressBasedOnWallet(asset);
|
||||||
|
|
||||||
|
// The homes settings would only be displayed for either of Ethereum, Polygon or Solana Wallets.
|
||||||
|
if (address == null) return false;
|
||||||
|
|
||||||
return searchText.isEmpty ||
|
return searchText.isEmpty ||
|
||||||
asset.fullName!.toLowerCase().contains(searchText.toLowerCase()) ||
|
asset.fullName!.toLowerCase().contains(searchText.toLowerCase()) ||
|
||||||
asset.title.toLowerCase().contains(searchText.toLowerCase()) ||
|
asset.title.toLowerCase().contains(searchText.toLowerCase()) ||
|
||||||
asset.contractAddress == searchText;
|
address == searchText;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? getTokenAddressBasedOnWallet(CryptoCurrency asset) {
|
||||||
|
if (_balanceViewModel.wallet.type == WalletType.solana) {
|
||||||
|
return solana!.getTokenAddress(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
|
||||||
|
return ethereum!.getTokenAddress(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_balanceViewModel.wallet.type == WalletType.polygon) {
|
||||||
|
return polygon!.getTokenAddress(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We return null if it's neither Polygin, Ethereum or Solana wallet (which is actually impossible because we only display home settings for either of these three wallets).
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/nano/nano.dart';
|
import 'package:cake_wallet/nano/nano.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/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
@ -105,6 +106,14 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||||
nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)),
|
nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)),
|
||||||
price: price);
|
price: price);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.solana:
|
||||||
|
final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||||
|
final price = balanceViewModel.fiatConvertationStore.prices[asset];
|
||||||
|
amount = calculateFiatAmountRaw(
|
||||||
|
cryptoAmount: solana!.getTransactionAmountRaw(transaction),
|
||||||
|
price: price,
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -625,6 +625,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||||
depositCurrency = CryptoCurrency.maticpoly;
|
depositCurrency = CryptoCurrency.maticpoly;
|
||||||
receiveCurrency = CryptoCurrency.xmr;
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.solana:
|
||||||
|
depositCurrency = CryptoCurrency.sol;
|
||||||
|
receiveCurrency = CryptoCurrency.xmr;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,9 @@ abstract class NodeListViewModelBase with Store {
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
node = getPolygonDefaultNode(nodes: _nodeSource)!;
|
node = getPolygonDefaultNode(nodes: _nodeSource)!;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.solana:
|
||||||
|
node = getSolanaDefaultNode(nodes: _nodeSource)!;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
|
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/nano/nano.dart';
|
import 'package:cake_wallet/nano/nano.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
@ -75,6 +76,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.createPolygonRestoreWalletFromPrivateKey(
|
return polygon!.createPolygonRestoreWalletFromPrivateKey(
|
||||||
name: name, password: password, privateKey: restoreWallet.privateKey!);
|
name: name, password: password, privateKey: restoreWallet.privateKey!);
|
||||||
|
case WalletType.solana:
|
||||||
|
return solana!.createSolanaRestoreWalletFromPrivateKey(
|
||||||
|
name: name, password: password, privateKey: restoreWallet.privateKey!);
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected type: ${restoreWallet.type.toString()}');
|
throw Exception('Unexpected type: ${restoreWallet.type.toString()}');
|
||||||
}
|
}
|
||||||
|
@ -102,6 +106,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
|
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
|
||||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||||
|
case WalletType.solana:
|
||||||
|
return solana!.createSolanaRestoreWalletFromSeedCredentials(
|
||||||
|
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected type: ${type.toString()}');
|
throw Exception('Unexpected type: ${type.toString()}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ class WalletRestoreFromQRCode {
|
||||||
'bitcoincash': WalletType.bitcoinCash,
|
'bitcoincash': WalletType.bitcoinCash,
|
||||||
'bitcoincash-wallet': WalletType.bitcoinCash,
|
'bitcoincash-wallet': WalletType.bitcoinCash,
|
||||||
'bitcoincash_wallet': WalletType.bitcoinCash,
|
'bitcoincash_wallet': WalletType.bitcoinCash,
|
||||||
|
'solana-wallet': WalletType.solana,
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool _containsAssetSpecifier(String code) => _extractWalletType(code) != null;
|
static bool _containsAssetSpecifier(String code) => _extractWalletType(code) != null;
|
||||||
|
@ -175,6 +176,14 @@ class WalletRestoreFromQRCode {
|
||||||
return WalletRestoreMode.seed;
|
return WalletRestoreMode.seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type == WalletType.solana && credentials.containsKey('private_key')) {
|
||||||
|
final privateKey = credentials['private_key'] as String;
|
||||||
|
if (privateKey.isEmpty) {
|
||||||
|
throw Exception('Unexpected restore mode: private_key');
|
||||||
|
}
|
||||||
|
return WalletRestoreMode.keys;
|
||||||
|
}
|
||||||
|
|
||||||
throw Exception('Unexpected restore mode: restore params are invalid');
|
throw Exception('Unexpected restore mode: restore params are invalid');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,9 +148,8 @@ abstract class OutputBase with Store {
|
||||||
@computed
|
@computed
|
||||||
String get estimatedFeeFiatAmount {
|
String get estimatedFeeFiatAmount {
|
||||||
try {
|
try {
|
||||||
final currency = isEVMCompatibleChain(_wallet.type)
|
final currency =
|
||||||
? _wallet.currency
|
isEVMCompatibleChain(_wallet.type) ? _wallet.currency : cryptoCurrencyHandler();
|
||||||
: cryptoCurrencyHandler();
|
|
||||||
final fiat = calculateFiatAmountRaw(
|
final fiat = calculateFiatAmountRaw(
|
||||||
price: _fiatConversationStore.prices[currency]!, cryptoAmount: estimatedFee);
|
price: _fiatConversationStore.prices[currency]!, cryptoAmount: estimatedFee);
|
||||||
return fiat;
|
return fiat;
|
||||||
|
@ -220,7 +219,6 @@ abstract class OutputBase with Store {
|
||||||
final crypto = double.parse(fiatAmount.replaceAll(',', '.')) /
|
final crypto = double.parse(fiatAmount.replaceAll(',', '.')) /
|
||||||
_fiatConversationStore.prices[cryptoCurrencyHandler()]!;
|
_fiatConversationStore.prices[cryptoCurrencyHandler()]!;
|
||||||
final cryptoAmountTmp = _cryptoNumberFormat.format(crypto);
|
final cryptoAmountTmp = _cryptoNumberFormat.format(crypto);
|
||||||
|
|
||||||
if (cryptoAmount != cryptoAmountTmp) {
|
if (cryptoAmount != cryptoAmountTmp) {
|
||||||
cryptoAmount = cryptoAmountTmp;
|
cryptoAmount = cryptoAmountTmp;
|
||||||
}
|
}
|
||||||
|
@ -252,6 +250,9 @@ abstract class OutputBase with Store {
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
maximumFractionDigits = 12;
|
maximumFractionDigits = 12;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.solana:
|
||||||
|
maximumFractionDigits = 12;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,8 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
bool get hasMultiRecipient =>
|
bool get hasMultiRecipient =>
|
||||||
_wallet.type != WalletType.haven &&
|
_wallet.type != WalletType.haven &&
|
||||||
_wallet.type != WalletType.ethereum &&
|
_wallet.type != WalletType.ethereum &&
|
||||||
_wallet.type != WalletType.polygon;
|
_wallet.type != WalletType.polygon &&
|
||||||
|
_wallet.type != WalletType.solana;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
CryptoCurrency get cryptoCurrency => _wallet.currency;
|
CryptoCurrency get cryptoCurrency => _wallet.currency;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||||
|
@ -44,7 +45,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
void onWalletChange(wallet) {
|
void onWalletChange(wallet) {
|
||||||
currencies = wallet.balance.keys.toList();
|
currencies = wallet.balance.keys.toList();
|
||||||
selectedCryptoCurrency = wallet.currency;
|
selectedCryptoCurrency = wallet.currency;
|
||||||
hasMultipleTokens = isEVMCompatibleChain(wallet.type);
|
hasMultipleTokens = isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana;
|
||||||
}
|
}
|
||||||
|
|
||||||
SendViewModelBase(
|
SendViewModelBase(
|
||||||
|
@ -57,7 +58,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
) : state = InitialExecutionState(),
|
) : state = InitialExecutionState(),
|
||||||
currencies = appStore.wallet!.balance.keys.toList(),
|
currencies = appStore.wallet!.balance.keys.toList(),
|
||||||
selectedCryptoCurrency = appStore.wallet!.currency,
|
selectedCryptoCurrency = appStore.wallet!.currency,
|
||||||
hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type),
|
hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) ||
|
||||||
|
appStore.wallet!.type == WalletType.solana,
|
||||||
outputs = ObservableList<Output>(),
|
outputs = ObservableList<Output>(),
|
||||||
_settingsStore = appStore.settingsStore,
|
_settingsStore = appStore.settingsStore,
|
||||||
fiatFromSettings = appStore.settingsStore.fiatCurrency,
|
fiatFromSettings = appStore.settingsStore.fiatCurrency,
|
||||||
|
@ -100,6 +102,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
@computed
|
@computed
|
||||||
bool get isBatchSending => outputs.length > 1;
|
bool get isBatchSending => outputs.length > 1;
|
||||||
|
|
||||||
|
bool get shouldDisplaySendALL => walletType != WalletType.solana;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
String get pendingTransactionFiatAmount {
|
String get pendingTransactionFiatAmount {
|
||||||
if (pendingTransaction == null) {
|
if (pendingTransaction == null) {
|
||||||
|
@ -297,6 +301,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
pendingTransaction = await wallet.createTransaction(_credentials());
|
pendingTransaction = await wallet.createTransaction(_credentials());
|
||||||
state = ExecutedSuccessfullyState();
|
state = ExecutedSuccessfullyState();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print('Failed with ${e.toString()}');
|
||||||
state = FailureState(e.toString());
|
state = FailureState(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -351,7 +356,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
Object _credentials() {
|
Object _credentials() {
|
||||||
final priority = _settingsStore.priority[wallet.type];
|
final priority = _settingsStore.priority[wallet.type];
|
||||||
|
|
||||||
if (priority == null && wallet.type != WalletType.nano) {
|
if (priority == null && wallet.type != WalletType.nano && wallet.type != WalletType.solana) {
|
||||||
throw Exception('Priority is null for wallet type: ${wallet.type}');
|
throw Exception('Priority is null for wallet type: ${wallet.type}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +382,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.createPolygonTransactionCredentials(outputs,
|
return polygon!.createPolygonTransactionCredentials(outputs,
|
||||||
priority: priority!, currency: selectedCryptoCurrency);
|
priority: priority!, currency: selectedCryptoCurrency);
|
||||||
|
case WalletType.solana:
|
||||||
|
return solana!
|
||||||
|
.createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency);
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${wallet.type}');
|
throw Exception('Unexpected wallet type: ${wallet.type}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,14 @@ import 'package:package_info/package_info.dart';
|
||||||
|
|
||||||
part 'other_settings_view_model.g.dart';
|
part 'other_settings_view_model.g.dart';
|
||||||
|
|
||||||
class OtherSettingsViewModel = OtherSettingsViewModelBase
|
class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel;
|
||||||
with _$OtherSettingsViewModel;
|
|
||||||
|
|
||||||
abstract class OtherSettingsViewModelBase with Store {
|
abstract class OtherSettingsViewModelBase with Store {
|
||||||
OtherSettingsViewModelBase(this._settingsStore, this._wallet)
|
OtherSettingsViewModelBase(this._settingsStore, this._wallet)
|
||||||
: walletType = _wallet.type,
|
: walletType = _wallet.type,
|
||||||
currentVersion = '' {
|
currentVersion = '' {
|
||||||
PackageInfo.fromPlatform().then(
|
PackageInfo.fromPlatform()
|
||||||
(PackageInfo packageInfo) => currentVersion = packageInfo.version);
|
.then((PackageInfo packageInfo) => currentVersion = packageInfo.version);
|
||||||
|
|
||||||
final priority = _settingsStore.priority[_wallet.type];
|
final priority = _settingsStore.priority[_wallet.type];
|
||||||
final priorities = priorityForWalletType(_wallet.type);
|
final priorities = priorityForWalletType(_wallet.type);
|
||||||
|
@ -33,8 +32,7 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
final WalletType walletType;
|
final WalletType walletType;
|
||||||
final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
|
||||||
TransactionInfo> _wallet;
|
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
String currentVersion;
|
String currentVersion;
|
||||||
|
@ -57,12 +55,14 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||||
_wallet.type == WalletType.nano || _wallet.type == WalletType.banano;
|
_wallet.type == WalletType.nano || _wallet.type == WalletType.banano;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get isEnabledBuyAction =>
|
bool get displayTransactionPriority =>
|
||||||
!_settingsStore.disableBuy && _wallet.type != WalletType.haven;
|
!(changeRepresentativeEnabled || _wallet.type == WalletType.solana);
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get isEnabledSellAction =>
|
bool get isEnabledBuyAction => !_settingsStore.disableBuy && _wallet.type != WalletType.haven;
|
||||||
!_settingsStore.disableSell && _wallet.type != WalletType.haven;
|
|
||||||
|
@computed
|
||||||
|
bool get isEnabledSellAction => !_settingsStore.disableSell && _wallet.type != WalletType.haven;
|
||||||
|
|
||||||
List<ProviderType> get availableBuyProvidersTypes {
|
List<ProviderType> get availableBuyProvidersTypes {
|
||||||
return ProvidersHelper.getAvailableBuyProviderTypes(walletType);
|
return ProvidersHelper.getAvailableBuyProviderTypes(walletType);
|
||||||
|
@ -72,12 +72,10 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||||
ProvidersHelper.getAvailableSellProviderTypes(walletType);
|
ProvidersHelper.getAvailableSellProviderTypes(walletType);
|
||||||
|
|
||||||
ProviderType get buyProviderType =>
|
ProviderType get buyProviderType =>
|
||||||
_settingsStore.defaultBuyProviders[walletType] ??
|
_settingsStore.defaultBuyProviders[walletType] ?? ProviderType.askEachTime;
|
||||||
ProviderType.askEachTime;
|
|
||||||
|
|
||||||
ProviderType get sellProviderType =>
|
ProviderType get sellProviderType =>
|
||||||
_settingsStore.defaultSellProviders[walletType] ??
|
_settingsStore.defaultSellProviders[walletType] ?? ProviderType.askEachTime;
|
||||||
ProviderType.askEachTime;
|
|
||||||
|
|
||||||
String getDisplayPriority(dynamic priority) {
|
String getDisplayPriority(dynamic priority) {
|
||||||
final _priority = priority as TransactionPriority;
|
final _priority = priority as TransactionPriority;
|
||||||
|
@ -114,7 +112,6 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||||
_settingsStore.defaultBuyProviders[walletType] = buyProviderType;
|
_settingsStore.defaultBuyProviders[walletType] = buyProviderType;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
ProviderType onSellProviderTypeSelected(
|
ProviderType onSellProviderTypeSelected(ProviderType sellProviderType) =>
|
||||||
ProviderType sellProviderType) =>
|
|
||||||
_settingsStore.defaultSellProviders[walletType] = sellProviderType;
|
_settingsStore.defaultSellProviders[walletType] = sellProviderType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
_addPolygonListItems(tx, dateFormat);
|
_addPolygonListItems(tx, dateFormat);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.solana:
|
||||||
|
_addSolanaListItems(tx, dateFormat);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -131,6 +134,8 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
return 'https://bananolooker.com/block/${txId}';
|
return 'https://bananolooker.com/block/${txId}';
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return 'https://polygonscan.com/tx/${txId}';
|
return 'https://polygonscan.com/tx/${txId}';
|
||||||
|
case WalletType.solana:
|
||||||
|
return 'https://solscan.io/tx/${txId}';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -155,6 +160,8 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
return S.current.view_transaction_on + 'bananolooker.com';
|
return S.current.view_transaction_on + 'bananolooker.com';
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return S.current.view_transaction_on + 'polygonscan.com';
|
return S.current.view_transaction_on + 'polygonscan.com';
|
||||||
|
case WalletType.solana:
|
||||||
|
return S.current.view_transaction_on + 'solscan.io';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -281,4 +288,21 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
|
|
||||||
items.addAll(_items);
|
items.addAll(_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||||
|
final _items = [
|
||||||
|
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||||
|
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||||
|
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||||
|
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||||
|
if (showRecipientAddress && tx.to != null)
|
||||||
|
StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
|
||||||
|
if (tx.from != null)
|
||||||
|
StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
|
||||||
|
];
|
||||||
|
|
||||||
|
items.addAll(_items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/haven/haven.dart';
|
import 'package:cake_wallet/haven/haven.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
@ -159,6 +160,21 @@ class PolygonURI extends PaymentURI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SolanaURI extends PaymentURI {
|
||||||
|
SolanaURI({required String amount, required String address})
|
||||||
|
: super(amount: amount, address: address);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
var base = 'solana:' + address;
|
||||||
|
if (amount.isNotEmpty) {
|
||||||
|
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
|
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
|
||||||
WalletAddressListViewModelBase({
|
WalletAddressListViewModelBase({
|
||||||
required AppStore appStore,
|
required AppStore appStore,
|
||||||
|
@ -257,6 +273,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
return PolygonURI(amount: amount, address: address.address);
|
return PolygonURI(amount: amount, address: address.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wallet.type == WalletType.solana) {
|
||||||
|
return SolanaURI(amount: amount, address: address.address);
|
||||||
|
}
|
||||||
|
|
||||||
throw Exception('Unexpected type: ${type.toString()}');
|
throw Exception('Unexpected type: ${type.toString()}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +346,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wallet.type == WalletType.solana) {
|
||||||
|
final primaryAddress = solana!.getAddress(wallet);
|
||||||
|
|
||||||
|
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
||||||
|
}
|
||||||
|
|
||||||
if (searchText.isNotEmpty) {
|
if (searchText.isNotEmpty) {
|
||||||
return ObservableList.of(addressList.where((item) {
|
return ObservableList.of(addressList.where((item) {
|
||||||
if (item is WalletAddressListItem) {
|
if (item is WalletAddressListItem) {
|
||||||
|
|
|
@ -110,7 +110,8 @@ abstract class WalletKeysViewModelBase with Store {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEVMCompatibleChain(_appStore.wallet!.type)) {
|
if (isEVMCompatibleChain(_appStore.wallet!.type) ||
|
||||||
|
_appStore.wallet!.type == WalletType.solana) {
|
||||||
items.addAll([
|
items.addAll([
|
||||||
if (_appStore.wallet!.privateKey != null)
|
if (_appStore.wallet!.privateKey != null)
|
||||||
StandartListItem(title: S.current.private_key, value: _appStore.wallet!.privateKey!),
|
StandartListItem(title: S.current.private_key, value: _appStore.wallet!.privateKey!),
|
||||||
|
@ -165,6 +166,8 @@ abstract class WalletKeysViewModelBase with Store {
|
||||||
return 'banano-wallet';
|
return 'banano-wallet';
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return 'polygon-wallet';
|
return 'polygon-wallet';
|
||||||
|
case WalletType.solana:
|
||||||
|
return 'solana-wallet';
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
|
throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
|
@ -36,19 +37,21 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||||
bool get hasLanguageSelector => type == WalletType.monero || type == WalletType.haven;
|
bool get hasLanguageSelector => type == WalletType.monero || type == WalletType.haven;
|
||||||
|
|
||||||
int get seedPhraseWordsLength {
|
int get seedPhraseWordsLength {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
if(advancedPrivacySettingsViewModel.isPolySeed) {
|
if (advancedPrivacySettingsViewModel.isPolySeed) {
|
||||||
return 16;
|
return 16;
|
||||||
}
|
}
|
||||||
return 25;
|
return 25;
|
||||||
case WalletType.ethereum:
|
case WalletType.solana:
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.polygon:
|
||||||
return advancedPrivacySettingsViewModel.seedPhraseLength.value;
|
case WalletType.ethereum:
|
||||||
default:
|
case WalletType.bitcoinCash:
|
||||||
return 24;
|
return advancedPrivacySettingsViewModel.seedPhraseLength.value;
|
||||||
}
|
default:
|
||||||
|
return 24;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool get hasSeedType => type == WalletType.monero;
|
bool get hasSeedType => type == WalletType.monero;
|
||||||
|
|
||||||
|
@ -64,8 +67,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return bitcoin!.createBitcoinNewWalletCredentials(name: name);
|
return bitcoin!.createBitcoinNewWalletCredentials(name: name);
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return haven!.createHavenNewWalletCredentials(
|
return haven!
|
||||||
name: name, language: options!.first as String);
|
.createHavenNewWalletCredentials(name: name, language: options!.first as String);
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
return ethereum!.createEthereumNewWalletCredentials(name: name);
|
return ethereum!.createEthereumNewWalletCredentials(name: name);
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
|
@ -74,6 +77,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||||
return nano!.createNanoNewWalletCredentials(name: name);
|
return nano!.createNanoNewWalletCredentials(name: name);
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.createPolygonNewWalletCredentials(name: name);
|
return polygon!.createPolygonNewWalletCredentials(name: name);
|
||||||
|
case WalletType.solana:
|
||||||
|
return solana!.createSolanaNewWalletCredentials(name: name);
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected type: ${type.toString()}');
|
throw Exception('Unexpected type: ${type.toString()}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:cake_wallet/nano/nano.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||||
import 'package:cake_wallet/polygon/polygon.dart';
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
|
import 'package:cake_wallet/solana/solana.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
|
@ -28,11 +29,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
{required WalletType type})
|
{required WalletType type})
|
||||||
: hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
|
: hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
|
||||||
hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
|
hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
|
||||||
hasRestoreFromPrivateKey =
|
hasRestoreFromPrivateKey = type == WalletType.ethereum ||
|
||||||
type == WalletType.ethereum ||
|
|
||||||
type == WalletType.polygon ||
|
type == WalletType.polygon ||
|
||||||
type == WalletType.nano ||
|
type == WalletType.nano ||
|
||||||
type == WalletType.banano,
|
type == WalletType.banano ||
|
||||||
|
type == WalletType.solana,
|
||||||
isButtonEnabled = false,
|
isButtonEnabled = false,
|
||||||
mode = WalletRestoreMode.seed,
|
mode = WalletRestoreMode.seed,
|
||||||
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
|
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
|
||||||
|
@ -45,6 +46,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
break;
|
break;
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
|
case WalletType.solana:
|
||||||
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
|
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -98,22 +100,21 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
name: name, height: height, mnemonic: seed, password: password);
|
name: name, height: height, mnemonic: seed, password: password);
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
|
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
|
||||||
name: name,
|
name: name, mnemonic: seed, password: password);
|
||||||
mnemonic: seed,
|
|
||||||
password: password);
|
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
|
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
|
||||||
name: name,
|
name: name, mnemonic: seed, password: password);
|
||||||
mnemonic: seed,
|
|
||||||
password: password);
|
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
return nano!.createNanoRestoreWalletFromSeedCredentials(
|
return nano!.createNanoRestoreWalletFromSeedCredentials(
|
||||||
|
name: name, mnemonic: seed, password: password, derivationType: derivationType);
|
||||||
|
case WalletType.polygon:
|
||||||
|
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
|
||||||
name: name,
|
name: name,
|
||||||
mnemonic: seed,
|
mnemonic: seed,
|
||||||
password: password,
|
password: password,
|
||||||
derivationType: derivationType);
|
);
|
||||||
case WalletType.polygon:
|
case WalletType.solana:
|
||||||
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
|
return solana!.createSolanaRestoreWalletFromSeedCredentials(
|
||||||
name: name,
|
name: name,
|
||||||
mnemonic: seed,
|
mnemonic: seed,
|
||||||
password: password,
|
password: password,
|
||||||
|
@ -160,16 +161,22 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
|
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
return nano!.createNanoRestoreWalletFromKeysCredentials(
|
return nano!.createNanoRestoreWalletFromKeysCredentials(
|
||||||
name: name,
|
name: name,
|
||||||
password: password,
|
password: password,
|
||||||
seedKey: options['private_key'] as String,
|
seedKey: options['private_key'] as String,
|
||||||
derivationType: options["derivationType"] as DerivationType);
|
derivationType: options["derivationType"] as DerivationType);
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.createPolygonRestoreWalletFromPrivateKey(
|
return polygon!.createPolygonRestoreWalletFromPrivateKey(
|
||||||
name: name,
|
name: name,
|
||||||
password: password,
|
password: password,
|
||||||
privateKey: options['private_key'] as String,
|
privateKey: options['private_key'] as String,
|
||||||
);
|
);
|
||||||
|
case WalletType.solana:
|
||||||
|
return solana!.createSolanaRestoreWalletFromPrivateKey(
|
||||||
|
name: name,
|
||||||
|
password: password,
|
||||||
|
privateKey: options['private_key'] as String,
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -187,10 +194,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
return nanoUtil!.compareDerivationMethods(
|
return nanoUtil!
|
||||||
mnemonic: mnemonic,
|
.compareDerivationMethods(mnemonic: mnemonic, privateKey: seedKey, node: node);
|
||||||
privateKey: seedKey,
|
|
||||||
node: node);
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,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
|
||||||
|
|
|
@ -106,6 +106,7 @@ dependencies:
|
||||||
flutter_svg: ^2.0.9
|
flutter_svg: ^2.0.9
|
||||||
polyseed: ^0.0.2
|
polyseed: ^0.0.2
|
||||||
nostr_tools: ^1.0.9
|
nostr_tools: ^1.0.9
|
||||||
|
solana: ^0.30.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -152,6 +153,7 @@ flutter:
|
||||||
- assets/nano_node_list.yml
|
- assets/nano_node_list.yml
|
||||||
- assets/nano_pow_node_list.yml
|
- assets/nano_pow_node_list.yml
|
||||||
- assets/polygon_node_list.yml
|
- assets/polygon_node_list.yml
|
||||||
|
- assets/solana_node_list.yml
|
||||||
- assets/text/
|
- assets/text/
|
||||||
- assets/faq/
|
- assets/faq/
|
||||||
- assets/animation/
|
- assets/animation/
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue