mirror of
https://github.com/we-promise/sure.git
synced 2026-09-09 00:24:15 +00:00
* fix(onchain): a deleted account leaves no wallet row behind Deleting a Sure account left its on-chain tracking row in place. The row had a callback to follow its account link into the grave, but the guard that stopped it destroying itself could not tell apart the two ways that link dies — by the row, or by the account — and so caught both. What survived was worse than untidy. The row synced nothing and showed nowhere, yet it still answered "yes" to "is this address already tracked?" and still held the asset's slot in the partial unique index. The address became unusable: adding it again was refused as a duplicate of something the user could not see, and moving another address onto it raised a database error. So three places learn the difference between a row that tracks an account and one that merely exists: the guard now skips only its own row's destruction, the family's linked check asks for a live link, and the linker and address change let a dead row make way instead of colliding with it. No migration: a row already orphaned on a running instance is absorbed the next time that address is tracked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nN1GZi7Dv3d7yZUozw3Yo * fix(onchain): stop an orphan row passing itself off as a tracked asset Review on #3182. Both findings are the same root: a row left behind by a deleted account has no Account and no AccountProvider, but nothing downstream was asking. `revise` counted any matching row as tracked, orphans included, so ticking an asset whose account had been deleted did nothing at all — no Account created, the row still orphaned, and the screen still reporting the asset as tracked. It now looks only at rows that are actually linked, and at the ones that survived the removal pass. `link` cleared only the rows matching the assets picked in that submit, so relinking a subset left the rest as orphans. They still show up in `grouped_accounts` and token review as tracked, and — before the fix above — they also stopped `revise` from ever rebuilding them. Reclaiming an address now clears every row it left behind. A live row is untouched, with a test that fails if the sweep widens. One existing test had to change its premise rather than its assertion. It built two unlinked rows and asserted the surviving row object was the same one; unlinked rows are orphans, so revising now rebuilds them and the survivor is a new row. Linking them first is what a tracked asset actually is, and the assertion then measures the removal it was written for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GTNba5qE5NwzaHzbp27ye --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
60 lines
2.2 KiB
Ruby
60 lines
2.2 KiB
Ruby
class AccountProvider < ApplicationRecord
|
|
belongs_to :account
|
|
belongs_to :provider, polymorphic: true
|
|
|
|
has_many :holdings, dependent: :nullify
|
|
|
|
validates :account_id, uniqueness: { scope: :provider_type }
|
|
validates :provider_id, uniqueness: { scope: :provider_type }
|
|
|
|
# When unlinking a CoinStats account, also destroy the CoinstatsAccount record
|
|
# so it doesn't remain orphaned and count as "needs setup".
|
|
# Other providers may legitimately enter a "needs setup" state.
|
|
after_destroy :destroy_coinstats_provider_account, if: :coinstats_provider?
|
|
|
|
# An on-chain tracking row IS the link: unlike a bank connection there is
|
|
# nothing to reconnect to and nothing worth keeping. Left behind by a generic
|
|
# unlink it stops syncing, because the syncer only reads linked rows, while
|
|
# its partial unique index still holds the (item, chain, address, asset) slot
|
|
# — so linking that same asset again would collide with a row nothing shows.
|
|
# The Sure account and its holdings are untouched and carry on as manual.
|
|
after_destroy :destroy_onchain_provider_account, if: :onchain_provider?
|
|
|
|
# Returns the provider adapter for this connection
|
|
def adapter
|
|
Provider::Factory.create_adapter(provider, account: account)
|
|
end
|
|
|
|
# Convenience method to get provider name
|
|
# Delegates to the adapter for consistency, falls back to underscored provider_type
|
|
def provider_name
|
|
adapter&.provider_name || provider_type.underscore
|
|
end
|
|
|
|
private
|
|
|
|
def coinstats_provider?
|
|
provider_type == "CoinstatsAccount"
|
|
end
|
|
|
|
def destroy_coinstats_provider_account
|
|
provider&.destroy
|
|
end
|
|
|
|
def onchain_provider?
|
|
provider_type == "OnchainWalletAccount"
|
|
end
|
|
|
|
def destroy_onchain_provider_account
|
|
# Skipped only when the row is the one destroying this link, through its
|
|
# own dependent: :destroy — answering that by destroying the row again
|
|
# would go round in circles. The account destroys this link by association
|
|
# too, and there the row must follow: a guard reading nothing but
|
|
# `destroyed_by_association` could not tell the two apart, and left the
|
|
# row behind with no account to track.
|
|
return if destroyed_by_association&.active_record == OnchainWalletAccount
|
|
|
|
provider&.destroy
|
|
end
|
|
end
|