Skip to content

Commit

Permalink
CW-829 Solana Enhancements (#1858)
Browse files Browse the repository at this point in the history
* feat: Solana enhancements with rent handling for accounts

* fix: Add exception classes with handled error messages to ensure proper error handling process

---------

Co-authored-by: Omar Hatem <[email protected]>
  • Loading branch information
Blazebrain and OmarHatem28 authored Dec 13, 2024
1 parent 9a60b01 commit c620d7f
Show file tree
Hide file tree
Showing 32 changed files with 206 additions and 18 deletions.
53 changes: 48 additions & 5 deletions cw_solana/lib/solana_client.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'dart:math' as math;

import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_solana/pending_solana_transaction.dart';
import 'package:cw_solana/solana_balance.dart';
import 'package:cw_solana/solana_exceptions.dart';
import 'package:cw_solana/solana_transaction_model.dart';
import 'package:http/http.dart' as http;
import 'package:solana/dto.dart';
Expand Down Expand Up @@ -180,7 +181,7 @@ class SolanaWalletClient {
bool isOutgoingTx = transfer.source == publicKey.toBase58();

double amount = (double.tryParse(transfer.amount) ?? 0.0) /
pow(10, splTokenDecimal ?? 9);
math.pow(10, splTokenDecimal ?? 9);

transactions.add(
SolanaTransactionModel(
Expand Down Expand Up @@ -276,6 +277,7 @@ class SolanaWalletClient {
required String destinationAddress,
required Ed25519HDKeyPair ownerKeypair,
required bool isSendAll,
required double solBalance,
String? tokenMint,
List<String> references = const [],
}) async {
Expand All @@ -290,6 +292,7 @@ class SolanaWalletClient {
ownerKeypair: ownerKeypair,
commitment: commitment,
isSendAll: isSendAll,
solBalance: solBalance,
);
return pendingNativeTokenTransaction;
} else {
Expand All @@ -301,6 +304,7 @@ class SolanaWalletClient {
destinationAddress: destinationAddress,
ownerKeypair: ownerKeypair,
commitment: commitment,
solBalance: solBalance,
);
return pendingSPLTokenTransaction;
}
Expand Down Expand Up @@ -353,6 +357,23 @@ class SolanaWalletClient {
return fee;
}

Future<bool> hasSufficientFundsLeftForRent({
required double inputAmount,
required double solBalance,
required double fee,
}) async {
final rent =
await _client!.getMinimumBalanceForMintRentExemption(commitment: Commitment.confirmed);

final rentInSol = (rent / lamportsPerSol).toDouble();

final remnant = solBalance - (inputAmount + fee);

if (remnant > rentInSol) return true;

return false;
}

Future<PendingSolanaTransaction> _signNativeTokenTransaction({
required String tokenTitle,
required int tokenDecimals,
Expand All @@ -361,6 +382,7 @@ class SolanaWalletClient {
required Ed25519HDKeyPair ownerKeypair,
required Commitment commitment,
required bool isSendAll,
required double solBalance,
}) async {
// Convert SOL to lamport
int lamports = (inputAmount * lamportsPerSol).toInt();
Expand All @@ -378,6 +400,16 @@ class SolanaWalletClient {
commitment,
);

bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
inputAmount: inputAmount,
fee: fee,
solBalance: solBalance,
);

if (!hasSufficientFundsLeft) {
throw SolanaSignNativeTokenTransactionRentException();
}

SignedTx signedTx;
if (isSendAll) {
final feeInLamports = (fee * lamportsPerSol).toInt();
Expand Down Expand Up @@ -425,6 +457,7 @@ class SolanaWalletClient {
required String destinationAddress,
required Ed25519HDKeyPair ownerKeypair,
required Commitment commitment,
required double solBalance,
}) async {
final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress);
final mint = Ed25519HDPublicKey.fromBase58(tokenMint);
Expand All @@ -447,7 +480,7 @@ class SolanaWalletClient {
// Throw an appropriate exception if the sender has no associated
// token account
if (associatedSenderAccount == null) {
throw NoAssociatedTokenAccountException(ownerKeypair.address, mint.toBase58());
throw SolanaNoAssociatedTokenAccountException(ownerKeypair.address, mint.toBase58());
}

try {
Expand All @@ -457,11 +490,11 @@ class SolanaWalletClient {
funder: ownerKeypair,
);
} catch (e) {
throw Exception('Insufficient SOL balance to complete this transaction: ${e.toString()}');
throw SolanaCreateAssociatedTokenAccountException(e.toString());
}

// Input by the user
final amount = (inputAmount * pow(10, tokenDecimals)).toInt();
final amount = (inputAmount * math.pow(10, tokenDecimals)).toInt();

final instruction = TokenInstruction.transfer(
source: Ed25519HDPublicKey.fromBase58(associatedSenderAccount.pubkey),
Expand All @@ -483,6 +516,16 @@ class SolanaWalletClient {
commitment,
);

bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
inputAmount: inputAmount,
fee: fee,
solBalance: solBalance,
);

if (!hasSufficientFundsLeft) {
throw SolanaSignSPLTokenTransactionRentException();
}

final signedTx = await _signTransactionInternal(
message: message,
signers: signers,
Expand Down
17 changes: 17 additions & 0 deletions cw_solana/lib/solana_exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,20 @@ class SolanaTransactionWrongBalanceException implements Exception {
@override
String toString() => exceptionMessage;
}

class SolanaSignNativeTokenTransactionRentException implements Exception {}

class SolanaCreateAssociatedTokenAccountException implements Exception {
final String exceptionMessage;

SolanaCreateAssociatedTokenAccountException(this.exceptionMessage);
}

class SolanaSignSPLTokenTransactionRentException implements Exception {}

class SolanaNoAssociatedTokenAccountException implements Exception {
const SolanaNoAssociatedTokenAccountException(this.account, this.mint);

final String account;
final String mint;
}
3 changes: 3 additions & 0 deletions cw_solana/lib/solana_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ abstract class SolanaWalletBase

final walletBalanceForCurrency = balance[transactionCurrency]!.balance;

final solBalance = balance[CryptoCurrency.sol]!.balance;

double totalAmount = 0.0;

bool isSendAll = false;
Expand Down Expand Up @@ -279,6 +281,7 @@ abstract class SolanaWalletBase
? solCredentials.outputs.first.extractedAddress!
: solCredentials.outputs.first.address,
isSendAll: isSendAll,
solBalance: solBalance,
);

return pendingSolanaTransaction;
Expand Down
37 changes: 25 additions & 12 deletions lib/view_model/send/send_view_model.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/evm_transaction_error_fees_handler.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
Expand All @@ -14,7 +13,6 @@ import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
Expand All @@ -29,6 +27,7 @@ import 'package:cw_core/unspent_coin_type.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_solana/solana_exceptions.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
Expand Down Expand Up @@ -427,7 +426,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
//
// state = FailureState(errorMsg);
// } else {
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
// }
}
return null;
Expand Down Expand Up @@ -487,10 +486,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor

try {
state = TransactionCommitting();

if (pendingTransaction!.shouldCommitUR()) {
final urstr = await pendingTransaction!.commitUR();
final result = await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: urstr);
final result =
await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: urstr);
if (result == null) {
state = FailureState("Canceled by user");
return;
Expand All @@ -507,12 +507,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}';
_settingsStore.shouldSaveRecipientAddress
? await transactionDescriptionBox.add(TransactionDescription(
id: descriptionKey,
recipientAddress: address,
transactionNote: note))
: await transactionDescriptionBox.add(TransactionDescription(
id: descriptionKey,
transactionNote: note));
id: descriptionKey, recipientAddress: address, transactionNote: note))
: await transactionDescriptionBox
.add(TransactionDescription(id: descriptionKey, transactionNote: note));
}

state = TransactionCommitted();
Expand Down Expand Up @@ -674,10 +671,26 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
lamportsNeeded != null ? ((lamportsNeeded + 5000) / lamportsPerSol) : 0.0;
return S.current.insufficient_lamports(solValueNeeded.toString());
} else {
printV("No match found.");
return S.current.insufficient_lamport_for_tx;
}
}

if (error is SolanaSignNativeTokenTransactionRentException) {
return S.current.solana_sign_native_transaction_rent_exception;
}

if (error is SolanaCreateAssociatedTokenAccountException) {
return S.current.solana_create_associated_token_account_exception;
}

if (error is SolanaSignSPLTokenTransactionRentException) {
return S.current.solana_sign_spl_token_transaction_rent_exception;
}

if (error is SolanaNoAssociatedTokenAccountException) {
return S.current.solana_no_associated_token_account_exception;
}

if (errorMessage.contains('insufficient funds for rent')) {
return S.current.insufficientFundsForRentError;
}
Expand Down
4 changes: 4 additions & 0 deletions res/values/strings_ar.arb
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,10 @@
"silent_payments_settings": "إعدادات المدفوعات الصامتة",
"single_seed_wallets_group": "محافظ بذرة واحدة",
"slidable": "قابل للانزلاق",
"solana_create_associated_token_account_exception": "خطأ في إنشاء حساب رمز المرتبط بعنوان المستلم.",
"solana_no_associated_token_account_exception": "لا يوجد حساب مميز مرتبط بهذا العنوان.",
"solana_sign_native_transaction_rent_exception": "لا يمكن إكمال المعاملة. غادر SOL غير كاف للإيجار بعد المعاملة. يرجى أن تصل إلى رصيد SOL أو تقليل كمية SOL التي ترسلها.",
"solana_sign_spl_token_transaction_rent_exception": "لا يمكن إكمال المعاملة. غادر SOL غير كاف للإيجار بعد المعاملة. يرجى أن تصل إلى توازن سولك.",
"sort_by": "ترتيب حسب",
"spend_key_private": "مفتاح الإنفاق (خاص)",
"spend_key_public": "مفتاح الإنفاق (عام)",
Expand Down
4 changes: 4 additions & 0 deletions res/values/strings_bg.arb
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,10 @@
"silent_payments_settings": "Настройки за безшумни плащания",
"single_seed_wallets_group": "Портфейли с единични семена",
"slidable": "Плъзгащ се",
"solana_create_associated_token_account_exception": "Грешка Създаване на свързана сметка за жетони за адреса на получател.",
"solana_no_associated_token_account_exception": "Няма свързана сметка за този адрес.",
"solana_sign_native_transaction_rent_exception": "Транзакцията не може да бъде завършена. Недостатъчен сол оставен под наем след транзакция. Любезно допълнете баланса си на SOL или намалете количеството SOL, което изпращате.",
"solana_sign_spl_token_transaction_rent_exception": "Транзакцията не може да бъде завършена. Недостатъчен сол оставен под наем след транзакция. Любезно допълнете баланса си на SOL.",
"sort_by": "Сортирай по",
"spend_key_private": "Spend key (таен)",
"spend_key_public": "Spend key (публичен)",
Expand Down
4 changes: 4 additions & 0 deletions res/values/strings_cs.arb
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,10 @@
"silent_payments_settings": "Nastavení tichých plateb",
"single_seed_wallets_group": "Jednorázové peněženky",
"slidable": "Posuvné",
"solana_create_associated_token_account_exception": "Vytvoření chyby přidruženého účtu tokenů pro adresu příjmu.",
"solana_no_associated_token_account_exception": "Pro tuto adresu není přidružen žádný přidružený token.",
"solana_sign_native_transaction_rent_exception": "Transakce nelze dokončit. Po transakci nedostatek Sol odešel k pronájmu. Laskavě doplňte rovnováhu SOL nebo snižte množství SOL, které odesíláte.",
"solana_sign_spl_token_transaction_rent_exception": "Transakce nelze dokončit. Po transakci nedostatek Sol odešel k pronájmu. Laskavě doplňte rovnováhu SOL.",
"sort_by": "Seřazeno podle",
"spend_key_private": "Klíč pro platby (soukromý)",
"spend_key_public": "Klíč pro platby (veřejný)",
Expand Down
6 changes: 5 additions & 1 deletion res/values/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,8 @@
"placeholder_transactions": "Ihre Transaktionen werden hier angezeigt",
"please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist",
"please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.",
"Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
"please_reference_document": "Bitte verweisen Sie auf die folgenden Dokumente, um weitere Informationen zu erhalten.",
"Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.",
"please_select": "Bitte auswählen:",
"please_select_backup_file": "Bitte wählen Sie die Sicherungsdatei und geben Sie das Sicherungskennwort ein.",
"please_try_to_connect_to_another_node": "Bitte versuchen Sie, sich mit einem anderen Knoten zu verbinden",
Expand Down Expand Up @@ -742,6 +742,10 @@
"silent_payments_settings": "Einstellungen für stille Zahlungen",
"single_seed_wallets_group": "Einzelne Wallets",
"slidable": "Verschiebbar",
"solana_create_associated_token_account_exception": "Fehler beim Erstellen des zugehörigen Token -Kontos für die Empfängeradresse.",
"solana_no_associated_token_account_exception": "Für diese Adresse ist kein Token -Konto zugeordnet.",
"solana_sign_native_transaction_rent_exception": "Transaktion kann nicht abgeschlossen werden. Unzureichende Sol ließen nach der Transaktion zur Miete gelassen. Bitte geben Sie Ihre SOL -Balance auf oder reduzieren Sie die Menge an SOL, die Sie senden.",
"solana_sign_spl_token_transaction_rent_exception": "Transaktion kann nicht abgeschlossen werden. Unzureichende Sol ließen nach der Transaktion zur Miete gelassen. Bitte geben Sie Ihre SOL -Balance auf.",
"sort_by": "Sortiere nach",
"spend_key_private": "Spend Key (geheim)",
"spend_key_public": "Spend Key (öffentlich)",
Expand Down
4 changes: 4 additions & 0 deletions res/values/strings_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,10 @@
"silent_payments_settings": "Silent Payments settings",
"single_seed_wallets_group": "Single Seed Wallets",
"slidable": "Slidable",
"solana_create_associated_token_account_exception": "Error creating associated token account for receipient address.",
"solana_no_associated_token_account_exception": "There is no associated token account for this address.",
"solana_sign_native_transaction_rent_exception": "Transaction cannot be completed. Insufficient SOL left for rent after transaction. Kindly top up your SOL balance or reduce the amount of SOL you are sending.",
"solana_sign_spl_token_transaction_rent_exception": "Transaction cannot be completed. Insufficient SOL left for rent after transaction. Kindly top up your SOL balance.",
"sort_by": "Sort by",
"spend_key_private": "Spend key (private)",
"spend_key_public": "Spend key (public)",
Expand Down
4 changes: 4 additions & 0 deletions res/values/strings_es.arb
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,10 @@
"silent_payments_settings": "Configuración de pagos silenciosos",
"single_seed_wallets_group": "Billeteras de semillas individuales",
"slidable": "deslizable",
"solana_create_associated_token_account_exception": "Error a crear una cuenta de token asociada para la dirección recibida.",
"solana_no_associated_token_account_exception": "No hay una cuenta de token asociada para esta dirección.",
"solana_sign_native_transaction_rent_exception": "La transacción no se puede completar. Sol insuficiente que queda en alquiler después de la transacción. Por favor, supere su saldo de sol o reduzca la cantidad de sol que está enviando.",
"solana_sign_spl_token_transaction_rent_exception": "La transacción no se puede completar. Sol insuficiente que queda en alquiler después de la transacción. Por favor, supere su balance de sol.",
"sort_by": "Ordenar por",
"spend_key_private": "Llave de gasto (privada)",
"spend_key_public": "Llave de gasto (pública)",
Expand Down
4 changes: 4 additions & 0 deletions res/values/strings_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,10 @@
"silent_payments_settings": "Paramètres de paiement silencieux",
"single_seed_wallets_group": "Portefeuilles de semences simples",
"slidable": "Glissable",
"solana_create_associated_token_account_exception": "Création d'erreur Création de jetons associés pour l'adresse détenue.",
"solana_no_associated_token_account_exception": "Il n'y a pas de compte de jeton associé pour cette adresse.",
"solana_sign_native_transaction_rent_exception": "La transaction ne peut pas être terminée. Sol insuffisant laissé à la location après la transaction. Veuillez compléter votre solde SOL ou réduire la quantité de Sol que vous envoyez.",
"solana_sign_spl_token_transaction_rent_exception": "La transaction ne peut pas être terminée. Sol insuffisant laissé à la location après la transaction. Veuillez compléter votre solde de Sol.",
"sort_by": "Trier par",
"spend_key_private": "Clef de dépense (spend key) (privée)",
"spend_key_public": "Clef de dépense (spend key) (publique)",
Expand Down
4 changes: 4 additions & 0 deletions res/values/strings_ha.arb
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,10 @@
"silent_payments_settings": "Saitunan Silent",
"single_seed_wallets_group": "Guaro",
"slidable": "Mai iya zamewa",
"solana_create_associated_token_account_exception": "Kuskuren ƙirƙirar asusun Asusun da aka danganta don Adireshin karɓar karɓa.",
"solana_no_associated_token_account_exception": "Babu wani haɗin yanar gizo mai alaƙa don wannan adireshin.",
"solana_sign_native_transaction_rent_exception": "Ma'amala ba za a iya kammala ba. Rashin cancanta na Sol ya bar haya bayan ma'amala. Da kyau a saman ma'auni na Solku ko rage adadin Sol da kuke aikawa.",
"solana_sign_spl_token_transaction_rent_exception": "Ma'amala ba za a iya kammala ba. Rashin cancanta na Sol ya bar haya bayan ma'amala. Da kyau sama da ma'auni na Sol.",
"sort_by": "Kasa",
"spend_key_private": "makullin biya (maɓallin kalmar sirri)",
"spend_key_public": "makullin biya (maɓallin jama'a)",
Expand Down
Loading

0 comments on commit c620d7f

Please sign in to comment.