Skip to content

Commit

Permalink
[Electric-Coin-Company#1092] Adopt proposal API
Browse files Browse the repository at this point in the history
- TCA sdkSYnchronizer dependency extended with 3 new Proposal APIs
- proposeTransfer tested, works as expected

[Electric-Coin-Company#1092] Adopt proposal API

- send transaction via new proposal API implemented

[Electric-Coin-Company#1092] Adopt proposal API

- code cleaned up and finished
  • Loading branch information
LukasKorba committed Mar 7, 2024
1 parent 6970b6f commit 1ecf29f
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 58 deletions.
2 changes: 1 addition & 1 deletion modules/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.1.0"),
.package(url: "https://github.com/pointfreeco/swift-url-routing", from: "0.6.0"),
.package(url: "https://github.com/zcash-hackworks/MnemonicSwift", from: "2.2.4"),
.package(url: "https://github.com/zcash/ZcashLightClientKit", from: "2.0.10"),
.package(url: "https://github.com/zcash/ZcashLightClientKit", branch: "1204-expose-proposals"),
.package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.17.0")
],
targets: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,9 @@ public struct SDKSynchronizerClient {
public var wipe: () -> AnyPublisher<Void, Error>?

public var switchToEndpoint: (LightWalletEndpoint) async throws -> Void

// Proposals
public var proposeTransfer: (Int, Recipient, Zatoshi, Memo?) async throws -> Proposal
public let createProposedTransactions: (Proposal, UnifiedSpendingKey) async throws -> AsyncThrowingStream<TransactionSubmitResult, Error>
public var proposeShielding: (Int, Zatoshi, Memo, TransparentAddress?) async throws -> Proposal?
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ extension SDKSynchronizerClient: DependencyKey {
wipe: { synchronizer.wipe() },
switchToEndpoint: { endpoint in
try await synchronizer.switchTo(endpoint: endpoint)
},
proposeTransfer: { accountIndex, recipient, amount, memo in
try await synchronizer.proposeTransfer(
accountIndex: accountIndex,
recipient: recipient,
amount: amount,
memo: memo
)
},
createProposedTransactions: { proposal, spendingKey in
try await synchronizer.createProposedTransactions(
proposal: proposal,
spendingKey: spendingKey
)
},
proposeShielding: { accountIndex, shieldingThreshold, memo, transparentReceiver in
try await synchronizer.proposeShielding(
accountIndex: accountIndex,
shieldingThreshold: shieldingThreshold,
memo: memo,
transparentReceiver: transparentReceiver
)
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ extension SDKSynchronizerClient: TestDependencyKey {
sendTransaction: XCTUnimplemented("\(Self.self).sendTransaction", placeholder: .placeholder()),
shieldFunds: XCTUnimplemented("\(Self.self).shieldFunds", placeholder: .placeholder()),
wipe: XCTUnimplemented("\(Self.self).wipe"),
switchToEndpoint: XCTUnimplemented("\(Self.self).switchToEndpoint")
switchToEndpoint: XCTUnimplemented("\(Self.self).switchToEndpoint"),
proposeTransfer: XCTUnimplemented("\(Self.self).proposeTransfer", placeholder: .testOnlyFakeProposal(totalFee: 0)),
createProposedTransactions: XCTUnimplemented("\(Self.self).createProposedTransactions", placeholder: .never),
proposeShielding: XCTUnimplemented("\(Self.self).proposeShielding", placeholder: nil)
)
}

Expand All @@ -52,7 +55,10 @@ extension SDKSynchronizerClient {
sendTransaction: { _, _, _, _ in return .placeholder() },
shieldFunds: { _, _, _ in return .placeholder() },
wipe: { Empty<Void, Error>().eraseToAnyPublisher() },
switchToEndpoint: { _ in }
switchToEndpoint: { _ in },
proposeTransfer: { _, _, _, _ in .testOnlyFakeProposal(totalFee: 0) },
createProposedTransactions: { _, _ in .never },
proposeShielding: { _, _, _, _ in nil }
)

public static let mock = Self.mocked()
Expand Down Expand Up @@ -172,7 +178,13 @@ extension SDKSynchronizerClient {
)
},
wipe: @escaping () -> AnyPublisher<Void, Error>? = { Fail(error: "Error").eraseToAnyPublisher() },
switchToEndpoint: @escaping (LightWalletEndpoint) async throws -> Void = { _ in }
switchToEndpoint: @escaping (LightWalletEndpoint) async throws -> Void = { _ in },
proposeTransfer:
@escaping (Int, Recipient, Zatoshi, Memo?) async throws -> Proposal = { _, _, _, _ in .testOnlyFakeProposal(totalFee: 0) },
createProposedTransactions:
@escaping (Proposal, UnifiedSpendingKey) async throws -> AsyncThrowingStream<TransactionSubmitResult, Error> = { _, _ in .never },
proposeShielding:
@escaping (Int, Zatoshi, Memo, TransparentAddress?) async throws -> Proposal? = { _, _, _, _ in nil }
) -> SDKSynchronizerClient {
SDKSynchronizerClient(
stateStream: stateStream,
Expand All @@ -191,7 +203,10 @@ extension SDKSynchronizerClient {
sendTransaction: sendTransaction,
shieldFunds: shieldFunds,
wipe: wipe,
switchToEndpoint: switchToEndpoint
switchToEndpoint: switchToEndpoint,
proposeTransfer: proposeTransfer,
createProposedTransactions: createProposedTransactions,
proposeShielding: proposeShielding
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,6 @@ public struct ZcashSDKEnvironment {
public let network: ZcashNetwork
public let requiredTransactionConfirmations: Int
public let sdkVersion: String
public let shieldingThreshold: Zatoshi
public let tokenName: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ extension ZcashSDKEnvironment {
network: network,
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
sdkVersion: "0.18.1-beta",
shieldingThreshold: Zatoshi(100_000),
tokenName: network.networkType == .testnet ? "TAZ" : "ZEC"
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extension ZcashSDKEnvironment: TestDependencyKey {
network: ZcashNetworkBuilder.network(for: .testnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
sdkVersion: "0.18.1-beta",
shieldingThreshold: .zero,
tokenName: "TAZ"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ public struct BalanceBreakdownReducer: Reducer {
case restoreWalletTask
case restoreWalletValue(Bool)
case shieldFunds
case shieldFundsSuccess(TransactionState)
case shieldFundsSuccess
case shieldFundsFailure(ZcashError)
case synchronizerStateChanged(RedactableSynchronizerState)
case syncProgress(SyncProgressReducer.Action)
case transactionSubmitResult(TransactionSubmitResult)
case updateHintBoxVisibility(Bool)
}

Expand Down Expand Up @@ -119,6 +120,7 @@ public struct BalanceBreakdownReducer: Reducer {
return .none

case .onAppear:
state.autoShieldingThreshold = zcashSDKEnvironment.shieldingThreshold
return .publisher {
sdkSynchronizer.stateStream()
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
Expand All @@ -143,27 +145,34 @@ public struct BalanceBreakdownReducer: Reducer {

case .shieldFunds:
state.isShieldingFunds = true
return .run { [state] send in
return .run { send in
do {
let storedWallet = try walletStorage.exportWallet()
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, 0, zcashSDKEnvironment.network.networkType)

guard let uAddress = try await sdkSynchronizer.getUnifiedAddress(0) else { throw "sdkSynchronizer.getUnifiedAddress" }

let transaction = try await sdkSynchronizer.shieldFunds(spendingKey, Memo(string: ""), state.autoShieldingThreshold)

await send(.shieldFundsSuccess(transaction))
let address = try uAddress.transparentReceiver()
let proposal = try await sdkSynchronizer.proposeShielding(0, zcashSDKEnvironment.shieldingThreshold, .empty, address)

guard let proposal else { throw "sdkSynchronizer.proposeShielding" }

let stream = try await sdkSynchronizer.createProposedTransactions(proposal, spendingKey)

for try await transactionSubmitResult in stream {
await send(.transactionSubmitResult(transactionSubmitResult))
}
} catch {
await send(.shieldFundsFailure(error.toZcashError()))
}
}

case .shieldFundsSuccess:
state.isShieldingFunds = false
state.transparentBalance = .zero
return .none

case .shieldFundsFailure(let error):
state.isShieldingFunds = false
state.alert = AlertState.shieldFundsFailure(error)
return .none

Expand All @@ -179,6 +188,22 @@ public struct BalanceBreakdownReducer: Reducer {
case .syncProgress:
return .none

case .transactionSubmitResult(let transactionSubmitResult):
state.isShieldingFunds = false
switch transactionSubmitResult {
case .success:
return .send(.shieldFundsSuccess)
case .grpcFailure(txId: let txId, error: let error):
let zcashError = error.toZcashError()
return .send(.shieldFundsFailure(zcashError))
case .submitFailure(txId: let txId, code: let code, description: let description):
let zcashError = "submitFailure, code(\(code) desc: \(description)".toZcashError()
return .send(.shieldFundsFailure(zcashError))
case .notAttempted(txId: let txId):
let zcashError = "transactionSubmitResult notAttempted".toZcashError()
return .send(.shieldFundsFailure(zcashError))
}

case .updateHintBoxVisibility(let visibility):
state.isHintBoxVisible = visibility
return .none
Expand All @@ -203,7 +228,7 @@ extension AlertState where Action == BalanceBreakdownReducer.Action {

extension BalanceBreakdownReducer.State {
public static let placeholder = BalanceBreakdownReducer.State(
autoShieldingThreshold: Zatoshi(1_000_000),
autoShieldingThreshold: .zero,
changePending: .zero,
isShieldingFunds: false,
pendingTransactions: .zero,
Expand All @@ -214,7 +239,7 @@ extension BalanceBreakdownReducer.State {
)

public static let initial = BalanceBreakdownReducer.State(
autoShieldingThreshold: Zatoshi(1_000_000),
autoShieldingThreshold: .zero,
changePending: .zero,
isShieldingFunds: false,
pendingTransactions: .zero,
Expand Down
100 changes: 68 additions & 32 deletions modules/Sources/Features/SendFlow/SendFlowStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public struct SendFlowReducer: Reducer {
public var destination: Destination?
public var isSending = false
public var memoState: MessageEditorReducer.State
public var proposal: Proposal?
public var scanState: Scan.State
public var spendableBalance = Zatoshi.zero
public var totalBalance = Zatoshi.zero
Expand Down Expand Up @@ -108,6 +109,7 @@ public struct SendFlowReducer: Reducer {
public init(
addMemoState: Bool,
destination: Destination? = nil,
isSending: Bool = false,
memoState: MessageEditorReducer.State,
scanState: Scan.State,
spendableBalance: Zatoshi = .zero,
Expand All @@ -117,6 +119,7 @@ public struct SendFlowReducer: Reducer {
) {
self.addMemoState = addMemoState
self.destination = destination
self.isSending = isSending
self.memoState = memoState
self.scanState = scanState
self.spendableBalance = spendableBalance
Expand All @@ -132,14 +135,16 @@ public struct SendFlowReducer: Reducer {
case memo(MessageEditorReducer.Action)
case onAppear
case onDisappear
case proposal(Proposal)
case reviewPressed
case scan(Scan.Action)
case sendPressed
case sendDone(TransactionState)
case sendDone
case sendFailed(ZcashError)
case synchronizerStateChanged(RedactableSynchronizerState)
case transactionAddressInput(TransactionAddressTextFieldReducer.Action)
case transactionAmountInput(TransactionAmountTextFieldReducer.Action)
case transactionSubmitResult(TransactionSubmitResult)
case updateDestination(SendFlowReducer.State.Destination?)
}

Expand Down Expand Up @@ -199,56 +204,71 @@ public struct SendFlowReducer: Reducer {
state.destination = nil
state.isSending = false
return .none


case let .proposal(proposal):
state.proposal = proposal
return .none

case let .updateDestination(destination):
state.destination = destination
return .none

case .reviewPressed:
state.destination = .sendConfirmation
return .none
return .run { [state] send in
do {
let recipient = try Recipient(state.address, network: zcashSDKEnvironment.network.networkType)

let memo: Memo?
if state.transactionAddressInputState.isValidTransparentAddress {
memo = nil
} else if let memoText = state.addMemoState ? state.memoState.text : nil {
memo = memoText.data.isEmpty ? nil : try Memo(string: memoText.data)
} else {
memo = nil
}

let proposal = try await sdkSynchronizer.proposeTransfer(0, recipient, state.amount, memo)

await send(.proposal(proposal))
await send(.updateDestination(.sendConfirmation))
} catch {
await send(.sendFailed(error.toZcashError()))
}
}

case .sendPressed:
state.isSending = true

guard let proposal = state.proposal else {
return .send(.sendFailed("missing proposal".toZcashError()))
}

state.amount = Zatoshi(state.transactionAmountInputState.amount.data)
state.address = state.transactionAddressInputState.textFieldState.text.data

do {
let storedWallet = try walletStorage.exportWallet()
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
let network = zcashSDKEnvironment.network.networkType
let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, 0, network)

let memo: Memo?
if state.transactionAddressInputState.isValidTransparentAddress {
memo = nil
} else if let memoText = state.addMemoState ? state.memoState.text : nil {
memo = memoText.data.isEmpty ? nil : try Memo(string: memoText.data)
} else {
memo = nil
}

let recipient = try Recipient(state.address, network: network)
state.isSending = true

return .run { [state] send in
do {
let transaction = try await sdkSynchronizer.sendTransaction(spendingKey, state.amount, recipient, memo)
await send(.sendDone(transaction))
} catch {
await send(.sendFailed(error.toZcashError()))
return .run { send in
do {
let storedWallet = try walletStorage.exportWallet()
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
let network = zcashSDKEnvironment.network.networkType
let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, 0, network)

let stream = try await sdkSynchronizer.createProposedTransactions(proposal, spendingKey)

for try await transactionSubmitResult in stream {
await send(.transactionSubmitResult(transactionSubmitResult))
}
} catch {
await send(.sendFailed(error.toZcashError()))
}
} catch {
return .send(.sendFailed(error.toZcashError()))
}

case .sendDone:
state.destination = nil
state.memoState.text = "".redacted
state.transactionAmountInputState.textFieldState.text = "".redacted
state.transactionAmountInputState.amount = Int64(0).redacted
state.transactionAddressInputState.textFieldState.text = "".redacted
state.isSending = false
return .none

case .sendFailed(let error):
Expand All @@ -271,6 +291,22 @@ public struct SendFlowReducer: Reducer {
case .transactionAddressInput:
return .none

case .transactionSubmitResult(let transactionSubmitResult):
switch transactionSubmitResult {
case .success:
state.isSending = false
return .send(.sendDone)
case .grpcFailure(txId: _, error: let error):
let zcashError = error.toZcashError()
return .send(.sendFailed(zcashError))
case let .submitFailure(txId: _, code: code, description: description):
let zcashError = "submitFailure, code(\(code) desc: \(description)".toZcashError()
return .send(.sendFailed(zcashError))
case .notAttempted:
let zcashError = "transactionSubmitResult notAttempted".toZcashError()
return .send(.sendFailed(zcashError))
}

case .synchronizerStateChanged(let latestState):
state.spendableBalance = latestState.data.accountBalance?.data?.saplingBalance.spendableValue ?? .zero
state.totalBalance = latestState.data.accountBalance?.data?.saplingBalance.total() ?? .zero
Expand Down
Loading

0 comments on commit 1ecf29f

Please sign in to comment.