Files
sure/app/models/coinstats_account.rb
foXaCe 302fb84086 feat: Support multiple crypto wallets with same token (#676)
* feat: Support multiple crypto wallets with same token

Allows users to import multiple wallets containing the same
cryptocurrency (e.g., ETH on different wallet addresses).

Changes:
- Add wallet_address column to coinstats_accounts
- Update uniqueness validation to include wallet_address
- Extract and store wallet address in WalletLinker
- Add composite unique index on [item_id, account_id, wallet_address]
- Add tests for multi-wallet support and backwards compatibility

Users can now have:
- ETH (0xAAA...) → "Ethereum (0xAA...AA)"
- ETH (0xBBB...) → "Ethereum (0xBB...BB)"

Backwards compatible: existing accounts with wallet_address: nil
continue to work.

* style: Fix array bracket spacing in migration

* chore: Update schema.rb with wallet_address column and index

Add the missing wallet_address column and composite unique index
to db/schema.rb for CI compatibility with db:schema:load

* test: Add test for wallet deletion with same token different addresses

Verifies that deleting one wallet does not affect other wallets
that share the same token but have different addresses.

Addresses review comment from @EthanC via @jjmata

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-18 11:27:09 +01:00

72 lines
2.5 KiB
Ruby

# Represents a single crypto token/coin within a CoinStats wallet.
# Each wallet address may have multiple CoinstatsAccounts (one per token).
class CoinstatsAccount < ApplicationRecord
include CurrencyNormalizable
belongs_to :coinstats_item
# Association through account_providers (standard pattern for all providers)
has_one :account_provider, as: :provider, dependent: :destroy
has_one :account, through: :account_provider, source: :account
validates :name, :currency, presence: true
validates :account_id, uniqueness: { scope: [ :coinstats_item_id, :wallet_address ], allow_nil: true }
# Alias for compatibility with provider adapter pattern
alias_method :current_account, :account
# Updates account with latest balance data from CoinStats API.
# @param account_snapshot [Hash] Normalized balance data from API
def upsert_coinstats_snapshot!(account_snapshot)
# Convert to symbol keys or handle both string and symbol keys
snapshot = account_snapshot.with_indifferent_access
# Build attributes to update
attrs = {
current_balance: snapshot[:balance] || snapshot[:current_balance],
currency: parse_currency(snapshot[:currency]) || "USD",
name: snapshot[:name],
account_status: snapshot[:status],
provider: snapshot[:provider],
institution_metadata: {
logo: snapshot[:institution_logo]
}.compact,
raw_payload: account_snapshot
}
# Only set account_id if provided and not already set (preserves ID from initial creation)
if snapshot[:id].present? && account_id.blank?
attrs[:account_id] = snapshot[:id].to_s
end
update!(attrs)
end
# Stores transaction data from CoinStats API for later processing.
# @param transactions_snapshot [Hash, Array] Raw transactions response or array
def upsert_coinstats_transactions_snapshot!(transactions_snapshot)
# CoinStats API returns: { meta: { page, limit }, result: [...] }
# Extract just the result array for storage, or use directly if already an array
transactions_array = if transactions_snapshot.is_a?(Hash)
snapshot = transactions_snapshot.with_indifferent_access
snapshot[:result] || []
elsif transactions_snapshot.is_a?(Array)
transactions_snapshot
else
[]
end
assign_attributes(
raw_transactions_payload: transactions_array
)
save!
end
private
def log_invalid_currency(currency_value)
Rails.logger.warn("Invalid currency code '#{currency_value}' for CoinstatsAccount #{id}, defaulting to USD")
end
end