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

Multi-account and PCZT support #1517

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a09f2b1
import-ufvk-ffi
LukasKorba Dec 3, 2024
3bfe5b3
ImportAccount UFVK
LukasKorba Dec 4, 2024
06181fb
AccountUUID refactor - phase 1
LukasKorba Dec 4, 2024
7a6bd72
AccountUUID refactor - phase 2
LukasKorba Dec 4, 2024
27af6f1
PR Comments addressed and code cleaned up
LukasKorba Dec 5, 2024
92bd2e6
Update swift.yml
LukasKorba Dec 5, 2024
05002a8
Generated mocks
LukasKorba Dec 5, 2024
bb03333
Performance, Network and Dark side tests
LukasKorba Dec 5, 2024
5179f4e
getAccount for UUID
LukasKorba Dec 6, 2024
e48cd60
Account conformace
LukasKorba Dec 6, 2024
df1b319
hd_account_index wrapped in Zip32AccountIndex
LukasKorba Dec 9, 2024
89773c6
account_id -> account_uuid refactor
LukasKorba Dec 9, 2024
f69aa19
Public importAccount method
LukasKorba Dec 9, 2024
8c099e3
seedFingerprint and zip32AccountIndex parameters in importAccount added
LukasKorba Dec 9, 2024
dcf6cd9
Codable UnifiedAddress conformance
LukasKorba Dec 9, 2024
7782d6f
Optional account_name
LukasKorba Dec 9, 2024
32bb9ba
FFI bump
LukasKorba Dec 10, 2024
08e5a73
DerivationTool.deriveUnifiedAddressFrom(ufvk)
LukasKorba Dec 10, 2024
6bab337
PCZT methods
LukasKorba Dec 10, 2024
0fba7f2
PCZT methods updated
LukasKorba Dec 10, 2024
5943d63
FFI bump
LukasKorba Dec 10, 2024
30af9c6
PCZT API updated
LukasKorba Dec 10, 2024
dd587ac
from_account_id reverted
LukasKorba Dec 10, 2024
26ad508
FFI bumped
LukasKorba Dec 10, 2024
632626a
Reset of synchronizer alias
LukasKorba Dec 11, 2024
ee32938
FFI bump
LukasKorba Dec 11, 2024
f499898
Create and submit PCZT
LukasKorba Dec 12, 2024
fb38ac8
Alias checker disconnected
LukasKorba Dec 12, 2024
da00a69
Pczt typealias and clean up
LukasKorba Dec 13, 2024
f16d098
Code cleanup
LukasKorba Dec 13, 2024
ce667eb
FFI 0.12.0 bump
LukasKorba Dec 17, 2024
a8b1524
Review comments addressed
LukasKorba Jan 7, 2025
3bea79c
Comments updated
LukasKorba Jan 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Electric-Coin-Company/zcash-light-client-ffi",
"state" : {
"revision" : "31a97a1478bc0354abdf208722b670f7fd3d9f8c",
"version" : "0.11.0"
"revision" : "8869ce252cd7c2c25531c377f1c314b0b3e6bee2"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ class GetAddressViewController: UIViewController {
let synchronizer = SDKSynchronizer.shared

Task { @MainActor in
guard let uAddress = try? await synchronizer.getUnifiedAddress(accountIndex: Zip32AccountIndex(0)) else {
guard let account = try? await synchronizer.listAccounts().first else {
nuttycom marked this conversation as resolved.
Show resolved Hide resolved
return
}

guard let uAddress = try? await synchronizer.getUnifiedAddress(accountUUID: account) else {
unifiedAddressLabel.text = "could not derive UA"
tAddressLabel.text = "could not derive tAddress"
saplingAddress.text = "could not derive zAddress"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ class GetBalanceViewController: UIViewController {
var accountBalance: AccountBalance?
var rate: FiatCurrencyResult?

let accountIndex = Zip32AccountIndex(0)

override func viewDidLoad() {
super.viewDidLoad()
let synchronizer = AppDelegate.shared.sharedSynchronizer
self.title = "Account 0 Balance"

Task { @MainActor [weak self] in
guard let accountIndex = self?.accountIndex else { return }

self?.accountBalance = try? await synchronizer.getAccountsBalances()[accountIndex]
guard let account = try? await synchronizer.listAccounts().first else {
nuttycom marked this conversation as resolved.
Show resolved Hide resolved
return
}

self?.accountBalance = try? await synchronizer.getAccountsBalances()[account]
self?.updateLabels()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,23 @@ class GetUTXOsViewController: UIViewController {

func updateUI() {
let synchronizer = SDKSynchronizer.shared

Task { @MainActor in
let tAddress = (try? await synchronizer.getTransparentAddress(accountIndex: accountIndex))?.stringEncoded ?? "no t-address found"
guard let account = try? await synchronizer.listAccounts().first else {
nuttycom marked this conversation as resolved.
Show resolved Hide resolved
return
}

let tAddress = (try? await synchronizer.getTransparentAddress(accountUUID: account))?.stringEncoded ?? "no t-address found"

self.transparentAddressLabel.text = tAddress

// swiftlint:disable:next force_try
let balance = try! await AppDelegate.shared.sharedSynchronizer.getAccountsBalances()[accountIndex]?.unshielded ?? .zero
let balance = try! await AppDelegate.shared.sharedSynchronizer.getAccountsBalances()[account]?.unshielded ?? .zero

self.totalBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.amount))
self.verifiedBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.amount))
}
}

@IBAction func shieldFunds(_ sender: Any) {
do {
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)

let usk = try derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: accountIndex)

KRProgressHUD.showMessage("🛡 Shielding 🛡")

Task { @MainActor in
let transaction = try await AppDelegate.shared.sharedSynchronizer.shieldFunds(
spendingKey: usk,
memo: try Memo(string: "shielding is fun!"),
shieldingThreshold: Zatoshi(10000)
)
KRProgressHUD.dismiss()
self.messageLabel.text = "funds shielded \(transaction)"
}
} catch {
self.messageLabel.text = "Shielding failed \(error)"
}
}
}

extension GetUTXOsViewController: UITextFieldDelegate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ class SendViewController: UIViewController {
@IBOutlet weak var charactersLeftLabel: UILabel!

let characterLimit: Int = 512
let accountIndex = Zip32AccountIndex(0)

var wallet = Initializer.shared

// swiftlint:disable:next implicitly_unwrapped_optional
Expand Down Expand Up @@ -104,12 +102,17 @@ class SendViewController: UIViewController {
}

func updateBalance() async {
balanceLabel.text = format(
balance: (try? await synchronizer.getAccountsBalances()[accountIndex])?.saplingBalance.total() ?? .zero
)
verifiedBalanceLabel.text = format(
balance: (try? await synchronizer.getAccountsBalances()[accountIndex])?.saplingBalance.spendableValue ?? .zero
)
Task { @MainActor in
guard let account = try? await synchronizer.listAccounts().first else {
return
}
balanceLabel.text = format(
balance: (try? await synchronizer.getAccountsBalances()[account])?.saplingBalance.total() ?? .zero
)
verifiedBalanceLabel.text = format(
balance: (try? await synchronizer.getAccountsBalances()[account])?.saplingBalance.spendableValue ?? .zero
)
}
}

func format(balance: Zatoshi = Zatoshi()) -> String {
Expand All @@ -122,8 +125,12 @@ class SendViewController: UIViewController {

func maxFundsOn() {
Task { @MainActor in
guard let account = try? await synchronizer.listAccounts().first else {
return
}

let fee = Zatoshi(10000)
let max: Zatoshi = ((try? await synchronizer.getAccountsBalances()[accountIndex])?.saplingBalance.spendableValue ?? .zero) - fee
let max: Zatoshi = ((try? await synchronizer.getAccountsBalances()[account])?.saplingBalance.spendableValue ?? .zero) - fee
amountTextField.text = format(balance: max)
amountTextField.isEnabled = false
}
Expand All @@ -145,12 +152,18 @@ class SendViewController: UIViewController {
}

func isBalanceValid() async -> Bool {
let balance = (try? await synchronizer.getAccountsBalances()[accountIndex])?.saplingBalance.spendableValue ?? .zero
guard let account = try? await synchronizer.listAccounts().first else {
return false
}
let balance = (try? await synchronizer.getAccountsBalances()[account])?.saplingBalance.spendableValue ?? .zero
return balance > .zero
}

func isAmountValid() async -> Bool {
let balance = (try? await synchronizer.getAccountsBalances()[accountIndex])?.saplingBalance.spendableValue ?? .zero
guard let account = try? await synchronizer.listAccounts().first else {
return false
}
let balance = (try? await synchronizer.getAccountsBalances()[account])?.saplingBalance.spendableValue ?? .zero
guard
let value = amountTextField.text,
let amount = NumberFormatter.zcashNumberFormatter.number(from: value).flatMap({ Zatoshi($0.int64Value) }),
Expand Down Expand Up @@ -229,23 +242,14 @@ class SendViewController: UIViewController {
}

let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
guard let spendingKey = try? derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: accountIndex) else {
guard let spendingKey = try? derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: Zip32AccountIndex(0)) else {
loggerProxy.error("NO SPENDING KEY")
return
}

KRProgressHUD.show()

do {
let pendingTransaction = try await synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: zec,
// swiftlint:disable:next force_try
toAddress: try! Recipient(recipient, network: kZcashNetwork.networkType),
// swiftlint:disable:next force_try
memo: try! self.memoField.text.asMemo()
)
loggerProxy.info("transaction created: \(pendingTransaction)")
KRProgressHUD.dismiss()
} catch {
loggerProxy.error("SEND FAILED: \(error)")
Expand Down
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.24.2"),
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3"),
.package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", exact: "0.11.0")
// .package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", exact: "0.11.0")
// .package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", branch: "preview/account_uuids")
.package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", revision: "8869ce252cd7c2c25531c377f1c314b0b3e6bee2")

],
targets: [
.target(
Expand Down
12 changes: 12 additions & 0 deletions Sources/ZcashLightClientKit/Account/Account.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

/// - Parameter index: the ZIP 32 account index, which must be less than ``1<<31``.
public init(_ index: UInt32) {
guard index < (1<<31) else {

Check warning on line 17 in Sources/ZcashLightClientKit/Account/Account.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Operator Usage Whitespace Violation: Operators should be surrounded by a single whitespace when they are being used (operator_usage_whitespace)
fatalError("Account index must be less than 1<<31. Input value is \(index).")
}

Expand All @@ -34,3 +34,15 @@
self.id = id
}
}

public struct AccountUUID: Equatable, Codable, Hashable, Identifiable {
public let id: [UInt8]

init(id: [UInt8]) {
guard id.count == 16 else {
fatalError("Account UUID must be 16 bytes long. Input value is \(id).")
}

self.id = id
}
}
12 changes: 6 additions & 6 deletions Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -837,16 +837,16 @@ extension CompactBlockProcessor {
}

extension CompactBlockProcessor {
func getUnifiedAddress(accountIndex: Zip32AccountIndex) async throws -> UnifiedAddress {
try await rustBackend.getCurrentAddress(accountIndex: accountIndex)
func getUnifiedAddress(accountUUID: AccountUUID) async throws -> UnifiedAddress {
try await rustBackend.getCurrentAddress(accountUUID: accountUUID)
}

func getSaplingAddress(accountIndex: Zip32AccountIndex) async throws -> SaplingAddress {
try await getUnifiedAddress(accountIndex: accountIndex).saplingReceiver()
func getSaplingAddress(accountUUID: AccountUUID) async throws -> SaplingAddress {
try await getUnifiedAddress(accountUUID: accountUUID).saplingReceiver()
}

func getTransparentAddress(accountIndex: Zip32AccountIndex) async throws -> TransparentAddress {
try await getUnifiedAddress(accountIndex: accountIndex).transparentReceiver()
func getTransparentAddress(accountUUID: AccountUUID) async throws -> TransparentAddress {
try await getUnifiedAddress(accountUUID: accountUUID).transparentReceiver()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ extension UTXOFetcherImpl: UTXOFetcher {
let accounts = try await rustBackend.listAccounts()

var tAddresses: [TransparentAddress] = []
for accountIndex in accounts {
tAddresses += try await rustBackend.listTransparentReceivers(accountIndex: accountIndex)
for account in accounts {
tAddresses += try await rustBackend.listTransparentReceivers(accountUUID: account)
}

var utxos: [UnspentTransactionOutputEntity] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,14 @@ extension SaplingParametersHandlerImpl: SaplingParametersHandler {
let accountBalances = try await rustBackend.getWalletSummary()?.accountBalances

for account in accounts {
let zip32AccountIndex = Zip32AccountIndex(account.index)

let totalSaplingBalance = accountBalances?[zip32AccountIndex]?.saplingBalance.total().amount ?? 0
let totalSaplingBalance = accountBalances?[account]?.saplingBalance.total().amount ?? 0

if totalSaplingBalance > 0 {
totalSaplingBalanceTrigger = true
break
}

let totalTransparentBalance = try await rustBackend.getTransparentBalance(accountIndex: zip32AccountIndex)
let totalTransparentBalance = try await rustBackend.getTransparentBalance(accountUUID: account)

if totalTransparentBalance > 0 {
totalTransparentBalanceTrigger = true
Expand Down
31 changes: 8 additions & 23 deletions Sources/ZcashLightClientKit/ClosureSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public protocol ClosureSynchronizer {
func start(retry: Bool, completion: @escaping (Error?) -> Void)
func stop()

func getSaplingAddress(accountIndex: Zip32AccountIndex, completion: @escaping (Result<SaplingAddress, Error>) -> Void)
func getUnifiedAddress(accountIndex: Zip32AccountIndex, completion: @escaping (Result<UnifiedAddress, Error>) -> Void)
func getTransparentAddress(accountIndex: Zip32AccountIndex, completion: @escaping (Result<TransparentAddress, Error>) -> Void)
func getSaplingAddress(accountUUID: AccountUUID, completion: @escaping (Result<SaplingAddress, Error>) -> Void)
func getUnifiedAddress(accountUUID: AccountUUID, completion: @escaping (Result<UnifiedAddress, Error>) -> Void)
func getTransparentAddress(accountUUID: AccountUUID, completion: @escaping (Result<TransparentAddress, Error>) -> Void)

/// Creates a proposal for transferring funds to the given recipient.
///
Expand All @@ -46,7 +46,7 @@ public protocol ClosureSynchronizer {
/// 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: Zip32AccountIndex,
accountUUID: AccountUUID,
recipient: Recipient,
amount: Zatoshi,
memo: Memo?,
Expand All @@ -69,7 +69,7 @@ public protocol ClosureSynchronizer {
/// 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: Zip32AccountIndex,
accountUUID: AccountUUID,
shieldingThreshold: Zatoshi,
memo: Memo,
transparentReceiver: TransparentAddress?,
Expand All @@ -93,23 +93,8 @@ public protocol ClosureSynchronizer {
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,
toAddress: Recipient,
memo: Memo?,
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,
shieldingThreshold: Zatoshi,
completion: @escaping (Result<ZcashTransaction.Overview, Error>) -> Void
)

func listAccounts(completion: @escaping (Result<[AccountUUID], Error>) -> Void)

func clearedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
func sentTranscations(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
func receivedTransactions(completion: @escaping ([ZcashTransaction.Overview]) -> Void)
Expand All @@ -127,7 +112,7 @@ public protocol ClosureSynchronizer {

func refreshUTXOs(address: TransparentAddress, from height: BlockHeight, completion: @escaping (Result<RefreshedUTXOs, Error>) -> Void)

func getAccountsBalances(completion: @escaping (Result<[Zip32AccountIndex: AccountBalance], Error>) -> Void)
func getAccountsBalances(completion: @escaping (Result<[AccountUUID: AccountBalance], Error>) -> Void)

func refreshExchangeRateUSD()

Expand Down
33 changes: 8 additions & 25 deletions Sources/ZcashLightClientKit/CombineSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ public protocol CombineSynchronizer {
func start(retry: Bool) -> CompletablePublisher<Error>
func stop()

func getSaplingAddress(accountIndex: Zip32AccountIndex) -> SinglePublisher<SaplingAddress, Error>
func getUnifiedAddress(accountIndex: Zip32AccountIndex) -> SinglePublisher<UnifiedAddress, Error>
func getTransparentAddress(accountIndex: Zip32AccountIndex) -> SinglePublisher<TransparentAddress, Error>
func getSaplingAddress(accountUUID: AccountUUID) -> SinglePublisher<SaplingAddress, Error>
func getUnifiedAddress(accountUUID: AccountUUID) -> SinglePublisher<UnifiedAddress, Error>
func getTransparentAddress(accountUUID: AccountUUID) -> SinglePublisher<TransparentAddress, Error>

/// Creates a proposal for transferring funds to the given recipient.
///
Expand All @@ -45,7 +45,7 @@ public protocol CombineSynchronizer {
/// 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: Zip32AccountIndex,
accountUUID: AccountUUID,
recipient: Recipient,
amount: Zatoshi,
memo: Memo?
Expand All @@ -67,7 +67,7 @@ public protocol CombineSynchronizer {
/// 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: Zip32AccountIndex,
accountUUID: AccountUUID,
shieldingThreshold: Zatoshi,
memo: Memo,
transparentReceiver: TransparentAddress?
Expand All @@ -89,30 +89,13 @@ public protocol CombineSynchronizer {
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. use `proposeShielding:` instead"
)
func shieldFunds(
spendingKey: UnifiedSpendingKey,
memo: Memo,
shieldingThreshold: Zatoshi
) -> SinglePublisher<ZcashTransaction.Overview, Error>

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

func listAccounts() -> SinglePublisher<[AccountUUID], Error>

var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var sentTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
Expand Down
Loading
Loading