mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-09 12:19:24 +00:00
Merge branch 'solana-tor' into testing
This commit is contained in:
commit
feef4d95ca
24 changed files with 740 additions and 53 deletions
|
@ -205,4 +205,4 @@ Run with `-v` or `--verbose` to see a more detailed error. Certain exceptions (
|
|||
|
||||
## Tor
|
||||
|
||||
To test Tor usage, run Stack Wallet from Android Studio. Click the Flutter DevTools icon in the Run tab (next to the Hot Reload and Hot Restart buttons) and navigate to the Network tab. Connections using Tor will show as `GET InternetAddress('127.0.0.1', IPv4) 101 ws`. Connections outside of Tor will show the destination address directly.
|
||||
To test Tor usage, run Stack Wallet from Android Studio. Click the Flutter DevTools icon in the Run tab (next to the Hot Reload and Hot Restart buttons) and navigate to the Network tab. Connections using Tor will show as `GET InternetAddress('127.0.0.1', IPv4) 101 ws`. Connections outside of Tor will show the destination address directly (although some Tor requests may also show the destination address directly, check the Headers take for *eg.* `{localPort: 59940, remoteAddress: 127.0.0.1, remotePort: 6725}`. `localPort` should match your Tor port.
|
||||
|
|
|
@ -164,6 +164,7 @@ enum AddressType {
|
|||
stellar,
|
||||
tezos,
|
||||
frostMS,
|
||||
solana,
|
||||
p2tr;
|
||||
|
||||
String get readableName {
|
||||
|
@ -196,6 +197,8 @@ enum AddressType {
|
|||
return "Tezos";
|
||||
case AddressType.frostMS:
|
||||
return "FrostMS";
|
||||
case AddressType.solana:
|
||||
return "Solana";
|
||||
case AddressType.p2tr:
|
||||
return "Taproot"; // Why not use P2TR, P2PKH, etc.?
|
||||
}
|
||||
|
|
|
@ -139,6 +139,11 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
|||
_coins.remove(Coin.bitcoinFrost);
|
||||
}
|
||||
|
||||
// Remove Solana from the list of coins based on our frostEnabled preference.
|
||||
if (!ref.read(prefsChangeNotifierProvider).solanaEnabled) {
|
||||
_coins.remove(Coin.solana);
|
||||
}
|
||||
|
||||
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
|
||||
|
||||
if (ref.read(prefsChangeNotifierProvider).showTestNetCoins) {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
@ -276,6 +277,36 @@ class HiddenSettings extends StatelessWidget {
|
|||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.solanaEnabled =
|
||||
!(ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.solanaEnabled);
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
"Solana enabled: ${ref.read(prefsChangeNotifierProvider).solanaEnabled}");
|
||||
}
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Toggle Solana",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
|
|
|
@ -14,6 +14,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:solana/solana.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
||||
|
@ -216,6 +217,20 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
);
|
||||
} catch (_) {}
|
||||
break;
|
||||
|
||||
case Coin.solana:
|
||||
try {
|
||||
RpcClient rpcClient;
|
||||
if (formData.host!.startsWith("http") || formData.host!.startsWith("https")) {
|
||||
rpcClient = RpcClient("${formData.host}:${formData.port}");
|
||||
} else {
|
||||
rpcClient = RpcClient("http://${formData.host}:${formData.port}");
|
||||
}
|
||||
await rpcClient.getEpochInfo().then((value) => testPassed = true);
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (showFlushBar && mounted) {
|
||||
|
@ -756,6 +771,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
|||
case Coin.nano:
|
||||
case Coin.banano:
|
||||
case Coin.eCash:
|
||||
case Coin.solana:
|
||||
case Coin.stellar:
|
||||
case Coin.stellarTestnet:
|
||||
case Coin.bitcoinFrost:
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:solana/solana.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
||||
|
@ -193,6 +194,20 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
|||
testPassed = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Coin.solana:
|
||||
try {
|
||||
RpcClient rpcClient;
|
||||
if (node!.host.startsWith("http") || node.host.startsWith("https")) {
|
||||
rpcClient = RpcClient("${node.host}:${node.port}");
|
||||
} else {
|
||||
rpcClient = RpcClient("http://${node.host}:${node.port}");
|
||||
}
|
||||
await rpcClient.getEpochInfo().then((value) => testPassed = true);
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (testPassed) {
|
||||
|
|
|
@ -101,7 +101,7 @@ class PriceAPI {
|
|||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency"
|
||||
"=${baseCurrency.toLowerCase()}"
|
||||
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar,tezos"
|
||||
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar,tezos,solana"
|
||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false");
|
||||
|
||||
final coinGeckoResponse = await client.get(
|
||||
|
|
|
@ -28,6 +28,7 @@ class CoinThemeColorDefault {
|
|||
Color get namecoin => const Color(0xFF91B1E1);
|
||||
Color get wownero => const Color(0xFFED80C1);
|
||||
Color get particl => const Color(0xFF8175BD);
|
||||
Color get solana => const Color(0xFFC696FF);
|
||||
Color get stellar => const Color(0xFF6600FF);
|
||||
Color get nano => const Color(0xFF209CE9);
|
||||
Color get banano => const Color(0xFFFBDD11);
|
||||
|
@ -66,6 +67,8 @@ class CoinThemeColorDefault {
|
|||
return wownero;
|
||||
case Coin.particl:
|
||||
return particl;
|
||||
case Coin.solana:
|
||||
return solana;
|
||||
case Coin.stellar:
|
||||
case Coin.stellarTestnet:
|
||||
return stellar;
|
||||
|
|
|
@ -1709,6 +1709,8 @@ class StackColors extends ThemeExtension<StackColors> {
|
|||
return _coin.wownero;
|
||||
case Coin.particl:
|
||||
return _coin.particl;
|
||||
case Coin.solana:
|
||||
return _coin.solana;
|
||||
case Coin.stellar:
|
||||
case Coin.stellarTestnet:
|
||||
return _coin.stellar;
|
||||
|
|
|
@ -26,6 +26,7 @@ import 'package:stackwallet/wallets/crypto_currency/coins/monero.dart';
|
|||
import 'package:stackwallet/wallets/crypto_currency/coins/namecoin.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/nano.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/particl.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/solana.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/stellar.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/tezos.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/wownero.dart';
|
||||
|
@ -67,6 +68,8 @@ class AddressUtils {
|
|||
return Namecoin(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
case Coin.particl:
|
||||
return Particl(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
case Coin.solana:
|
||||
return Solana(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
case Coin.stellar:
|
||||
return Stellar(CryptoCurrencyNetwork.main).validateAddress(address);
|
||||
case Coin.nano:
|
||||
|
|
|
@ -55,6 +55,7 @@ enum AmountUnit {
|
|||
case Coin.stellar: // TODO: check if this is correct
|
||||
case Coin.stellarTestnet:
|
||||
case Coin.tezos:
|
||||
case Coin.solana:
|
||||
return AmountUnit.values.sublist(0, 4);
|
||||
|
||||
case Coin.monero:
|
||||
|
|
|
@ -66,6 +66,8 @@ Uri getDefaultBlockExplorerUrlFor({
|
|||
return Uri.parse("https://testnet.stellarchain.io/transactions/$txid");
|
||||
case Coin.tezos:
|
||||
return Uri.parse("https://tzstats.com/$txid");
|
||||
case Coin.solana:
|
||||
return Uri.parse("https://explorer.solana.com/tx/$txid");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ abstract class Constants {
|
|||
10000000); // https://developers.stellar.org/docs/fundamentals-and-concepts/stellar-data-structures/assets#amount-precision
|
||||
static final BigInt _satsPerCoin = BigInt.from(100000000);
|
||||
static final BigInt _satsPerCoinTezos = BigInt.from(1000000);
|
||||
static final BigInt _satsPerCoinSolana = BigInt.from(1000000000);
|
||||
static const int _decimalPlaces = 8;
|
||||
static const int _decimalPlacesNano = 30;
|
||||
static const int _decimalPlacesBanano = 29;
|
||||
|
@ -55,6 +56,7 @@ abstract class Constants {
|
|||
static const int _decimalPlacesECash = 2;
|
||||
static const int _decimalPlacesStellar = 7;
|
||||
static const int _decimalPlacesTezos = 6;
|
||||
static const int _decimalPlacesSolana = 9;
|
||||
|
||||
static const int notificationsMax = 0xFFFFFFFF;
|
||||
static const Duration networkAliveTimerDuration = Duration(seconds: 10);
|
||||
|
@ -109,6 +111,9 @@ abstract class Constants {
|
|||
|
||||
case Coin.tezos:
|
||||
return _satsPerCoinTezos;
|
||||
|
||||
case Coin.solana:
|
||||
return _satsPerCoinSolana;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,6 +160,9 @@ abstract class Constants {
|
|||
|
||||
case Coin.tezos:
|
||||
return _decimalPlacesTezos;
|
||||
|
||||
case Coin.solana:
|
||||
return _decimalPlacesSolana;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,6 +184,7 @@ abstract class Constants {
|
|||
case Coin.ethereum:
|
||||
case Coin.namecoin:
|
||||
case Coin.particl:
|
||||
case Coin.solana:
|
||||
case Coin.nano:
|
||||
case Coin.stellar:
|
||||
case Coin.stellarTestnet:
|
||||
|
@ -245,6 +254,7 @@ abstract class Constants {
|
|||
|
||||
case Coin.nano: // TODO: Verify this
|
||||
case Coin.banano: // TODO: Verify this
|
||||
case Coin.solana:
|
||||
return 1;
|
||||
|
||||
case Coin.stellar:
|
||||
|
@ -272,6 +282,7 @@ abstract class Constants {
|
|||
case Coin.namecoin:
|
||||
case Coin.particl:
|
||||
case Coin.ethereum:
|
||||
case Coin.solana:
|
||||
return 12;
|
||||
|
||||
case Coin.wownero:
|
||||
|
|
|
@ -188,6 +188,18 @@ abstract class DefaultNodes {
|
|||
isDown: false,
|
||||
);
|
||||
|
||||
static NodeModel get solana => NodeModel(
|
||||
host: "https://api.mainnet-beta.solana.com", // TODO: Change this to stack wallet one
|
||||
port: 443,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(Coin.solana),
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: Coin.solana.name,
|
||||
isFailover: true,
|
||||
isDown: false,
|
||||
);
|
||||
|
||||
static NodeModel get stellar => NodeModel(
|
||||
host: "https://horizon.stellar.org",
|
||||
port: 443,
|
||||
|
@ -348,6 +360,9 @@ abstract class DefaultNodes {
|
|||
case Coin.particl:
|
||||
return particl;
|
||||
|
||||
case Coin.solana:
|
||||
return solana;
|
||||
|
||||
case Coin.stellar:
|
||||
return stellar;
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ enum Coin {
|
|||
namecoin,
|
||||
nano,
|
||||
particl,
|
||||
solana,
|
||||
stellar,
|
||||
tezos,
|
||||
wownero,
|
||||
|
@ -69,6 +70,8 @@ extension CoinExt on Coin {
|
|||
return "Monero";
|
||||
case Coin.particl:
|
||||
return "Particl";
|
||||
case Coin.solana:
|
||||
return "Solana";
|
||||
case Coin.stellar:
|
||||
return "Stellar";
|
||||
case Coin.tezos:
|
||||
|
@ -121,6 +124,8 @@ extension CoinExt on Coin {
|
|||
return "XMR";
|
||||
case Coin.particl:
|
||||
return "PART";
|
||||
case Coin.solana:
|
||||
return "SOL";
|
||||
case Coin.stellar:
|
||||
return "XLM";
|
||||
case Coin.tezos:
|
||||
|
@ -173,6 +178,8 @@ extension CoinExt on Coin {
|
|||
return "monero";
|
||||
case Coin.particl:
|
||||
return "particl";
|
||||
case Coin.solana:
|
||||
return "solana";
|
||||
case Coin.stellar:
|
||||
return "stellar";
|
||||
case Coin.tezos:
|
||||
|
@ -229,6 +236,7 @@ extension CoinExt on Coin {
|
|||
case Coin.nano:
|
||||
case Coin.banano:
|
||||
case Coin.tezos:
|
||||
case Coin.solana:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -259,6 +267,7 @@ extension CoinExt on Coin {
|
|||
case Coin.firoTestNet:
|
||||
case Coin.nano:
|
||||
case Coin.banano:
|
||||
case Coin.solana:
|
||||
case Coin.stellar:
|
||||
case Coin.stellarTestnet:
|
||||
return false;
|
||||
|
@ -284,6 +293,7 @@ extension CoinExt on Coin {
|
|||
case Coin.banano:
|
||||
case Coin.eCash:
|
||||
case Coin.stellar:
|
||||
case Coin.solana:
|
||||
return false;
|
||||
|
||||
case Coin.dogecoinTestNet:
|
||||
|
@ -327,6 +337,7 @@ extension CoinExt on Coin {
|
|||
case Coin.banano:
|
||||
case Coin.eCash:
|
||||
case Coin.stellar:
|
||||
case Coin.solana:
|
||||
return this;
|
||||
|
||||
case Coin.dogecoinTestNet:
|
||||
|
@ -400,6 +411,9 @@ extension CoinExt on Coin {
|
|||
case Coin.stellar:
|
||||
case Coin.stellarTestnet:
|
||||
return AddressType.stellar;
|
||||
|
||||
case Coin.solana:
|
||||
return AddressType.solana;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -448,6 +462,10 @@ Coin coinFromPrettyName(String name) {
|
|||
case "particl":
|
||||
return Coin.particl;
|
||||
|
||||
case "Solana":
|
||||
case "solana":
|
||||
return Coin.solana;
|
||||
|
||||
case "Stellar":
|
||||
case "stellar":
|
||||
return Coin.stellar;
|
||||
|
@ -548,6 +566,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) {
|
|||
return Coin.namecoin;
|
||||
case "part":
|
||||
return Coin.particl;
|
||||
case "sol":
|
||||
return Coin.solana;
|
||||
case "xlm":
|
||||
return Coin.stellar;
|
||||
case "xtz":
|
||||
|
|
|
@ -17,6 +17,7 @@ enum DerivePathType {
|
|||
bip84,
|
||||
eth,
|
||||
eCash44,
|
||||
solana,
|
||||
bip86,
|
||||
}
|
||||
|
||||
|
@ -45,6 +46,9 @@ extension DerivePathTypeExt on DerivePathType {
|
|||
case Coin.ethereum: // TODO: do we need something here?
|
||||
return DerivePathType.eth;
|
||||
|
||||
case Coin.solana:
|
||||
return DerivePathType.solana;
|
||||
|
||||
case Coin.bitcoinFrost:
|
||||
case Coin.bitcoinFrostTestNet:
|
||||
case Coin.epicCash:
|
||||
|
|
|
@ -68,6 +68,7 @@ class Prefs extends ChangeNotifier {
|
|||
await _setMaxDecimals();
|
||||
_useTor = await _getUseTor();
|
||||
_fusionServerInfo = await _getFusionServerInfo();
|
||||
_solanaEnabled = await _getSolanaEnabled();
|
||||
_frostEnabled = await _getFrostEnabled();
|
||||
|
||||
_initialized = true;
|
||||
|
@ -1010,6 +1011,27 @@ class Prefs extends ChangeNotifier {
|
|||
return actualMap;
|
||||
}
|
||||
|
||||
// Solana
|
||||
|
||||
bool _solanaEnabled = false;
|
||||
|
||||
bool get solanaEnabled => _solanaEnabled;
|
||||
|
||||
set solanaEnabled(bool solanaEnabled) {
|
||||
if (_solanaEnabled != solanaEnabled) {
|
||||
DB.instance.put<dynamic>(
|
||||
boxName: DB.boxNamePrefs, key: "solanaEnabled", value: solanaEnabled);
|
||||
_solanaEnabled = solanaEnabled;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _getSolanaEnabled() async {
|
||||
return await DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNamePrefs, key: "solanaEnabled") as bool? ??
|
||||
false;
|
||||
}
|
||||
|
||||
// FROST multisig
|
||||
|
||||
bool _frostEnabled = false;
|
||||
|
|
48
lib/wallets/crypto_currency/coins/solana.dart
Normal file
48
lib/wallets/crypto_currency/coins/solana.dart
Normal file
|
@ -0,0 +1,48 @@
|
|||
import 'package:solana/solana.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_currency.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
|
||||
class Solana extends Bip39Currency {
|
||||
Solana(super.network) {
|
||||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
coin = Coin.solana;
|
||||
default:
|
||||
throw Exception("Unsupported network: $network");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
NodeModel get defaultNode {
|
||||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return NodeModel(
|
||||
host: "https://api.mainnet-beta.solana.com/", // TODO: Change this to stack wallet one
|
||||
port: 443,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(Coin.solana),
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: Coin.solana.name,
|
||||
isFailover: true,
|
||||
isDown: false,
|
||||
);
|
||||
default:
|
||||
throw Exception("Unsupported network: $network");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get minConfirms => 21;
|
||||
|
||||
@override
|
||||
bool validateAddress(String address) {
|
||||
return isPointOnEd25519Curve(Ed25519HDPublicKey.fromBase58(address).toByteArray());
|
||||
}
|
||||
|
||||
@override
|
||||
String get genesisHash => throw UnimplementedError();
|
||||
}
|
431
lib/wallets/wallet/impl/solana_wallet.dart
Normal file
431
lib/wallets/wallet/impl/solana_wallet.dart
Normal file
|
@ -0,0 +1,431 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:solana/dto.dart';
|
||||
import 'package:solana/solana.dart';
|
||||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'
|
||||
as isar;
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
import 'package:stackwallet/services/tor_service.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/solana.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/intermediate/bip39_wallet.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class SolanaWallet extends Bip39Wallet<Solana> {
|
||||
SolanaWallet(CryptoCurrencyNetwork network) : super(Solana(network));
|
||||
|
||||
NodeModel? _solNode;
|
||||
|
||||
RpcClient? rpcClient; // The Solana RpcClient.
|
||||
|
||||
Future<Ed25519HDKeyPair> _getKeyPair() async {
|
||||
return Ed25519HDKeyPair.fromMnemonic(await getMnemonic(),
|
||||
account: 0, change: 0);
|
||||
}
|
||||
|
||||
Future<Address> _getCurrentAddress() async {
|
||||
var addressStruct = Address(
|
||||
walletId: walletId,
|
||||
value: (await _getKeyPair()).address,
|
||||
publicKey: List<int>.empty(),
|
||||
derivationIndex: 0,
|
||||
derivationPath: DerivationPath()..value = "m/44'/501'/0'/0'",
|
||||
type: cryptoCurrency.coin.primaryAddressType,
|
||||
subType: AddressSubType.unknown);
|
||||
return addressStruct;
|
||||
}
|
||||
|
||||
Future<int> _getCurrentBalanceInLamports() async {
|
||||
await _checkClient();
|
||||
var balance = await rpcClient?.getBalance((await _getKeyPair()).address);
|
||||
return balance!.value;
|
||||
}
|
||||
|
||||
@override
|
||||
FilterOperation? get changeAddressFilterOperation =>
|
||||
throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<void> checkSaveInitialReceivingAddress() async {
|
||||
try {
|
||||
var address = (await _getKeyPair()).address;
|
||||
|
||||
await mainDB.updateOrPutAddresses([
|
||||
Address(
|
||||
walletId: walletId,
|
||||
value: address,
|
||||
publicKey: List<int>.empty(),
|
||||
derivationIndex: 0,
|
||||
derivationPath: DerivationPath()..value = "m/44'/501'/0'/0'",
|
||||
type: cryptoCurrency.coin.primaryAddressType,
|
||||
subType: AddressSubType.unknown)
|
||||
]);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$runtimeType checkSaveInitialReceivingAddress() failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TxData> prepareSend({required TxData txData}) async {
|
||||
try {
|
||||
await _checkClient();
|
||||
|
||||
if (txData.recipients == null || txData.recipients!.length != 1) {
|
||||
throw Exception("$runtimeType prepareSend requires 1 recipient");
|
||||
}
|
||||
|
||||
Amount sendAmount = txData.amount!;
|
||||
|
||||
if (sendAmount > info.cachedBalance.spendable) {
|
||||
throw Exception("Insufficient available balance");
|
||||
}
|
||||
|
||||
int feeAmount;
|
||||
var currentFees = await fees;
|
||||
switch (txData.feeRateType) {
|
||||
case FeeRateType.fast:
|
||||
feeAmount = currentFees.fast;
|
||||
break;
|
||||
case FeeRateType.slow:
|
||||
feeAmount = currentFees.slow;
|
||||
break;
|
||||
case FeeRateType.average:
|
||||
default:
|
||||
feeAmount = currentFees.medium;
|
||||
break;
|
||||
}
|
||||
|
||||
// Rent exemption of Solana
|
||||
final accInfo =
|
||||
await rpcClient?.getAccountInfo((await _getKeyPair()).address);
|
||||
int minimumRent = await rpcClient?.getMinimumBalanceForRentExemption(
|
||||
accInfo!.value!.data.toString().length) ??
|
||||
0; // TODO revisit null condition.
|
||||
if (minimumRent >
|
||||
((await _getCurrentBalanceInLamports()) -
|
||||
txData.amount!.raw.toInt() -
|
||||
feeAmount)) {
|
||||
throw Exception(
|
||||
"Insufficient remaining balance for rent exemption, minimum rent: ${minimumRent / pow(10, cryptoCurrency.fractionDigits)}");
|
||||
}
|
||||
|
||||
return txData.copyWith(
|
||||
fee: Amount(
|
||||
rawValue: BigInt.from(feeAmount),
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$runtimeType Solana prepareSend failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TxData> confirmSend({required TxData txData}) async {
|
||||
try {
|
||||
await _checkClient();
|
||||
|
||||
final keyPair = await _getKeyPair();
|
||||
var recipientAccount = txData.recipients!.first;
|
||||
var recipientPubKey =
|
||||
Ed25519HDPublicKey.fromBase58(recipientAccount.address);
|
||||
final message = Message(
|
||||
instructions: [
|
||||
SystemInstruction.transfer(
|
||||
fundingAccount: keyPair.publicKey,
|
||||
recipientAccount: recipientPubKey,
|
||||
lamports: txData.amount!.raw.toInt()),
|
||||
ComputeBudgetInstruction.setComputeUnitPrice(
|
||||
microLamports: txData.fee!.raw.toInt()),
|
||||
],
|
||||
);
|
||||
|
||||
final txid = await rpcClient?.signAndSendTransaction(message, [keyPair]);
|
||||
return txData.copyWith(
|
||||
txid: txid,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$runtimeType Solana confirmSend failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
|
||||
await _checkClient();
|
||||
|
||||
if (info.cachedBalance.spendable.raw == BigInt.zero) {
|
||||
return Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
}
|
||||
|
||||
final fee = await rpcClient?.getFees();
|
||||
// TODO [prio=low]: handle null fee.
|
||||
|
||||
return Amount(
|
||||
rawValue: BigInt.from(fee!.value.feeCalculator.lamportsPerSignature),
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FeeObject> get fees async {
|
||||
await _checkClient();
|
||||
|
||||
final fees = await rpcClient?.getFees();
|
||||
// TODO [prio=low]: handle null fees.
|
||||
return FeeObject(
|
||||
numberOfBlocksFast: 1,
|
||||
numberOfBlocksAverage: 1,
|
||||
numberOfBlocksSlow: 1,
|
||||
fast: fees!.value.feeCalculator.lamportsPerSignature,
|
||||
medium: fees!.value.feeCalculator.lamportsPerSignature,
|
||||
slow: fees!.value.feeCalculator.lamportsPerSignature);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> pingCheck() {
|
||||
try {
|
||||
_checkClient();
|
||||
rpcClient?.getHealth();
|
||||
return Future.value(true);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$runtimeType Solana pingCheck failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
return Future.value(false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FilterOperation? get receivingAddressFilterOperation =>
|
||||
FilterGroup.and(standardReceivingAddressFilters);
|
||||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) async {
|
||||
await refreshMutex.protect(() async {
|
||||
var addressStruct = await _getCurrentAddress();
|
||||
|
||||
await mainDB.updateOrPutAddresses([addressStruct]);
|
||||
|
||||
if (info.cachedReceivingAddress != addressStruct.value) {
|
||||
await info.updateReceivingAddress(
|
||||
newAddress: addressStruct.value,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
}
|
||||
|
||||
await Future.wait([
|
||||
updateBalance(),
|
||||
updateChainHeight(),
|
||||
updateTransactions(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() async {
|
||||
try {
|
||||
await _checkClient();
|
||||
|
||||
var balance = await rpcClient?.getBalance(info.cachedReceivingAddress);
|
||||
|
||||
// Rent exemption of Solana
|
||||
final accInfo =
|
||||
await rpcClient?.getAccountInfo((await _getKeyPair()).address);
|
||||
// TODO [prio=low]: handle null account info.
|
||||
final int minimumRent =
|
||||
await rpcClient?.getMinimumBalanceForRentExemption(
|
||||
accInfo!.value!.data.toString().length) ??
|
||||
0;
|
||||
// TODO [prio=low]: revisit null condition.
|
||||
var spendableBalance = balance!.value - minimumRent;
|
||||
|
||||
final newBalance = Balance(
|
||||
total: Amount(
|
||||
rawValue: BigInt.from(balance.value),
|
||||
fractionDigits: Coin.solana.decimals,
|
||||
),
|
||||
spendable: Amount(
|
||||
rawValue: BigInt.from(spendableBalance),
|
||||
fractionDigits: Coin.solana.decimals,
|
||||
),
|
||||
blockedTotal: Amount(
|
||||
rawValue: BigInt.from(minimumRent),
|
||||
fractionDigits: Coin.solana.decimals,
|
||||
),
|
||||
pendingSpendable: Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: Coin.solana.decimals,
|
||||
),
|
||||
);
|
||||
|
||||
await info.updateBalance(newBalance: newBalance, isar: mainDB.isar);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Error getting balance in solana_wallet.dart: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateChainHeight() async {
|
||||
try {
|
||||
await _checkClient();
|
||||
|
||||
int blockHeight = await rpcClient?.getSlot() ?? 0;
|
||||
// TODO [prio=low]: Revisit null condition.
|
||||
|
||||
await info.updateCachedChainHeight(
|
||||
newHeight: blockHeight,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Error occurred in solana_wallet.dart while getting"
|
||||
" chain height for solana: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateNode() async {
|
||||
_solNode = getCurrentNode();
|
||||
await refresh();
|
||||
}
|
||||
|
||||
@override
|
||||
NodeModel getCurrentNode() {
|
||||
return _solNode ??
|
||||
NodeService(secureStorageInterface: secureStorageInterface)
|
||||
.getPrimaryNodeFor(coin: info.coin) ??
|
||||
DefaultNodes.getNodeFor(info.coin);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
try {
|
||||
await _checkClient();
|
||||
|
||||
var transactionsList = await rpcClient?.getTransactionsList(
|
||||
(await _getKeyPair()).publicKey,
|
||||
encoding: Encoding.jsonParsed);
|
||||
var txsList =
|
||||
List<Tuple2<isar.Transaction, Address>>.empty(growable: true);
|
||||
|
||||
// TODO [prio=low]: Revisit null assertion below.
|
||||
|
||||
for (final tx in transactionsList!) {
|
||||
var senderAddress =
|
||||
(tx.transaction as ParsedTransaction).message.accountKeys[0].pubkey;
|
||||
var receiverAddress =
|
||||
(tx.transaction as ParsedTransaction).message.accountKeys[1].pubkey;
|
||||
var txType = isar.TransactionType.unknown;
|
||||
var txAmount = Amount(
|
||||
rawValue:
|
||||
BigInt.from(tx.meta!.postBalances[1] - tx.meta!.preBalances[1]),
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
||||
if ((senderAddress == (await _getKeyPair()).address) &&
|
||||
(receiverAddress == (await _getKeyPair()).address)) {
|
||||
txType = isar.TransactionType.sentToSelf;
|
||||
} else if (senderAddress == (await _getKeyPair()).address) {
|
||||
txType = isar.TransactionType.outgoing;
|
||||
} else if (receiverAddress == (await _getKeyPair()).address) {
|
||||
txType = isar.TransactionType.incoming;
|
||||
}
|
||||
|
||||
var transaction = isar.Transaction(
|
||||
walletId: walletId,
|
||||
txid: (tx.transaction as ParsedTransaction).signatures[0],
|
||||
timestamp: tx.blockTime!,
|
||||
type: txType,
|
||||
subType: isar.TransactionSubType.none,
|
||||
amount: tx.meta!.postBalances[1] - tx.meta!.preBalances[1],
|
||||
amountString: txAmount.toJsonString(),
|
||||
fee: tx.meta!.fee,
|
||||
height: tx.slot,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
slateId: null,
|
||||
otherData: null,
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
nonce: null,
|
||||
numberOfMessages: 0,
|
||||
);
|
||||
|
||||
var txAddress = Address(
|
||||
walletId: walletId,
|
||||
value: receiverAddress,
|
||||
publicKey: List<int>.empty(),
|
||||
derivationIndex: 0,
|
||||
derivationPath: DerivationPath()..value = "m/44'/501'/0'/0'",
|
||||
type: AddressType.solana,
|
||||
subType: txType == isar.TransactionType.outgoing
|
||||
? AddressSubType.unknown
|
||||
: AddressSubType.receiving);
|
||||
|
||||
txsList.add(Tuple2(transaction, txAddress));
|
||||
}
|
||||
await mainDB.addNewTransactionData(txsList, walletId);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Error occurred in solana_wallet.dart while getting"
|
||||
" transactions for solana: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> updateUTXOs() {
|
||||
// No UTXOs in Solana
|
||||
return Future.value(false);
|
||||
}
|
||||
|
||||
/// Make sure the Solana RpcClient uses Tor if it's enabled.
|
||||
///
|
||||
/// TODO: Make synchronous.
|
||||
Future<void> _checkClient() async {
|
||||
if (prefs.useTor) {
|
||||
final ({InternetAddress host, int port}) proxyInfo =
|
||||
TorService.sharedInstance.getProxyInfo();
|
||||
// If Tor is enabled, pass the optional proxyInfo to the Solana RpcClient.
|
||||
rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}",
|
||||
proxyInfo: {'host': proxyInfo.host.address, 'port': proxyInfo.port});
|
||||
} else {
|
||||
rpcClient ??=
|
||||
RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -52,6 +52,8 @@ import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interf
|
|||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
|
||||
import 'impl/solana_wallet.dart';
|
||||
|
||||
abstract class Wallet<T extends CryptoCurrency> {
|
||||
// default to Transaction class. For TransactionV2 set to 2
|
||||
int get isarTransactionVersion => 1;
|
||||
|
@ -362,6 +364,9 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
case Coin.particl:
|
||||
return ParticlWallet(CryptoCurrencyNetwork.main);
|
||||
|
||||
case Coin.solana:
|
||||
return SolanaWallet(CryptoCurrencyNetwork.main);
|
||||
|
||||
case Coin.stellar:
|
||||
return StellarWallet(CryptoCurrencyNetwork.main);
|
||||
case Coin.stellarTestnet:
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:solana/solana.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||
|
@ -213,6 +214,20 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
|||
testPassed = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Coin.solana:
|
||||
try {
|
||||
RpcClient rpcClient;
|
||||
if (node.host.startsWith("http") || node.host.startsWith("https")) {
|
||||
rpcClient = RpcClient("${node.host}:${node.port}");
|
||||
} else {
|
||||
rpcClient = RpcClient("http://${node.host}:${node.port}");
|
||||
}
|
||||
await rpcClient.getEpochInfo().then((value) => testPassed = true);
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (testPassed) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:solana/solana.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||
|
@ -182,6 +183,20 @@ class NodeOptionsSheet extends ConsumerWidget {
|
|||
case Coin.stellarTestnet:
|
||||
throw UnimplementedError();
|
||||
//TODO: check network/node
|
||||
|
||||
case Coin.solana:
|
||||
try {
|
||||
RpcClient rpcClient;
|
||||
if (node.host.startsWith("http") || node.host.startsWith("https")) {
|
||||
rpcClient = RpcClient("${node.host}:${node.port}");
|
||||
} else {
|
||||
rpcClient = RpcClient("http://${node.host}:${node.port}");
|
||||
}
|
||||
await rpcClient.getEpochInfo().then((value) => testPassed = true);
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (testPassed) {
|
||||
|
|
111
pubspec.lock
111
pubspec.lock
|
@ -158,6 +158,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
borsh_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: borsh_annotation
|
||||
sha256: "4a226cf8b7a165ecf8020c0c8d366b2728167fd102ef9b9e89d94d86f89ac57b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1+5"
|
||||
bs58check:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -322,10 +330,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76"
|
||||
sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.7.2"
|
||||
version: "1.6.4"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -506,6 +514,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.9"
|
||||
ed25519_hd_key:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ed25519_hd_key
|
||||
sha256: c5c9f11a03f5789bf9dcd9ae88d641571c802640851f1cacdb13123f171b3a26
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
eip1559:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -588,13 +604,13 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
version: "6.1.4"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -815,6 +831,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
freezed_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: freezed_annotation
|
||||
sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1041,30 +1065,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
lelantus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1108,18 +1108,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
version: "0.12.16"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
version: "0.5.0"
|
||||
memoize:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1132,10 +1132,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: meta
|
||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
version: "1.10.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1236,10 +1236,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
version: "1.8.3"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1356,10 +1356,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
version: "3.1.2"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1393,13 +1393,13 @@ packages:
|
|||
source: hosted
|
||||
version: "1.2.0-beta-1"
|
||||
process:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: process
|
||||
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
version: "4.2.4"
|
||||
protobuf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1545,10 +1545,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: socks5_proxy
|
||||
sha256: e0cba6917cd374de6f6cb0ce081e50e6efc24c61644b8e9f20c8bf8b91bb0b75
|
||||
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3+dev.3"
|
||||
version: "1.0.4"
|
||||
socks_socket:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1558,6 +1558,15 @@ packages:
|
|||
url: "https://github.com/cypherstack/socks_socket.git"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
solana:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/solana"
|
||||
ref: "2d7189d31f1bfd5d6779268c81a897f03f339f5d"
|
||||
resolved-ref: "2d7189d31f1bfd5d6779268c81a897f03f339f5d"
|
||||
url: "https://github.com/cypherstack/espresso-cash-public.git"
|
||||
source: git
|
||||
version: "0.30.4"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1901,10 +1910,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.0"
|
||||
version: "11.10.0"
|
||||
wakelock:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1998,10 +2007,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: webdriver
|
||||
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
|
||||
sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.2"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -2036,13 +2045,13 @@ packages:
|
|||
source: git
|
||||
version: "0.1.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "0.2.0+3"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
11
pubspec.yaml
11
pubspec.yaml
|
@ -177,6 +177,11 @@ dependencies:
|
|||
url: https://github.com/cypherstack/electrum_adapter.git
|
||||
ref: 9e9441fc1e9ace8907256fff05fe2c607b0933b6
|
||||
stream_channel: ^2.1.0
|
||||
solana:
|
||||
git: # TODO: Revert to official package once Tor support is merged upstream.
|
||||
url: https://github.com/cypherstack/espresso-cash-public.git
|
||||
ref: 2d7189d31f1bfd5d6779268c81a897f03f339f5d # tor branch.
|
||||
path: packages/solana
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -251,6 +256,12 @@ dependency_overrides:
|
|||
crypto: 3.0.2
|
||||
analyzer: ^5.2.0
|
||||
pinenacl: ^0.3.3
|
||||
xdg_directories: ^0.2.0
|
||||
# flutter_local_notifications_linux: ^0.5.0+1 # Overridden by Solana's package (from espresso_cash
|
||||
# _public). Disabled for compatibility reasons, may affect Linux desktop notifications.
|
||||
process: ^4.0.0
|
||||
file: ^6.0.0
|
||||
http: ^0.13.0
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
|
|
Loading…
Reference in a new issue