add solana

This commit is contained in:
dethe 2024-03-20 03:50:42 +03:00 committed by sneurlax
parent ff86cbccf6
commit 68210b2765
20 changed files with 580 additions and 5 deletions

View file

@ -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.?
}

View file

@ -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:

View file

@ -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) {

View file

@ -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(

View file

@ -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;

View file

@ -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;

View file

@ -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:

View file

@ -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:

View file

@ -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");
}
}

View file

@ -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:

View file

@ -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;

View file

@ -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":

View file

@ -55,6 +55,7 @@ extension DerivePathTypeExt on DerivePathType {
case Coin.stellar:
case Coin.stellarTestnet:
case Coin.tezos: // TODO: Is this true?
case Coin.solana:
throw UnsupportedError(
"$coin does not use bitcoin style derivation paths");
}

View file

@ -0,0 +1,48 @@
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) {
RegExp regex = RegExp(r'^[a-zA-Z0-9]{44}$');
return regex.hasMatch(address);
}
@override
String get genesisHash => throw UnimplementedError();
}

View file

@ -0,0 +1,362 @@
import 'dart:math';
import 'package:isar/isar.dart';
import 'package:solana/dto.dart';
import 'package:solana/solana.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/paymint/fee_object_model.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/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:stackwallet/utilities/logger.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:tuple/tuple.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/services/node_service.dart';
class SolanaWallet extends Bip39Wallet<Solana> {
SolanaWallet(CryptoCurrencyNetwork network) : super(Solana(network));
NodeModel? _solNode;
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: null,
type: cryptoCurrency.coin.primaryAddressType,
subType: AddressSubType.unknown);
return addressStruct;
}
Future<int> _getCurrentBalanceInLamports() async {
var rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
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: null,
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 {
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 rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
final accInfo = await rpcClient.getAccountInfo((await _getKeyPair()).address);
final minimumRent = await rpcClient.getMinimumBalanceForRentExemption(accInfo.value!.data.toString().length);
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 {
final keyPair = await _getKeyPair();
final rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
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 {
if (info.cachedBalance.spendable.raw == BigInt.zero) {
return Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
);
}
final rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
final fee = await rpcClient.getFees();
return Amount(
rawValue: BigInt.from(fee.value.feeCalculator.lamportsPerSignature),
fractionDigits: cryptoCurrency.fractionDigits,
);
}
@override
Future<FeeObject> get fees async {
final rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
final fees = await rpcClient.getFees();
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() {
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 {
var rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
var balance = await rpcClient.getBalance(info.cachedReceivingAddress);
// Rent exemption of Solana
final accInfo = await rpcClient.getAccountInfo((await _getKeyPair()).address);
final minimumRent = await rpcClient.getMinimumBalanceForRentExemption(accInfo.value!.data.toString().length);
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 {
var rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
var blockHeight = await rpcClient.getSlot();
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 {
var rpcClient = RpcClient("${getCurrentNode().host}:${getCurrentNode().port}");
var transactionsList = await rpcClient.getTransactionsList((await _getKeyPair()).publicKey, encoding: Encoding.jsonParsed);
var txsList = List<Tuple2<isar.Transaction, Address>>.empty(growable: true);
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: null,
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);
}
}

View file

@ -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:

View file

@ -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) {

View file

@ -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) {

View file

@ -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:
@ -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,7 +604,7 @@ packages:
source: hosted
version: "2.1.0"
file:
dependency: transitive
dependency: "direct overridden"
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
@ -694,7 +710,7 @@ packages:
source: hosted
version: "17.0.0"
flutter_local_notifications_linux:
dependency: transitive
dependency: "direct overridden"
description:
name: flutter_local_notifications_linux
sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03"
@ -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:
@ -1393,7 +1417,7 @@ packages:
source: hosted
version: "1.2.0-beta-1"
process:
dependency: transitive
dependency: "direct overridden"
description:
name: process
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
@ -1558,6 +1582,14 @@ packages:
url: "https://github.com/cypherstack/socks_socket.git"
source: git
version: "0.1.0"
solana:
dependency: "direct main"
description:
name: solana
sha256: "99a6a40a847f57ccf4687a730413d67fcaef4fc6778ddd9c3258e7fe8e4c6743"
url: "https://pub.dev"
source: hosted
version: "0.30.3"
source_gen:
dependency: transitive
description:
@ -2036,7 +2068,7 @@ packages:
source: git
version: "0.1.0"
xdg_directories:
dependency: transitive
dependency: "direct overridden"
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d

View file

@ -177,6 +177,7 @@ dependencies:
url: https://github.com/cypherstack/electrum_adapter.git
ref: 9e9441fc1e9ace8907256fff05fe2c607b0933b6
stream_channel: ^2.1.0
solana: ^0.30.3
dev_dependencies:
flutter_test:
@ -251,6 +252,11 @@ 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
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