Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#1379] Fulfill Payment from a valid ZIP-321 request #1390

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ 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
### [#1379] Fulfill Payment from a valid ZIP-321 request
New API implemented that allows clients to use a ZIP-321 Payment URI to create transaction.
```
func fulfillPaymentURI(
_ uri: String,
spendingKey: UnifiedSpendingKey
) async throws -> ZcashTransaction.Overview
```

Possible errors:
- `ZcashError.rustProposeTransferFromURI`
- Other errors that `sentToAddress` can throw

# 2.0.11 - 2024-03-08

Expand Down
9 changes: 7 additions & 2 deletions Sources/ZcashLightClientKit/CombineSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,19 @@
toAddress: Recipient,
memo: Memo?
) -> SinglePublisher<ZcashTransaction.Overview, Error>

@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.")
@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients. use `proposeShielding:` instead")

Check warning on line 100 in Sources/ZcashLightClientKit/CombineSynchronizer.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Line Length Violation: Line should be 150 characters or less; currently it has 153 characters (line_length)
func shieldFunds(
spendingKey: UnifiedSpendingKey,
memo: Memo,
shieldingThreshold: Zatoshi
) -> SinglePublisher<ZcashTransaction.Overview, Error>

func proposefulfillingPaymentURI(
_ uri: String,
accountIndex: Int
) -> SinglePublisher<Proposal, Error>

var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var sentTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
Expand Down
6 changes: 6 additions & 0 deletions Sources/ZcashLightClientKit/Error/ZcashError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@ public enum ZcashError: Equatable, Error {
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0056
case rustGetWalletSummary(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0057
case rustProposeTransferFromURI(_ rustError: String)
/// SQLite query failed when fetching all accounts from the database.
/// - `sqliteError` is error produced by SQLite library.
/// ZADAO0001
Expand Down Expand Up @@ -676,6 +680,7 @@ public enum ZcashError: Equatable, Error {
case .rustLatestCachedBlockHeight: return "Error from rust layer when calling ZcashRustBackend.latestCachedBlockHeight"
case .rustScanProgressOutOfRange: return "Rust layer's call ZcashRustBackend.getScanProgress returned values that after computation are outside of allowed range 0-100%."
case .rustGetWalletSummary: return "Error from rust layer when calling ZcashRustBackend.getWalletSummary"
case .rustProposeTransferFromURI: return "Error from rust layer when calling ZcashRustBackend."
case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database."
case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them."
case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database."
Expand Down Expand Up @@ -850,6 +855,7 @@ public enum ZcashError: Equatable, Error {
case .rustLatestCachedBlockHeight: return .rustLatestCachedBlockHeight
case .rustScanProgressOutOfRange: return .rustScanProgressOutOfRange
case .rustGetWalletSummary: return .rustGetWalletSummary
case .rustProposeTransferFromURI: return .rustProposeTransferFromURI
case .accountDAOGetAll: return .accountDAOGetAll
case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
case .accountDAOFindBy: return .accountDAOFindBy
Expand Down
2 changes: 2 additions & 0 deletions Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ public enum ZcashErrorCode: String {
case rustScanProgressOutOfRange = "ZRUST0055"
/// Error from rust layer when calling ZcashRustBackend.getWalletSummary
case rustGetWalletSummary = "ZRUST0056"
/// Error from rust layer when calling ZcashRustBackend.
case rustProposeTransferFromURI = "ZRUST0057"
/// SQLite query failed when fetching all accounts from the database.
case accountDAOGetAll = "ZADAO0001"
/// Fetched accounts from SQLite but can't decode them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ enum ZcashErrorDefinition {
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0056"
case rustGetWalletSummary(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0057"
case rustProposeTransferFromURI(_ rustError: String)

// MARK: - Account DAO

Expand Down
28 changes: 28 additions & 0 deletions Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by Jack Grigg on 5/8/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
// swiftlint:disable type_body_length

Check warning on line 8 in Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Blanket Disable Command Violation: The disabled 'type_body_length' rule should be re-enabled before the end of the file (blanket_disable_command)
import Foundation
import libzcashlc

Expand Down Expand Up @@ -112,6 +112,34 @@
))
}

func proposeTransferFromURI(
_ uri: String,
account: Int32
) async throws -> FfiProposal {
globalDBLock.lock()
let proposal = zcashlc_propose_transfer_from_uri(
dbData.0,
dbData.1,
account,
[CChar](uri.utf8CString),
networkType.networkId,
minimumConfirmations,
useZIP317Fees
)
globalDBLock.unlock()

guard let proposal else {
throw ZcashError.rustCreateToAddress(lastErrorMessage(fallback: "`proposeTransfer` failed with unknown error"))
}

defer { zcashlc_free_boxed_slice(proposal) }

return try FfiProposal(contiguousBytes: Data(
bytes: proposal.pointee.ptr,
count: Int(proposal.pointee.len)
))
}

func decryptAndStoreTransaction(txBytes: [UInt8], minedHeight: Int32) async throws {
globalDBLock.lock()
let result = zcashlc_decrypt_and_store_transaction(
Expand Down Expand Up @@ -829,7 +857,7 @@
}

struct FfiTxId {
var tuple: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)

Check failure on line 860 in Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Large Tuple Violation: Tuples should have at most 2 members (large_tuple)

Check warning on line 860 in Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Line Length Violation: Line should be 150 characters or less; currently it has 239 characters (line_length)
var array: [UInt8] {
withUnsafeBytes(of: self.tuple) { buf in
[UInt8](buf)
Expand Down
17 changes: 16 additions & 1 deletion Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// ZcashRustBackendWelding.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 12/09/2019.
// Created by Francisco 'Pacu' Gindre on 2019-12-09.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//

Expand Down Expand Up @@ -208,6 +208,21 @@ protocol ZcashRustBackendWelding {
memo: MemoBytes?
) async throws -> FfiProposal

/// Select transaction inputs, compute fees, and construct a proposal for a transaction
/// that can then be authorized and made ready for submission to the network with
/// `createProposedTransaction` from a valid [ZIP-321](https://zips.z.cash/zip-0321) Payment Request UR
///
/// - parameter uri: the URI String that the proposal will be made from.
/// - parameter account: index of the given account
/// - Parameter to: recipient address
/// - Parameter value: transaction amount in Zatoshi
/// - Parameter memo: the `MemoBytes` for this transaction. pass `nil` when sending to transparent receivers
/// - Throws: `rustCreateToAddress`.
func proposeTransferFromURI(
_ uri: String,
account: Int32
) async throws -> FfiProposal

/// Constructs a transaction proposal to shield all found UTXOs in data db for the given account,
/// that can then be authorized and made ready for submission to the network with
/// `createProposedTransaction`.
Expand Down
15 changes: 13 additions & 2 deletions Sources/ZcashLightClientKit/Synchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ public protocol Synchronizer: AnyObject {
/// - Parameter toAddress: the recipient's address.
/// - Parameter memo: an `Optional<Memo>`with the memo to include as part of the transaction. send `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
/// - NOTE: If `prepare()` hasn't already been called since creating of synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.")
func sendToAddress(
Expand All @@ -225,12 +225,23 @@ public protocol Synchronizer: AnyObject {
memo: Memo?
) async throws -> ZcashTransaction.Overview

/// 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 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
/// `SynchronizerErrors.notPrepared`.
func proposefulfillingPaymentURI(
_ uri: String,
accountIndex: Int
) async throws -> Proposal

/// 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 memo: the optional memo to include as part of the transaction.
/// - Parameter shieldingThreshold: the minimum transparent balance required before a transaction will be created.
///
/// If `prepare()` hasn't already been called since creation of the 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`.
@available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.")
func shieldFunds(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@
}
}

public func proposefulfillingPaymentURI(

Check warning on line 83 in Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Indentation Width Violation: Code should be indented using one tab or 4 spaces (indentation_width)
_ uri: String,
accountIndex: Int
) -> SinglePublisher<Proposal, Error> {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.proposefulfillingPaymentURI(
uri,
accountIndex: accountIndex
)
}
}

public func proposeShielding(
accountIndex: Int,
shieldingThreshold: Zatoshi,
Expand Down
20 changes: 18 additions & 2 deletions Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
import Combine

/// Synchronizer implementation for UIKit and iOS 13+
// swiftlint:disable type_body_length

Check warning on line 13 in Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Blanket Disable Command Violation: The disabled 'type_body_length' rule should be re-enabled before the end of the file (blanket_disable_command)
public class SDKSynchronizer: Synchronizer {

Check warning on line 15 in Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Vertical Whitespace after Opening Braces Violation: Don't include vertical whitespace (empty line) after opening braces (vertical_whitespace_opening_braces)
public var alias: ZcashSynchronizerAlias { initializer.alias }

private lazy var streamsUpdateQueue = { DispatchQueue(label: "streamsUpdateQueue_\(initializer.alias.description)") }()
Expand Down Expand Up @@ -290,14 +291,29 @@
) async throws -> Proposal? {
try throwIfUnprepared()

let proposal = try await transactionEncoder.proposeShielding(
return try await transactionEncoder.proposeShielding(
accountIndex: accountIndex,
shieldingThreshold: shieldingThreshold,
memoBytes: memo.asMemoBytes(),
transparentReceiver: transparentReceiver?.stringEncoded
)
}

return proposal
public func proposefulfillingPaymentURI(
_ uri: String,
accountIndex: Int
) async throws -> Proposal {
do {
try throwIfUnprepared()
return try await transactionEncoder.proposeFulfillingPaymentFromURI(
uri,

Check warning on line 309 in Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Indentation Width Violation: Code should be indented using one tab or 4 spaces (indentation_width)
accountIndex: accountIndex
)
} catch ZcashError.rustCreateToAddress(let e) {

Check failure on line 312 in Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Identifier Name Violation: Variable name 'e' should be between 3 and 40 characters long (identifier_name)
throw ZcashError.rustProposeTransferFromURI(e)
} catch {
throw error
}
}

public func createProposedTransactions(
Expand Down
17 changes: 16 additions & 1 deletion Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// TransactionEncoder.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 11/20/19.
// Created by Francisco Gindre on 2019-11-20.
//

import Foundation
Expand Down Expand Up @@ -72,6 +72,21 @@ protocol TransactionEncoder {
spendingKey: UnifiedSpendingKey
) async throws -> [ZcashTransaction.Overview]

/// 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
/// double-bangs for things).
///
/// - Parameters:
/// - Parameter uri: a valid ZIP-321 payment URI.
/// - Parameter accountIndex: the index of the account the proposal should be made from.
/// - Throws:
/// - `walletTransEncoderCreateTransactionMissingSaplingParams` if the sapling parameters aren't downloaded.
/// - Some `ZcashError.rust*` if the creation of transaction fails.
func proposeFulfillingPaymentFromURI(
_ uri: String,
accountIndex: Int
) async throws -> Proposal

/// submits a transaction to the Zcash peer-to-peer network.
/// - Parameter transaction: a transaction overview
func submit(transaction: EncodedTransaction) async throws
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ class WalletTransactionEncoder: TransactionEncoder {
return Proposal(inner: proposal)
}

func proposeFulfillingPaymentFromURI(
_ uri: String,
accountIndex: Int
) async throws -> Proposal {
let proposal = try await rustBackend.proposeTransferFromURI(
uri,
account: Int32(accountIndex)
)
return Proposal(inner: proposal)
}

func createProposedTransactions(
proposal: Proposal,
spendingKey: UnifiedSpendingKey
Expand Down