Skip to content

Commit

Permalink
refresh-rates
Browse files Browse the repository at this point in the history
- API refactored to Combine's CurrentValueSubject

refresh-rate

- FiatCurrencyResult is now Equatable

refresh-rates

- cleanup

refresh-rates

- The API has been refactored to follow the same principles as for state and events.
- Review comments addressed

refresh-rates

- The API has been extended to send a result of the operation, success or failure

refresh-rates

- bugfix of the try vs try?

refresh-rates

- reverted the error state
  • Loading branch information
LukasKorba committed Aug 1, 2024
1 parent bce8085 commit da8aad2
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 61 deletions.
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ 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

## Added
- `Synchronizer.getExchangeRateUSD() -> NSDecimalNumber`, which fetches the latest USD/ZEC
exchange rate. Prices are queried over Tor (to hide the wallet's IP address) on Binance,
Coinbase, and Gemini.
- `Synchronizer.exchangeRateUSDStream: AnyPublisher<Result<FiatCurrencyResult?, Error>, Never>`,
which returns the currently-cached USD/ZEC exchange rate, or `nil` if it has not yet been
fetched.
- `Synchronizer.refreshExchangeRateUSD()`, , which refreshes the rate returned by
`Synchronizer.exchangeRateUSDStream`. Prices are queried over Tor (to hide the wallet's
IP address).

# 2.1.12 - 2024-07-04

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,49 @@

import UIKit
import ZcashLightClientKit
import Combine

class GetBalanceViewController: UIViewController {
@IBOutlet weak var balance: UILabel!
@IBOutlet weak var verified: UILabel!

var cancellable: AnyCancellable?

var accountBalance: AccountBalance?
var rate: FiatCurrencyResult?

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

Task { @MainActor in
let balance = try? await synchronizer.getAccountBalance()
let balanceText = (balance?.saplingBalance.total().formattedString) ?? "0.0"
let verifiedText = (balance?.saplingBalance.spendableValue.formattedString) ?? "0.0"
let usdZecRate = try await synchronizer.getExchangeRateUSD()
let usdBalance = (balance?.saplingBalance.total().decimalValue ?? 0).multiplying(by: usdZecRate)
let usdVerified = (balance?.saplingBalance.spendableValue.decimalValue ?? 0).multiplying(by: usdZecRate)
self.balance.text = "\(balanceText) ZEC\n\(usdBalance) USD\n\n(\(usdZecRate) USD/ZEC)"
self.verified.text = "\(verifiedText) ZEC\n\(usdVerified) USD"
Task { @MainActor [weak self] in
self?.accountBalance = try? await synchronizer.getAccountBalance()
self?.updateLabels()
}

cancellable = synchronizer.exchangeRateUSDStream.sink { [weak self] result in
self?.rate = result
self?.updateLabels()
}

synchronizer.refreshExchangeRateUSD()
}

func updateLabels() {
DispatchQueue.main.async { [weak self] in
let balanceText = (self?.accountBalance?.saplingBalance.total().formattedString) ?? "0.0"
let verifiedText = (self?.accountBalance?.saplingBalance.spendableValue.formattedString) ?? "0.0"

if let usdZecRate = self?.rate {
let usdBalance = (self?.accountBalance?.saplingBalance.total().decimalValue ?? 0).multiplying(by: usdZecRate.rate)
let usdVerified = (self?.accountBalance?.saplingBalance.spendableValue.decimalValue ?? 0).multiplying(by: usdZecRate.rate)
self?.balance.text = "\(balanceText) ZEC\n\(usdBalance) USD\n\n(\(usdZecRate.rate) USD/ZEC)"
self?.verified.text = "\(verifiedText) ZEC\n\(usdVerified) USD"
} else {
self?.balance.text = "\(balanceText) ZEC"
self?.verified.text = "\(verifiedText) ZEC"
}
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/ZcashLightClientKit/ClosureSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,7 @@ public protocol ClosureSynchronizer {

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

/// Fetches the latest ZEC-USD exchange rate.
func getExchangeRateUSD(completion: @escaping (Result<NSDecimalNumber, Error>) -> Void)
func refreshExchangeRateUSD()

/*
It can be missleading that these two methods are returning Publisher even this protocol is closure based. Reason is that Synchronizer doesn't
Expand Down
5 changes: 1 addition & 4 deletions Sources/ZcashLightClientKit/CombineSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,7 @@ public protocol CombineSynchronizer {

func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) -> SinglePublisher<RefreshedUTXOs, Error>

func getAccountBalance(accountIndex: Int) -> SinglePublisher<AccountBalance?, Error>

/// Fetches the latest ZEC-USD exchange rate.
func getExchangeRateUSD() -> SinglePublisher<NSDecimalNumber, Error>
func refreshExchangeRateUSD()

func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error>
func wipe() -> CompletablePublisher<Error>
Expand Down
13 changes: 13 additions & 0 deletions Sources/ZcashLightClientKit/Model/FiatCurrencyResult.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// FiatCurrencyResult.swift
//
//
// Created by Lukáš Korba on 31.07.2024.
//

import Foundation

public struct FiatCurrencyResult: Equatable {
public let rate: NSDecimalNumber
public let date: Date
}
7 changes: 5 additions & 2 deletions Sources/ZcashLightClientKit/Synchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ public protocol Synchronizer: AnyObject {
/// This stream is backed by `PassthroughSubject`. Check `SynchronizerEvent` to see which events may be emitted.
var eventStream: AnyPublisher<SynchronizerEvent, Never> { get }

/// This stream emits the latest known USD/ZEC exchange rate, paired with the time it was queried. See `FiatCurrencyResult`.
var exchangeRateUSDStream: AnyPublisher<FiatCurrencyResult?, Never> { get }

/// Initialize the wallet. The ZIP-32 seed bytes can optionally be passed to perform
/// database migrations. most of the times the seed won't be needed. If they do and are
/// not provided this will fail with `InitializationResult.seedRequired`. It could
Expand Down Expand Up @@ -309,8 +312,8 @@ public protocol Synchronizer: AnyObject {
/// - Returns: `AccountBalance`, struct that holds sapling and unshielded balances or `nil` when no account is associated with `accountIndex`
func getAccountBalance(accountIndex: Int) async throws -> AccountBalance?

/// Fetches the latest ZEC-USD exchange rate.
func getExchangeRateUSD() async throws -> NSDecimalNumber
/// Fetches the latest ZEC-USD exchange rate and updates `exchangeRateUSDSubject`.
func refreshExchangeRateUSD()

/// Rescans the known blocks with the current keys.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,8 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
}
}

public func getExchangeRateUSD(completion: @escaping (Result<NSDecimalNumber, Error>) -> Void) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.getExchangeRateUSD()
}
public func refreshExchangeRateUSD() {
synchronizer.refreshExchangeRateUSD()
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,8 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
}
}

public func getExchangeRateUSD() -> SinglePublisher<NSDecimalNumber, Error> {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.getExchangeRateUSD()
}
public func refreshExchangeRateUSD() {
synchronizer.refreshExchangeRateUSD()
}

public func rewind(_ policy: RewindPolicy) -> CompletablePublisher<Error> { synchronizer.rewind(policy) }
Expand Down
26 changes: 12 additions & 14 deletions Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public class SDKSynchronizer: Synchronizer {
private let eventSubject = PassthroughSubject<SynchronizerEvent, Never>()
public var eventStream: AnyPublisher<SynchronizerEvent, Never> { eventSubject.eraseToAnyPublisher() }

private let exchangeRateUSDSubject = CurrentValueSubject<FiatCurrencyResult?, Never>(nil)
public var exchangeRateUSDStream: AnyPublisher<FiatCurrencyResult?, Never> { exchangeRateUSDSubject.eraseToAnyPublisher() }

let metrics: SDKMetrics
public let logger: Logger

Expand Down Expand Up @@ -508,21 +511,16 @@ public class SDKSynchronizer: Synchronizer {
try await initializer.rustBackend.getWalletSummary()?.accountBalances[UInt32(accountIndex)]
}

public func getExchangeRateUSD() async throws -> NSDecimalNumber {
logger.info("Bootstrapping Tor client for fetching exchange rates")
let tor: TorClient
do {
tor = try await TorClient(torDir: initializer.torDirURL)
} catch {
logger.error("failed to bootstrap Tor client: \(error)")
throw error
}
/// Fetches the latest ZEC-USD exchange rate.
public func refreshExchangeRateUSD() {
Task {
logger.info("Bootstrapping Tor client for fetching exchange rates")

do {
return try await tor.getExchangeRateUSD()
} catch {
logger.error("Failed to fetch exchange rate through Tor: \(error)")
throw error
guard let tor = try? await TorClient(torDir: initializer.torDirURL) else {
return
}

exchangeRateUSDSubject.send(try? await tor.getExchangeRateUSD())
}
}

Expand Down
7 changes: 5 additions & 2 deletions Sources/ZcashLightClientKit/Tor/TorClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,16 @@ public class TorClient {
zcashlc_free_tor_runtime(runtime)
}

public func getExchangeRateUSD() async throws -> NSDecimalNumber {
public func getExchangeRateUSD() async throws -> FiatCurrencyResult {
let rate = zcashlc_get_exchange_rate_usd(runtime)

if rate.is_sign_negative {
throw ZcashError.rustTorClientGet(lastErrorMessage(fallback: "`TorClient.get` failed with unknown error"))
}

return NSDecimalNumber(mantissa: rate.mantissa, exponent: rate.exponent, isNegative: rate.is_sign_negative)
return FiatCurrencyResult(
rate: NSDecimalNumber(mantissa: rate.mantissa, exponent: rate.exponent, isNegative: rate.is_sign_negative),
date: Date()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,10 @@ class SynchronizerMock: Synchronizer {
get { return underlyingEventStream }
}
var underlyingEventStream: AnyPublisher<SynchronizerEvent, Never>!
var exchangeRateUSDStream: AnyPublisher<FiatCurrencyResult?, Never> {
get { return underlyingExchangeRateUSDStream }
}
var underlyingExchangeRateUSDStream: AnyPublisher<FiatCurrencyResult?, Never>!
var transactions: [ZcashTransaction.Overview] {
get async { return underlyingTransactions }
}
Expand Down Expand Up @@ -1798,26 +1802,17 @@ class SynchronizerMock: Synchronizer {
}
}

// MARK: - getExchangeRateUSD
// MARK: - refreshExchangeRateUSD

var getExchangeRateUSDThrowableError: Error?
var getExchangeRateUSDCallsCount = 0
var getExchangeRateUSDCalled: Bool {
return getExchangeRateUSDCallsCount > 0
var refreshExchangeRateUSDCallsCount = 0
var refreshExchangeRateUSDCalled: Bool {
return refreshExchangeRateUSDCallsCount > 0
}
var getExchangeRateUSDReturnValue: NSDecimalNumber!
var getExchangeRateUSDClosure: (() async throws -> NSDecimalNumber)?
var refreshExchangeRateUSDClosure: (() -> Void)?

func getExchangeRateUSD() async throws -> NSDecimalNumber {
if let error = getExchangeRateUSDThrowableError {
throw error
}
getExchangeRateUSDCallsCount += 1
if let closure = getExchangeRateUSDClosure {
return try await closure()
} else {
return getExchangeRateUSDReturnValue
}
func refreshExchangeRateUSD() {
refreshExchangeRateUSDCallsCount += 1
refreshExchangeRateUSDClosure!()
}

// MARK: - rewind
Expand Down

0 comments on commit da8aad2

Please sign in to comment.