Skip to content

Commit

Permalink
Merge pull request #68 from kinnytips/createTokenAccountForDestinatio…
Browse files Browse the repository at this point in the history
…nOwner

Implemented NEW createTokenAccountForDestinationOwner
  • Loading branch information
mocolicious authored Dec 10, 2021
2 parents eb0d631 + 18e4e89 commit 3a55b12
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 37 deletions.
34 changes: 26 additions & 8 deletions base/lib/base/kin_account_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -53,6 +55,8 @@ abstract class KinAccountReadOperations extends KinAccountReadOperationsAltIdiom

Future<KinAccount?> getAccount({ bool forceUpdate = false , Callback<KinAccount?>? accountCallback }) ;

Future<KinAccount> getAccountUpdated() ;

}

abstract class KinPaymentReadOperationsAltIdioms {
Expand Down Expand Up @@ -419,6 +423,7 @@ class KinAccountContextBase implements KinAccountReadOperations , KinPaymentRead
}
}

@override
Future<KinAccount> getAccountUpdated() async {
var accountUpdated = await maybeFetchAccountDetails() ;
var accountStatus = accountUpdated!.status;
Expand Down Expand Up @@ -655,7 +660,7 @@ class KinAccountContextImpl extends KinAccountContextBase with KinAccountContext
return _sendKinPaymentsImpl(payments, memo);
}

Future<List<KinPayment>> _sendKinPaymentsImpl(List<KinPaymentItem> payments, KinMemo? memo, { AccountSpec? sourceAccountSpec, AccountSpec? destinationAccountSpec , QuarkAmount? feeOverride}) async {
Future<List<KinPayment>> _sendKinPaymentsImpl(List<KinPaymentItem> payments, KinMemo? memo, { AccountSpec sourceAccountSpec = AccountSpec.Preferred, AccountSpec destinationAccountSpec = AccountSpec.Preferred, QuarkAmount? feeOverride}) async {
log!.log("sendKinPayments");

memo ??= KinMemo.none;
Expand All @@ -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 ?? <KinPayment>[] ;
}
Expand Down Expand Up @@ -709,7 +714,7 @@ class KinAccountContextImpl extends KinAccountContextBase with KinAccountContext
throw lastError! ;
}

Future<KinTransaction> _buildPaymentTransaction(List<KinPaymentItem> payments, KinMemo? memo, AccountSpec? sourceAccountSpec, AccountSpec? destinationAccountSpec , QuarkAmount? feeOverride, int attemptCount,) async {
Future<KinTransaction> _buildPaymentTransaction(List<KinPaymentItem> payments, KinMemo? memo, AccountSpec? sourceAccountSpec, AccountSpec? destinationAccountSpec , QuarkAmount? feeOverride, int attemptCount, Error? lastError) async {
var account = await getAccount();

log!.log('_buildPaymentTransaction> account: $account');
Expand All @@ -735,25 +740,38 @@ class KinAccountContextImpl extends KinAccountContextBase with KinAccountContext
);
}

var createAccountInstructions = <Instruction>[];
var additionalSigners = <PrivateKey>[];

List<KinPaymentItem> 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) {
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) {
Expand Down
2 changes: 1 addition & 1 deletion base/lib/base/models/solana/programs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
4 changes: 2 additions & 2 deletions base/lib/base/models/solana/transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class Transaction {
);

static Transaction newTransaction(
PublicKey payer, List<Instruction?> instructions) {
PublicKey payer, List<Instruction> instructions) {
final accounts = [
AccountMeta(
publicKey: payer,
Expand Down Expand Up @@ -213,7 +213,7 @@ class Transaction {

final messageInstructions = instructions.map((e) {
return CompiledInstruction(
programIndex: _indexOf(accountPublicKeys, e!.program),
programIndex: _indexOf(accountPublicKeys, e.program),
data: e.data,
accounts: e.accounts != null ? Uint8List.fromList(e.accounts!.map((e2) => _indexOf(accountPublicKeys, e2.publicKey)).toList()) : Uint8List.fromList(List.empty()),
);
Expand Down
9 changes: 8 additions & 1 deletion base/lib/base/network/api/agora/agora_v4_apis.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -240,7 +241,13 @@ class AgoraKinTransactionsApiV4 extends GrpcApi implements KinTransactionApiV4 {

@override
Future<KinServiceInvoiceResponse<KinTransaction>> 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
Expand Down
7 changes: 6 additions & 1 deletion base/lib/base/network/services/kin_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<KinAccount?> createAccount(KinAccountId accountId, PrivateKey signer) ;

Future<Pair<List<Instruction>, PrivateKey>> createTokenAccountForDestinationOwner(PublicKey owner) ;

Future<KinAccount?> getAccount(KinAccountId accountId) ;

Future<List<PublicKey>> resolveTokenAccounts(KinAccountId accountId) ;
Expand All @@ -37,7 +41,8 @@ abstract class KinService {
int nonce,
List<KinPaymentItem> paymentItems,
KinMemo? memo,
QuarkAmount fee
List<Instruction> createAccountInstructions,
List<PrivateKey> additionalSigners
) ;

Future<KinTransaction?> submitTransaction(KinTransaction transaction) ;
Expand Down
10 changes: 9 additions & 1 deletion base/lib/base/network/services/kin_service_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -44,7 +46,9 @@ class KinServiceImpl extends KinService {
this.logger);

@override
Future<KinTransaction> buildAndSignTransaction(PrivateKey ownerKey, PublicKey sourceKey, int nonce, List<KinPaymentItem> paymentItems, KinMemo? memo, QuarkAmount fee) {
Future<KinTransaction> buildAndSignTransaction(PrivateKey ownerKey, PublicKey sourceKey, int nonce, List<KinPaymentItem> paymentItems, KinMemo? memo,
List<Instruction> createAccountInstructions,
List<PrivateKey> additionalSigners) {
// TODO: implement buildAndSignTransaction
throw UnimplementedError();
}
Expand All @@ -64,6 +68,10 @@ class KinServiceImpl extends KinService {
throw UnimplementedError();
}

Future<Pair<List<Instruction>, PrivateKey>> createTokenAccountForDestinationOwner(PublicKey owner) {
throw UnimplementedError();
}

@override
Future<KinAccount> getAccount(KinAccountId accountId) {
// TODO: implement getAccount
Expand Down
98 changes: 79 additions & 19 deletions base/lib/base/network/services/kin_service_impl_v4.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -74,7 +75,7 @@ class KinServiceImplV4 extends KinService {
Future<KinServiceResponse<int>?> _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 );
});
});
}
Expand Down Expand Up @@ -134,41 +135,95 @@ class KinServiceImplV4 extends KinService {
});
}

Future<Pair<List<Instruction>, 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<List<Instruction>, 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<KinTransaction> buildAndSignTransaction(PrivateKey ownerKey, PublicKey sourceKey, int nonce, List<KinPaymentItem> paymentItems, KinMemo? memo, QuarkAmount fee) {
log!.log("buildAndSignTransaction: ownerKey: $ownerKey ; sourceKey: $sourceKey ; nonce: $nonce ; paymentItems: $paymentItems ; memo: $memo ; fee:$fee");
Future<KinTransaction> buildAndSignTransaction(PrivateKey ownerKey, PublicKey sourceKey, int nonce, List<KinPaymentItem> paymentItems, KinMemo? memo,
List<Instruction> createAccountInstructions,
List<PrivateKey> 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
Expand All @@ -179,16 +234,21 @@ class KinServiceImplV4 extends KinService {

var tx = Transaction.newTransaction(
subsidizer,
[memoInstruction, ...(paymentInstructions.toList())].whereNotNull(),
).copyAndSetRecentBlockhash(recentBlockHash).copyAndSign([ownerKey]);
[
memoInstruction,
...createAccountInstructions,
...paymentInstructions,
].whereNotNull<Instruction>(),
)
.copyAndSetRecentBlockhash(recentBlockHash)
.copyAndSign([ownerKey, ...additionalSigners]);

var kinTransaction = SolanaKinTransaction(
tx.marshal(), null,
networkEnvironment,
paymentItems.toInvoiceList()
);


log!.log('serviceConfig: $serviceConfig');
log!.log('recentBlockHash: $recentBlockHash');
log!.log('ownerAccount: $ownerAccount');
Expand Down Expand Up @@ -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]);

Expand Down
Loading

0 comments on commit 3a55b12

Please sign in to comment.