1
import 'dart:async';
2
import 'dart:convert';
3
-import 'dart:math' as math;
3
4
import 'package:blockchain_utils/blockchain_utils.dart';
5
+import 'package:cw_core/amount/money.dart';
6
import 'package:cw_core/crypto_currency.dart';
7
+import 'package:cw_core/currency.dart';
8
import 'package:cw_core/node.dart';
9
import 'package:cw_core/utils/proxy_wrapper.dart';
10
import 'package:cw_core/solana_rpc_http_service.dart';
33
34
class SolanaWalletClient {
35
// Minimum amount in SOL to consider a transaction valid (to filter spam)
35
- static const double minValidAmount = 0.00000003;
36
+ static Money minValidAmount = Money.parse("0.00000003", CryptoCurrency.sol);
37
late final client = ProxyWrapper().getHttpIOClient();
38
SolanaRPC? _provider;
39
62
}
63
}
64
64
- Future<double> getBalance(String walletAddress, {bool throwOnError = false}) async {
65
+ Future<Money> getBalance(String walletAddress, {bool throwOnError = false}) async {
66
try {
67
final balance = await _provider!.requestWithContext(
67
- SolanaRPCGetBalance(
68
- account: SolAddress(walletAddress),
69
- ),
68
+ SolanaRPCGetBalance(account: SolAddress(walletAddress)),
69
);
71
-
72
- final balInLamp = balance.result.toDouble();
73
-
74
- final solBalance = balInLamp / SolanaUtils.lamportsPerSol;
75
-
76
- return solBalance;
70
+ return Money(balance.result, CryptoCurrency.sol);
71
} catch (_) {
72
if (throwOnError) {
73
rethrow;
74
}
81
- return 0.0;
75
+ return Money.zero(CryptoCurrency.sol);
76
}
77
}
78
94
}
95
}
96
103
- Future<SolanaBalance?> getSplTokenBalance(String mintAddress, String walletAddress,
97
+ Future<SolanaBalance?> getSplTokenBalance(SPLToken token, String walletAddress,
98
{bool throwOnError = false}) async {
99
try {
100
// Fetch the token accounts (a token can have multiple accounts for various uses)
107
- final tokenAccounts = await getSPLTokenAccounts(mintAddress, walletAddress);
101
+ final tokenAccounts = await getSPLTokenAccounts(token.mintAddress, walletAddress);
102
103
// Handle scenario where there is no token account
104
if (tokenAccounts == null || tokenAccounts.isEmpty) {
106
}
107
108
// Sum raw amounts and ui amounts across all token accounts
115
- BigInt totalRaw = BigInt.zero;
116
- double totalUi = 0.0;
109
+ var totalRaw = BigInt.zero;
110
111
for (var tokenAccount in tokenAccounts) {
112
final tokenAmountResult = await _provider!.request(
115
116
final raw = BigInt.tryParse(tokenAmountResult.amount) ?? BigInt.zero;
117
totalRaw += raw;
125
-
126
- final ui = tokenAmountResult.uiAmount ??
127
- (double.tryParse(tokenAmountResult.uiAmountString ?? '0') ?? 0.0);
128
- totalUi += ui;
118
}
119
131
- return SolanaBalance.forToken(totalRaw, totalUi);
120
+ return SolanaBalance(Money(totalRaw, token));
121
} catch (_) {
133
- if (throwOnError) {
134
- rethrow;
135
- }
122
+ if (throwOnError) rethrow;
123
+
124
return null;
125
}
126
}
127
140
- Future<double> getFeeForMessage(String message, Commitment commitment) async {
128
+ Future<Money> getFeeForMessage(String message, Commitment commitment) async {
129
try {
130
final feeForMessage = await _provider!.request(
143
- SolanaRPCGetFeeForMessage(
144
- encodedMessage: message,
145
- commitment: commitment,
146
- ),
131
+ SolanaRPCGetFeeForMessage(encodedMessage: message, commitment: commitment),
132
);
133
149
- final fee = (feeForMessage?.toDouble() ?? 0.0) / SolanaUtils.lamportsPerSol;
150
- return fee;
134
+ return Money(feeForMessage ?? BigInt.zero, CryptoCurrency.sol);
135
} catch (_) {
152
- return 0.0;
136
+ return Money.zero(CryptoCurrency.sol);
137
}
138
}
139
156
- Future<double> getEstimatedFee(SolanaPublicKey publicKey, Commitment commitment) async {
140
+ Future<Money> getEstimatedFee(SolanaPublicKey publicKey, Commitment commitment) async {
141
final message = await _getMessageForNativeTransaction(
142
publicKey: publicKey,
143
destinationAddress: publicKey.toAddress().address,
160
- lamports: SolanaUtils.lamportsPerSol,
144
+ lamports: Money(BigInt.from(1000000000), CryptoCurrency.sol),
145
commitment: commitment,
146
);
147
164
- final estimatedFee = await _getFeeFromCompiledMessage(
165
- message,
166
- commitment,
167
- );
168
- return estimatedFee;
148
+ return _getFeeFromCompiledMessage(message, commitment);
149
}
150
151
Future<List<SolanaTransactionModel>?> parseTransaction({
152
VersionedTransactionResponse? txResponse,
153
required String walletAddress,
174
- String? splTokenSymbol,
154
+ SPLToken? splToken,
155
}) async {
156
if (txResponse == null) return null;
157
162
163
if (meta == null || transaction == null) return null;
164
185
- final int fee = meta.fee;
186
- final feeInSol = fee / SolanaUtils.lamportsPerSol;
165
+ final fee = meta.fee;
166
167
final message = transaction.message;
168
final instructions = message.compiledInstructions;
180
message: message,
181
meta: meta,
182
fee: fee,
204
- feeInSol: feeInSol,
183
walletAddress: walletAddress,
184
signature: signature,
185
blockTime: blockTime,
186
instructions: instructions,
209
- splTokenSymbol: splTokenSymbol,
187
);
188
189
if (swapTransactions.isNotEmpty) return swapTransactions;
207
message: message,
208
meta: meta,
209
fee: fee,
233
- feeInSol: feeInSol,
210
feePayerIndex: feePayerIndex,
211
walletAddress: walletAddress,
212
signature: signature,
224
message: message,
225
meta: meta,
226
fee: fee,
251
- feeInSol: feeInSol,
227
instruction: instruction,
228
walletAddress: walletAddress,
229
signature: signature,
230
blockTime: blockTime,
256
- splTokenSymbol: splTokenSymbol,
231
+ splToken: splToken,
232
);
233
234
if (transactionModel != null) {
376
required VersionedMessage message,
377
required ConfirmedTransactionMeta meta,
378
required int fee,
404
- required double feeInSol,
379
required String walletAddress,
380
required String signature,
381
required BigInt? blockTime,
382
required List<CompiledInstruction> instructions,
409
- String? splTokenSymbol,
383
}) async {
384
final List<SolanaTransactionModel> swapTransactions = [];
385
425
426
// Parse outgoing side (what was sent)
427
double outgoingAmount = 0.0;
455
- String outgoingTokenSymbol = '';
428
+ Currency outgoingToken = CryptoCurrency.sol;
429
String? outgoingMintAddress;
430
String? outgoingFrom;
431
String? outgoingTo;
448
if (balanceChange > BigInt.zero) {
449
// The wallet sent SOL
450
outgoingAmount = balanceChange.toDouble() / SolanaUtils.lamportsPerSol;
478
- outgoingTokenSymbol = 'SOL';
451
+ outgoingToken = CryptoCurrency.sol;
452
outgoingMintAddress = null;
453
outgoingFrom = walletAddress;
454
// We find the intermediate account or swap program account
492
outgoingAmount = diff.toDouble();
493
outgoingMintAddress = mint;
494
final token = await getTokenInfo(mint);
522
- outgoingTokenSymbol = token?.symbol ?? 'TOKEN';
495
+ outgoingToken = token ?? const CryptoCurrency(name: "TOKEN", title: "TOKEN", decimals: 6);
496
outgoingFrom = walletAddress;
497
// We find the intermediate account
498
if (instructions.isNotEmpty && instructions[0].accounts.isNotEmpty) {
514
515
// Parse incoming side (what was received)
516
double incomingAmount = 0.0;
544
- String incomingTokenSymbol = '';
517
+ Currency incomingToken = CryptoCurrency.sol;
518
String? incomingMintAddress;
519
String? incomingFrom;
520
String? incomingTo;
536
if (balanceChange > BigInt.zero) {
537
// The wallet received SOL
538
incomingAmount = balanceChange.toDouble() / SolanaUtils.lamportsPerSol;
566
- incomingTokenSymbol = 'SOL';
539
+ incomingToken = CryptoCurrency.sol;
540
incomingMintAddress = null;
541
incomingTo = walletAddress;
542
// We find the intermediate account
623
incomingAmount = diff.toDouble();
624
incomingMintAddress = mint;
625
final token = await getTokenInfo(mint);
653
- incomingTokenSymbol = token?.symbol ?? 'TOKEN';
626
+ printV(token?.symbol);
627
+ printV(token?.decimals);
628
+ incomingToken = token ?? const CryptoCurrency(name: "TOKEN", title: "TOKEN", decimals: 6);
629
incomingTo = walletAddress;
630
// We find the intermediate account
631
if (instructions.isNotEmpty && instructions[0].accounts.isNotEmpty) {
654
from: outgoingFrom,
655
to: outgoingTo,
656
id: outgoingId,
682
- amount: outgoingAmount,
657
+ amount: Money.parse(outgoingAmount.toStringAsFixed(outgoingToken.decimals), outgoingToken),
658
programId: outgoingMintAddress == null
659
? SystemProgramConst.programId.address
660
: SPLTokenProgramConst.tokenProgramId.address,
661
blockTimeInInt: blockTime?.toInt() ?? 0,
687
- tokenSymbol: outgoingTokenSymbol,
688
- fee: feeInSol,
662
+ fee: Money.fromInt(fee, CryptoCurrency.sol),
663
));
664
}
665
672
from: incomingFrom,
673
to: incomingTo,
674
id: incomingId,
701
- amount: incomingAmount,
675
+ amount: Money.parse(incomingAmount.toStringAsFixed(incomingToken.decimals), incomingToken),
676
programId: incomingMintAddress == null
677
? SystemProgramConst.programId.address
678
: SPLTokenProgramConst.tokenProgramId.address,
679
blockTimeInInt: blockTime?.toInt() ?? 0,
706
- tokenSymbol: incomingTokenSymbol,
707
- fee: 0.0, // Fee only charged on outgoing side
680
+ fee: Money.zero(CryptoCurrency.sol), // Fee only charged on outgoing side
681
));
682
}
683
688
required VersionedMessage message,
689
required ConfirmedTransactionMeta meta,
690
required int fee,
718
- required double feeInSol,
691
required int feePayerIndex,
692
required String walletAddress,
693
required String signature,
726
final netChange = walletPaidFee ? walletChange - BigInt.from(fee) : walletChange;
727
728
final isOutgoing = netChange > BigInt.zero;
757
- final amountLamports = netChange.abs();
758
- final amountInSol = amountLamports.toDouble() / SolanaUtils.lamportsPerSol;
729
+ final amountLamports = Money(netChange.abs(), CryptoCurrency.sol);
730
760
- if (amountInSol < minValidAmount) return null;
731
+ if (amountLamports < minValidAmount) return null;
732
733
// Find the most likely receiver, the account that has the largest opposite balance change.
734
String? receiver;
754
from: isOutgoing ? walletAddress : receiver,
755
to: isOutgoing ? receiver : walletAddress,
756
id: signature,
786
- amount: amountInSol,
757
+ amount: amountLamports,
758
programId: SystemProgramConst.programId.address,
788
- tokenSymbol: 'SOL',
759
blockTimeInInt: blockTime?.toInt() ?? 0,
790
- fee: feeInSol,
760
+ fee: Money.fromInt(fee, CryptoCurrency.sol),
761
);
762
}
763
765
required VersionedMessage message,
766
required ConfirmedTransactionMeta meta,
767
required int fee,
798
- required double feeInSol,
768
required CompiledInstruction instruction,
769
required String walletAddress,
770
required String signature,
771
required BigInt? blockTime,
803
- String? splTokenSymbol,
772
+ SPLToken? splToken,
773
}) async {
774
final preTokenBalances = meta.preTokenBalances;
775
final postTokenBalances = meta.postTokenBalances;
860
final sender = senderOwner ?? accountKeys[sourceAccountIndex].address;
861
final receiver = receiverOwner ?? accountKeys[destinationAccountIndex].address;
862
894
- String? tokenSymbol = splTokenSymbol;
895
-
896
- if (tokenSymbol == null && mintAddress != null) {
897
- final token = await getTokenInfo(mintAddress);
898
- tokenSymbol = token?.symbol;
863
+ if (splToken == null && mintAddress != null) {
864
+ splToken = await getTokenInfo(mintAddress);
865
}
866
867
return SolanaTransactionModel(
869
from: sender,
870
to: receiver,
871
id: signature,
906
- amount: amount,
872
+ amount: Money.parse(amount.toStringAsFixed((splToken ?? CryptoCurrency.sol).decimals),
873
+ splToken ?? CryptoCurrency.sol),
874
programId: SPLTokenProgramConst.tokenProgramId.address,
875
blockTimeInInt: blockTime?.toInt() ?? 0,
909
- tokenSymbol: tokenSymbol ?? '',
910
- fee: feeInSol,
876
+ fee: Money.fromInt(fee, CryptoCurrency.sol),
877
);
878
}
879
880
/// Fetches a specific transaction by signature and parses it
915
- /// It returns a TransactionFetchResult object containing both transactions and token mints extracted from the transaction or null if the transaction is not found or cannot be parsed
881
+ /// It returns a TransactionFetchResult object containing both transactions and token mints
882
+ /// extracted from the transaction or null if the transaction is not found or cannot be parsed
883
Future<TransactionFetchResult?> fetchTransactionBySignature({
884
required String signature,
885
required String walletAddress,
919
- String? splTokenSymbol,
886
+ SPLToken? splToken,
887
}) async {
888
try {
889
final txResponse = await _provider!.request(
903
final parsed = await parseTransaction(
904
txResponse: versionedResponse,
905
walletAddress: walletAddress,
939
- splTokenSymbol: splTokenSymbol,
906
+ splToken: splToken,
907
);
908
909
if (parsed == null) return null;
952
/// Load the Address's transactions into the account
953
Future<List<SolanaTransactionModel>> fetchTransactions(
954
SolAddress address, {
988
- String? splTokenSymbol,
989
- int? splTokenDecimal,
955
+ SPLToken? splToken,
956
Commitment? commitment,
957
SolAddress? walletAddress,
958
required void Function(List<SolanaTransactionModel>) onUpdate,
991
992
final parsedTransactionsFutures = versionedBatchResponses.map((tx) => parseTransaction(
993
txResponse: tx,
1028
- splTokenSymbol: splTokenSymbol,
994
+ splToken: splToken,
995
walletAddress: walletAddress?.address ?? address.address,
996
));
997
1023
1024
Future<List<SolanaTransactionModel>> getSPLTokenTransfers({
1025
required String mintAddress,
1060
- required String splTokenSymbol,
1061
- required int splTokenDecimal,
1026
+ required SPLToken splToken,
1027
required SolanaPrivateKey privateKey,
1028
required void Function(List<SolanaTransactionModel>) onUpdate,
1029
}) async {
1042
1043
if (associatedTokenAccount == null) return [];
1044
1080
- final accountPublicKey = associatedTokenAccount.address;
1081
-
1082
- final tokenTransactions = await fetchTransactions(
1083
- accountPublicKey,
1084
- splTokenSymbol: splTokenSymbol,
1085
- splTokenDecimal: splTokenDecimal,
1045
+ return fetchTransactions(
1046
+ associatedTokenAccount.address,
1047
+ splToken: splToken,
1048
walletAddress: ownerWalletAddress,
1049
onUpdate: onUpdate,
1050
);
1089
-
1090
- return tokenTransactions;
1051
}
1052
1053
final Map<String, SPLToken?> tokenInfoCache = {};
1054
1055
Future<SPLToken?> getTokenInfo(String mintAddress) async {
1056
if (tokenInfoCache.containsKey(mintAddress)) {
1057
+ printV("Cached");
1058
return tokenInfoCache[mintAddress];
1059
} else {
1060
final token = await fetchSPLTokenInfo(mintAddress);
1080
},
1081
);
1082
1083
+ if (response.statusCode != 200) return null;
1084
final decodedResponse = jsonDecode(response.body) as Map<String, dynamic>;
1085
1124
- final symbol = (decodedResponse['symbol'] ?? '') as String;
1125
-
1086
+ final symbol = decodedResponse['symbol'] ?? '';
1087
final name = decodedResponse['name'] ?? '';
1088
final decimal = decodedResponse['decimals'] ?? '0';
1089
final iconPath = decodedResponse['logo'] ?? '';
1090
1130
- String filteredTokenSymbol =
1131
- symbol.replaceFirst(RegExp('^\\\$'), '').replaceAll('\u0000', '');
1091
+ final filteredTokenSymbol = symbol.replaceFirst(RegExp('^\\\$'), '').replaceAll('\u0000', '');
1092
1093
return SPLToken(
1094
name: name,
1142
SolanaRPC? get getSolanaProvider => _provider;
1143
1144
Future<PendingSolanaTransaction> signSolanaTransaction({
1185
- required String tokenTitle,
1186
- required int tokenDecimals,
1187
- required double inputAmount,
1145
+ required Money inputAmount,
1146
required String destinationAddress,
1147
required SolanaPrivateKey ownerPrivateKey,
1148
required bool isSendAll,
1191
- required double solBalance,
1149
+ required Money solBalance,
1150
String? tokenMint,
1151
List<String> references = const [],
1152
}) async {
1153
const commitment = Commitment.confirmed;
1154
1197
- if (tokenTitle == CryptoCurrency.sol.title) {
1198
- final pendingNativeTokenTransaction = await _signNativeTokenTransaction(
1155
+ if (inputAmount.currency == CryptoCurrency.sol) {
1156
+ return _signNativeTokenTransaction(
1157
inputAmount: inputAmount,
1158
destinationAddress: destinationAddress,
1159
ownerPrivateKey: ownerPrivateKey,
1161
isSendAll: isSendAll,
1162
solBalance: solBalance,
1163
);
1206
- return pendingNativeTokenTransaction;
1164
} else {
1208
- final pendingSPLTokenTransaction = _signSPLTokenTransaction(
1209
- tokenDecimals: tokenDecimals,
1165
+ return _signSPLTokenTransaction(
1166
+ tokenDecimals: inputAmount.currency.decimals,
1167
tokenMint: tokenMint!,
1168
inputAmount: inputAmount,
1169
ownerPrivateKey: ownerPrivateKey,
1171
commitment: commitment,
1172
solBalance: solBalance,
1173
);
1217
- return pendingSPLTokenTransaction;
1174
}
1175
}
1176
1185
Future<Message> _getMessageForNativeTransaction({
1186
required SolanaPublicKey publicKey,
1187
required String destinationAddress,
1232
- required int lamports,
1188
+ required Money lamports,
1189
required Commitment commitment,
1190
}) async {
1191
final instructions = [
1192
SystemProgram.transfer(
1193
from: publicKey.toAddress(),
1238
- layout: SystemTransferLayout(lamports: BigInt.from(lamports)),
1194
+ layout: SystemTransferLayout(lamports: lamports.amount),
1195
to: SolAddress(destinationAddress),
1196
),
1197
];
1198
1199
final latestBlockhash = await _getLatestBlockhash(commitment);
1200
1245
- final message = Message.compile(
1201
+ return Message.compile(
1202
transactionInstructions: instructions,
1203
payer: publicKey.toAddress(),
1204
recentBlockhash: latestBlockhash,
1205
);
1250
- return message;
1206
}
1207
1208
Future<Message> _getMessageForSPLTokenTransaction({
1211
required int tokenDecimals,
1212
required SolAddress mintAddress,
1213
required SolAddress sourceAccount,
1259
- required int amount,
1214
+ required Money amount,
1215
required Commitment commitment,
1216
required SolAddress tokenProgramId,
1217
}) async {
1218
final instructions = [
1219
SPLTokenProgram.transferChecked(
1220
layout: SPLTokenTransferCheckedLayout(
1266
- amount: BigInt.from(amount),
1221
+ amount: amount.amount,
1222
decimals: tokenDecimals,
1223
),
1224
mint: mintAddress,
1230
1231
final latestBlockhash = await _getLatestBlockhash(commitment);
1232
1278
- final message = Message.compile(
1233
+ return Message.compile(
1234
transactionInstructions: instructions,
1235
payer: ownerAddress,
1236
recentBlockhash: latestBlockhash,
1237
);
1283
- return message;
1238
}
1239
1286
- Future<double> _getFeeFromCompiledMessage(Message message, Commitment commitment) async {
1240
+ Future<Money> _getFeeFromCompiledMessage(Message message, Commitment commitment) {
1241
final base64Message = base64Encode(message.serialize());
1288
-
1289
- final fee = await getFeeForMessage(base64Message, commitment);
1290
-
1291
- return fee;
1242
+ return getFeeForMessage(base64Message, commitment);
1243
}
1244
1245
Future<bool> hasSufficientFundsLeftForRent({
1295
- required double inputAmount,
1296
- required double solBalance,
1297
- required double fee,
1246
+ required Money inputAmount,
1247
+ required Money solBalance,
1248
+ required Money fee,
1249
}) async {
1250
final rent = await _provider!.request(
1300
- SolanaRPCGetMinimumBalanceForRentExemption(
1301
- size: SolanaTokenAccountUtils.accountSize,
1302
- ),
1251
+ SolanaRPCGetMinimumBalanceForRentExemption(size: SolanaTokenAccountUtils.accountSize),
1252
);
1253
1305
- final rentInSol = (rent.toDouble() / SolanaUtils.lamportsPerSol).toDouble();
1306
-
1307
- final remnant = solBalance - (inputAmount + fee);
1308
-
1309
- if (remnant > rentInSol) return true;
1310
-
1311
- return false;
1254
+ return (solBalance - (inputAmount + fee)) > Money(rent, CryptoCurrency.sol);
1255
}
1256
1257
Future<PendingSolanaTransaction> _signNativeTokenTransaction({
1315
- required double inputAmount,
1258
+ required Money inputAmount,
1259
required String destinationAddress,
1260
required SolanaPrivateKey ownerPrivateKey,
1261
required Commitment commitment,
1262
required bool isSendAll,
1320
- required double solBalance,
1263
+ required Money solBalance,
1264
}) async {
1322
- // Convert SOL to lamport
1323
- int lamports = (inputAmount * SolanaUtils.lamportsPerSol).toInt();
1324
-
1325
- Message message = await _getMessageForNativeTransaction(
1265
+ final message = await _getMessageForNativeTransaction(
1266
publicKey: ownerPrivateKey.publicKey(),
1267
destinationAddress: destinationAddress,
1328
- lamports: lamports,
1268
+ lamports: inputAmount,
1269
commitment: commitment,
1270
);
1271
1332
- SolAddress latestBlockhash = await _getLatestBlockhash(commitment);
1272
+ final latestBlockhash = await _getLatestBlockhash(commitment);
1273
1274
final fee = await _getFeeFromCompiledMessage(
1275
message,
1277
);
1278
1279
if (!isSendAll) {
1340
- bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
1280
+ final hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
1281
inputAmount: inputAmount,
1282
fee: fee,
1283
solBalance: solBalance,
1290
1291
String serializedTransaction;
1292
if (isSendAll) {
1353
- final feeInLamports = (fee * SolanaUtils.lamportsPerSol).toInt();
1354
- final updatedLamports = lamports - feeInLamports;
1293
+ final updatedLamports = inputAmount - fee;
1294
1295
final transaction = _constructNativeTransaction(
1296
ownerPrivateKey: ownerPrivateKey,
1308
ownerPrivateKey: ownerPrivateKey,
1309
destinationAddress: destinationAddress,
1310
latestBlockhash: latestBlockhash,
1372
- lamports: lamports,
1311
+ lamports: inputAmount,
1312
);
1313
1314
serializedTransaction = await _signTransactionInternal(
1322
commitment: commitment,
1323
);
1324
1386
- final pendingTransaction = PendingSolanaTransaction(
1325
+ return PendingSolanaTransaction(
1326
amount: inputAmount,
1327
serializedTransaction: serializedTransaction,
1328
destinationAddress: destinationAddress,
1329
sendTransaction: sendTx,
1330
fee: fee,
1331
);
1393
-
1394
- return pendingTransaction;
1332
}
1333
1334
SolanaTransaction _constructNativeTransaction({
1335
required SolanaPrivateKey ownerPrivateKey,
1336
required String destinationAddress,
1337
required SolAddress latestBlockhash,
1401
- required int lamports,
1338
+ required Money lamports,
1339
}) {
1340
final owner = ownerPrivateKey.publicKey().toAddress();
1341
1405
- /// Create a transfer instruction to move funds from the owner to the receiver.
1342
final transferInstruction = SystemProgram.transfer(
1343
from: owner,
1408
- layout: SystemTransferLayout(lamports: BigInt.from(lamports)),
1344
+ layout: SystemTransferLayout(lamports: lamports.amount),
1345
to: SolAddress(destinationAddress),
1346
);
1347
1412
- /// Construct a Solana transaction with the transfer instruction.
1348
return SolanaTransaction(
1349
instructions: [transferInstruction],
1350
recentBlockhash: latestBlockhash,
1544
Future<PendingSolanaTransaction> _signSPLTokenTransaction({
1545
required int tokenDecimals,
1546
required String tokenMint,
1612
- required double inputAmount,
1547
+ required Money inputAmount,
1548
required String destinationAddress,
1549
required SolanaPrivateKey ownerPrivateKey,
1550
required Commitment commitment,
1616
- required double solBalance,
1551
+ required Money solBalance,
1552
}) async {
1553
final mintAddress = SolAddress(tokenMint);
1619
-
1620
- // Input by the user
1621
- final amount = (inputAmount * math.pow(10, tokenDecimals)).toInt();
1622
-
1554
final tokenProgramId = await _getTokenProgramId(mintAddress);
1555
1556
ProgramDerivedAddress? associatedSenderAccount;
1682
destination: associatedRecipientAccount.address,
1683
mint: mintAddress,
1684
owner: ownerPrivateKey.publicKey().toAddress(),
1754
- amount: BigInt.from(amount),
1685
+ amount: inputAmount.amount,
1686
decimals: tokenDecimals,
1687
);
1688
1700
mintAddress: mintAddress,
1701
destinationAddress: associatedRecipientAccount.address,
1702
sourceAccount: associatedSenderAccount.address,
1772
- amount: amount,
1703
+ amount: inputAmount,
1704
commitment: commitment,
1705
tokenProgramId: tokenProgramId,
1706
);
1707
1708
final fee = await _getFeeFromCompiledMessage(message, commitment);
1709
1779
- bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
1780
- inputAmount: 0,
1710
+ final hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
1711
+ inputAmount: Money.zero(CryptoCurrency.sol),
1712
fee: fee,
1713
solBalance: solBalance,
1714
);
1715
1785
- if (!hasSufficientFundsLeft) {
1786
- throw SolanaSignSPLTokenTransactionRentException();
1787
- }
1716
+ if (!hasSufficientFundsLeft) throw SolanaSignSPLTokenTransactionRentException();
1717
1718
final serializedTransaction = await _signTransactionInternal(
1719
ownerPrivateKey: ownerPrivateKey,
1720
transaction: transaction,
1721
);
1722
1794
- sendTx() async => await sendTransaction(
1723
+ sendTx() => sendTransaction(
1724
serializedTransaction: serializedTransaction,
1725
commitment: commitment,
1726
);
1727
1799
- final pendingTransaction = PendingSolanaTransaction(
1728
+ return PendingSolanaTransaction(
1729
amount: inputAmount,
1730
serializedTransaction: serializedTransaction,
1731
destinationAddress: destinationAddress,
1732
sendTransaction: sendTx,
1733
fee: fee,
1734
);
1806
- return pendingTransaction;
1735
}
1736
1737
Future<String> _signTransactionInternal({
1738
required SolanaPrivateKey ownerPrivateKey,
1739
required SolanaTransaction transaction,
1740
}) async {
1813
- /// Sign the transaction with the owner's private key.
1741
final ownerSignature = ownerPrivateKey.sign(transaction.serializeMessage());
1742
1743
transaction.addSignature(ownerPrivateKey.publicKey().toAddress(), ownerSignature);
1744
1818
- /// Serialize the transaction.
1819
- final serializedTransaction = transaction.serializeString();
1820
-
1821
- return serializedTransaction;
1745
+ return transaction.serializeString();
1746
}
1747
1748
Future<String> sendTransaction({
1749
required String serializedTransaction,
1826
- required Commitment commitment,
1827
- }) async {
1828
- try {
1829
- /// Send the transaction to the Solana network.
1830
- final signature = await _provider!.request(
1750
+ required Commitment commitment
1751
+ }) =>
1752
+ _provider!.request(
1753
SolanaRPCSendTransaction(
1754
encodedTransaction: serializedTransaction,
1755
commitment: commitment,
1756
),
1757
);
1836
- return signature;
1837
- } catch (e) {
1838
- throw Exception(e);
1839
- }
1840
- }
1758
1759
Future<String?> getIconImageFromTokenUri(String uri) async {
1760
if (uri.isEmpty || uri == '…') return null;