Skip to content

Commit

Permalink
Merge pull request #1382 from Electric-Coin-Company/1204-expose-propo…
Browse files Browse the repository at this point in the history
…sals

Expose APIs for working with transaction proposals
  • Loading branch information
str4d committed Mar 8, 2024
2 parents 2ef0e00 + 129ac43 commit 7fcf1fa
Show file tree
Hide file tree
Showing 19 changed files with 1,184 additions and 252 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ All notable changes to this library will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# Unreleased

## Changed
- Migrated to `zcash-light-client-ffi 0.6.0`.

### [#1186] Enable ZIP 317 fees
- The SDK now generates transactions using [ZIP 317](https://zips.z.cash/zip-0317) fees,
instead of a fixed fee of 10,000 Zatoshi. Use `Proposal.totalFeeRequired` to check the
total fee for a transfer before creating it.

## Added

### [#1204] Expose APIs for working with transaction proposals
New `Synchronizer` APIs that enable constructing a proposal for transferring or
shielding funds, and then creating transactions from a proposal. The intermediate
proposal can be used to determine the required fee, before committing to producing
transactions.

The old `Synchronizer.sendToAddress` and `Synchronizer.shieldFunds` APIs have been
deprecated, and will be removed in 2.1.0 (which will create multiple transactions
at once for some recipients).

# 2.0.10 - 2024-02-12

## Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "c90afd6cc092468e71810bc715ddb49be8210b75",
"version" : "0.5.1"
"revision" : "7c801be1f445402a433b32835a50d832e8a50437",
"version" : "0.6.0"
}
}
],
Expand Down
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "c90afd6cc092468e71810bc715ddb49be8210b75",
"version" : "0.5.1"
"revision" : "7c801be1f445402a433b32835a50d832e8a50437",
"version" : "0.6.0"
}
}
],
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.19.1"),
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"),
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.5.1")
.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.6.0")
],
targets: [
.target(
Expand Down
59 changes: 59 additions & 0 deletions Sources/ZcashLightClientKit/ClosureSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,64 @@ public protocol ClosureSynchronizer {
func getUnifiedAddress(accountIndex: Int, completion: @escaping (Result<UnifiedAddress, Error>) -> Void)
func getTransparentAddress(accountIndex: Int, completion: @escaping (Result<TransparentAddress, Error>) -> Void)

/// Creates a proposal for transferring funds to the given recipient.
///
/// - Parameter accountIndex: the account from which to transfer funds.
/// - Parameter recipient: the recipient's address.
/// - Parameter amount: the amount to send in Zatoshi.
/// - Parameter memo: an optional memo to include as part of the proposal's transactions. Use `nil` when sending to transparent receivers otherwise the function will throw an error.
///
/// If `prepare()` hasn't already been called since creation of the synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
func proposeTransfer(
accountIndex: Int,
recipient: Recipient,
amount: Zatoshi,
memo: Memo?,
completion: @escaping (Result<Proposal, Error>) -> Void
)

/// Creates a proposal for shielding any transparent funds received by the given account.
///
/// - Parameter accountIndex: the account for which to shield funds.
/// - Parameter shieldingThreshold: the minimum transparent balance required before a proposal will be created.
/// - Parameter memo: an optional memo to include as part of the proposal's transactions.
/// - Parameter transparentReceiver: a specific transparent receiver within the account
/// that should be the source of transparent funds. Default is `nil` which
/// will select whichever of the account's transparent receivers has funds
/// to shield.
///
/// Returns the proposal, or `nil` if the transparent balance that would be shielded
/// is zero or below `shieldingThreshold`.
///
/// If `prepare()` hasn't already been called since creation of the synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
func proposeShielding(
accountIndex: Int,
shieldingThreshold: Zatoshi,
memo: Memo,
transparentReceiver: TransparentAddress?,
completion: @escaping (Result<Proposal?, Error>) -> Void
)

/// Creates the transactions in the given proposal.
///
/// - Parameter proposal: the proposal for which to create transactions.
/// - Parameter spendingKey: the `UnifiedSpendingKey` associated with the account for which the proposal was created.
///
/// Returns a stream of objects for the transactions that were created as part of the
/// proposal, indicating whether they were submitted to the network or if an error
/// occurred.
///
/// If `prepare()` hasn't already been called since creation of the synchronizer instance
/// or since the last wipe then this method throws `SynchronizerErrors.notPrepared`.
func createProposedTransactions(
proposal: Proposal,
spendingKey: UnifiedSpendingKey,
completion: @escaping (Result<AsyncThrowingStream<TransactionSubmitResult, Error>, Error>) -> Void
)

@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.")
func sendToAddress(
spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi,
Expand All @@ -44,6 +102,7 @@ public protocol ClosureSynchronizer {
completion: @escaping (Result<ZcashTransaction.Overview, Error>) -> Void
)

@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.")
func shieldFunds(
spendingKey: UnifiedSpendingKey,
memo: Memo,
Expand Down
56 changes: 56 additions & 0 deletions Sources/ZcashLightClientKit/CombineSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,69 @@ public protocol CombineSynchronizer {
func getUnifiedAddress(accountIndex: Int) -> SinglePublisher<UnifiedAddress, Error>
func getTransparentAddress(accountIndex: Int) -> SinglePublisher<TransparentAddress, Error>

/// Creates a proposal for transferring funds to the given recipient.
///
/// - Parameter accountIndex: the account from which to transfer funds.
/// - Parameter recipient: the recipient's address.
/// - Parameter amount: the amount to send in Zatoshi.
/// - Parameter memo: an optional memo to include as part of the proposal's transactions. Use `nil` when sending to transparent receivers otherwise the function will throw an error.
///
/// If `prepare()` hasn't already been called since creation of the synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
func proposeTransfer(
accountIndex: Int,
recipient: Recipient,
amount: Zatoshi,
memo: Memo?
) -> SinglePublisher<Proposal, Error>

/// Creates a proposal for shielding any transparent funds received by the given account.
///
/// - Parameter accountIndex: the account for which to shield funds.
/// - Parameter shieldingThreshold: the minimum transparent balance required before a proposal will be created.
/// - Parameter memo: an optional memo to include as part of the proposal's transactions.
/// - Parameter transparentReceiver: a specific transparent receiver within the account
/// that should be the source of transparent funds. Default is `nil` which
/// will select whichever of the account's transparent receivers has funds
/// to shield.
///
/// Returns the proposal, or `nil` if the transparent balance that would be shielded
/// is zero or below `shieldingThreshold`.
///
/// If `prepare()` hasn't already been called since creation of the synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
func proposeShielding(
accountIndex: Int,
shieldingThreshold: Zatoshi,
memo: Memo,
transparentReceiver: TransparentAddress?
) -> SinglePublisher<Proposal?, Error>

/// Creates the transactions in the given proposal.
///
/// - Parameter proposal: the proposal for which to create transactions.
/// - Parameter spendingKey: the `UnifiedSpendingKey` associated with the account for which the proposal was created.
///
/// Returns a stream of objects for the transactions that were created as part of the
/// proposal, indicating whether they were submitted to the network or if an error
/// occurred.
///
/// If `prepare()` hasn't already been called since creation of the synchronizer instance
/// or since the last wipe then this method throws `SynchronizerErrors.notPrepared`.
func createProposedTransactions(
proposal: Proposal,
spendingKey: UnifiedSpendingKey
) -> SinglePublisher<AsyncThrowingStream<TransactionSubmitResult, Error>, Error>

@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.")
func sendToAddress(
spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi,
toAddress: Recipient,
memo: Memo?
) -> SinglePublisher<ZcashTransaction.Overview, Error>

@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.")
func shieldFunds(
spendingKey: UnifiedSpendingKey,
memo: Memo,
Expand Down
45 changes: 45 additions & 0 deletions Sources/ZcashLightClientKit/Model/Proposal.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Proposal.swift
//
//
// Created by Jack Grigg on 20/02/2024.
//

import Foundation

/// A data structure that describes a series of transactions to be created.
public struct Proposal: Equatable {
let inner: FfiProposal

/// Returns the number of transactions that this proposal will create.
///
/// This is equal to the number of `TransactionSubmitResult`s that will be returned
/// from `Synchronizer.createProposedTransactions`.
///
/// Proposals always create at least one transaction.
public func transactionCount() -> Int {
inner.steps.count
}

/// Returns the total fee to be paid across all proposed transactions, in zatoshis.
public func totalFeeRequired() -> Zatoshi {
inner.steps.reduce(Zatoshi.zero) { acc, step in
acc + Zatoshi(Int64(step.balance.feeRequired))
}
}
}

public extension Proposal {
/// IMPORTANT: This function is for testing purposes only. It produces fake invalid
/// data that can be used to check UI elements, but will always produce an error when
/// passed to `Synchronizer.createProposedTransactions`. It should never be called in
/// production code.
static func testOnlyFakeProposal(totalFee: UInt64) -> Self {
var ffiProposal = FfiProposal()
var balance = FfiTransactionBalance()

balance.feeRequired = totalFee

return Self(inner: ffiProposal)
}
}

0 comments on commit 7fcf1fa

Please sign in to comment.