Skip to content

Add Yielder#1608

Open
0xh3rman wants to merge 59 commits intomainfrom
gem-ios-yielder
Open

Add Yielder#1608
0xh3rman wants to merge 59 commits intomainfrom
gem-ios-yielder

Conversation

@0xh3rman
Copy link
Contributor

@0xh3rman 0xh3rman commented Jan 20, 2026

Simulator Screenshot - iPhone 17 Pro - 2026-01-25 at 11 31 55 Simulator Screenshot - iPhone 17 Pro - 2026-01-19 at 20 47 18 Simulator Screenshot - iPhone 17 Pro - 2026-01-19 at 20 47 21 Simulator Screenshot - iPhone 17 Pro - 2026-01-19 at 20 47 44 Simulator Screenshot - iPhone 17 Pro - 2026-01-25 at 11 31 46 Simulator Screenshot - iPhone 17 Pro - 2026-01-19 at 20 47 51

Introduces the Yield module, including Swift Package setup, scene, view models, and UI components for displaying yield opportunities and positions. Integrates Yield into Assets and Staking features, updates navigation, and extends transfer and primitives types to support yield actions. Adds new resources and images for yield providers, and updates dependencies and project configuration accordingly.
Deleted the unused 'name' property from GemYieldProvider and removed debug print statements from YieldSceneViewModel's fetch method for cleaner code. Updated core submodule reference.
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @0xh3rman, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a comprehensive 'Yield' feature, enabling users to engage with yield-generating protocols directly within the application. It establishes a new dedicated module for yield management, integrates yield information into existing asset and staking views, and extends the transaction processing system to support yield deposits and withdrawals. The changes ensure a cohesive user experience for discovering and participating in yield opportunities.

Highlights

  • New Yield Feature Module: A new 'Yield' feature module has been introduced, including YieldScene for displaying yield opportunities and positions, YieldInput for data passing, YieldOpportunityViewModel for individual opportunity presentation, and YieldSceneViewModel for managing the scene's logic and state.
  • Asset Scene Integration: The AssetScene now conditionally displays yield positions and opportunities. The AssetSceneViewModel has been updated to integrate YieldService for fetching yield data, and new UI components (yieldView, yieldViewEmpty) have been added to present this information.
  • Staking Scene Integration: The StakeScene can now navigate to the new YieldScene via an 'Earn' section, allowing users to access yield opportunities directly from their staking view. The StakeSceneViewModel was updated to support this navigation.
  • Transaction Handling for Yield Operations: The Transfer module has been updated to support 'yield' related transactions, including deposit and withdrawal. This involved modifying TransactionFactory, TransferExecutor, AmountDataProvider, AmountInputConfig, and various Confirm view models to recognize and process yieldDeposit and yieldWithdraw transaction types.
  • Core Service and Model Updates: The TransactionType enum now includes yieldDeposit and yieldWithdraw. GatewayService and Signer have been updated to handle these new transaction types, particularly for EVM chains, including ERC20 approvals when necessary. ViewModelFactory and AppResolver were extended to provide the YieldService and YieldSceneViewModel throughout the application.
  • New Yield Service: A YieldService and its corresponding YieldServiceTestKit have been added to FeatureServices, providing an interface to interact with the underlying GemYielder for fetching yield data, checking availability, and executing deposit/withdraw operations.
  • Simulator and Build Process Enhancements: The justfile has been updated to use the 'iPhone 17 Pro' simulator and includes a new run command to streamline the process of building, installing, and launching the application on the simulator.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

- Resolve conflicts with yield feature integration
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant new "Yield" feature, allowing users to earn on their assets. The changes are extensive, touching multiple features to integrate this functionality, including UI additions in the Assets and Staking scenes, and core logic modifications in Transfer, Signer, and the newly created Yield and YieldService packages. My review focuses on ensuring the correctness and precision of balance calculations by advocating for the use of BigInt over Double, and on improving error handling for better debuggability. Overall, the implementation is well-structured and consistent with the existing architecture.

0xh3rman and others added 29 commits February 2, 2026 22:25
Introduce native 'earn' balance handling across the app and tidy related UI/state code.

- Add earn BigInt field to Balance model and BalanceType; update BalanceViewModel total calculation.
- Extend BalanceRecord DB schema and migrations to store earn/earnAmount and include earnAmount in totalAmount SQL. Update Store models and tests.
- Add UpdateEarnBalance type and plumbing to UpdateBalanceType.
- Implement EarnBalanceService enhancements: inject AssetsService and BalanceStore, compute total earn from positions, and write updates to BalanceStore. Add BigInt & Formatters usage and package dependency for BigInt in FeatureServices.
- Wire EarnBalanceService into ServicesFactory, WalletsService and BalanceUpdateService so earn positions are updated alongside regular balances (parallel async updates).
- Remove EarnPosition.name from primitives, GemstonePrimitives and store models/records; adjust mappings and table creation accordingly.
- Update UI/ViewModels: make StateView public and return proper error view; refactor Earn protocols and scenes to use StateView for consistent state handling; remove local earnPositions tracking in AssetScene and read earn from assetData.balance. Add small view/model helpers (makeEarnData, delegationsViewState, etc.).
- Minor: remove an observeQuery for earnPositions in AssetNavigationView, bump core submodule commit.

These changes add end-to-end support for displaying and persisting 'earn' balances and unify state rendering via StateView.
* Buffer pending WebSocket messages while connecting or reconnecting (#1671)

* Buffer pending WebSocket messages while connecting or reconnecting

* Buffer pending WebSocket messages while connecting or reconnecting

* Add do-catch with debugLog for WebSocket pending message send

* Build fix (#1676)

* Buffer pending WebSocket messages while connecting or reconnecting

* Buffer pending WebSocket messages while connecting or reconnecting

* Add do-catch with debugLog for WebSocket pending message send

* Use NSLog instead of debugLog

* Tron unstake fix (#1670)

* Add Tron stakeData type and adjust signing

Introduce a strongly-typed TronStakeData model (TronVote, TronUnfreeze, TronStakeData) in Primitives and replace the previous votes dictionary in TransactionLoadMetadata with stakeData to represent both voting and unfreeze actions. Add mapping extensions between Gemstone and Primitives for stake data, votes and unfreeze types. Update TronSigner to consume the new stakeData: create vote witness contracts from TronVote objects, handle stake/redelegate with votes, and handle unstake by signing unfreeze contracts per TronUnfreeze entries. Remove the legacy getVotes accessor and adjust minor error/message fixes. Also update the core submodule pointer.

* Update core

* Remove throws from map; add Tron stake types

Convert several GemstonePrimitives mapping helpers to non-throwing (removed try usage): GemFreezeData.map, GemFreezeType.map, GemResource.map, GemStakeType.map, GemTransactionLoadMetadata stakeData mapping, Gemstone.TronStakeData.map and Gemstone.TronUnfreeze.map. Move TronVote/TronUnfreeze/TronStakeData definitions into Primitives/StakeType.swift with Codable conformance and custom encoding/decoding, and delete the old TronStakeData.swift. These changes simplify mapping logic and centralize Tron stake models in the Primitives package.

* Update core

* Add Resource.key and use it in TronSigner

Introduce a computed property Resource.key that returns rawValue.uppercased(), and update TronSigner to use resource.key instead of calling rawValue.uppercased() inline. This centralizes the uppercasing logic and removes duplicated formatting across contract construction sites (Packages/Primitives/Sources/Extensions/Resource+Primitives.swift and Packages/Signer/Sources/Chains/TronSigner.swift).

* Update ResourceViewModel.swift

* Minor updates

* Update core

* Extract InputAccessoryView component for suggestions and action button (#1673)

* Extract InputAccessoryView component for suggestions and action button

* Move safeAreaView to Components and refactor SelectableSheet to use safeAreaButton

* Resettable timer + RefreshableTimer modifier (#1674)

* Refactor timers and magic time intervals into reusable constants (#1666)

- Add TimerModifier with .onTimer(every:action:) view modifier
- Add Interval type with time constants (seconds30, minute1, minutes5)
- Add Interval.AnimationDuration constants (fast, normal, slow, verySlow)
- Add Duration.Debounce constants (fast, normal, slow)
- Replace inline Timer.publish/onReceive with .onTimer in existing screens
- Add periodic refresh timers to TransactionsScene, AssetScene, FiatScene, PerpetualsScene
- Replace hardcoded debounce durations across search and input scenes
- Replace hardcoded animation durations in FloatTextField, ActivityIndicator, ChartView, PulsingDotView

* Add resettable RefreshableTimerModifier and modernize timers

Replace Combine-based TimerModifier with Task.sleep loop for structured
concurrency. Add RefreshableTimerModifier that resets the timer countdown
on pull-to-refresh, preventing redundant fetches. Migrate 5 scenes from
separate .refreshable + .onTimer to unified .refreshableTimer modifier.

* Fix price alert toast visibility and locale-aware price suggestions (#1680)

Move toast modifier outside NavigationStack so it renders above pushed
screens. Change PriceSuggestion.value to Double and format inputValue
with locale-aware number formatting to avoid wrong decimal separators.

* Refactor Earn protocols to providers

Rename and refactor the Earn feature to use 'providers' (yield) instead of 'protocols'. Key changes: rename scenes/views/viewmodels (EarnProtocols -> EarnProviders), replace EarnProtocol model with EarnProvider and YieldProvider types, introduce EarnPositionBase and update EarnPosition to include a base property, and adapt formatting/fields (balance, shares, apy, providerId). Update EarnService API (EarnServiceable) and implementations to use YieldProvider and GemEarnPositionBase, add new GemstonePrimitives mappings, and adjust tests and factories accordingly. Persisted schema changes: update EarnPositionRecord, add EarnProviderRecord, and register migrations to create/adjust tables. Misc: project file and navigation updates, removal of legacy mapping files, and an added architecture doc (docs/earn-architecture-plan.md).

* Use safeAreaView for word suggestions instead of keyboard toolbar (#1681)

* Avoid pushing duplicate screens (#1675)

* Avoid pushing duplicate screens

- Add NavigationPathState that encapsulates NavigationPath with a shadow stack for deduplication
- Replace NavigationPath properties in NavigationStateManager with NavigationPathState
- Route all programmatic navigation through NavigationPathState.append which skips if the same screen is already on top

* Move NavigationPathState to Components package with Codable-based duplicate detection

Replace AnyHashable stack tracking with NavigationPath.CodableRepresentation encoding
to detect duplicate pushes. Add Codable conformance to all Scenes types. Add unit tests.

* Add Scenes.Transaction wrapper for TransactionExtended navigation

Wrap TransactionExtended in Scenes.Transaction for consistent navigation
pattern across all scene types pushed onto NavigationPath.

* Add new stream events, NFT chain enum

Introduce new streaming event types and related models to support NFTs, price alerts, perpetuals, and in-app notifications, plus realtime price subscribe messages.

- Add NFTChain enum (Packages/Primitives/Sources/ChainNft.swift) (generated by typeshare).
- Extend Stream types (Packages/Primitives/Sources/Stream.swift):
  - Remove chain and address from StreamBalanceUpdate initializer.
  - Change StreamTransactionsUpdate to use [TransactionId] instead of [Transaction].
  - Add StreamNftUpdate, StreamPriceAlertUpdate, StreamPerpetualUpdate, StreamNotificationlUpdate.
  - Add StreamEvent cases: priceAlerts, nft, perpetual, inAppNotification and wire up Codable encode/decode.
  - Add StreamMessage realtime price cases: subscribeRealtimePrices, unsubscribeRealtimePrices and wire up Codable.
- Update PriceObserverService (Packages/FeatureServices/PriceService/PriceObserverService.swift) to handle the new StreamEvent cases (no-op placeholders).
- Bump core submodule commit.

Migration notes: update any call sites constructing StreamBalanceUpdate or StreamTransactionsUpdate to match the new initializers/types, and handle the new StreamEvent/StreamMessage variants where applicable.

* Remove SupportService and pass deviceId to Support

Remove the legacy SupportService and related support-device registration plumbing, and switch to passing a deviceId string through the UI stack. Changes include:

- Remove SupportService, its tests and calls to registerSupport; drop support-related preferences keys.
- Propagate deviceId from Settings -> SupportSceneViewModel -> ChatwootWebViewModel and rename supportDeviceId -> deviceId; Chatwoot custom attribute uses device_id.
- Remove GemAPI support endpoints/types (GemAPISupportService, addSupportDevice, GemDeviceAPI support routes) and related Support structs (NewSupportDevice, SupportDeviceRequest).
- Remove Support package dependency on GemAPI and PreferencesTestKit, and update Feature package manifest.
- Remove environment/service wiring and DI for SupportService across AppResolver, ServicesFactory, EnvironmentValues and view injection.
- Add StreamNewAssetsUpdate and newAssets case to StreamEvent and handle in PriceObserverService.
- Update core submodule commit.

These changes centralize device id handling in callers and drop server-side support device registration code and state.

* Fix perpetual chart showing data from wrong asset (#1684)

* Fix perpetual chart showing data from wrong asset

When navigating between perpetual screens, candle updates from one asset
could be applied to another asset's chart. Now handleChartUpdate validates
that candle.coin matches the current subscription before applying updates.

* Refactor chart services to use ChartCandleUpdate

- Update ChartStreamable protocol to use ChartCandleUpdate
- Update ChartObserverService to stream ChartCandleUpdate
- Update HyperliquidObserverService to use GemChartCandleUpdate
- Update PerpetualSceneViewModel to extract candle from update
- Add GemChartCandleUpdate mapping extension
- Handle new StreamEvent cases in PriceObserverService

* Refactor chart & push notification models

Remove interval from ChartCandleStick and simplify its initializer so candles are interval-agnostic. Add ChartCandleUpdate (coin, interval, candle) to represent interval-scoped candle updates. Remove the optional walletIndex from PushNotificationTransaction and update its initializer accordingly. Also update the core submodule reference to the new commit.

* Bump to 1.3.344 (45)

* Set current wallet before transaction navigation

Call walletService.setCurrent(for:) at the start of navigateToTransaction to ensure the active wallet context is set before fetching assets and adding the transaction. Also updates the core submodule pointer. This prevents services from operating on an incorrect or stale wallet state when navigating to a transaction.

* Guard missing wallet, add getter, fix mapping

Add WalletService.getWallet to expose wallet lookup and use it in NavigationHandler to guard against missing wallets before setting current and adding a transaction (prevents crashes/no-op if wallet not found). Also simplify GatewayService candlestick mapping by removing an unnecessary try when the mapped operation is nonthrowing.

* Bump to 1.3.345 (46)

* Delete earn-architecture-plan.md

* Rename EarnData to YieldData and consolidate earn types

- Rename EarnData → YieldData, EarnService → YieldService
- Add YieldType with YieldTransaction from Rust core
- Add EarnProviderType to DelegationValidator for yield/stake filtering
- Remove EarnPosition/EarnPositionBase (use DelegationBase)
- Remove EarnStore/EarnServices (consolidated into StakeStore)
- Update mappers for new Gemstone yield types
- Add providerType migration to stake_validators table
- Remove SupportService references (not yet implemented)

* Update Primitives and typeshare types for Earn rename

- Add typeshare-generated EarnData, EarnType, EarnTransaction
- Add GrowthProviderType (replaces EarnProviderType)
- Remove old YieldData, YieldType, EarnProvider files
- Update TransactionLoadMetadata with earnData accessor
- Update TransferDataType and AmountType for new Earn types

* Rename YieldService to EarnProviderService and update FFI mappers

- Rename YieldService to EarnProviderService with TestKit mock
- Update GemstonePrimitives mappers for EarnType and EarnData
- Rename GemYieldData mapper to GemEarnData
- Update EthereumSigner to use earnData from metadata

* Update features, ViewModels, Store, and tests for Earn types

- Update EarnSceneViewModel, EarnProvidersSceneViewModel, and
  EarnPositionViewModel to use EarnProviderService
- Update AmountEarnViewModel and AmountStakeViewModel for new
  EarnType/EarnData structure
- Update TransactionFactory to read earnData from metadata
- Update ServicesFactory and ViewModelFactory references
- Update Store migrations and StakeValidatorRecord for
  GrowthProviderType
- Update core submodule pointer

* Add Staking package split from Earn

* Update Earn package, move staking files to Staking

* In progress yield

* Removed hardcoded updateEarnAssets

---------

Co-authored-by: Radmir <52320354+DRadmir@users.noreply.github.com>
Co-authored-by: gemcoder21 <104884878+gemcoder21@users.noreply.github.com>
  - Gateway: added earnProviders, earnPositions, earnBalance to GatewayService
  - Balance pipeline: added .earn case to AssetBalanceType, wired through BalanceFetcher → BalanceService
  - GemBalance.map(): now maps the earn field
  - Removed YieldService entirely (+ TestKit)
  - Removed earnService from BalanceUpdateService/WalletsService
  - Removed unused BigInt dep from FeatureServices/Package.swift
  - EarnService simplified to use gatewayService only
  - Updated ServicesFactory/ViewModelFactory to remove yieldService
  - Core submodule pointer updated
Resolved core submodule merge conflicts:
- Updated testing guidelines
- Adopted with_provider pattern
- Updated error types

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed compilation errors:
- Removed duplicate encode_with_0x function
- Added missing StakeData import

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
stakeView
}

if model.hasYieldPosition {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if model.hasYieldPosition {
if model.hasEarnPositions {

NavigationCustomLink(
with: HStack(spacing: .space12) {
EmojiView(color: Colors.grayVeryLight, emoji: "💰")
with: HStack(spacing: Spacing.medium) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
with: HStack(spacing: Spacing.medium) {
with: HStack(spacing:.medium) {

private func shouldShowBanner(_ banner: Banner) -> Bool {
switch banner.event {
case .enableNotifications, .accountBlockedMultiSignature, .tradePerpetuals: true
case .yield: assetData.isEarnable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case .yield: assetData.isEarnable
case .earn: assetData.isEarnable

private let transactionsService: TransactionsService
private let priceObserverService: PriceObserverService
private let bannerService: BannerService
private let yieldService: any YieldServiceType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private let yieldService: any YieldServiceType
private let earnService: any EarnServiceType

ProgressView()
Spacer()
}
.padding(.vertical, Spacing.medium)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.padding(.vertical, Spacing.medium)
.padding(.vertical, .medium)

}

extension TransactionLoadMetadata {
public var earnData: EarnData? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public var earnData: EarnData? {
public getEarnData() throws EarnData {

)
}

static func earn(assetId: AssetId) -> NewBanner {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove it

var isActive: Bool
var commission: Double
var apr: Double
var providerType: String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var providerType: String
var providerType: EarnProviderType

try setColumn(for: assetIds, column: AssetRecord.Columns.isEarnable, value: value)
}

public func setEarnApr(assetId: String, apr: Double) throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

}

@discardableResult
public func setAssetIsEarnable(for assetIds: [String], value: Bool) throws -> Int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments