Price/balance fetching, unified money model (decimal/numeric), wallet sync hardening#20
Merged
Conversation
… value - entity.NormalizeSymbol: canonical (trim+upper) symbol, shared store/portfolio - store normalizes symbol on create/update/lookup -> case-insensitive, no dup assets - resolveAssetID delegates normalization to store (single source of truth) - SyncAccount merges same-symbol balances across chains by real quantity at max decimals (USDC 6dec + 18dec no longer corrupts the total); keyed by canonical symbol - tests: mixed-decimals merge; symbol-resolution mocks pass input verbatim Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ed forex FetchExternalPrices created every quote base asset as forex, mislabeling USDT (a crypto stablecoin). Providers now declare BaseAssetType: CoinGecko USD -> forex, Binance USDT -> cryptocurrency. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Response quote_asset_id echoes the request value, which may be a ticker symbol (e.g. "USD") rather than a UUID. Document the field on request and response. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e import Make the eye.v1 contract importable by sibling modules (greedy-eye-mcp). Change go_package and buf.gen out dir internal/api -> api, regenerate, update all import paths. Wire contract unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ListHoldings returned holdings of all users; ListPortfolios and ListAccounts were already user-scoped. Filter by owning portfolio (holding's own or inherited from account), falling back to account owner for holdings outside any portfolio. To be replaced with a portfolio-visibility predicate when shared ownership lands.
Scam tokens clone real symbols and merged into legitimate holdings: a fake USDT airdrop (possible_spam=false but verified_contract=false) summed with real Tether into a 4.1M USDT holding. Filter out tokens flagged possible_spam or lacking verified_contract before they reach the symbol-keyed merge.
The chains endpoint defaults to eth only when no chains parameter is given, hiding L2/sidechain balances (ARB, optimism ETH, DAI were missing). Pass the supported chain list explicitly and keep only chains with actual transactions. The endpoint rejects scroll/zksync/ fantom, so those remain uncovered.
CreateRule failed with "user_id is required" because the handler never populated UserID from the request context, unlike PortfolioService. Inject the context user on CreateRule and default ListRules to the caller (with an admin override), so rule creation works and listings are properly scoped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Summary
Builds out external price/balance fetching, unifies the money representation across
the stack, hardens wallet sync, and adds a smoke-test suite. Backend-only — the
frontend is not yet wired (
NEXT_PUBLIC_USE_BACKEND=false).Changes
Prices & balances
prices; base-asset (USD/USDT) resolved/created on demand in
FetchExternalPrices.symbol. Provider mechanics moved behind a single
WalletSyncer.SyncWallet(addr, chains)port — the portfolio service keeps only domain logic.
Holding.excludedflag (spam/scam tokens excluded from portfolio calculations).portfolio_idviaCOALESCE(h.portfolio_id, a.portfolio_id).Unified money representation
numeric↔ Godecimal.Decimal↔ protostring(raw-integer model, value =
amount / 10^decimals).Holding.Amount,StoredPrice.Last/Open/High/Low/Close/Volume,PortfolioValueResponse.total_value_amount.postgres.NewPool.int64/bigint).Tests & cleanup
test/smoke/(assets, portfolio, prices, wallet sync) againstthe live compose stack;
make test-smoke.pkg/folder.Breaking changes
Holding.amount,Price.last/OHLCV,PortfolioValueResponse.total_value_amountare now
string(wereint64). Acceptable — no live API consumers yet.Migration / ops
holdings.amountandprices.{last,open,high,low,close,volume}→numeric.Apply with Atlas declarative (
make schema-apply/atlas schema apply) — both are safewidening ALTERs (
bigint→numeric), no drop/recreate.github.com/jackc/pgx-shopspring-decimal. Pools must be built viapostgres.NewPoolso the codec is registered.Testing
make test-unit,make test-integration(testcontainers),make test-smoke— all green.vitalik.ethwallet sync (large uint256 balances)through the full decimal/numeric stack.