From 89716605c585de06461ced4772a55e43451e8125 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 27 Aug 2021 05:23:12 -0300 Subject: [PATCH 1/2] Implemented NEW createTokenAccountForDestinationOwner --- base/lib/base/kin_account_context.dart | 34 +++++-- base/lib/base/models/solana/programs.dart | 2 +- base/lib/base/models/solana/transaction.dart | 6 +- .../base/network/api/agora/agora_v4_apis.dart | 9 +- .../base/network/services/kin_service.dart | 7 +- .../network/services/kin_service_impl.dart | 10 +- .../network/services/kin_service_impl_v4.dart | 98 +++++++++++++++---- base/lib/base/storage/kin_file_storage.dart | 2 +- base/lib/base/tools/extensions.dart | 2 +- .../example_console/bin/example_features.dart | 13 +-- 10 files changed, 141 insertions(+), 42 deletions(-) diff --git a/base/lib/base/kin_account_context.dart b/base/lib/base/kin_account_context.dart index a717f6a..f83c1e9 100644 --- a/base/lib/base/kin_account_context.dart +++ b/base/lib/base/kin_account_context.dart @@ -24,13 +24,15 @@ import 'models/kin_memo.dart'; import 'models/kin_payment.dart'; import 'models/kin_payment_item.dart'; import 'models/quark_amount.dart'; +import 'models/solana/instruction.dart'; import 'models/transaction_hash.dart'; import 'network/services/app_info_providers.dart'; import 'storage/storage.dart'; import 'tools/executor_service.dart'; import 'tools/extensions.dart'; - +import 'network/api/agora/model_to_proto_v4.dart'; +import 'network/api/agora/proto_to_model_v4.dart' hide ModelInvoiceListExtension; enum ObservationMode { Passive, @@ -53,6 +55,8 @@ abstract class KinAccountReadOperations extends KinAccountReadOperationsAltIdiom Future getAccount({ bool forceUpdate = false , Callback? accountCallback }) ; + Future getAccountUpdated() ; + } abstract class KinPaymentReadOperationsAltIdioms { @@ -419,6 +423,7 @@ class KinAccountContextBase implements KinAccountReadOperations , KinPaymentRead } } + @override Future getAccountUpdated() async { var accountUpdated = await maybeFetchAccountDetails() ; var accountStatus = accountUpdated!.status; @@ -655,7 +660,7 @@ class KinAccountContextImpl extends KinAccountContextBase with KinAccountContext return _sendKinPaymentsImpl(payments, memo); } - Future> _sendKinPaymentsImpl(List payments, KinMemo? memo, { AccountSpec? sourceAccountSpec, AccountSpec? destinationAccountSpec , QuarkAmount? feeOverride}) async { + Future> _sendKinPaymentsImpl(List payments, KinMemo? memo, { AccountSpec sourceAccountSpec = AccountSpec.Preferred, AccountSpec destinationAccountSpec = AccountSpec.Preferred, QuarkAmount? feeOverride}) async { log!.log("sendKinPayments"); memo ??= KinMemo.none; @@ -678,7 +683,7 @@ class KinAccountContextImpl extends KinAccountContextBase with KinAccountContext for (var attemptCount = 0; attemptCount < MAX_ATTEMPTS; ++attemptCount) { try { var ret = await executors.parallelIO.execute(() async { - return await sendKinTransaction(() => _buildPaymentTransaction(payments, memo, sourceAccountSpec, destinationAccountSpec, feeOverride, attemptCount)); + return await sendKinTransaction(() => _buildPaymentTransaction(payments, memo, sourceAccountSpec, destinationAccountSpec, feeOverride, attemptCount, lastError)); }); return ret ?? [] ; } @@ -709,7 +714,7 @@ class KinAccountContextImpl extends KinAccountContextBase with KinAccountContext throw lastError! ; } - Future _buildPaymentTransaction(List payments, KinMemo? memo, AccountSpec? sourceAccountSpec, AccountSpec? destinationAccountSpec , QuarkAmount? feeOverride, int attemptCount,) async { + Future _buildPaymentTransaction(List payments, KinMemo? memo, AccountSpec? sourceAccountSpec, AccountSpec? destinationAccountSpec , QuarkAmount? feeOverride, int attemptCount, Error? lastError) async { var account = await getAccount(); log!.log('_buildPaymentTransaction> account: $account'); @@ -735,25 +740,38 @@ class KinAccountContextImpl extends KinAccountContextBase with KinAccountContext ); } + var createAccountInstructions = []; + var additionalSigners = []; + List paymentItems ; if (attemptCount == 0 || destinationAccountSpec == AccountSpec.Exact) { paymentItems = payments ; } else { paymentItems = await Future.wait( payments.map((paymentItem) async { var destinationTokenAccounts = await service.resolveTokenAccounts(paymentItem.destinationAccount) ; - return paymentItem.copy(destinationAccount: destinationTokenAccounts.first.asKinAccountId()); + + if (destinationTokenAccounts.isEmpty && lastError == KinServiceResponseType.invoiceError) { + var ret = await service.createTokenAccountForDestinationOwner(paymentItem.destinationAccount.toProtoSolanaAccountId().toPublicKey()); + var destAccountInstructions = ret.first; + var signer = ret.second; + createAccountInstructions.addAll(destAccountInstructions); + additionalSigners.add(signer); + return paymentItem.copy(destinationAccount: signer.asKinAccountId()); + } + else { + return paymentItem.copy(destinationAccount: destinationTokenAccounts.first.asKinAccountId()); + } })); } - var fee = await calculateFee(payments.length); - var transaction = service.buildAndSignTransaction( sourceAccount.ownerKey, sourceAccount.sourceKey, sourceAccount.nonce, paymentItems, memo, - feeOverride ?? fee + createAccountInstructions, + additionalSigners ); if (transaction is StellarKinTransaction) { diff --git a/base/lib/base/models/solana/programs.dart b/base/lib/base/models/solana/programs.dart index 3ff3fb5..c531c2b 100644 --- a/base/lib/base/models/solana/programs.dart +++ b/base/lib/base/models/solana/programs.dart @@ -173,7 +173,7 @@ class CreateAccount { class TokenProgram { // Reference: https://github.com/solana-labs/solana-program-library/blob/11b1e3eefdd4e523768d63f7c70a7aa391ea0d02/token/program/src/state.rs#L125 - int accountSize = 165; + static const int accountSize = 165; // ProgramKey is the address of the token program that should be used. // diff --git a/base/lib/base/models/solana/transaction.dart b/base/lib/base/models/solana/transaction.dart index 0f04442..ab77e7b 100644 --- a/base/lib/base/models/solana/transaction.dart +++ b/base/lib/base/models/solana/transaction.dart @@ -169,7 +169,7 @@ class Transaction { ); static Transaction newTransaction( - PublicKey payer, List instructions) { + PublicKey payer, List instructions) { final accounts = [ AccountMeta( publicKey: payer, @@ -181,7 +181,7 @@ class Transaction { // Extract all of the unique accounts from the instructions. instructions.forEach((element) { - accounts.add(AccountMeta(publicKey: element!.program, isProgram: true)); + accounts.add(AccountMeta(publicKey: element.program, isProgram: true)); accounts.addAll(element.accounts!); }); @@ -207,7 +207,7 @@ class Transaction { final messageInstructions = instructions.map((e) { return CompiledInstruction( - programIndex: _indexOf(accountPublicKeys, e!.program), + programIndex: _indexOf(accountPublicKeys, e.program), data: e.data, accounts: Uint8List.fromList(e.accounts!.map((e2) => _indexOf(accountPublicKeys, e2.publicKey)).toList()), ); diff --git a/base/lib/base/network/api/agora/agora_v4_apis.dart b/base/lib/base/network/api/agora/agora_v4_apis.dart index 4187e9f..2f7e2b0 100644 --- a/base/lib/base/network/api/agora/agora_v4_apis.dart +++ b/base/lib/base/network/api/agora/agora_v4_apis.dart @@ -4,6 +4,7 @@ import 'package:grpc/grpc.dart'; import 'package:kin_base/base/models/invoices.dart'; import 'package:kin_base/base/models/key.dart'; import 'package:kin_base/base/models/kin_account.dart'; +import 'package:kin_base/base/models/kin_amount.dart'; import 'package:kin_base/base/models/quark_amount.dart'; import 'package:kin_base/base/models/solana/transaction.dart'; import 'package:kin_base/base/models/stellar_base_type_conversions.dart'; @@ -240,7 +241,13 @@ class AgoraKinTransactionsApiV4 extends GrpcApi implements KinTransactionApiV4 { @override Future> submitTransaction(Transaction transaction, InvoiceList? invoiceList) async { - var amount = transaction.totalAmount ; + KinAmount amount ; + try { + amount = transaction.totalAmount; + } catch(e){ + amount = KinAmount.one; + } + var commitment ; if (amount.value < Decimal.fromInt(50000) ) { // ~1 $USD diff --git a/base/lib/base/network/services/kin_service.dart b/base/lib/base/network/services/kin_service.dart index 0464d76..2d57fbc 100644 --- a/base/lib/base/network/services/kin_service.dart +++ b/base/lib/base/network/services/kin_service.dart @@ -5,14 +5,18 @@ import 'package:kin_base/base/models/kin_account.dart'; import 'package:kin_base/base/models/kin_memo.dart'; import 'package:kin_base/base/models/kin_payment_item.dart'; import 'package:kin_base/base/models/quark_amount.dart'; +import 'package:kin_base/base/models/solana/instruction.dart'; import 'package:kin_base/base/models/transaction_hash.dart'; import 'package:kin_base/base/stellar/models/kin_transaction.dart'; import 'package:kin_base/base/stellar/models/paging_token.dart'; +import 'package:kin_base/base/tools/cache.dart'; import 'package:kin_base/base/tools/observers.dart'; abstract class KinService { Future createAccount(KinAccountId accountId, PrivateKey signer) ; + Future, PrivateKey>> createTokenAccountForDestinationOwner(PublicKey owner) ; + Future getAccount(KinAccountId accountId) ; Future> resolveTokenAccounts(KinAccountId accountId) ; @@ -37,7 +41,8 @@ abstract class KinService { int nonce, List paymentItems, KinMemo? memo, - QuarkAmount fee + List createAccountInstructions, + List additionalSigners ) ; Future submitTransaction(KinTransaction transaction) ; diff --git a/base/lib/base/network/services/kin_service_impl.dart b/base/lib/base/network/services/kin_service_impl.dart index 486da54..b4fec91 100644 --- a/base/lib/base/network/services/kin_service_impl.dart +++ b/base/lib/base/network/services/kin_service_impl.dart @@ -7,6 +7,7 @@ import 'package:kin_base/base/models/kin_account.dart'; import 'package:kin_base/base/models/kin_memo.dart'; import 'package:kin_base/base/models/kin_payment_item.dart'; import 'package:kin_base/base/models/quark_amount.dart'; +import 'package:kin_base/base/models/solana/instruction.dart'; import 'package:kin_base/base/models/transaction_hash.dart'; import 'package:kin_base/base/network/api/kin_transaction_api.dart'; import 'package:kin_base/base/network/api/kin_account_api.dart'; @@ -16,6 +17,7 @@ import 'package:kin_base/base/network/api/kin_transaction_whitelisting_api.dart' import 'package:kin_base/base/stellar/models/kin_transaction.dart'; import 'package:kin_base/base/stellar/models/network_environment.dart'; import 'package:kin_base/base/stellar/models/paging_token.dart'; +import 'package:kin_base/base/tools/cache.dart'; import 'package:kin_base/base/tools/kin_logger.dart'; import 'package:kin_base/base/tools/network_operations_handler.dart'; import 'package:kin_base/base/tools/observers.dart'; @@ -44,7 +46,9 @@ class KinServiceImpl extends KinService { this.logger); @override - Future buildAndSignTransaction(PrivateKey ownerKey, PublicKey sourceKey, int nonce, List paymentItems, KinMemo? memo, QuarkAmount fee) { + Future buildAndSignTransaction(PrivateKey ownerKey, PublicKey sourceKey, int nonce, List paymentItems, KinMemo? memo, + List createAccountInstructions, + List additionalSigners) { // TODO: implement buildAndSignTransaction throw UnimplementedError(); } @@ -64,6 +68,10 @@ class KinServiceImpl extends KinService { throw UnimplementedError(); } + Future, PrivateKey>> createTokenAccountForDestinationOwner(PublicKey owner) { + throw UnimplementedError(); + } + @override Future getAccount(KinAccountId accountId) { // TODO: implement getAccount diff --git a/base/lib/base/network/services/kin_service_impl_v4.dart b/base/lib/base/network/services/kin_service_impl_v4.dart index 4a5e5fd..7c0b2ca 100644 --- a/base/lib/base/network/services/kin_service_impl_v4.dart +++ b/base/lib/base/network/services/kin_service_impl_v4.dart @@ -6,6 +6,7 @@ import 'package:kin_base/base/models/kin_account.dart'; import 'package:kin_base/base/models/kin_memo.dart'; import 'package:kin_base/base/models/kin_payment_item.dart'; import 'package:kin_base/base/models/quark_amount.dart'; +import 'package:kin_base/base/models/solana/instruction.dart'; import 'package:kin_base/base/models/solana/programs.dart'; import 'package:kin_base/base/models/solana/transaction.dart'; import 'package:kin_base/base/models/stellar_base_type_conversions.dart'; @@ -74,7 +75,7 @@ class KinServiceImplV4 extends KinService { Future?> _cachedMinRentExemption() async { return await _cache.resolve("minRentExemption", timeoutOverride: Duration(minutes: 30), fault: (key) { return networkOperationsHandler.queueWork('KinServiceImplV4._cachedMinRentExemption', () async { - return await transactionApi.getMinimumBalanceForRentExemption( TokenProgram().accountSize ); + return await transactionApi.getMinimumBalanceForRentExemption( TokenProgram.accountSize ); }); }); } @@ -134,41 +135,95 @@ class KinServiceImplV4 extends KinService { }); } + Future, PrivateKey>> createTokenAccountForDestinationOwner(PublicKey owner) async{ + ServiceConfig serviceConfig ; + int minRentExemption ; + try { + var ret = await Future.wait([ + _cachedServiceConfig(), + _cachedMinRentExemption(), + ]); + + serviceConfig = ret[0]!.payload as ServiceConfig; + minRentExemption = ret[1]!.payload as int; + } + catch(e) { + throw StateError("Pre-requisite response failed! $e"); + } + + var subsidizer = serviceConfig.subsidizerAccount.toKeyPair().asPublicKey(); + var programKey = serviceConfig.tokenProgram.toKeyPair().asPublicKey(); + var token = serviceConfig.token.toKeyPair().asPublicKey(); + + return createTokenAccountForDest( + owner, + subsidizer, + programKey, + token, + minRentExemption + ); + } + + Pair, PrivateKey> createTokenAccountForDest( + PublicKey dest, + PublicKey subsidizer, + PublicKey programKey, + PublicKey token, + int lamports, { + int accountSize = TokenProgram.accountSize, + }) { + var ephemeralKeypair = PrivateKey.random(); + var pub = ephemeralKeypair.asPublicKey(); + + return Pair([ + CreateAccount(subsidizer, pub, programKey, lamports, accountSize) + .instruction!, + TokenProgramInitializeAccount(pub, token, pub, programKey).instruction!, + SetAuthority(pub, pub, subsidizer, + TokenProgramAuthorityTypeCloseAccount(), programKey) + .instruction!, + SetAuthority(pub, pub, dest, TokenProgramAuthorityTypeAccountHolder(), + programKey) + .instruction!, + ], ephemeralKeypair.toSigningKeyPair().asPrivateKey()); + } + @override - Future buildAndSignTransaction(PrivateKey ownerKey, PublicKey sourceKey, int nonce, List paymentItems, KinMemo? memo, QuarkAmount fee) { - log!.log("buildAndSignTransaction: ownerKey: $ownerKey ; sourceKey: $sourceKey ; nonce: $nonce ; paymentItems: $paymentItems ; memo: $memo ; fee:$fee"); + Future buildAndSignTransaction(PrivateKey ownerKey, PublicKey sourceKey, int nonce, List paymentItems, KinMemo? memo, + List createAccountInstructions, + List additionalSigners) { + log!.log("buildAndSignTransaction: ownerKey: $ownerKey ; sourceKey: $sourceKey ; nonce: $nonce ; paymentItems: $paymentItems ; memo: $memo ; createAccountInstructions:$createAccountInstructions ; additionalSigners: $additionalSigners"); return networkOperationsHandler.queueWork('buildAndSignTransaction', () async { - ServiceConfig? serviceConfig ; - Hash? recentBlockHash ; + ServiceConfig serviceConfig ; + Hash recentBlockHash ; try { var ret = await Future.wait([ _cachedServiceConfig(), _cachedRecentBlockHash(), ]); - serviceConfig = ret[0]!.payload as ServiceConfig?; - recentBlockHash = ret[1]!.payload as Hash?; + serviceConfig = ret[0]!.payload as ServiceConfig; + recentBlockHash = ret[1]!.payload as Hash; } catch(e) { - StateError("Pre-requisite response failed! $e"); + throw StateError("Pre-requisite response failed! $e"); } var ownerAccount = ownerKey.asPublicKey(); - PublicKey subsidizer = serviceConfig!.subsidizerAccount.toKeyPair().asPublicKey() ; + PublicKey subsidizer = serviceConfig.subsidizerAccount.toKeyPair().asPublicKey() ; var programKey = serviceConfig.tokenProgram.toKeyPair().asPublicKey(); var paymentInstructions = paymentItems.map((paymentItem) { var destinationAccount = paymentItem.destinationAccount.toKeyPair().asPublicKey(); - return TokenProgramTransfer( sourceKey, destinationAccount, ownerAccount, paymentItem.amount, programKey = programKey - ).instruction ; - }); + ).instruction! ; + }).toList(); var memoInstruction = memo != KinMemo.none ? (memo!.type is KinMemoTypeNoEncoding @@ -179,8 +234,14 @@ class KinServiceImplV4 extends KinService { var tx = Transaction.newTransaction( subsidizer, - [memoInstruction, ...(paymentInstructions.toList())].whereNotNull(), - ).copyAndSetRecentBlockhash(recentBlockHash).copyAndSign([ownerKey]); + [ + memoInstruction, + ...createAccountInstructions, + ...paymentInstructions, + ].whereNotNull(), + ) + .copyAndSetRecentBlockhash(recentBlockHash) + .copyAndSign([ownerKey, ...additionalSigners]); var kinTransaction = SolanaKinTransaction( tx.marshal(), null, @@ -188,7 +249,6 @@ class KinServiceImplV4 extends KinService { paymentItems.toInvoiceList() ); - log!.log('serviceConfig: $serviceConfig'); log!.log('recentBlockHash: $recentBlockHash'); log!.log('ownerAccount: $ownerAccount'); @@ -248,21 +308,21 @@ class KinServiceImplV4 extends KinService { tokenAccountPub, programKey, minRentExemption, - TokenProgram().accountSize, - ).instruction, + TokenProgram.accountSize, + ).instruction!, TokenProgramInitializeAccount( tokenAccountPub, mint, owner, programKey, - ).instruction, + ).instruction!, SetAuthority( tokenAccountPub, owner, subsidizer, TokenProgramAuthorityTypeCloseAccount(), programKey, - ).instruction + ).instruction! ]).copyAndSetRecentBlockhash(recentBlockHash).copyAndSign( [tokenAccount, signer]); diff --git a/base/lib/base/storage/kin_file_storage.dart b/base/lib/base/storage/kin_file_storage.dart index 3eee90d..b1f9b5f 100644 --- a/base/lib/base/storage/kin_file_storage.dart +++ b/base/lib/base/storage/kin_file_storage.dart @@ -252,7 +252,7 @@ class KinFileStorage implements Storage { var storedTransactions = await getStoredTransactions(accountId); var items = storedTransactions?.items ?? [] ; - var list = [newTransaction , ...items].whereNotNull(); + var list = [newTransaction , ...items].whereNotNull(); return storeTransactions(accountId, list); } diff --git a/base/lib/base/tools/extensions.dart b/base/lib/base/tools/extensions.dart index 74d78bc..a61f20d 100644 --- a/base/lib/base/tools/extensions.dart +++ b/base/lib/base/tools/extensions.dart @@ -85,7 +85,7 @@ extension ListExtension on List { return this ; } - List whereNotNull() => where((e) => e != null).toList(); + List whereNotNull() => where((e) => e != null).whereType().toList(); T? get firstOrNull => isEmpty ? null : first ; } diff --git a/examples/example_console/bin/example_features.dart b/examples/example_console/bin/example_features.dart index 3c584b4..bfddfba 100644 --- a/examples/example_console/bin/example_features.dart +++ b/examples/example_console/bin/example_features.dart @@ -25,8 +25,7 @@ void main(List args) async { production, appIndex, 'Example App', - storageLocation: - '/tmp/kin-flutter-example-${DateTime.now().millisecondsSinceEpoch}', + //storageLocation: '/tmp/kin-flutter-example-${DateTime.now().millisecondsSinceEpoch}', ); print(kin); @@ -58,13 +57,15 @@ void main(List args) async { kin.setContextByAccountID(accountId); - var account = await kin.getKinContext()!.getAccount(); + var account = await kin.getKinContext()!.getAccountUpdated(); print('Current context account: $account'); - print('Current context balance: ${account?.balance}'); + print('Current context balance: ${account.balance}'); - var sentPayment = await submitTransaction( - kin, '3RXbFoTTTHHKXu2MikKz8NWbGLnV5PfbcTaQR8Z7oxME', 0.10); + //await Future.delayed(Duration(seconds: 10)); + + var sentPayment = await submitTransaction(kin, '3RXbFoTTTHHKXu2MikKz8NWbGLnV5PfbcTaQR8Z7oxME', 0.10); + //var sentPayment = await submitTransaction(kin, '26toq28ewEfQZxXZh7MrZTkP3aTKkWemG2PQNg6TixXr', 0.10); if (sentPayment != null) { showPaymentsForAccount(kin, accountId.base58Encode()); From 18e4e89f45d5101420ba3d700b3c2733e89ba780 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 9 Dec 2021 18:02:02 -0600 Subject: [PATCH 2/2] why check error? if there's no tok account create --- base/lib/base/kin_account_context.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/lib/base/kin_account_context.dart b/base/lib/base/kin_account_context.dart index 928f2a0..068c034 100644 --- a/base/lib/base/kin_account_context.dart +++ b/base/lib/base/kin_account_context.dart @@ -750,7 +750,7 @@ class KinAccountContextImpl extends KinAccountContextBase with KinAccountContext paymentItems = await Future.wait( payments.map((paymentItem) async { var destinationTokenAccounts = await service.resolveTokenAccounts(paymentItem.destinationAccount) ; - if (destinationTokenAccounts.isEmpty && lastError == KinServiceResponseType.invoiceError) { + if (destinationTokenAccounts.isEmpty) { var ret = await service.createTokenAccountForDestinationOwner(paymentItem.destinationAccount.toProtoSolanaAccountId().toPublicKey()); var destAccountInstructions = ret.first; var signer = ret.second;