Solana Wallet New Implementation (#2011)

* Feat: Implement Solana wallet using on_chain

* v4.23.0 release candidate (#1974)

* v4.23.0 release candidate

* - Fix restoring zano from QR
- Fix Zano confirmations count
- Fix birdpay
- Fix balance display

* Fix Zano assets showing amount before they are added

* - handle fetching token data while the API is busy
- potential fix for duplicate transactions

* fix receive confirmations, maybe

* revert onChangeWallet cleanup

* Fix confirmations not updating

* improve zano wallet opening, fix CI commands and messages on slack (#1979)

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Cache wallet when creating/restoring as well

* - hardcode Trocador Maximum limit for Zano temporarily
- Configure Cake Zano node to use SSL

* reformatting [skip ci]

* revert to non-ssl

* update build numbers [skip ci]

* disable zano for desktop [skip ci]

---------

Co-authored-by: cyan <cyjan@mrcyjanek.net>

* CW-711 passphrase for XMR/WOWcreation (#1992)

* add monero passphrase
add wownero passphrase
add passphrase to seed screen

* obscure passphrase by default
disable passphrase create for zano

* Update lib/view_model/wallet_keys_view_model.dart [skip ci]

* Update lib/src/screens/wallet_keys/wallet_keys_page.dart [skip ci]

* Update lib/view_model/advanced_privacy_settings_view_model.dart

* dynamic passphrase icon

* fix polyseed not being encrypted by passphrase

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* show Zano keys properly in the keys tab (#2004)

* fix: Switch private key hex encoding

* Modified existing implementation to use older version of packages

* fix: Fetch direct transaction history amounts instead of decimals, and add Create Account Instructions to Transaction History List

* fix: Remove Create Account entries in Transaction History and disable activating token accounts of selected tokens

* feat: Add passphrase support to Solana

* fix: Issues with transaction amount and dissappearing transaction history items (very annoying bug)

* fix: Issue with flipping transactions and incorrect transaction status

* PR Review fixes

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Co-authored-by: cyan <cyjan@mrcyjanek.net>
This commit is contained in:
David Adegoke 2025-03-14 15:42:17 +01:00 committed by GitHub
parent 1c29be7993
commit 1b5be705f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 716 additions and 561 deletions

View file

@ -0,0 +1,26 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:on_chain/solana/solana.dart';
class SolanaRPCHTTPService implements SolanaJSONRPCService {
SolanaRPCHTTPService(
{required this.url, Client? client, this.defaultRequestTimeout = const Duration(seconds: 30)})
: client = client ?? Client();
@override
final String url;
final Client client;
final Duration defaultRequestTimeout;
@override
Future<Map<String, dynamic>> call(SolanaRequestDetails params, [Duration? timeout]) async {
final response = await client.post(
Uri.parse(url),
body: params.toRequestBody(),
headers: {
'Content-Type': 'application/json',
},
).timeout(timeout ?? defaultRequestTimeout);
final data = json.decode(response.body) as Map<String, dynamic>;
return data;
}
}

View file

@ -27,6 +27,10 @@ dependencies:
version: 1.0.0 version: 1.0.0
socks5_proxy: ^1.0.4 socks5_proxy: ^1.0.4
unorm_dart: ^0.3.0 unorm_dart: ^0.3.0
on_chain:
git:
url: https://github.com/cake-tech/on_chain.git
ref: cake-update-v2
# tor: # tor:
# git: # git:
# url: https://github.com/cake-tech/tor.git # url: https://github.com/cake-tech/tor.git

View file

@ -26,7 +26,7 @@ class DefaultSPLTokens {
decimal: 5, decimal: 5,
mint: 'Bonk', mint: 'Bonk',
iconPath: 'assets/images/bonk_icon.png', iconPath: 'assets/images/bonk_icon.png',
enabled: true, enabled: false,
), ),
SPLToken( SPLToken(
name: 'Raydium', name: 'Raydium',
@ -35,7 +35,7 @@ class DefaultSPLTokens {
decimal: 6, decimal: 6,
mint: 'ray', mint: 'ray',
iconPath: 'assets/images/ray_icon.png', iconPath: 'assets/images/ray_icon.png',
enabled: true, enabled: false,
), ),
SPLToken( SPLToken(
name: 'Wrapped Ethereum (Sollet)', name: 'Wrapped Ethereum (Sollet)',

View file

@ -1,9 +1,8 @@
import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/pending_transaction.dart';
import 'package:solana/encoder.dart';
class PendingSolanaTransaction with PendingTransaction { class PendingSolanaTransaction with PendingTransaction {
final double amount; final double amount;
final SignedTx signedTransaction; final String serializedTransaction;
final String destinationAddress; final String destinationAddress;
final Function sendTransaction; final Function sendTransaction;
final double fee; final double fee;
@ -11,7 +10,7 @@ class PendingSolanaTransaction with PendingTransaction {
PendingSolanaTransaction({ PendingSolanaTransaction({
required this.fee, required this.fee,
required this.amount, required this.amount,
required this.signedTransaction, required this.serializedTransaction,
required this.destinationAddress, required this.destinationAddress,
required this.sendTransaction, required this.sendTransaction,
}); });
@ -36,7 +35,7 @@ class PendingSolanaTransaction with PendingTransaction {
String get feeFormatted => fee.toString(); String get feeFormatted => fee.toString();
@override @override
String get hex => signedTransaction.encode(); String get hex => serializedTransaction;
@override @override
String get id => ''; String get id => '';

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,9 @@ class SolanaTransactionInfo extends TransactionInfo {
@override @override
String amountFormatted() { String amountFormatted() {
String stringBalance = solAmount.toString(); String stringBalance = solAmount.toString();
if (stringBalance.toString().length >= 12) {
stringBalance = stringBalance.substring(0, 12);
}
return '$stringBalance $tokenSymbol'; return '$stringBalance $tokenSymbol';
} }

View file

@ -30,9 +30,9 @@ import 'package:hex/hex.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:solana/base58.dart'; import 'package:on_chain/solana/solana.dart' hide Store;
import 'package:solana/metaplex.dart' as metaplex; import 'package:bip39/bip39.dart' as bip39;
import 'package:solana/solana.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
part 'solana_wallet.g.dart'; part 'solana_wallet.g.dart';
@ -77,14 +77,6 @@ abstract class SolanaWalletBase
final String? _hexPrivateKey; final String? _hexPrivateKey;
final EncryptionFileUtils encryptionFileUtils; final EncryptionFileUtils encryptionFileUtils;
// The Solana WalletPair
Ed25519HDKeyPair? _walletKeyPair;
Ed25519HDKeyPair? get walletKeyPair => _walletKeyPair;
// To access the privateKey bytes.
Ed25519HDKeyPairData? _keyPairData;
late final SolanaWalletClient _client; late final SolanaWalletClient _client;
@observable @observable
@ -108,29 +100,23 @@ abstract class SolanaWalletBase
final Completer<SharedPreferences> _sharedPrefs = Completer(); final Completer<SharedPreferences> _sharedPrefs = Completer();
@override @override
Ed25519HDKeyPairData get keys { Object get keys => throw UnimplementedError("keys");
if (_keyPairData == null) {
return Ed25519HDKeyPairData([], publicKey: const Ed25519HDPublicKey([]));
}
return _keyPairData!; late final SolanaPrivateKey _solanaPrivateKey;
}
late final SolanaPublicKey _solanaPublicKey;
SolanaPublicKey get solanaPublicKey => _solanaPublicKey;
SolanaPrivateKey get solanaPrivateKey => _solanaPrivateKey;
String get solanaAddress => _solanaPublicKey.toAddress().address;
@override @override
String? get seed => _mnemonic; String? get seed => _mnemonic;
@override @override
String get privateKey { String get privateKey => _solanaPrivateKey.seedHex();
final privateKeyBytes = _keyPairData!.bytes;
final publicKeyBytes = _keyPairData!.publicKey.bytes;
final encodedBytes = privateKeyBytes + publicKeyBytes;
final privateKey = base58encode(encodedBytes);
return privateKey;
}
@override @override
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey); WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
@ -140,35 +126,47 @@ abstract class SolanaWalletBase
splTokensBox = await CakeHive.openBox<SPLToken>(boxName); splTokensBox = await CakeHive.openBox<SPLToken>(boxName);
// Create WalletPair using either the mnemonic or the privateKey // Create the privatekey using either the mnemonic or the privateKey
_walletKeyPair = await getWalletPair( _solanaPrivateKey = await getPrivateKey(
mnemonic: _mnemonic, mnemonic: _mnemonic,
privateKey: _hexPrivateKey, privateKey: _hexPrivateKey,
passphrase: passphrase,
); );
// Extract the keyPairData containing both the privateKey bytes and the publicKey hex. // Extract the public key and wallet address
_keyPairData = await _walletKeyPair!.extract(); _solanaPublicKey = _solanaPrivateKey.publicKey();
walletInfo.address = _walletKeyPair!.address; walletInfo.address = _solanaPublicKey.toAddress().address;
await walletAddresses.init(); await walletAddresses.init();
await transactionHistory.init(); await transactionHistory.init();
await save(); await save();
} }
Future<Wallet> getWalletPair({String? mnemonic, String? privateKey}) async { Future<SolanaPrivateKey> getPrivateKey({
String? mnemonic,
String? privateKey,
String? passphrase,
}) async {
assert(mnemonic != null || privateKey != null); assert(mnemonic != null || privateKey != null);
if (mnemonic != null) { if (mnemonic != null) {
return Wallet.fromMnemonic(mnemonic, account: 0, change: 0); final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
// Derive a Solana private key from the seed
final bip44 = Bip44.fromSeed(seed, Bip44Coins.solana);
final childKey = bip44.deriveDefaultPath.change(Bip44Changes.chainExt);
return SolanaPrivateKey.fromSeed(childKey.privateKey.raw);
} }
try { try {
final privateKeyBytes = base58decode(privateKey!); final keypairBytes = Base58Decoder.decode(privateKey!);
return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes.take(32).toList()); return SolanaPrivateKey.fromSeed(keypairBytes);
} catch (_) { } catch (_) {
final privateKeyBytes = HEX.decode(privateKey!); final privateKeyBytes = HEX.decode(privateKey!);
return await Wallet.fromPrivateKeyBytes(privateKey: privateKeyBytes); return SolanaPrivateKey.fromBytes(privateKeyBytes);
} }
} }
@ -206,7 +204,8 @@ abstract class SolanaWalletBase
Future<void> _getEstimatedFees() async { Future<void> _getEstimatedFees() async {
try { try {
estimatedFee = await _client.getEstimatedFee(_walletKeyPair!); estimatedFee = await _client.getEstimatedFee(_solanaPublicKey, Commitment.confirmed);
printV(estimatedFee.toString());
} catch (e) { } catch (e) {
estimatedFee = 0.0; estimatedFee = 0.0;
} }
@ -274,7 +273,7 @@ abstract class SolanaWalletBase
tokenMint: tokenMint, tokenMint: tokenMint,
tokenTitle: transactionCurrency.title, tokenTitle: transactionCurrency.title,
inputAmount: totalAmount, inputAmount: totalAmount,
ownerKeypair: _walletKeyPair!, ownerPrivateKey: _solanaPrivateKey,
tokenDecimals: transactionCurrency.decimals, tokenDecimals: transactionCurrency.decimals,
destinationAddress: solCredentials.outputs.first.isParsedAddress destinationAddress: solCredentials.outputs.first.isParsedAddress
? solCredentials.outputs.first.extractedAddress! ? solCredentials.outputs.first.extractedAddress!
@ -291,9 +290,7 @@ abstract class SolanaWalletBase
/// Fetches the native SOL transactions linked to the wallet Public Key /// Fetches the native SOL transactions linked to the wallet Public Key
Future<void> _updateNativeSOLTransactions() async { Future<void> _updateNativeSOLTransactions() async {
final address = Ed25519HDPublicKey.fromBase58(_walletKeyPair!.address); final transactions = await _client.fetchTransactions(_solanaPublicKey.toAddress());
final transactions = await _client.fetchTransactions(address);
await _addTransactionsToTransactionHistory(transactions); await _addTransactionsToTransactionHistory(transactions);
} }
@ -308,10 +305,10 @@ abstract class SolanaWalletBase
for (var token in tokenKeys) { for (var token in tokenKeys) {
if (token is SPLToken) { if (token is SPLToken) {
final tokenTxs = await _client.getSPLTokenTransfers( final tokenTxs = await _client.getSPLTokenTransfers(
token.mintAddress, mintAddress: token.mintAddress,
token.symbol, splTokenSymbol: token.symbol,
token.decimal, splTokenDecimal: token.decimal,
_walletKeyPair!, privateKey: _solanaPrivateKey,
); );
// splTokenTransactions.addAll(tokenTxs); // splTokenTransactions.addAll(tokenTxs);
@ -387,6 +384,7 @@ abstract class SolanaWalletBase
'mnemonic': _mnemonic, 'mnemonic': _mnemonic,
'private_key': _hexPrivateKey, 'private_key': _hexPrivateKey,
'balance': balance[currency]!.toJSON(), 'balance': balance[currency]!.toJSON(),
'passphrase': passphrase,
}); });
static Future<SolanaWallet> open({ static Future<SolanaWallet> open({
@ -414,8 +412,9 @@ abstract class SolanaWalletBase
if (!hasKeysFile) { if (!hasKeysFile) {
final mnemonic = data!['mnemonic'] as String?; final mnemonic = data!['mnemonic'] as String?;
final privateKey = data['private_key'] as String?; final privateKey = data['private_key'] as String?;
final passphrase = data['passphrase'] as String?;
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey); keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
} else { } else {
keysData = await WalletKeysFile.readKeysFile( keysData = await WalletKeysFile.readKeysFile(
name, name,
@ -428,6 +427,7 @@ abstract class SolanaWalletBase
return SolanaWallet( return SolanaWallet(
walletInfo: walletInfo, walletInfo: walletInfo,
password: password, password: password,
passphrase: keysData.passphrase,
mnemonic: keysData.mnemonic, mnemonic: keysData.mnemonic,
privateKey: keysData.privateKey, privateKey: keysData.privateKey,
initialBalance: balance, initialBalance: balance,
@ -442,7 +442,7 @@ abstract class SolanaWalletBase
} }
Future<SolanaBalance> _fetchSOLBalance() async { Future<SolanaBalance> _fetchSOLBalance() async {
final balance = await _client.getBalance(_walletKeyPair!.address); final balance = await _client.getBalance(solanaAddress);
return SolanaBalance(balance); return SolanaBalance(balance);
} }
@ -451,8 +451,7 @@ abstract class SolanaWalletBase
for (var token in splTokensBox.values) { for (var token in splTokensBox.values) {
if (token.enabled) { if (token.enabled) {
try { try {
final tokenBalance = final tokenBalance = await _client.getSplTokenBalance(token.mintAddress, solanaAddress) ??
await _client.getSplTokenBalance(token.mintAddress, _walletKeyPair!.address) ??
balance[token] ?? balance[token] ??
SolanaBalance(0.0); SolanaBalance(0.0);
balance[token] = tokenBalance; balance[token] = tokenBalance;
@ -482,8 +481,7 @@ abstract class SolanaWalletBase
await splTokensBox.put(token.mintAddress, token); await splTokensBox.put(token.mintAddress, token);
if (token.enabled) { if (token.enabled) {
final tokenBalance = final tokenBalance = await _client.getSplTokenBalance(token.mintAddress, solanaAddress) ??
await _client.getSplTokenBalance(token.mintAddress, _walletKeyPair!.address) ??
balance[token] ?? balance[token] ??
SolanaBalance(0.0); SolanaBalance(0.0);
@ -507,37 +505,10 @@ abstract class SolanaWalletBase
} }
Future<SPLToken?> getSPLToken(String mintAddress) async { Future<SPLToken?> getSPLToken(String mintAddress) async {
// Convert SPL token mint address to public key
final Ed25519HDPublicKey mintPublicKey;
try { try {
mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress); return await _client.fetchSPLTokenInfo(mintAddress);
} catch (_) { } catch (e, s) {
return null; printV('Error fetching token: ${e.toString()}, ${s.toString()}');
}
// Fetch token's metadata account
try {
final token = await solanaClient!.rpcClient.getMetadata(mint: mintPublicKey);
if (token == null) {
return null;
}
String? iconPath;
try {
iconPath = await _client.getIconImageFromTokenUri(token.uri);
} catch (_) {}
String filteredTokenSymbol = token.symbol.replaceFirst(RegExp('^\\\$'), '');
return SPLToken.fromMetadata(
name: token.name,
mint: token.mint,
symbol: filteredTokenSymbol,
mintAddress: mintAddress,
iconPath: iconPath,
);
} catch (e) {
return null; return null;
} }
} }
@ -582,7 +553,7 @@ abstract class SolanaWalletBase
final messageBytes = utf8.encode(message); final messageBytes = utf8.encode(message);
// Sign the message bytes with the wallet's private key // Sign the message bytes with the wallet's private key
final signature = (await _walletKeyPair!.sign(messageBytes)).toString(); final signature = (_solanaPrivateKey.sign(messageBytes)).toString();
return HEX.encode(utf8.encode(signature)).toUpperCase(); return HEX.encode(utf8.encode(signature)).toUpperCase();
} }
@ -596,7 +567,7 @@ abstract class SolanaWalletBase
final base58EncodedPublicKeyString = match.group(2)!; final base58EncodedPublicKeyString = match.group(2)!;
final sigBytes = bytesString.split(', ').map(int.parse).toList(); final sigBytes = bytesString.split(', ').map(int.parse).toList();
List<int> pubKeyBytes = base58decode(base58EncodedPublicKeyString); List<int> pubKeyBytes = SolAddrDecoder().decodeAddr(base58EncodedPublicKeyString);
return [sigBytes, pubKeyBytes]; return [sigBytes, pubKeyBytes];
} else { } else {
@ -619,19 +590,18 @@ abstract class SolanaWalletBase
} }
// make sure the address derived from the public key provided matches the one we expect // make sure the address derived from the public key provided matches the one we expect
final pub = Ed25519HDPublicKey(pubKeyBytes); final pub = SolanaPublicKey.fromBytes(pubKeyBytes);
if (address != pub.toBase58()) { if (address != pub.toAddress().address) {
return false; return false;
} }
return await verifySignature( return pub.verify(
message: messageBytes, message: messageBytes,
signature: sigBytes, signature: sigBytes,
publicKey: Ed25519HDPublicKey(pubKeyBytes),
); );
} }
SolanaClient? get solanaClient => _client.getSolanaClient; SolanaRPC? get solanaProvider => _client.getSolanaProvider;
@override @override
String get password => _password; String get password => _password;

View file

@ -33,6 +33,7 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
mnemonic: mnemonic, mnemonic: mnemonic,
password: credentials.password!, password: credentials.password!,
passphrase: credentials.passphrase,
encryptionFileUtils: encryptionFileUtilsFor(isDirect), encryptionFileUtils: encryptionFileUtilsFor(isDirect),
); );
@ -118,6 +119,7 @@ class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
password: credentials.password!, password: credentials.password!,
mnemonic: credentials.mnemonic, mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
passphrase: credentials.passphrase,
encryptionFileUtils: encryptionFileUtilsFor(isDirect), encryptionFileUtils: encryptionFileUtilsFor(isDirect),
); );

View file

@ -1,7 +1,6 @@
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/hive_type_ids.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:solana/metaplex.dart';
part 'spl_token.g.dart'; part 'spl_token.g.dart';
@ -55,7 +54,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
required String mint, required String mint,
required String symbol, required String symbol,
required String mintAddress, required String mintAddress,
String? iconPath String? iconPath,
}) { }) {
return SPLToken( return SPLToken(
name: name, name: name,
@ -117,31 +116,3 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
@override @override
int get hashCode => mintAddress.hashCode; 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);
}

View file

@ -11,7 +11,6 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
solana: ^0.31.0+1
cw_core: cw_core:
path: ../cw_core path: ../cw_core
http: ^1.1.0 http: ^1.1.0
@ -21,6 +20,14 @@ dependencies:
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15
bip32: ^2.0.0 bip32: ^2.0.0
hex: ^0.2.0 hex: ^0.2.0
on_chain:
git:
url: https://github.com/cake-tech/on_chain.git
ref: cake-update-v2
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -17,7 +17,7 @@ dependencies:
path: ../cw_evm path: ../cw_evm
on_chain: on_chain:
git: git:
url: https://github.com/cake-tech/On_chain url: https://github.com/cake-tech/on_chain.git
ref: cake-update-v2 ref: cake-update-v2
blockchain_utils: blockchain_utils:
git: git:

View file

@ -3,38 +3,8 @@ PODS:
- Flutter - Flutter
- ReachabilitySwift - ReachabilitySwift
- CryptoSwift (1.8.3) - CryptoSwift (1.8.3)
- cw_haven (0.0.1):
- cw_haven/Boost (= 0.0.1)
- cw_haven/Haven (= 0.0.1)
- cw_haven/OpenSSL (= 0.0.1)
- cw_haven/Sodium (= 0.0.1)
- cw_shared_external
- Flutter
- cw_haven/Boost (0.0.1):
- cw_shared_external
- Flutter
- cw_haven/Haven (0.0.1):
- cw_shared_external
- Flutter
- cw_haven/OpenSSL (0.0.1):
- cw_shared_external
- Flutter
- cw_haven/Sodium (0.0.1):
- cw_shared_external
- Flutter
- cw_mweb (0.0.1): - cw_mweb (0.0.1):
- Flutter - Flutter
- cw_shared_external (0.0.1):
- cw_shared_external/Boost (= 0.0.1)
- cw_shared_external/OpenSSL (= 0.0.1)
- cw_shared_external/Sodium (= 0.0.1)
- Flutter
- cw_shared_external/Boost (0.0.1):
- Flutter
- cw_shared_external/OpenSSL (0.0.1):
- Flutter
- cw_shared_external/Sodium (0.0.1):
- Flutter
- device_display_brightness (0.0.1): - device_display_brightness (0.0.1):
- Flutter - Flutter
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
@ -136,9 +106,7 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- CryptoSwift - CryptoSwift
- cw_haven (from `.symlinks/plugins/cw_haven/ios`)
- cw_mweb (from `.symlinks/plugins/cw_mweb/ios`) - cw_mweb (from `.symlinks/plugins/cw_mweb/ios`)
- cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
- device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- devicelocale (from `.symlinks/plugins/devicelocale/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`)
@ -179,12 +147,8 @@ SPEC REPOS:
EXTERNAL SOURCES: EXTERNAL SOURCES:
connectivity_plus: connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios" :path: ".symlinks/plugins/connectivity_plus/ios"
cw_haven:
:path: ".symlinks/plugins/cw_haven/ios"
cw_mweb: cw_mweb:
:path: ".symlinks/plugins/cw_mweb/ios" :path: ".symlinks/plugins/cw_mweb/ios"
cw_shared_external:
:path: ".symlinks/plugins/cw_shared_external/ios"
device_display_brightness: device_display_brightness:
:path: ".symlinks/plugins/device_display_brightness/ios" :path: ".symlinks/plugins/device_display_brightness/ios"
device_info_plus: device_info_plus:
@ -239,9 +203,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483 CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3 cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926 devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926

View file

@ -1,5 +1,7 @@
import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:blockchain_utils/blockchain_utils.dart';
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/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/chain_service/solana/solana_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';
@ -8,9 +10,9 @@ import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_w
import 'package:cake_wallet/core/wallet_connect/models/connection_model.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/connection_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:cw_core/solana_rpc_http_service.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:solana/base58.dart'; import 'package:on_chain/solana/solana.dart';
import 'package:solana/solana.dart';
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
import '../chain_service.dart'; import '../chain_service.dart';
import '../../wallet_connect_key_service.dart'; import '../../wallet_connect_key_service.dart';
@ -27,25 +29,19 @@ class SolanaChainServiceImpl implements ChainService {
final SolanaChainId reference; final SolanaChainId reference;
final SolanaClient solanaClient; final SolanaRPC solanaProvider;
final Ed25519HDKeyPair? ownerKeyPair; final SolanaPrivateKey? ownerPrivateKey;
SolanaChainServiceImpl({ SolanaChainServiceImpl({
required this.reference, required this.reference,
required this.wcKeyService, required this.wcKeyService,
required this.bottomSheetService, required this.bottomSheetService,
required this.wallet, required this.wallet,
required this.ownerKeyPair, required this.ownerPrivateKey,
required String webSocketUrl, required String formattedRPCUrl,
required Uri rpcUrl, SolanaRPC? solanaProvider,
SolanaClient? solanaClient, }) : solanaProvider = solanaProvider ?? SolanaRPC(SolanaRPCHTTPService(url: formattedRPCUrl)) {
}) : solanaClient = solanaClient ??
SolanaClient(
rpcUrl: rpcUrl,
websocketUrl: Uri.parse(webSocketUrl),
timeout: const Duration(minutes: 5),
) {
for (final String event in getEvents()) { for (final String event in getEvents()) {
wallet.registerEventEmitter(chainId: getChainId(), event: event); wallet.registerEventEmitter(chainId: getChainId(), event: event);
} }
@ -110,26 +106,20 @@ class SolanaChainServiceImpl implements ChainService {
} }
try { try {
final message = // Convert transaction string to bytes
await solanaClient.rpcClient.getMessageFromEncodedTx(solanaSignTx.transaction); List<int> transactionBytes = base64Decode(solanaSignTx.transaction);
final sign = await ownerKeyPair?.signMessage( final message = SolanaTransactionUtils.deserializeMessageLegacy(transactionBytes);
message: message,
recentBlockhash: solanaSignTx.recentBlockhash ?? '',
);
if (sign == null) { final sign = ownerPrivateKey!.sign(message.serialize());
return '';
}
String signature = await solanaClient.sendAndConfirmTransaction( final signature = solanaProvider.request(
message: message, SolanaRPCSendTransaction(
signers: [ownerKeyPair!], encodedTransaction: Base58Encoder.encode(sign),
commitment: Commitment.confirmed, commitment: Commitment.confirmed,
),
); );
printV(signature);
bottomSheetService.queueBottomSheet( bottomSheetService.queueBottomSheet(
isModalDismissible: true, isModalDismissible: true,
widget: BottomSheetMessageDisplayWidget( widget: BottomSheetMessageDisplayWidget(
@ -161,10 +151,10 @@ class SolanaChainServiceImpl implements ChainService {
if (authError != null) { if (authError != null) {
return authError; return authError;
} }
Signature? sign; List<int>? sign;
try { try {
sign = await ownerKeyPair?.sign(base58decode(solanaSignMessage.message)); sign = ownerPrivateKey!.sign(Base58Decoder.decode(solanaSignMessage.message));
} catch (e) { } catch (e) {
printV(e); printV(e);
} }
@ -173,7 +163,7 @@ class SolanaChainServiceImpl implements ChainService {
return ''; return '';
} }
String signature = sign.toBase58(); final signature = Base58Encoder.encode(sign);
return signature; return signature;
} }

View file

@ -22,6 +22,7 @@ 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:on_chain/solana/solana.dart' hide Store;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
@ -140,29 +141,28 @@ abstract class Web3WalletServiceBase with Store {
for (final cId in SolanaChainId.values) { for (final cId in SolanaChainId.values) {
final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type); final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type);
Uri rpcUri = node.uri; String formattedUrl;
String webSocketUrl = 'wss://${node.uriRaw}'; String protocolUsed = node.isSSL ? "https" : "http";
if (node.uriRaw == 'rpc.ankr.com') { if (node.uriRaw == 'rpc.ankr.com') {
String ankrApiKey = secrets.ankrApiKey; String ankrApiKey = secrets.ankrApiKey;
rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey'); formattedUrl = '$protocolUsed://${node.uriRaw}/$ankrApiKey';
webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
} else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') { } else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') {
String chainStackApiKey = secrets.chainStackApiKey; String chainStackApiKey = secrets.chainStackApiKey;
rpcUri = Uri.https(node.uriRaw, '/$chainStackApiKey'); formattedUrl = '$protocolUsed://${node.uriRaw}/$chainStackApiKey';
webSocketUrl = 'wss://${node.uriRaw}/$chainStackApiKey'; } else {
formattedUrl = '$protocolUsed://${node.uriRaw}';
} }
SolanaChainServiceImpl( SolanaChainServiceImpl(
reference: cId, reference: cId,
rpcUrl: rpcUri, formattedRPCUrl: formattedUrl,
webSocketUrl: webSocketUrl,
wcKeyService: walletKeyService, wcKeyService: walletKeyService,
bottomSheetService: _bottomSheetHandler, bottomSheetService: _bottomSheetHandler,
wallet: _web3Wallet, wallet: _web3Wallet,
ownerKeyPair: solana!.getWalletKeyPair(appStore.wallet!), ownerPrivateKey: SolanaPrivateKey.fromSeedHex(solana!.getPrivateKey(appStore.wallet!)),
); );
} }
} }

View file

@ -52,11 +52,8 @@ class CWSolana extends Solana {
String getPrivateKey(WalletBase wallet) => (wallet as SolanaWallet).privateKey; String getPrivateKey(WalletBase wallet) => (wallet as SolanaWallet).privateKey;
@override @override
String getPublicKey(WalletBase wallet) => (wallet as SolanaWallet).keys.publicKey.toBase58(); String getPublicKey(WalletBase wallet) =>
(wallet as SolanaWallet).solanaPublicKey.toAddress().address;
@override
Ed25519HDKeyPair? getWalletKeyPair(WalletBase wallet) => (wallet as SolanaWallet).walletKeyPair;
Object createSolanaTransactionCredentials( Object createSolanaTransactionCredentials(
List<Output> outputs, { List<Output> outputs, {
required CryptoCurrency currency, required CryptoCurrency currency,

View file

@ -78,6 +78,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
WalletType.ethereum, WalletType.ethereum,
WalletType.polygon, WalletType.polygon,
WalletType.tron, WalletType.tron,
WalletType.solana,
WalletType.monero, WalletType.monero,
WalletType.wownero, WalletType.wownero,
WalletType.zano, WalletType.zano,

View file

@ -106,12 +106,19 @@ dependencies:
flutter_svg: ^2.0.9 flutter_svg: ^2.0.9
polyseed: ^0.0.6 polyseed: ^0.0.6
nostr_tools: ^1.0.9 nostr_tools: ^1.0.9
solana: ^0.31.0+1
ledger_flutter_plus: ledger_flutter_plus:
git: git:
url: https://github.com/vespr-wallet/ledger-flutter-plus url: https://github.com/vespr-wallet/ledger-flutter-plus
ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76 ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76
hashlib: ^1.19.2 hashlib: ^1.19.2
on_chain:
git:
url: https://github.com/cake-tech/on_chain.git
ref: cake-update-v2
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -1261,7 +1261,6 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_service.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:solana/solana.dart';
"""; """;
const solanaCWHeaders = """ const solanaCWHeaders = """
@ -1289,7 +1288,6 @@ abstract class Solana {
String getAddress(WalletBase wallet); String getAddress(WalletBase wallet);
String getPrivateKey(WalletBase wallet); String getPrivateKey(WalletBase wallet);
String getPublicKey(WalletBase wallet); String getPublicKey(WalletBase wallet);
Ed25519HDKeyPair? getWalletKeyPair(WalletBase wallet);
Object createSolanaTransactionCredentials( Object createSolanaTransactionCredentials(
List<Output> outputs, { List<Output> outputs, {