feat(payments): scaffold Balance service for GetBalance - #658
Merged
Conversation
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.
Picks up the Balance service added upstream in ea6418c5.
bmc08gt
marked this pull request as ready for review
August 26, 2026 15:56
bmc08gt
added a commit
that referenced
this pull request
Aug 26, 2026
`FlipcashAPI/Package.swift` moved to `exact: "0.2.0"` in #658, but the workspace `Package.resolved` still held 0.1.0, so every build re-resolved it and left the tree dirty.
bmc08gt
added a commit
that referenced
this pull request
Aug 26, 2026
…, chat and discover (#659) * fix(tests): rewrite the buy/sell regressions as convert flows The tab-bar UI has no Buy or Sell tile on a currency the account holds — `CurrencyInfoContentV2` offers Give / Convert / Withdraw, and Convert is what replaced both. It moves value between balances you already hold, so each of the three skipped tests is a convert in one direction: token → Dollars was Sell, Dollars → token was buy-paying-with-reserves, and token → token was buy-with-currency. The tests are renamed for the direction they exercise, and `SellConfirmationScreen` goes with the v1 sell sheet that was its only subject. Two of the three need no picker interaction: `ConvertAmountViewModel` defaults a non-Dollars source to Dollars and a Dollars source to the largest other holding. `ConvertBetweenTokensRegressionTests` is the one that opens the picker, so `CurrencyPickerSheet` rows get `currency-picker-row` with Dollars distinguished as `currency-picker-row-usdf` — otherwise picking "the first token" could land on Dollars depending on how balances sort. Two v1 assertions are dropped rather than ported. The nested-sheet swipe-down regression can't recur: convert is pushed, so there is no sheet behind the processing screen. And a finished convert now pops to the Wallet root instead of returning to the currency page, per `ConvertFlowDestinationView`. `CurrencyInfoUIScreen` splits `assertReached` into held and unheld variants, since which tiles exist depends on the balance. * fix(tests): route the settings, wallet-tile and give tests through the tab bar Nine more `skipPendingTabBarRewrite` call sites, in three groups. Settings rows: the You tab's list has only My Account and Advanced, so each test opens the You tab and scrolls to its row. Access Key and Application Logs are on Advanced, not My Account — `SettingsMyAccountScreen` keeps the account-level actions off itself and says so in its header doc. Blocked stays on My Account. Money rows: Add Money and Withdraw Money are Wallet tiles now, so `SettingsUIScreen` loses both properties and the four tests enter through `wallet-tile-add-money` / `wallet-tile-withdraw-money`. Give: `navigateToGiveAmount()` goes Wallet → first currency card → its Give tile. The keypad pops itself as the bill appears — `GiveScreen.onBillPresented` when `isPushed` — so both callers end on `CurrencyInfoScreen` rather than a tab root, and the cash link reaches its history from there without a second trip through the wallet. That history is the "Recent" section header in the tab-bar UI; the v1 "Transaction History" button went with the old footer. `GiveDiscoverGateRegressionTests` is deleted rather than rewritten: USDF is giveable now, so `GiveCashGate.discoverCurrencies` is unreachable. The "No Balance Yet" sibling keeps its skip with a corrected reason — the gate has no fresh-account entry either, since the only caller that gated a give was `ScanBottomBar`, which the embedded Scan tab does not render. Also drops the balance-retry loop in `navigateToGiveAmount()`: the Give tile is only drawn for a currency the account holds, so that path raises no gate dialog. * fix(tests): route the chat-group UI tests through the tab bar `BlockUnblockSmokeTests` opened the Tips list as a sheet off the scanner's bottom bar and closed it through `navigationBars["Tips"]`. Embedded as the Chat tab, that list has no toolbar and no leading tip-card cell, so the page object now opens `app.buttons["Chat"]`, waits on the "Chats" title, and treats every cell as a conversation. Its `tearDown` unblock also still tapped `app.buttons["Settings"]`, which the tab bar removed — so a run that failed after the block left the shared account with a user blocked. It goes through You › My Account › Blocked now, with a non-asserting scroll of its own: `scrollUpToAndTap` would fail the teardown and mask the real failure. `ProfileCreationSmokeTests` walked Tips intro → name → photo → tipcard, and none of those four steps has an entry left. `OnboardingNameScreen` is mandatory after the access key, so no account reaches the app without a name and both name-less prompts stay gated off; `ProfileNameScreen` skips the photo step for every caller because the card omits the photo. Replaced by `DisplayNameSmokeTests`, covering what survived: a freshly registered account lands on the You tab with a card it can share, and the name behind it changes through My Account. `selectFirstPhotoFromLibrary` went with its last caller. Both pass on iPhone 17. * test(give): delete the no-balance gate regression `GiveCashGate`'s "No Balance Yet" branch has no entry a test can reach. The only caller that gated a give from a tab root was `ScanBottomBar`, which renders under `if !isEmbedded`, and the Scan tab embeds `ScanScreen`. The gate still fires from a chat's Send Cash, `TipFlow`, and the give deeplink, but the test's premise was a fresh empty account, which reaches none of those — and give now starts from a held currency's Give tile, which an empty account has no card for. Same call as `GiveDiscoverGateRegressionTests`, which went for the same reason. * test(discover): route the discover-group UI tests through the wallet tiles Discover moved from a scanner sheet to a Wallet tile, and currency creation moved from Discover's promo card to a sibling tile, so the two entries are now checked side by side instead of one through the other. Both tiles are gated on funding: WalletScreen draws walletTiles only for session.hasEverAddedMoney(), and gives an unfunded account the new-user tutorial in their place. So DiscoverCurrenciesSmokeTests takes the standing account rather than creating a fresh one, and the currency-creation gate moves out of AddMoneyGateRegressionTests into its own class on the same account. That gate still fires there because shouldAddMoneyBeforeLaunch is a shortfall check, not a $0 check — the account holds money but not the launch cost — and the test skips if that ever stops being true. The buy gate has no fixture left and stays skipped, with the reason recorded on the class: flipcash://discover reaches the same destination, but app.open relaunches the app and a freshly created account comes back on "Create a New Account", while both standing accounts hold displayable USDF, so BuyAmountViewModel.paymentOptions is non-empty and the button reads Next instead of Add Money. * test(uitests): drop skipPendingTabBarRewrite with its last call site Every test that entered through the v1 scanner chrome has been rewritten or deleted, so the helper has no callers. The rewrite plan keeps the map of where each flow moved. * test(buy): delete the no-assets add-money gate regression The test needs an account with nothing spendable, and the tab-bar UI gives such an account no door to a currency's Get button. The Wallet's Discover tile is drawn only for session.hasEverAddedMoney(); flipcash://discover reaches the same destination, but app.open relaunches the app and a freshly created account comes back on "Create a New Account"; and both standing accounts hold displayable USDF, so BuyAmountViewModel.paymentOptions is non-empty and the button reads Next instead of Add Money. The gate is still live app code — a funded account spent down to nothing hits it. Restoring the test needs a spent-down fixture, which the rewrite plan records. * chore(api): record ocp-client-protocol 0.2.0 in the workspace lockfile `FlipcashAPI/Package.swift` moved to `exact: "0.2.0"` in #658, but the workspace `Package.resolved` still held 0.1.0, so every build re-resolved it and left the tree dirty.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scaffolds the OpenCode
Balanceservice added upstream inocp-protobuf-api@ea6418c5, and bumps theocp-client-protocolpin to0.2.0to pick it up.What the contract adds
One unary RPC,
ocp.balance.v1.Balance/GetBalance. The request takes an owner account; the response carries a result enum (OK/DENIED/NOT_FOUND) andcore_mint_value, auint64in quarks. Nothing pre-existing changed, so no existing result enum was renumbered and no existingError*(rawValue:)mapping shifts.What this adds
BalanceService→ErrorGetBalance→Client.getBalance, registered onClient, plus theTransportClassificationTestsregistry line every newTransportClassifiableErrorconformer needs.Nothing consumes it yet. No screen, view model, or
Sessionintegration, because there is no balance surface asking for this today.Two calls worth review:
GetBalanceRequesthas no auth field, unlike every other Payments API request, sogetBalancetakes a barePublicKeyrather than a signingKeyPair. That asymmetry is in the contract, not an oversight in this scaffold.core_mint_valuemaps toTokenAmount(quarks:mint:.usdf). The mint is not on the wire: the field is core-mint quarks by definition, and the contract carries a core mint address nowhere, so USDF is a client-side constant here exactly as it is inSwapInstructionBuilder+NewCurrency.swift, which takesMintMetadata.usdfascoreMint. If the core mint ever stops being USDF, this call site is one of the places that has to change.Android keeps the same field as a raw
Longrather than materializing the implied mint. Both platforms hold the same information; only the domain typing differs.ErrorGetBalanceordersok/denied/notFoundat rawValues 0/1/2 to match the proto, with the transport cases at negative rawValues as this codebase does elsewhere.Blocked on
0.2.0does not exist yet.exact: "0.2.0"has no tag to resolve until code-payments/ocp-client-protocol#4 merges and is published, so this stays a draft until then.