From b3aaeddbf14e4f40734839953bcf7ce194f034dd Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 14 Sep 2026 14:20:02 -0400 Subject: [PATCH 1/2] feat(balance): move to the batched GetBalances RPC ocp-protobuf-api removed GetBalance in favour of GetBalances, which takes a list of owners and returns an owner -> mint -> value map instead of a flat total. BalanceService.getBalance stays single-owner-shaped for now (nothing in the app needs the batched or mint-filtered form yet) and reads its one owner back out of the map. The response's NOT_FOUND result case is gone; an owner absent from the map now means zero balance rather than a distinct error, so ErrorGetBalance drops .notFound and getBalance treats a missing entry as a zero TokenAmount. --- .../Services/BalanceService.swift | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/FlipcashCore/Sources/FlipcashCore/Clients/Payments API/Services/BalanceService.swift b/FlipcashCore/Sources/FlipcashCore/Clients/Payments API/Services/BalanceService.swift index 1338f7c07..35ebb6247 100644 --- a/FlipcashCore/Sources/FlipcashCore/Clients/Payments API/Services/BalanceService.swift +++ b/FlipcashCore/Sources/FlipcashCore/Clients/Payments API/Services/BalanceService.swift @@ -17,27 +17,38 @@ final class BalanceService: Sendable { self.service = Ocp_Balance_V1_Balance.Client(wrapping: client) } - /// Fetches the core-mint balance for any owner account. Unlike every other - /// Payments API request, `GetBalanceRequest` carries no auth/signature field — - /// the server allows reading balance for any owner, not just the caller's own, - /// so this takes a bare `PublicKey` rather than a signing `KeyPair`. + /// Fetches the core-mint balance for a single owner account. Unlike every + /// other Payments API request, `GetBalancesRequest` carries no auth/signature + /// field — the server allows reading balance for any owner, not just the + /// caller's own, so this takes a bare `PublicKey` rather than a signing + /// `KeyPair`. + /// + /// The underlying RPC is batched — `GetBalances` takes a list of owners and + /// an optional mint filter, returning an owner → mint → value map — but + /// nothing in the app currently needs more than one owner's balance at a + /// time, so this wrapper stays single-owner-shaped and reads the one entry + /// it asked for back out of the map. func getBalance(owner: PublicKey, completion: @Sendable @escaping (Result) -> Void) { logger.info("Fetching balance") - let request = Ocp_Balance_V1_GetBalanceRequest.with { - $0.owner = owner.solanaAccountID + let request = Ocp_Balance_V1_GetBalancesRequest.with { + $0.owners = [owner.solanaAccountID] } Task { do { - let response = try await service.getBalance(request, options: .unaryDefault) + let response = try await service.getBalances(request, options: .unaryDefault) let error = ErrorGetBalance(rawValue: response.result.rawValue) ?? .unknown guard error == .ok else { logger.error("Failed to fetch balance", metadata: ["error": "\(error)"]) await MainActor.run { completion(.failure(error)) } return } - let balance = TokenAmount(quarks: response.coreMintValue, mint: .usdf) + // An owner absent from the map simply has no balances — that's + // zero, not an error. (The old response's NOT_FOUND result case + // is gone; this replaces it.) + let quarks = response.balancesByOwner[owner.base58]?.coreMintValue ?? 0 + let balance = TokenAmount(quarks: quarks, mint: .usdf) await MainActor.run { completion(.success(balance)) } } catch let error as RPCError { await MainActor.run { completion(.failure(.from(transportError: error))) } @@ -53,7 +64,6 @@ final class BalanceService: Sendable { public enum ErrorGetBalance: Int, Error, Equatable, Sendable { case ok case denied - case notFound case unknown = -1 case transportFailure = -2 case cancelled = -3 @@ -65,7 +75,7 @@ extension ErrorGetBalance: ServerError, TransportClassifiableError { switch self { case .ok, .transportFailure: .suppressed case .cancelled: .info - case .denied, .notFound: .info + case .denied: .info case .unknown, .rejected: .error } } From 78b744c7649e56336251a8c3f715823b35f076c2 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 14 Sep 2026 14:43:58 -0400 Subject: [PATCH 2/2] build(deps): pin ocp-client-protocol 0.4.0 --- FlipcashAPI/Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlipcashAPI/Package.swift b/FlipcashAPI/Package.swift index 5008c5bb7..476c3972f 100644 --- a/FlipcashAPI/Package.swift +++ b/FlipcashAPI/Package.swift @@ -55,7 +55,7 @@ enum ContractPackage: String, CaseIterable { /// The pinned version consumed when this package isn't building against a local checkout. var version: Version { switch self { - case .ocp: return "0.3.0" + case .ocp: return "0.4.0" case .flipcash2: return "0.5.0" } }