From c68b2e0d02e51e2980e3c1be18a4c10757caf50f Mon Sep 17 00:00:00 2001 From: likho <likhojiba@gmail.com> Date: Fri, 21 Jul 2023 14:59:47 +0200 Subject: [PATCH] allow sending to unfunded account --- .../coins/stellar/stellar_wallet.dart | 274 +++++++++++------- 1 file changed, 170 insertions(+), 104 deletions(-) diff --git a/lib/services/coins/stellar/stellar_wallet.dart b/lib/services/coins/stellar/stellar_wallet.dart index 23d884ad2..af7399896 100644 --- a/lib/services/coins/stellar/stellar_wallet.dart +++ b/lib/services/coins/stellar/stellar_wallet.dart @@ -153,38 +153,65 @@ class StellarWallet extends CoinServiceAPI Coin get coin => _coin; late Coin _coin; + Future<bool> accoutExists(String accountId) async { + bool exists = false; + + try { + AccountResponse receiverAccount = await stellarSdk.accounts + .account(accountId); + if (receiverAccount.accountId != "") { + exists = true; + } + } catch(e, s) { + Logging.instance.log("Error getting account ${e.toString()} - ${s.toString()}", level: LogLevel.Error); + } + return exists; + } @override Future<String> confirmSend({required Map<String, dynamic> txData}) async { - print("TX DATA IS $txData"); final secretSeed = await _secureStore.read( key: '${_walletId}_secretSeed' ); - - // final amt = Amount( - // rawValue: BigInt.from(txData['recipientAmt']), - // fractionDigits: coin.decimals, - // ); - // - // print("THIS AMOUNT IS $amount"); - //First check if account exists, can be skipped, but if the account does not exist, - // the transaction fee will be charged when the transaction fails. - AccountResponse receiverAccount = await stellarSdk.accounts - .account(txData['address'] as String).onError( - (error, stackTrace) => throw("Error getting account :: Cannot send transaction")); - KeyPair senderKeyPair = KeyPair.fromSecretSeed(secretSeed!); AccountResponse sender = await stellarSdk.accounts.account(senderKeyPair.accountId); - Transaction transaction = TransactionBuilder(sender) - .addOperation(PaymentOperationBuilder(receiverAccount.accountId, Asset.NATIVE, "100").build()) - .build(); - transaction.sign(senderKeyPair, Network.TESTNET); - SubmitTransactionResponse response = await stellarSdk.submitTransaction(transaction); + final amountToSend = txData['recipientAmt'] as Amount; - if (!response.success) { - throw("Unable to send transaction"); + //First check if account exists, can be skipped, but if the account does not exist, + // the transaction fee will be charged when the transaction fails. + bool validAccount = await accoutExists(txData['address'] as String); + Transaction transaction; + + if (!validAccount) { + //Fund the account, user must ensure account is correct + CreateAccountOperationBuilder createAccBuilder = + CreateAccountOperationBuilder( + txData['address'] as String, amountToSend.decimal.toString() + ); + transaction = TransactionBuilder(sender) + .addOperation(createAccBuilder.build()) + .build(); + } else { + transaction = TransactionBuilder(sender) + .addOperation(PaymentOperationBuilder( + txData['address'] as String, Asset.NATIVE, + amountToSend.decimal.toString()) + .build() + ).build(); } - return response.hash!; + transaction.sign(senderKeyPair, Network.TESTNET); + try { + SubmitTransactionResponse response = await stellarSdk.submitTransaction(transaction); + + if (!response.success) { + throw("Unable to send transaction"); + } + return response.hash!; + } catch(e, s) { + Logging.instance.log("Error sending TX $e - $s", level: LogLevel.Error); + rethrow; + } + } Future<SWAddress.Address?> get _currentReceivingAddress => @@ -460,68 +487,72 @@ class StellarWallet extends CoinServiceAPI ); for (OperationResponse response in payments.records!) { + PaymentOperationResponse por; if (response is PaymentOperationResponse) { - var por = response; + por = response; + + Logging.instance.log( + "ALL TRANSACTIONS IS $por", + level: LogLevel.Info); SWTransaction.TransactionType type; if (por.sourceAccount == await getAddressSW()) { type = SWTransaction.TransactionType.outgoing; } else { type = SWTransaction.TransactionType.incoming; } - final amount = Amount( - rawValue: BigInt.parse(float.parse(por.amount!).toStringAsFixed(7).replaceAll(".", "")), - fractionDigits: 7, -<<<<<<< HEAD - ); - int fee = 0; - int height = 0; - var transaction = por.transaction; - if (transaction != null) { - fee = transaction.feeCharged!; - height = transaction.ledger; - } - var theTransaction = SWTransaction.Transaction( - walletId: walletId, - txid: por.transactionHash!, - timestamp: DateTime.parse(por.createdAt!).millisecondsSinceEpoch ~/ 1000, - type: type, - subType: SWTransaction.TransactionSubType.none, - amount: 0, - amountString: amount.toJsonString(), - fee: fee, - height: height, - isCancelled: false, - isLelantus: false, - slateId: "", - otherData: "", - inputs: [], - outputs: [], - nonce: 0, - numberOfMessages: null, - ); - SWAddress.Address? receivingAddress = await _currentReceivingAddress; - SWAddress.Address address = type == SWTransaction.TransactionType.incoming - ? receivingAddress! - : SWAddress.Address( - walletId: walletId, - value: por.sourceAccount!, - publicKey: KeyPair.fromAccountId(por.sourceAccount!).publicKey, - derivationIndex: 0, - derivationPath: null, - type: SWAddress.AddressType.unknown, // TODO: set type - subType: SWAddress.AddressSubType.unknown - ); - Tuple2<SWTransaction.Transaction, SWAddress.Address> tuple = Tuple2(theTransaction, address); - transactionList.add(tuple); - } else if (response is CreateAccountOperationResponse) { - var caor = response; - SWTransaction.TransactionType type; - if (caor.sourceAccount == await getAddressSW()) { - type = SWTransaction.TransactionType.outgoing; - } else { - type = SWTransaction.TransactionType.incoming; - } final amount = Amount( + rawValue: BigInt.parse(float.parse(por.amount!).toStringAsFixed(coin.decimals).replaceAll(".", "")), + fractionDigits: coin.decimals, + ); + int fee = 0; + int height = 0; + var transaction = por.transaction; + if (transaction != null) { + fee = transaction.feeCharged!; + height = transaction.ledger; + } + var theTransaction = SWTransaction.Transaction( + walletId: walletId, + txid: por.transactionHash!, + timestamp: DateTime.parse(por.createdAt!).millisecondsSinceEpoch ~/ 1000, + type: type, + subType: SWTransaction.TransactionSubType.none, + amount: 0, + amountString: amount.toJsonString(), + fee: fee, + height: height, + isCancelled: false, + isLelantus: false, + slateId: "", + otherData: "", + inputs: [], + outputs: [], + nonce: 0, + numberOfMessages: null, + ); + SWAddress.Address? receivingAddress = await _currentReceivingAddress; + SWAddress.Address address = type == SWTransaction.TransactionType.incoming + ? receivingAddress! + : SWAddress.Address( + walletId: walletId, + value: por.sourceAccount!, + publicKey: KeyPair.fromAccountId(por.sourceAccount!).publicKey, + derivationIndex: 0, + derivationPath: null, + type: SWAddress.AddressType.unknown, // TODO: set type + subType: SWAddress.AddressSubType.unknown, + ); + Tuple2<SWTransaction.Transaction, SWAddress.Address> tuple = Tuple2(theTransaction, address); + transactionList.add(tuple); + } else if (response is CreateAccountOperationResponse) { + var caor = response; + SWTransaction.TransactionType type; + if (caor.sourceAccount == await getAddressSW()) { + type = SWTransaction.TransactionType.outgoing; + } else { + type = SWTransaction.TransactionType.incoming; + } + final amount = Amount( rawValue: BigInt.parse(float.parse(caor.startingBalance!).toStringAsFixed(coin.decimals).replaceAll(".", "")), fractionDigits: coin.decimals, ); @@ -572,37 +603,43 @@ class StellarWallet extends CoinServiceAPI Logging.instance.log( "Exception rethrown from updateTransactions(): $e\n$s", level: LogLevel.Error); - rethrow; } } Future<void> updateBalance() async { - AccountResponse accountResponse = await stellarSdk.accounts.account(await getAddressSW()); - for (Balance balance in accountResponse.balances) { - switch (balance.assetType) { - case Asset.TYPE_NATIVE: - _balance = SWBalance.Balance( - total: Amount( - rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee - fractionDigits: 7, - ), - spendable: Amount( - rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee - fractionDigits: 7, - ), - blockedTotal: Amount( - rawValue: BigInt.from(0), - fractionDigits: 7, - ), - pendingSpendable: Amount( - rawValue: BigInt.from(0), - fractionDigits: 7, - ), - ); - Logging.instance.log(_balance, level: LogLevel.Info); - await updateCachedBalance(_balance!); + try { + AccountResponse accountResponse = await stellarSdk.accounts.account(await getAddressSW()); + for (Balance balance in accountResponse.balances) { + switch (balance.assetType) { + case Asset.TYPE_NATIVE: + _balance = SWBalance.Balance( + total: Amount( + rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee + fractionDigits: coin.decimals, + ), + spendable: Amount( + rawValue: BigInt.from(float.parse(balance.balance) * 10000000 - 10000000), // Minus 1 XLM for account activation fee + fractionDigits: coin.decimals, + ), + blockedTotal: Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ), + pendingSpendable: Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ), + ); + Logging.instance.log(_balance, level: LogLevel.Info); + await updateCachedBalance(_balance!); + } } + } catch(e, s) { + Logging.instance.log( + "ERROR GETTING BALANCE $e\$s", + level: LogLevel.Info, + ); } } @@ -697,9 +734,38 @@ class StellarWallet extends CoinServiceAPI } @override - Future<void> updateSentCachedTxData(Map<String, dynamic> txData) { - // TODO: implement updateSentCachedTxData - throw UnimplementedError(); + Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async { + final transaction = SWTransaction.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: SWTransaction.TransactionType.outgoing, + subType: SWTransaction.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, + inputs: [], + outputs: [], + numberOfMessages: null, + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } @override