Skip to content

USDC issuer diverges across adapters and constants: collateral targets a different asset #412

Description

@YaronZaki

Labels / Complexity: bug, financial, soroban, protocol · Extremely High — 500

Problem

The same "USDC" token is resolved to two different Stellar issuers in different layers of the codebase:

  • quantara/web_app/contract_tools/constants.py defines USDC_ASSET_ISSUER = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NOJ4VBH6THS2G2V" and builds USDC_ASSET_ID = "USDC:<issuer>".
  • quantara/soroban/adapters/soroswap_adapter.py and quantara/soroban/adapters/blend_adapter.py hardcode USDC as "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGCS3FOGTICSJCWV5X2HGM" in their _TokenResolver._TOKENS.
  • quantara/frontend/src/utils/constants.js defaults USDC_ASSET.issuer to GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NOJ4VBH6THS2G2V (matching constants.py).

CollateralManager (quantara/soroban/adapters/CollateralManager.py) imports USDC_ASSET_ID from web_app.contract_tools.constants, so its USDC config resolves to GBBD47...S2G2V, while BlendLendingAdapter / SoroswapAMMAdapter resolve "USDC" to GA5ZSE...X2HGM.

Consequence: the collateral engine and the AMM/lending adapters disagree about which on-chain asset "USDC" is. A position whose collateral is valued using the GBBD47... asset while its debt is quoted/borrowed against the GA5ZSE... asset operates on two different tokens; balances, health ratios, and swap quotes will not match, and in production this silently routes real user funds against the wrong issuer. There is no single source of truth, and no code cross-checks that the three layers agree.

Root cause

# constants.py
USDC_ASSET_ISSUER = os.getenv("USDC_ASSET_ISSUER", "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NOJ4VBH6THS2G2V")
# soroswap_adapter.py / blend_adapter.py
"USDC": {"addresses": ["USDC", "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGCS3FOGTICSJCWV5X2HGM"], ...}  # ← different issuer

Why this is architecturally hard

  1. The shortcut — hardcode one issuer everywhere — reintroduces the exact failure mode the code comments warn about ("no TODO placeholders or hardcoded addresses"). The fix must make the issuer a single environment-driven source of truth consumed by Python and the frontend, and ideally validated at startup.
  2. The adapters use their own private _TokenResolver with a duplicated token table rather than importing TokenParams from web_app.contract_tools.constants. Consolidating means deciding whether the adapters may depend on web_app (they currently import nothing from it), or whether the constants module moves to a shared location.
  3. The frontend has its own independent default in quantara/frontend/src/utils/constants.js (VITE_USDC_ISSUER), so the fix must also reconcile the build-time JS default with the backend env var, or emit it from the API.
  4. CollateralManager builds SUPPORTED_TOKENS once at class-definition time from the constants, so any change must ensure the env var is resolved before import and remains consistent across the worker process.

Proposed design

Single source of truth, consumed by all three layers:

Layer Source Action
Backend USDC_ASSET_ISSUER env var (one default) constants.py remains canonical; adapters import it or a shared resolver
Adapters same env var replace _TokenResolver USDC entry with the canonical issuer
Frontend backend-provided value or same VITE_USDC_ISSUER default align the default string with the backend

Add a startup assertion (quantara/web_app/config_validator.py already does env validation in assert_valid_config()) that fails fast if a USDC issuer is unset or mismatched.

Downstream impact

This touches the canonical asset identifier used by CollateralManager, DepositMixin/TokenParams (quantara/web_app/contract_tools/), and the frontend balance display (quantara/frontend/src/services/wallet.jsx). Any consumer that has persisted the old GA5ZSE... asset must be migrated or the change gated by environment.

Acceptance criteria

Backend

  • One canonical USDC issuer is used across constants.py, soroswap_adapter.py, blend_adapter.py, and CollateralManager.py.
  • assert_valid_config() fails fast when the issuer is missing or inconsistent.

Frontend

  • The frontend USDC issuer default matches the backend default, or is sourced from the backend.

Tests

  • A test asserts the adapters and CollateralManager resolve "USDC" to the same issuer.
  • Tests run via cd quantara && poetry run pytest web_app/tests.

Out of scope

Do not add new tokens or change issuer-selection UX in this issue; only reconcile USDC (and, if trivially adjacent, the ETH issuer already shared between layers).

Getting started

Files in scope: quantara/web_app/contract_tools/constants.py, quantara/soroban/adapters/soroswap_adapter.py, quantara/soroban/adapters/blend_adapter.py, quantara/frontend/src/utils/constants.js. Verify with:

cd quantara && poetry run pytest web_app/tests
cd quantara/frontend && yarn test:run

Good first files to read: quantara/web_app/contract_tools/constants.py, quantara/soroban/adapters/soroswap_adapter.py, quantara/soroban/adapters/CollateralManager.py.

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third CampaignbugSomething isn't workingfinancialImported from PRODUCTION_ISSUES.mdprotocolImported from PRODUCTION_ISSUES.mdsorobanImported from PRODUCTION_ISSUES.md

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions