Refactor code to build on top of Proposal API

This commit is contained in:
Francisco Gindre 2024-02-22 21:00:16 -03:00
parent 41841c9458
commit a9e8a40267
No known key found for this signature in database
GPG Key ID: 6B61CD8DAA2862B4
8 changed files with 108 additions and 90 deletions

View File

@ -35,19 +35,39 @@ public protocol CombineSynchronizer {
func getUnifiedAddress(accountIndex: Int) -> SinglePublisher<UnifiedAddress, Error> func getUnifiedAddress(accountIndex: Int) -> SinglePublisher<UnifiedAddress, Error>
func getTransparentAddress(accountIndex: Int) -> SinglePublisher<TransparentAddress, Error> func getTransparentAddress(accountIndex: Int) -> SinglePublisher<TransparentAddress, Error>
@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients. use `proposeTransfer` instead")
func sendToAddress( func sendToAddress(
spendingKey: UnifiedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,
toAddress: Recipient, toAddress: Recipient,
memo: Memo? memo: Memo?
) -> SinglePublisher<ZcashTransaction.Overview, Error> ) -> SinglePublisher<ZcashTransaction.Overview, Error>
@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients. use `proposeShielding:` instead")
func shieldFunds( func shieldFunds(
spendingKey: UnifiedSpendingKey, spendingKey: UnifiedSpendingKey,
memo: Memo, memo: Memo,
shieldingThreshold: Zatoshi shieldingThreshold: Zatoshi
) -> SinglePublisher<ZcashTransaction.Overview, Error> ) -> SinglePublisher<ZcashTransaction.Overview, Error>
func proposeTransfer(
accountIndex: Int,
recipient: Recipient,
amount: Zatoshi,
memo: Memo?
) -> SinglePublisher<Proposal, Error>
func proposeShielding(
accountIndex: Int,
shieldingThreshold: Zatoshi,
memo: Memo
) -> SinglePublisher<Proposal, Error>
func proposefulfillingPaymentURI(
_ uri: String,
accountIndex: Int
) -> SinglePublisher<Proposal, Error>
var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var sentTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } var sentTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }

View File

@ -225,16 +225,16 @@ public protocol Synchronizer: AnyObject {
memo: Memo? memo: Memo?
) async throws -> ZcashTransaction.Overview ) async throws -> ZcashTransaction.Overview
/// Attempts to fulfill a [ZIP-321](https://zips.z.cash/zip-0321) payment URI using the given `UnifiedSpendingKey` /// Attempts to propose fulfilling a [ZIP-321](https://zips.z.cash/zip-0321) payment URI using the given `accountIndex`
/// - Parameter uri: a valid ZIP-321 payment URI /// - Parameter uri: a valid ZIP-321 payment URI
/// - Parameter spendingKey: the `UnifiedSpendingKey` that allows spends to occur. /// - Parameter accountIndex: the account index that allows spends to occur.
/// ///
/// - NOTE: If `prepare()` hasn't already been called since creating of synchronizer instance or since the last wipe then this method throws /// - NOTE: If `prepare()` hasn't already been called since creating of synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`. /// `SynchronizerErrors.notPrepared`.
func fulfillPaymentURI( func proposefulfillingPaymentURI(
_ uri: String, _ uri: String,
spendingKey: UnifiedSpendingKey accountIndex: Int
) async throws -> ZcashTransaction.Overview ) async throws -> Proposal
/// Shields transparent funds from the given private key into the best shielded pool of the account associated to the given `UnifiedSpendingKey`. /// Shields transparent funds from the given private key into the best shielded pool of the account associated to the given `UnifiedSpendingKey`.
/// - Parameter spendingKey: the `UnifiedSpendingKey` that allows to spend transparent funds /// - Parameter spendingKey: the `UnifiedSpendingKey` that allows to spend transparent funds

View File

@ -90,6 +90,18 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
} }
} }
public func proposefulfillingPaymentURI(
_ uri: String,
accountIndex: Int
) -> SinglePublisher<Proposal, Error> {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.proposefulfillingPaymentURI(
uri,
accountIndex: accountIndex
)
}
}
public func createProposedTransactions( public func createProposedTransactions(
proposal: Proposal, proposal: Proposal,
spendingKey: UnifiedSpendingKey spendingKey: UnifiedSpendingKey

View File

@ -291,14 +291,29 @@ public class SDKSynchronizer: Synchronizer {
) async throws -> Proposal? { ) async throws -> Proposal? {
try throwIfUnprepared() try throwIfUnprepared()
let proposal = try await transactionEncoder.proposeShielding( return try await transactionEncoder.proposeShielding(
accountIndex: accountIndex, accountIndex: accountIndex,
shieldingThreshold: shieldingThreshold, shieldingThreshold: shieldingThreshold,
memoBytes: memo.asMemoBytes(), memoBytes: memo.asMemoBytes(),
transparentReceiver: transparentReceiver?.stringEncoded transparentReceiver: transparentReceiver?.stringEncoded
) )
}
return proposal public func proposefulfillingPaymentURI(
_ uri: String,
accountIndex: Int
) async throws -> Proposal {
do {
try throwIfUnprepared()
return try await transactionEncoder.proposeFulfillingPaymentFromURI(
uri,
accountIndex: accountIndex
)
} catch ZcashError.rustCreateToAddress(let e) {
throw ZcashError.rustProposeTransferFromURI(e)
} catch {
throw error
}
} }
public func createProposedTransactions( public func createProposedTransactions(
@ -344,25 +359,6 @@ public class SDKSynchronizer: Synchronizer {
} }
} }
public func fulfillPaymentURI(_ uri: String, spendingKey: UnifiedSpendingKey) async throws -> ZcashTransaction.Overview {
do {
let transaction = try await transactionEncoder.createTransactionFromPaymentURI(
uri,
spendingKey: spendingKey
)
let encodedTransaction = try transaction.encodedTransaction()
try await transactionEncoder.submit(transaction: encodedTransaction)
return transaction
} catch ZcashError.rustCreateToAddress(let e) {
throw ZcashError.rustProposeTransferFromURI(e)
} catch {
throw error
}
}
public func sendToAddress( public func sendToAddress(
spendingKey: UnifiedSpendingKey, spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi, zatoshi: Zatoshi,

View File

@ -2,7 +2,7 @@
// TransactionEncoder.swift // TransactionEncoder.swift
// ZcashLightClientKit // ZcashLightClientKit
// //
// Created by Francisco Gindre on 11/20/19. // Created by Francisco Gindre on 2019-11-20.
// //
import Foundation import Foundation
@ -72,20 +72,20 @@ protocol TransactionEncoder {
spendingKey: UnifiedSpendingKey spendingKey: UnifiedSpendingKey
) async throws -> [ZcashTransaction.Overview] ) async throws -> [ZcashTransaction.Overview]
/// Creates a transaction to fulfill a [ZIP-321](https://zips.z.cash/zip-0321), throwing an exception whenever things are missing. When the provided wallet implementation /// Creates a transaction proposal to fulfill a [ZIP-321](https://zips.z.cash/zip-0321), throwing an exception whenever things are missing. When the provided wallet implementation
/// doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using /// doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using
/// double-bangs for things). /// double-bangs for things).
/// ///
/// - Parameters: /// - Parameters:
/// - Parameter uri: a valid ZIP-321 payment URI. /// - Parameter uri: a valid ZIP-321 payment URI.
/// - Parameter spendingKey: a `UnifiedSpendingKey` containing the spending key /// - Parameter accountIndex: the index of the account the proposal should be made from.
/// - Throws: /// - Throws:
/// - `walletTransEncoderCreateTransactionMissingSaplingParams` if the sapling parameters aren't downloaded. /// - `walletTransEncoderCreateTransactionMissingSaplingParams` if the sapling parameters aren't downloaded.
/// - Some `ZcashError.rust*` if the creation of transaction fails. /// - Some `ZcashError.rust*` if the creation of transaction fails.
func createTransactionFromPaymentURI( func proposeFulfillingPaymentFromURI(
_ uri: String, _ uri: String,
spendingKey: UnifiedSpendingKey accountIndex: Int
) async throws -> ZcashTransaction.Overview ) async throws -> Proposal
/// submits a transaction to the Zcash peer-to-peer network. /// submits a transaction to the Zcash peer-to-peer network.
/// - Parameter transaction: a transaction overview /// - Parameter transaction: a transaction overview

View File

@ -87,39 +87,15 @@ class WalletTransactionEncoder: TransactionEncoder {
return Proposal(inner: proposal) return Proposal(inner: proposal)
} }
func createTransactionFromPaymentURI( func proposeFulfillingPaymentFromURI(
_ uri: String, _ uri: String,
spendingKey: UnifiedSpendingKey accountIndex: Int
) async throws -> ZcashTransaction.Overview { ) async throws -> Proposal {
let txId = try await createSpendFromPaymentURI(
uri,
spendingKey: spendingKey
)
logger.debug("transaction id: \(txId)")
return try await repository.find(rawID: txId)
}
func createSpendFromPaymentURI(
_ uri: String,
spendingKey: UnifiedSpendingKey
) async throws -> Data {
guard ensureParams(spend: self.spendParamsURL, output: self.outputParamsURL) else {
throw ZcashError.walletTransEncoderCreateTransactionMissingSaplingParams
}
// TODO: Expose the proposal in a way that enables querying its fee.
let proposal = try await rustBackend.proposeTransferFromURI( let proposal = try await rustBackend.proposeTransferFromURI(
uri, uri,
account: Int32(spendingKey.account) account: Int32(accountIndex)
) )
return Proposal(inner: proposal)
let txId = try await rustBackend.createProposedTransaction(
proposal: proposal,
usk: spendingKey
)
return txId
} }
func createProposedTransactions( func createProposedTransactions(

View File

@ -101,7 +101,7 @@ class PaymentURIFulfillmentTests: ZcashTestCase {
sleep(1) sleep(1)
let sendExpectation = XCTestExpectation(description: "send expectation") let sendExpectation = XCTestExpectation(description: "send expectation")
var pendingEntity: ZcashTransaction.Overview? var proposal: ZcashTransaction.Overview?
/* /*
2. send transaction to recipient address 2. send transaction to recipient address
@ -111,24 +111,38 @@ class PaymentURIFulfillmentTests: ZcashTestCase {
let paymentURI = "zcash:\(Environment.testRecipientAddress)?amount=0.0002&memo=\(memo)&message=Thank%20you%20for%20your%20purchase&label=Your%20Purchase" let paymentURI = "zcash:\(Environment.testRecipientAddress)?amount=0.0002&memo=\(memo)&message=Thank%20you%20for%20your%20purchase&label=Your%20Purchase"
do { do {
let pendingTx = try await coordinator.synchronizer.fulfillPaymentURI( let proposal = try await coordinator.synchronizer.proposefulfillingPaymentURI(
paymentURI, paymentURI,
spendingKey: self.coordinator.spendingKey accountIndex: 0
) )
pendingEntity = pendingTx let transactions = try await coordinator.synchronizer.createProposedTransactions(
proposal: proposal,
spendingKey: coordinator.spendingKey
)
for try await tx in transactions {
switch tx {
case .grpcFailure(_, let error):
XCTFail("transaction failed to submit with error:\(error.localizedDescription)")
return
case .success(txId: let txId):
continue
case .submitFailure(txId: let txId, code: let code, description: let description):
XCTFail("transaction failed to submit with code: \(code) - description: \(description)")
return
case .notAttempted(txId: let txId):
XCTFail("transaction not attempted")
return
}
}
sendExpectation.fulfill() sendExpectation.fulfill()
} catch { } catch {
await handleError(error) await handleError(error)
} }
await fulfillment(of: [sendExpectation], timeout: 11) await fulfillment(of: [sendExpectation], timeout: 13)
guard pendingEntity != nil else {
XCTFail("no pending transaction after sending")
try await coordinator.stop()
return
}
/** /**
3. getIncomingTransaction 3. getIncomingTransaction
@ -304,13 +318,13 @@ class PaymentURIFulfillmentTests: ZcashTestCase {
let paymentURI = "zcash:zecIsGreat17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a?amount=0.0002&memo=\(memo)&message=Thank%20you%20for%20your%20purchase&label=Your%20Purchase" let paymentURI = "zcash:zecIsGreat17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a?amount=0.0002&memo=\(memo)&message=Thank%20you%20for%20your%20purchase&label=Your%20Purchase"
do { do {
let _ = try await coordinator.synchronizer.fulfillPaymentURI( let _ = try await coordinator.synchronizer.proposefulfillingPaymentURI(
paymentURI, paymentURI,
spendingKey: self.coordinator.spendingKey accountIndex: 0
) )
XCTFail("`fulfillPaymentURI` should have failed") XCTFail("`fulfillPaymentURI` should have failed")
} catch ZcashError.rustCreateToAddress { } catch ZcashError.rustProposeTransferFromURI {
XCTAssertTrue(true) XCTAssertTrue(true)
} catch { } catch {
XCTFail("Expected ZcashError.rustCreateToAddress but got \(error.localizedDescription)") XCTFail("Expected ZcashError.rustCreateToAddress but got \(error.localizedDescription)")

View File

@ -1493,27 +1493,27 @@ class SynchronizerMock: Synchronizer {
} }
} }
// MARK: - fulfillPaymentURI // MARK: - proposefulfillingPaymentURI
var fulfillPaymentURISpendingKeyThrowableError: Error? var proposefulfillingPaymentURIAccountIndexThrowableError: Error?
var fulfillPaymentURISpendingKeyCallsCount = 0 var proposefulfillingPaymentURIAccountIndexCallsCount = 0
var fulfillPaymentURISpendingKeyCalled: Bool { var proposefulfillingPaymentURIAccountIndexCalled: Bool {
return fulfillPaymentURISpendingKeyCallsCount > 0 return proposefulfillingPaymentURIAccountIndexCallsCount > 0
} }
var fulfillPaymentURISpendingKeyReceivedArguments: (uri: String, spendingKey: UnifiedSpendingKey)? var proposefulfillingPaymentURIAccountIndexReceivedArguments: (uri: String, accountIndex: Int)?
var fulfillPaymentURISpendingKeyReturnValue: ZcashTransaction.Overview! var proposefulfillingPaymentURIAccountIndexReturnValue: Proposal!
var fulfillPaymentURISpendingKeyClosure: ((String, UnifiedSpendingKey) async throws -> ZcashTransaction.Overview)? var proposefulfillingPaymentURIAccountIndexClosure: ((String, Int) async throws -> Proposal)?
func fulfillPaymentURI(_ uri: String, spendingKey: UnifiedSpendingKey) async throws -> ZcashTransaction.Overview { func proposefulfillingPaymentURI(_ uri: String, accountIndex: Int) async throws -> Proposal {
if let error = fulfillPaymentURISpendingKeyThrowableError { if let error = proposefulfillingPaymentURIAccountIndexThrowableError {
throw error throw error
} }
fulfillPaymentURISpendingKeyCallsCount += 1 proposefulfillingPaymentURIAccountIndexCallsCount += 1
fulfillPaymentURISpendingKeyReceivedArguments = (uri: uri, spendingKey: spendingKey) proposefulfillingPaymentURIAccountIndexReceivedArguments = (uri: uri, accountIndex: accountIndex)
if let closure = fulfillPaymentURISpendingKeyClosure { if let closure = proposefulfillingPaymentURIAccountIndexClosure {
return try await closure(uri, spendingKey) return try await closure(uri, accountIndex)
} else { } else {
return fulfillPaymentURISpendingKeyReturnValue return proposefulfillingPaymentURIAccountIndexReturnValue
} }
} }