Skip to content

Commit 08af5b5

Browse files
authored
feat(payments): scaffold Balance service for GetBalance (#658)
* feat(payments): scaffold Balance service for GetBalance Adds the app-side service/client layering for the new ocp.balance.v1.Balance RPC: BalanceService wraps the generated client, ErrorGetBalance maps the proto result enum and conforms to TransportClassifiableError, and Client+Balance exposes an async getBalance(owner:) wrapper. GetBalanceRequest carries no auth field, so this takes a bare PublicKey rather than a signing KeyPair, unlike every other Payments API request. * chore(deps): bump ocp-client-protocol to 0.2.0 Picks up the Balance service added upstream in ea6418c5.
1 parent bec251e commit 08af5b5

5 files changed

Lines changed: 93 additions & 1 deletion

File tree

‎FlipcashAPI/Package.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let contractDependencies: [Package.Dependency] = protoLocalRoot.map { root in
3434
.package(path: "\(root)/flipcash2-client-protocol"),
3535
]
3636
} ?? [
37-
.package(url: "https://github.com/code-payments/ocp-client-protocol", exact: "0.1.0"),
37+
.package(url: "https://github.com/code-payments/ocp-client-protocol", exact: "0.2.0"),
3838
.package(url: "https://github.com/code-payments/flipcash2-client-protocol", exact: "0.1.0"),
3939
]
4040

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// Client+Balance.swift
3+
// FlipcashCore
4+
//
5+
6+
import Foundation
7+
import FlipcashAPI
8+
9+
extension Client {
10+
/// Fetches the core-mint balance for any owner account. No signature is
11+
/// required — the server allows reading balance for any owner.
12+
public func getBalance(owner: PublicKey) async throws -> TokenAmount {
13+
try await withCheckedThrowingContinuation { c in
14+
balanceService.getBalance(owner: owner) { c.resume(with: $0) }
15+
}
16+
}
17+
}

‎FlipcashCore/Sources/FlipcashCore/Clients/Payments API/Client.swift‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class Client: ObservableObject {
2727
internal let transactionService: TransactionService
2828
internal let currencyService: CurrencyService
2929
internal let messagingService: MessagingService
30+
internal let balanceService: BalanceService
3031

3132
// MARK: - Init -
3233

@@ -53,6 +54,7 @@ public class Client: ObservableObject {
5354
self.transactionService = TransactionService(client: client)
5455
self.currencyService = CurrencyService(client: client)
5556
self.messagingService = MessagingService(client: client)
57+
self.balanceService = BalanceService(client: client)
5658
}
5759

5860
deinit {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//
2+
// BalanceService.swift
3+
// FlipcashCore
4+
//
5+
6+
import Foundation
7+
import FlipcashAPI
8+
import GRPCCore
9+
10+
private let logger = Logger(label: "flipcash.balance-service")
11+
12+
final class BalanceService: Sendable {
13+
14+
private let service: Ocp_Balance_V1_Balance.Client<AppTransport>
15+
16+
init(client: GRPCClient<AppTransport>) {
17+
self.service = Ocp_Balance_V1_Balance.Client(wrapping: client)
18+
}
19+
20+
/// Fetches the core-mint balance for any owner account. Unlike every other
21+
/// Payments API request, `GetBalanceRequest` carries no auth/signature field —
22+
/// the server allows reading balance for any owner, not just the caller's own,
23+
/// so this takes a bare `PublicKey` rather than a signing `KeyPair`.
24+
func getBalance(owner: PublicKey, completion: @Sendable @escaping (Result<TokenAmount, ErrorGetBalance>) -> Void) {
25+
logger.info("Fetching balance")
26+
27+
let request = Ocp_Balance_V1_GetBalanceRequest.with {
28+
$0.owner = owner.solanaAccountID
29+
}
30+
31+
Task {
32+
do {
33+
let response = try await service.getBalance(request, options: .unaryDefault)
34+
let error = ErrorGetBalance(rawValue: response.result.rawValue) ?? .unknown
35+
guard error == .ok else {
36+
logger.error("Failed to fetch balance", metadata: ["error": "\(error)"])
37+
await MainActor.run { completion(.failure(error)) }
38+
return
39+
}
40+
let balance = TokenAmount(quarks: response.coreMintValue, mint: .usdf)
41+
await MainActor.run { completion(.success(balance)) }
42+
} catch let error as RPCError {
43+
await MainActor.run { completion(.failure(.from(transportError: error))) }
44+
} catch {
45+
await MainActor.run { completion(.failure(.unknown)) }
46+
}
47+
}
48+
}
49+
}
50+
51+
// MARK: - Errors -
52+
53+
public enum ErrorGetBalance: Int, Error, Equatable, Sendable {
54+
case ok
55+
case denied
56+
case notFound
57+
case unknown = -1
58+
case transportFailure = -2
59+
case cancelled = -3
60+
case rejected = -4
61+
}
62+
63+
extension ErrorGetBalance: ServerError, TransportClassifiableError {
64+
public var reportingLevel: ErrorReportingLevel {
65+
switch self {
66+
case .ok, .transportFailure: .suppressed
67+
case .cancelled: .info
68+
case .denied, .notFound: .info
69+
case .unknown, .rejected: .error
70+
}
71+
}
72+
}

‎FlipcashCore/Tests/FlipcashCoreTests/TransportClassificationTests.swift‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct TransportClassificationTests {
4040
// MARK: - Registry (one line per TransportClassifiableError conformer) -
4141

4242
@Test func errorFetchBalance() { assertClassifies(ErrorFetchBalance.self) }
43+
@Test func errorGetBalance() { assertClassifies(ErrorGetBalance.self) }
4344

4445
@Test func errorRegisterAccount() { assertClassifies(ErrorRegisterAccount.self) }
4546
@Test func errorLoginAccount() { assertClassifies(ErrorLoginAccount.self) }

0 commit comments

Comments
 (0)