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

[#1514] finish multi account support #1522

Merged
merged 46 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a0535a6
[#1511] Refactor account representation from Int to a dedicated Accou…
LukasKorba Nov 19, 2024
4462a8f
GRPC bump
LukasKorba Nov 19, 2024
d00c970
Account id public
LukasKorba Nov 21, 2024
b31e1c9
Zip32Account and AccountId
LukasKorba Nov 22, 2024
9713751
Zip32AccountIndex UInt32 refactor
LukasKorba Nov 25, 2024
ea549c3
Account balances refactor UInt32 to Zip32AccountIndex
LukasKorba Nov 25, 2024
6505d5f
PR Comments addressed
LukasKorba Nov 29, 2024
670f2ce
Merge pull request #1515 from LukasKorba/1511-Refactor-account-repres…
LukasKorba Nov 29, 2024
35ddf87
[#1512] Ensure that the SDK does not assume a default account anywhere
LukasKorba Dec 3, 2024
db3a4b9
getWalletSummary out of the loop
LukasKorba Dec 3, 2024
a48a75d
Merge pull request #1516 from LukasKorba/1512-Ensure-that-the-SDK-doe…
LukasKorba Dec 3, 2024
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
2b3ff26
Update swift.yml
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
f7029ed
Merge pull request #1517 from LukasKorba/import-ufvk-ffi-preview
str4d 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
4 changes: 2 additions & 2 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ jobs:
permissions:
contents: read

runs-on: macos-13
runs-on: macos-14

steps:
- uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
timeout-minutes: 1
- name: Select Xcode version
run: sudo xcode-select -s '/Applications/Xcode_15.0.1.app/Contents/Developer'
run: sudo xcode-select -s '/Applications/Xcode_16.0.app/Contents/Developer'
- name: Build ZcashLightClientKit Swift Package
timeout-minutes: 15
run: swift build -v
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,21 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Added
- `DerivationTool.deriveArbitraryWalletKey`
- `DerivationTool.deriveArbitraryAccountKey`
- `DerivationTool.deriveUnifiedAddressFrom(ufvk)`
- `SDKSynchronizer.listAccounts` Returns a list of the accounts in the wallet.
- `SDKSynchronizer.importAccount` Imports a new account for unified full viewing key.
- `SDKSynchronizer.createPCZTFromProposal` Creates a partially-created (unsigned without proofs) transaction from the given proposal.
- `SDKSynchronizer.addProofsToPCZT` Adds proofs to the given PCZT
- `SDKSynchronizer.createTransactionFromPCZT` Takes a PCZT that has been separately proven and signed, finalizes it, and stores it in the wallet. Internally, this logic also submits and checks the newly stored and encoded transaction.

## Changed
- `zcashlc_propose_transfer`, `zcashlc_propose_transfer_from_uri` and `zcashlc_propose_shielding` no longer accpt a `use_zip317_fees` parameter; ZIP 317 standard fees are now always used and are not configurable.
- The SDK no longer assumes a default account. All business logic with instances of Zip32AccountIndex(<index>) has been refactored.
- `SDKSynchronizer.getAccountBalance -> AccountBalance?` into `SDKSynchronizer.getAccountsBalances -> [AccountUUID: AccountBalance]`

## Removed
- `SDKSynchronizer.sendToAddress`, deprecated in 2.1
- `SDKSynchronizer.shieldFunds`, deprecated in 2.1

## Checkpoints

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/grpc/grpc-swift.git",
"state" : {
"revision" : "3ef3a9f42a11bfca767de880f1a0aedd2b65b840",
"version" : "1.24.1"
"revision" : "8c5e99d0255c373e0330730d191a3423c57373fb",
"version" : "1.24.2"
}
},
{
Expand Down Expand Up @@ -176,8 +176,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Electric-Coin-Company/zcash-light-client-ffi",
"state" : {
"revision" : "31a97a1478bc0354abdf208722b670f7fd3d9f8c",
"version" : "0.11.0"
"revision" : "11b0db058288b12ada9c5a95ed56f17f82d2868f",
"version" : "0.12.0"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private var wallet: Initializer?
private var synchronizer: SDKSynchronizer?

let accountIndex = Zip32AccountIndex(0)

var sharedSynchronizer: SDKSynchronizer {
if let sync = synchronizer {
return sync
Expand All @@ -34,7 +36,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var sharedViewingKey: UnifiedFullViewingKey {
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
let spendingKey = try! derivationTool
.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0)
.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: accountIndex)

return try! derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
}
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: 0) else {
guard let account = try? await synchronizer.listAccounts().first else {
return
}

guard let uAddress = try? await synchronizer.getUnifiedAddress(accountUUID: account.id) 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 @@ -25,7 +25,11 @@ class GetBalanceViewController: UIViewController {
self.title = "Account 0 Balance"

Task { @MainActor [weak self] in
self?.accountBalance = try? await synchronizer.getAccountBalance()
guard let account = try? await synchronizer.listAccounts().first else {
return
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ class GetUTXOsViewController: UIViewController {
@IBOutlet weak var totalBalanceLabel: UILabel!
@IBOutlet weak var messageLabel: UILabel!
@IBOutlet weak var shieldFundsButton: UIButton!


let accountIndex = Zip32AccountIndex(0)

override func viewDidLoad() {
super.viewDidLoad()

Expand All @@ -24,41 +26,23 @@ class GetUTXOsViewController: UIViewController {

func updateUI() {
let synchronizer = SDKSynchronizer.shared

Task { @MainActor in
let tAddress = (try? await synchronizer.getTransparentAddress(accountIndex: 0))?.stringEncoded ?? "no t-address found"
guard let account = try? await synchronizer.listAccounts().first else {
return
}

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

self.transparentAddressLabel.text = tAddress

// swiftlint:disable:next force_try
let balance = try! await AppDelegate.shared.sharedSynchronizer.getAccountBalance(accountIndex: 0)?.unshielded ?? .zero
let balance = try! await AppDelegate.shared.sharedSynchronizer.getAccountsBalances()[account.id]?.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: 0)

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,7 +25,6 @@ class SendViewController: UIViewController {
@IBOutlet weak var charactersLeftLabel: UILabel!

let characterLimit: Int = 512

var wallet = Initializer.shared

// swiftlint:disable:next implicitly_unwrapped_optional
Expand All @@ -46,7 +45,9 @@ class SendViewController: UIViewController {
closureSynchronizer.prepare(
with: DemoAppConfig.defaultSeed,
walletBirthday: DemoAppConfig.defaultBirthdayHeight,
for: .existingWallet
for: .existingWallet,
name: "",
keySource: nil
) { result in
loggerProxy.debug("Prepare result: \(result)")
}
Expand Down Expand Up @@ -103,12 +104,17 @@ class SendViewController: UIViewController {
}

func updateBalance() async {
balanceLabel.text = format(
balance: (try? await synchronizer.getAccountBalance(accountIndex: 0))?.saplingBalance.total() ?? .zero
)
verifiedBalanceLabel.text = format(
balance: (try? await synchronizer.getAccountBalance(accountIndex: 0))?.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.id])?.saplingBalance.total() ?? .zero
)
verifiedBalanceLabel.text = format(
balance: (try? await synchronizer.getAccountsBalances()[account.id])?.saplingBalance.spendableValue ?? .zero
)
}
}

func format(balance: Zatoshi = Zatoshi()) -> String {
Expand All @@ -121,8 +127,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.getAccountBalance(accountIndex: 0))?.saplingBalance.spendableValue ?? .zero) - fee
let max: Zatoshi = ((try? await synchronizer.getAccountsBalances()[account.id])?.saplingBalance.spendableValue ?? .zero) - fee
amountTextField.text = format(balance: max)
amountTextField.isEnabled = false
}
Expand All @@ -144,12 +154,18 @@ class SendViewController: UIViewController {
}

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

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

let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
guard let spendingKey = try? derivationTool.deriveUnifiedSpendingKey(seed: DemoAppConfig.defaultSeed, accountIndex: 0) 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
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ class SyncBlocksListViewController: UIViewController {
_ = try! await synchronizer.prepare(
with: synchronizerData.seed,
walletBirthday: synchronizerData.birthday,
for: .existingWallet
for: .existingWallet,
name: "",
keySource: nil
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ class SyncBlocksViewController: UIViewController {
_ = try await synchronizer.prepare(
with: DemoAppConfig.defaultSeed,
walletBirthday: DemoAppConfig.defaultBirthdayHeight,
for: .existingWallet
for: .existingWallet,
name: "",
keySource: nil
)
} catch {
loggerProxy.error(error.toZcashError().message)
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.23.0"),
.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.12.0")
],
targets: [
.target(
Expand Down
48 changes: 48 additions & 0 deletions Sources/ZcashLightClientKit/Account/Account.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Account.swift
// ZcashLightClientKit
//
// Created by Lukáš Korba on 2024-11-19.
//

/// A [ZIP 32](https://zips.z.cash/zip-0032) account index.
///
/// This index must be paired with a seed, or other context that determines a seed,
/// in order to identify a ZIP 32 *account*.
public struct Zip32AccountIndex: Equatable, Codable, Hashable {
public let index: UInt32

/// - Parameter index: the ZIP 32 account index, which must be less than ``1<<31``.
public init(_ index: UInt32) {
guard index < (1 << 31) else {
fatalError("Account index must be less than 1<<31. Input value is \(index).")
}

self.index = index
}
}

public struct AccountId: Equatable, Codable, Hashable {
public let id: Int

/// - Parameter id: the local account id, which must be nonnegative.
public init(_ id: Int) {
guard id >= 0 else {
fatalError("Account id must be >= 0. Input value is \(id).")
}

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: Int) async throws -> UnifiedAddress {
try await rustBackend.getCurrentAddress(account: Int32(accountIndex))
func getUnifiedAddress(accountUUID: AccountUUID) async throws -> UnifiedAddress {
try await rustBackend.getCurrentAddress(accountUUID: accountUUID)
}

func getSaplingAddress(accountIndex: Int) 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: Int) 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 @@ -40,7 +40,7 @@ extension UTXOFetcherImpl: UTXOFetcher {

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

var utxos: [UnspentTransactionOutputEntity] = []
Expand Down
Loading
Loading