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

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

- unit tests fixed

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

- Typical Fee < 0.001 localized and updated in the UI

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

- awaiting all transaction results with use of new proposal.transactionCount() method
  • Loading branch information
LukasKorba committed Mar 8, 2024
1 parent 6970b6f commit eb01593
Show file tree
Hide file tree
Showing 18 changed files with 219 additions and 111 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ directly impact users rather than highlighting other crucial architectural updat
### Added
- Pending values (changes) at the Balances tab.
- Choose a Server feature: available at settings, pre-defined servers + custom server setup.
- ZIP317: dynamic fees integrated

### Fixed
- Restore mode in the UI was missing when Zashi was deleted from an iPhone and reinstalled again.
Expand Down
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 var 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: Zatoshi(100_000),
tokenName: "TAZ"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ 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)
Expand Down Expand Up @@ -119,6 +119,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,15 +144,37 @@ 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)
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)

let transactionCount = proposal.transactionCount()
var iterator = stream.makeAsyncIterator()

for _ in 1...transactionCount {
if let transactionSubmitResult = try await iterator.next() {
switch transactionSubmitResult {
case .success:
continue
case .grpcFailure, .submitFailure, .notAttempted:
await send(.shieldFundsFailure("sdkSynchronizer.createProposedTransactions failed with \(transactionSubmitResult)".toZcashError()))
break
}
}
}

await send(.shieldFundsSuccess(transaction))
await send(.shieldFundsSuccess)
} catch {
await send(.shieldFundsFailure(error.toZcashError()))
}
Expand All @@ -178,7 +201,7 @@ public struct BalanceBreakdownReducer: Reducer {

case .syncProgress:
return .none

case .updateHintBoxVisibility(let visibility):
state.isHintBoxVisible = visibility
return .none
Expand All @@ -203,7 +226,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 +237,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
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ extension BalanceBreakdownView {
.padding(.bottom, 15)
.disabled(!viewStore.isShieldableBalanceAvailable || viewStore.isShieldingFunds)

Text(L10n.Balances.fee(ZatoshiStringRepresentation.feeFormat))
Text(ZatoshiStringRepresentation.feeFormat)
.font(.custom(FontFamily.Inter.semiBold.name, size: 11))
}
}
Expand Down
101 changes: 67 additions & 34 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 All @@ -59,7 +60,7 @@ public struct SendFlowReducer: Reducer {
}

public var feeFormat: String {
L10n.Send.fee(ZatoshiStringRepresentation.feeFormat)
ZatoshiStringRepresentation.feeFormat
}

public var message: String {
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,10 +135,11 @@ 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)
Expand Down Expand Up @@ -199,56 +203,85 @@ 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)

let transactionCount = proposal.transactionCount()
var iterator = stream.makeAsyncIterator()

for _ in 1...transactionCount {
if let transactionSubmitResult = try await iterator.next() {
switch transactionSubmitResult {
case .success:
continue
case .grpcFailure, .submitFailure, .notAttempted:
await send(.sendFailed("sdkSynchronizer.createProposedTransactions failed with \(transactionSubmitResult)".toZcashError()))
break
}
}
}

await send(.sendDone)
} catch {
await send(.sendFailed(error.toZcashError()))
}
} catch {
return .send(.sendFailed(error.toZcashError()))
}

case .sendDone:
state.isSending = false
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 @@ -270,7 +303,7 @@ public struct SendFlowReducer: Reducer {

case .transactionAddressInput:
return .none

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 eb01593

Please sign in to comment.