Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

Commit

Permalink
Merge pull request #28 from kinecosystem/sender-create
Browse files Browse the repository at this point in the history
Sender create
  • Loading branch information
samdowd authored Sep 3, 2021
2 parents 5d6cb97 + f540694 commit 1bbadbd
Show file tree
Hide file tree
Showing 16 changed files with 120 additions and 35 deletions.
2 changes: 1 addition & 1 deletion KinBase.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#
Pod::Spec.new do |s|
s.name = 'KinBase'
s.version = '2.0.0'
s.version = '2.1.0'
s.summary = 'Kin SDK for iOS'

s.description = <<-DESC
Expand Down
4 changes: 2 additions & 2 deletions KinBase/KinBase.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1799,7 +1799,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-l\"c++\"",
Expand Down Expand Up @@ -1840,7 +1840,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.1.0;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = (
"$(inherited)",
Expand Down
31 changes: 22 additions & 9 deletions KinBase/KinBase/Src/KinAccountContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ extension KinAccountContext: KinPaymentWriteOperations {
var attemptNumber = 0
let invalidAccountErrorRetryStrategy = BackoffStrategy.fixed(after: 3, maxAttempts: MAX_ATTEMPTS)

func buildAttempt() -> Promise<KinTransaction> {
func buildAttempt(error: KinServiceV4.Errors? = nil) -> Promise<KinTransaction> {
all(self.getAccount(), self.getFee())
.then(on: self.dispatchQueue) { account, fee -> Promise<KinTransaction> in
var sourceAccountPromise: Promise<SourceAccountSigningData>
Expand Down Expand Up @@ -453,6 +453,9 @@ extension KinAccountContext: KinPaymentWriteOperations {
}

var paymentItemsPromise: Promise<[KinPaymentItem]>

var createAccountInstructions = [Instruction]()
var additionalSigners = [KeyPair]()

if attemptNumber == 0 || destinationAccountSpec == .exact {
paymentItemsPromise = Promise { payments }
Expand All @@ -461,7 +464,16 @@ extension KinAccountContext: KinPaymentWriteOperations {
payments.map { paymentItem in
self.service.resolveTokenAccounts(account: paymentItem.destAccount)
.then {
paymentItem.copy(destAccount: $0.first?.publicKey)
if $0.isEmpty && error == KinServiceV4.Errors.invalidAccount {
return self.service.createTokenAccountForDestination(account: paymentItem.destAccount)
.then { (instructions: [Instruction], newKeypair: KeyPair) in
createAccountInstructions.append(contentsOf: instructions)
additionalSigners.append(newKeypair)
return Promise(paymentItem.copy(destAccount: newKeypair.asPublicKey()))
}
} else {
return Promise(paymentItem.copy(destAccount: $0.first?.publicKey))
}
}
.recover { _ in Promise { paymentItem } }
}
Expand All @@ -478,17 +490,19 @@ extension KinAccountContext: KinPaymentWriteOperations {
nonce: accountData.nonce,
paymentItems: it,
memo: memo,
fee: fee // feeOverride ?: fee
fee: fee,
createAccountInstructions: createAccountInstructions,
additionalSigners: additionalSigners
)
}
}
}
}
}

func attempt() -> Promise<[KinPayment]> {
func attempt(error: KinServiceV4.Errors? = nil) -> Promise<[KinPayment]> {
func buildSignSubmit() -> Promise<KinTransaction> {
return buildAttempt().then { signedTransaction -> Promise<KinTransaction> in
return buildAttempt(error: error).then { signedTransaction -> Promise<KinTransaction> in
guard let transaction: KinTransaction = try? KinTransaction(envelopeXdrBytes: signedTransaction.envelopeXdrBytes, record: signedTransaction.record, network: signedTransaction.network, invoiceList: invoiceList) else {
return Promise(Errors.unknown)
}
Expand Down Expand Up @@ -530,11 +544,10 @@ extension KinAccountContext: KinPaymentWriteOperations {
}
if (error as? KinServiceV4.Errors) == KinServiceV4.Errors.badSequenceNumber {
self.service.invalidateRecentBlockHashCache()
return attempt()

} else if (error as? KinServiceV4.Errors == KinServiceV4.Errors.invalidAccount) {
return attempt(error: error as? KinServiceV4.Errors)
} else if (error as? KinServiceV4.Errors) == KinServiceV4.Errors.invalidAccount {
Thread.sleep(until: Date().addingTimeInterval((try? invalidAccountErrorRetryStrategy.nextDelay()) ?? 0))
return attempt()
return attempt(error: error as? KinServiceV4.Errors)
} else {
return Promise.init(error)
}
Expand Down
67 changes: 63 additions & 4 deletions KinBase/KinBase/Src/Network/KinService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public protocol KinServiceType {

func createAccount(account: PublicKey, signer: KeyPair, appIndex: AppIndex?) -> Promise<KinAccount>

func createTokenAccountForDestination(account: PublicKey) -> Promise<([Instruction], KeyPair)>

func getAccount(account: PublicKey) -> Promise<KinAccount>

func resolveTokenAccounts(account: PublicKey) -> Promise<[AccountDescription]>
Expand All @@ -35,7 +37,7 @@ public protocol KinServiceType {

func canWhitelistTransactions() -> Promise<Bool>

func buildAndSignTransaction(ownerKey: KeyPair, sourceKey: PublicKey, nonce: Int64, paymentItems: [KinPaymentItem], memo: KinMemo, fee: Quark) -> Promise<KinTransaction>
func buildAndSignTransaction(ownerKey: KeyPair, sourceKey: PublicKey, nonce: Int64, paymentItems: [KinPaymentItem], memo: KinMemo, fee: Quark, createAccountInstructions: [Instruction], additionalSigners: [KeyPair]) -> Promise<KinTransaction>

func submitTransaction(transaction: KinTransaction) -> Promise<KinTransaction>

Expand Down Expand Up @@ -442,6 +444,59 @@ extension KinServiceV4 : KinServiceType {
}
}
}

public func createTokenAccountForDestination(account: PublicKey) -> Promise<([Instruction], KeyPair)> {
networkOperationHandler.queueWork { [weak self] respond in
guard let self = self else {
respond.onError?(Errors.unknown)
return
}

all(self.cachedServiceConfig(), self.cachedMinRentExemption()).then { serviceConfig, minRentExemption in
guard
serviceConfig.result == GetServiceConfigResponseV4.Result.ok ||
minRentExemption.result == GetMinimumBalanceForRentExemptionResponseV4.Result.ok
else {
respond.onError?(Errors.unknown)
return
}

let subsidizer = serviceConfig.subsidizerAccount!
let programKey = serviceConfig.tokenProgram!
let mint = serviceConfig.token!

guard let ephemeralKeypair = KeyPair.generate()
else {
respond.onError?(Errors.unknown)
return
}
let pub = ephemeralKeypair.asPublicKey()

let instructions: [Instruction] = [
SystemProgram.createAccountInstruction(
subsidizer: subsidizer,
address: pub,
owner: programKey,
lamports: minRentExemption.lamports,
size: TokenProgram.accountSize
),
TokenProgram.initializeAccountInstruction(
account: pub,
mint: mint,
owner: pub,
programKey: programKey
),
TokenProgram.setAuthority(account: pub, currentAuthority: pub, newAuthority: subsidizer, authorityType: .authorityCloseAccount, programKey: programKey),
TokenProgram.setAuthority(account: pub, currentAuthority: pub, newAuthority: account, authorityType: .authorityAccountHolder, programKey: programKey)
]

respond.onSuccess((instructions, ephemeralKeypair))
}
.catch {
respond.onError?($0)
}
}
}

public func getAccount(account: PublicKey) -> Promise<KinAccount> {
return networkOperationHandler.queueWork { [weak self] respond in
Expand Down Expand Up @@ -641,7 +696,7 @@ extension KinServiceV4 : KinServiceType {
return Promise.init(true)
}

public func buildAndSignTransaction(ownerKey: KeyPair, sourceKey: PublicKey, nonce: Int64, paymentItems: [KinPaymentItem], memo: KinMemo, fee: Quark) -> Promise<KinTransaction> {
public func buildAndSignTransaction(ownerKey: KeyPair, sourceKey: PublicKey, nonce: Int64, paymentItems: [KinPaymentItem], memo: KinMemo, fee: Quark, createAccountInstructions: [Instruction], additionalSigners: [KeyPair]) -> Promise<KinTransaction> {
return networkOperationHandler.queueWork { [weak self] respond in
guard let self = self else {
respond.onError?(Errors.unknown)
Expand Down Expand Up @@ -680,6 +735,8 @@ extension KinServiceV4 : KinServiceType {
)
}
}

instructions.append(contentsOf: createAccountInstructions)

instructions.append(contentsOf: paymentItems.map { paymentItem in
TokenProgram.transferInstruction(
Expand All @@ -690,13 +747,15 @@ extension KinServiceV4 : KinServiceType {
programKey: programKey
)
})


var signers = additionalSigners
signers.insert(signer, at: 0)
let transaction = try! Transaction(
payer: subsidizer,
instructions: instructions
)
.updatingBlockhash(recentBlockHash.blockHash!)
.signing(using: signer)
.signing(using: signers)

print(transaction)

Expand Down
4 changes: 4 additions & 0 deletions KinBase/KinBase/Src/Solana/Encoding/Transaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ public struct Transaction {
// MARK: - Signing -

public func signing(using keyPairs: KeyPair...) throws -> Transaction {
// Swift does not support splatting, wrapping the function this way allows it to be called with an array or variadic
return try signing(using: keyPairs)
}
public func signing(using keyPairs: [KeyPair]) throws -> Transaction {
let requiredSignatureCount = message.header.signatureCount
if keyPairs.count > requiredSignatureCount {
throw SigningError.tooManySigners
Expand Down
8 changes: 6 additions & 2 deletions KinBase/KinBaseTests/Network/KinServiceTestsV4.swift
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,9 @@ class KinServiceTestsV4: XCTestCase {
ownerKey: sourceKey, sourceKey: account.publicKey, nonce: account.sequence!,
paymentItems: paymentItems,
memo: KinMemo(text: "1-kek-blah"),
fee: Quark(100)
fee: Quark(100),
createAccountInstructions: [],
additionalSigners: []
)
.then { transaction in
XCTAssertEqual(Data(transaction.envelopeXdrBytes).base64EncodedString(), expectEnvelope)
Expand Down Expand Up @@ -333,7 +335,9 @@ class KinServiceTestsV4: XCTestCase {
ownerKey: sourceKey, sourceKey: account.publicKey, nonce: account.sequence!,
paymentItems: paymentItems,
memo: agoraMemo.kinMemo,
fee: Quark(100)
fee: Quark(100),
createAccountInstructions: [],
additionalSigners: []
)
.then { transaction in
XCTAssertEqual(Data(transaction.envelopeXdrBytes).base64EncodedString(), expectEnvelope)
Expand Down
7 changes: 6 additions & 1 deletion KinBase/KinBaseTests/Tools/MockKinService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class MockKinService: KinServiceType {
var stubGetAccountResult: KinAccount?
var stubGetAccountResultPromise: Promise<KinAccount>?
var stubCreateAccountResult: KinAccount?
var stubCreateTokenAccountForDestinationResult: Promise<([Instruction], KeyPair)>?
var stubGetTransactionResult: KinTransaction?
var stubBuildAndSignTransactionResult: Promise<KinTransaction>?
var stubSubmitTransactionResult: Promise<KinTransaction>?
Expand Down Expand Up @@ -45,6 +46,10 @@ class MockKinService: KinServiceType {
return .init(stubCreateAccountResult!)
}

func createTokenAccountForDestination(account: PublicKey) -> Promise<([Instruction], KeyPair)> {
return stubCreateTokenAccountForDestinationResult!
}

func streamAccount(account: PublicKey) -> Observable<KinAccount> {
return stubStreamAccountObservable!
}
Expand All @@ -69,7 +74,7 @@ class MockKinService: KinServiceType {
return .init(stubCanWhitelistTransactionResult!)
}

func buildAndSignTransaction(ownerKey: KeyPair, sourceKey: PublicKey, nonce: Int64, paymentItems: [KinPaymentItem], memo: KinMemo, fee: Quark) -> Promise<KinTransaction> {
func buildAndSignTransaction(ownerKey: KeyPair, sourceKey: PublicKey, nonce: Int64, paymentItems: [KinPaymentItem], memo: KinMemo, fee: Quark, createAccountInstructions: [Instruction], additionalSigners: [KeyPair]) -> Promise<KinTransaction> {
return stubBuildAndSignTransactionResult!
}

Expand Down
2 changes: 1 addition & 1 deletion KinBase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The KinBase module is the foundation upon which the rest of the SDK stands.
## Installation
Add the following to your Podfile.
```
pod 'KinBase', '~> 2.0.0'
pod 'KinBase', '~> 2.1.0'
```

## Overview
Expand Down
2 changes: 1 addition & 1 deletion KinDesign.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
Pod::Spec.new do |s|
s.name = 'KinDesign'
s.version = '2.0.0'
s.version = '2.1.0'
s.summary = 'Kin Design Library for iOS'

s.description = <<-DESC
Expand Down
4 changes: 2 additions & 2 deletions KinDesign/KinDesign.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.kin..KinDesign;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -458,7 +458,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.kin..KinDesign;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
Expand Down
2 changes: 1 addition & 1 deletion KinDesign/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ All of these components can be tested out and browsed in the [sample app](../Kin
## Installation
Add the following to your project's Podfile.
```
pod 'KinDesign', '~> 2.0.0'
pod 'KinDesign', '~> 2.1.0'
```

### Primary Button
Expand Down
4 changes: 2 additions & 2 deletions KinSampleApp/KinSampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.kin..KinSampleApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand All @@ -602,7 +602,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.kin..KinSampleApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand Down
6 changes: 3 additions & 3 deletions KinUX.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
Pod::Spec.new do |s|
s.name = 'KinUX'
s.version = '2.0.0'
s.version = '2.1.0'
s.summary = 'UX Library for Kin SDK iOS'

s.description = <<-DESC
Expand All @@ -20,8 +20,8 @@ Pod::Spec.new do |s|

s.source_files = 'KinUX/KinUX/Src/**/*'

s.dependency 'KinBase', '~> 2.0.0'
s.dependency 'KinDesign', '~> 2.0.0'
s.dependency 'KinBase', '~> 2.1.0'
s.dependency 'KinDesign', '~> 2.1.0'

# Dependencies needed for KinGrpcApi
s.dependency 'gRPC-ProtoRPC'
Expand Down
4 changes: 2 additions & 2 deletions KinUX/KinUX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.kin..KinUX;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -619,7 +619,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.kin..KinUX;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
Expand Down
2 changes: 1 addition & 1 deletion KinUX/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The KinUX module provides an easy to use out of the box spend flow UI that is pr
Add the following to your project's Podfile.
This will also transitively pull in KinBase and KinDesign into your project as well.
```
pod 'KinUX', '~> 2.0.0'
pod 'KinUX', '~> 2.1.0'
```

## Overview
Expand Down
Loading

0 comments on commit 1bbadbd

Please sign in to comment.