# frozen_string_literal: true # Fetches Binance Simple Earn (flexible + locked) positions. # Merges both into a single asset list with source tag "earn". class BinanceItem::EarnImporter attr_reader :binance_item, :provider def initialize(binance_item, provider:) @binance_item = binance_item @provider = provider end def import flexible_raw = fetch_flexible locked_raw = fetch_locked assets = merge_earn_assets( parse_flexible(flexible_raw), parse_locked(locked_raw) ) { assets: assets, raw: { "flexible" => flexible_raw, "locked" => locked_raw }, source: "earn" } rescue => e Rails.logger.error "BinanceItem::EarnImporter #{binance_item.id} - #{e.message}" { assets: [], raw: nil, source: "earn", error: e.message } end private def fetch_flexible provider.get_simple_earn_flexible rescue => e Rails.logger.warn "BinanceItem::EarnImporter #{binance_item.id} - flexible failed: #{e.message}" nil end def fetch_locked provider.get_simple_earn_locked rescue => e Rails.logger.warn "BinanceItem::EarnImporter #{binance_item.id} - locked failed: #{e.message}" nil end def parse_flexible(raw) return {} unless raw.is_a?(Hash) (raw["rows"] || []).each_with_object({}) do |row, acc| symbol = row["asset"] amount = row["totalAmount"].to_d acc[symbol] = (acc[symbol] || 0) + amount end end def parse_locked(raw) return {} unless raw.is_a?(Hash) (raw["rows"] || []).each_with_object({}) do |row, acc| symbol = row["asset"] amount = row["amount"].to_d acc[symbol] = (acc[symbol] || 0) + amount end end # Merge two symbol→amount hashes and emit normalized asset list def merge_earn_assets(flexible_totals, locked_totals) all_symbols = (flexible_totals.keys + locked_totals.keys).uniq all_symbols.filter_map do |symbol| flex = flexible_totals[symbol] || BigDecimal("0") lock = locked_totals[symbol] || BigDecimal("0") total = flex + lock next if total.zero? { symbol: symbol, free: flex.to_s("F"), locked: lock.to_s("F"), total: total.to_s("F") } end end end