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

[#1511] Refactor account representation from int to a dedicated account structure #1515

Open
wants to merge 6 commits into
base: 1514-Finish-multi-account-support
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -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
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,7 @@ class GetAddressViewController: UIViewController {
let synchronizer = SDKSynchronizer.shared

Task { @MainActor in
guard let uAddress = try? await synchronizer.getUnifiedAddress(accountIndex: 0) else {
guard let uAddress = try? await synchronizer.getUnifiedAddress(accountIndex: Zip32AccountIndex(0)) 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,13 +19,15 @@ 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
self?.accountBalance = try? await synchronizer.getAccountBalance()
self?.accountBalance = try? await synchronizer.getAccountBalance(accountIndex: accountIndex)
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 @@ -26,12 +28,12 @@ class GetUTXOsViewController: UIViewController {
let synchronizer = SDKSynchronizer.shared

Task { @MainActor in
let tAddress = (try? await synchronizer.getTransparentAddress(accountIndex: 0))?.stringEncoded ?? "no t-address found"
let tAddress = (try? await synchronizer.getTransparentAddress(accountIndex: accountIndex))?.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.getAccountBalance(accountIndex: accountIndex)?.unshielded ?? .zero

self.totalBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.amount))
self.verifiedBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.amount))
Expand All @@ -42,7 +44,7 @@ class GetUTXOsViewController: UIViewController {
do {
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)

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

KRProgressHUD.showMessage("🛡 Shielding 🛡")

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

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

var wallet = Initializer.shared

Expand Down Expand Up @@ -104,10 +105,10 @@ class SendViewController: UIViewController {

func updateBalance() async {
balanceLabel.text = format(
balance: (try? await synchronizer.getAccountBalance(accountIndex: 0))?.saplingBalance.total() ?? .zero
balance: (try? await synchronizer.getAccountBalance(accountIndex: accountIndex))?.saplingBalance.total() ?? .zero
)
verifiedBalanceLabel.text = format(
balance: (try? await synchronizer.getAccountBalance(accountIndex: 0))?.saplingBalance.spendableValue ?? .zero
balance: (try? await synchronizer.getAccountBalance(accountIndex: accountIndex))?.saplingBalance.spendableValue ?? .zero
)
}

Expand All @@ -122,7 +123,7 @@ class SendViewController: UIViewController {
func maxFundsOn() {
Task { @MainActor in
let fee = Zatoshi(10000)
let max: Zatoshi = ((try? await synchronizer.getAccountBalance(accountIndex: 0))?.saplingBalance.spendableValue ?? .zero) - fee
let max: Zatoshi = ((try? await synchronizer.getAccountBalance(accountIndex: accountIndex))?.saplingBalance.spendableValue ?? .zero) - fee
amountTextField.text = format(balance: max)
amountTextField.isEnabled = false
}
Expand All @@ -144,12 +145,12 @@ class SendViewController: UIViewController {
}

func isBalanceValid() async -> Bool {
let balance = (try? await synchronizer.getAccountBalance(accountIndex: 0))?.saplingBalance.spendableValue ?? .zero
let balance = (try? await synchronizer.getAccountBalance(accountIndex: accountIndex))?.saplingBalance.spendableValue ?? .zero
return balance > .zero
}

func isAmountValid() async -> Bool {
let balance = (try? await synchronizer.getAccountBalance(accountIndex: 0))?.saplingBalance.spendableValue ?? .zero
let balance = (try? await synchronizer.getAccountBalance(accountIndex: accountIndex))?.saplingBalance.spendableValue ?? .zero
guard
let value = amountTextField.text,
let amount = NumberFormatter.zcashNumberFormatter.number(from: value).flatMap({ Zatoshi($0.int64Value) }),
Expand Down Expand Up @@ -228,7 +229,7 @@ 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: accountIndex) else {
loggerProxy.error("NO SPENDING KEY")
return
}
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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")
],
Expand Down
36 changes: 36 additions & 0 deletions Sources/ZcashLightClientKit/Account/Account.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// 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 {

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).")
}

self.index = index
}
}

public struct AccountId: Equatable, Codable, Hashable {
LukasKorba marked this conversation as resolved.
Show resolved Hide resolved
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ extension SaplingParamsAction: Action {

func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
logger.debug("Fetching sapling parameters")
try await saplingParametersHandler.handleIfNeeded()
// TODO: [#1512] This is hardcoded Zip32AccountIndex for index 0, must be updated
// https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk/issues/1512
try await saplingParametersHandler.handleIfNeeded(accountIndex: Zip32AccountIndex(0))

await context.update(state: .updateSubtreeRoots)

Expand Down
8 changes: 4 additions & 4 deletions Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -837,15 +837,15 @@ extension CompactBlockProcessor {
}

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

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

func getTransparentAddress(accountIndex: Int) async throws -> TransparentAddress {
func getTransparentAddress(accountIndex: Zip32AccountIndex) async throws -> TransparentAddress {
try await getUnifiedAddress(accountIndex: accountIndex).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 account in accounts {
tAddresses += try await rustBackend.listTransparentReceivers(account: Int32(account))
for accountIndex in accounts {
tAddresses += try await rustBackend.listTransparentReceivers(accountIndex: accountIndex)
}

var utxos: [UnspentTransactionOutputEntity] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct SaplingParametersHandlerConfig {
}

protocol SaplingParametersHandler {
func handleIfNeeded() async throws
func handleIfNeeded(accountIndex: Zip32AccountIndex) async throws
}

struct SaplingParametersHandlerImpl {
Expand All @@ -24,14 +24,14 @@ struct SaplingParametersHandlerImpl {
}

extension SaplingParametersHandlerImpl: SaplingParametersHandler {
func handleIfNeeded() async throws {
func handleIfNeeded(accountIndex: Zip32AccountIndex) async throws {
try Task.checkCancellation()

do {
let totalSaplingBalance =
try await rustBackend.getWalletSummary()?.accountBalances[0]?.saplingBalance.total().amount
try await rustBackend.getWalletSummary()?.accountBalances[accountIndex]?.saplingBalance.total().amount
?? 0
let totalTransparentBalance = try await rustBackend.getTransparentBalance(account: Int32(0))
let totalTransparentBalance = try await rustBackend.getTransparentBalance(accountIndex: accountIndex)

// Download Sapling parameters only if sapling funds are detected.
guard totalSaplingBalance > 0 || totalTransparentBalance > 0 else { return }
Expand Down
16 changes: 8 additions & 8 deletions Sources/ZcashLightClientKit/ClosureSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ public protocol ClosureSynchronizer {
func start(retry: Bool, completion: @escaping (Error?) -> Void)
func stop()

func getSaplingAddress(accountIndex: Int, completion: @escaping (Result<SaplingAddress, Error>) -> Void)
func getUnifiedAddress(accountIndex: Int, completion: @escaping (Result<UnifiedAddress, Error>) -> Void)
func getTransparentAddress(accountIndex: Int, completion: @escaping (Result<TransparentAddress, Error>) -> Void)
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)

/// Creates a proposal for transferring funds to the given recipient.
///
/// - Parameter accountIndex: the account from which to transfer funds.
/// - Parameter accountIndex: the ZIP 32 index of 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,
accountIndex: Zip32AccountIndex,
recipient: Recipient,
amount: Zatoshi,
memo: Memo?,
Expand All @@ -55,7 +55,7 @@ public protocol ClosureSynchronizer {

/// Creates a proposal for shielding any transparent funds received by the given account.
///
/// - Parameter accountIndex: the account for which to shield funds.
/// - Parameter accountIndex: the ZIP 32 index of the account from 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
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: Int,
accountIndex: Zip32AccountIndex,
shieldingThreshold: Zatoshi,
memo: Memo,
transparentReceiver: TransparentAddress?,
Expand Down Expand Up @@ -127,7 +127,7 @@ public protocol ClosureSynchronizer {

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

func getAccountBalance(accountIndex: Int, completion: @escaping (Result<AccountBalance?, Error>) -> Void)
func getAccountBalance(accountIndex: Zip32AccountIndex, completion: @escaping (Result<AccountBalance?, Error>) -> Void)

func refreshExchangeRateUSD()

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

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

/// Creates a proposal for transferring funds to the given recipient.
///
/// - Parameter accountIndex: the account from which to transfer funds.
/// - Parameter accountIndex: the ZIP 32 index of 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,
accountIndex: Zip32AccountIndex,
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 accountIndex: the ZIP 32 index of the account from 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
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: Int,
accountIndex: Zip32AccountIndex,
shieldingThreshold: Zatoshi,
memo: Memo,
transparentReceiver: TransparentAddress?
Expand Down Expand Up @@ -110,7 +110,7 @@ public protocol CombineSynchronizer {

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

var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get }
Expand Down
8 changes: 4 additions & 4 deletions Sources/ZcashLightClientKit/Entity/AccountEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@ import Foundation
import SQLite

protocol AccountEntity {
var account: Int { get }
var accountIndex: Zip32AccountIndex { get }
var ufvk: String { get }
}

struct DbAccount: AccountEntity, Encodable, Decodable {
let account: Int
let accountIndex: Zip32AccountIndex
let ufvk: String
}

extension DbAccount: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(account)
hasher.combine(accountIndex.index)
hasher.combine(ufvk)
}

static func == (lhs: Self, rhs: Self) -> Bool {
guard
lhs.account == rhs.account,
lhs.accountIndex == rhs.accountIndex,
lhs.ufvk == rhs.ufvk
else { return false }

Expand Down
Loading
Loading