diff --git a/.env.example b/.env.example index cbf19192a..6ecaea953 100644 --- a/.env.example +++ b/.env.example @@ -163,11 +163,16 @@ NUXT_PUBLIC_CONFIG_LOGO_URL= # Recommended: 1200x630 or larger (2400x1260), PNG or JPG. Must be publicly # reachable — X, Slack, Discord fetch it directly. Empty = no preview image. NUXT_PUBLIC_CONFIG_SOCIAL_IMAGE_URL= -NUXT_PUBLIC_CONFIG_LABELS_REPO="euler-xyz/euler-labels" -NUXT_PUBLIC_CONFIG_LABELS_REPO_BRANCH="master" -# Base URL for labels data (S3/CDN). When set, LABELS_REPO and LABELS_REPO_BRANCH are ignored. -# Must serve the same directory structure (e.g. {baseUrl}/{chainId}/products.json, {baseUrl}/logo/{file}). -NUXT_PUBLIC_CONFIG_LABELS_BASE_URL= +# Hosted mode reads V3 only. Forks can select their own static label files. +LABELS_SOURCE=v3 +# V3 metadata dataset and default publication (latest or an immutable published version key). +# Geo policies, visibility and governance addresses remain live and shared. +LABELS_V3_SET=public +LABELS_V3_VERSION=latest +# Enabled DEPRECATED_CHAINS use V3 metadata with Lite on-chain verification. +# They must also be in ONCHAIN_SDK_CHAINS. Other enabled chains use V3 assessments. +# Required only for LABELS_SOURCE=static: operator-owned five-file labels directory. +STATIC_LABELS_BASE_URL= # Full URL for EulerChains.json (contract addresses). Takes precedence over # EULER_SDK_EULER_INTERFACES_BRANCH and its public aliases — it is the # emergency repoint lever when the default GitHub source is unavailable. @@ -224,3 +229,7 @@ SUBGRAPH_URL_80094="https://api.goldsky.com/api/public/project_cm4iagnemt1wp01xn SUBGRAPH_URL_8453="https://api.goldsky.com/api/public/project_cm4iagnemt1wp01xn4gh1agft/subgraphs/euler-simple-base/latest/gn" SUBGRAPH_URL_9745="https://api.goldsky.com/api/public/project_cm4iagnemt1wp01xn4gh1agft/subgraphs/euler-simple-plasma/latest/gn" SUBGRAPH_URL_999=https://api.goldsky.com/api/public/project_cm4iagnemt1wp01xn4gh1agft/subgraphs/euler-simple-hyperevm/1.0.2/gn + +# Mount this directory on persistent storage to retain geo policies across redeploys. +# Default: .data/geo-policies (survives process restarts only on the same filesystem). +GEO_POLICY_CACHE_DIR= diff --git a/README.md b/README.md index 28e352495..fc05a8dc8 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ Euler Lite uses the [Euler V2 SDK](https://github.com/euler-xyz/euler-sdks) for | `TENDERLY_ACCESS_KEY`, `TENDERLY_ACCOUNT_SLUG`, `TENDERLY_PROJECT_SLUG` | Optional Tenderly simulation configuration. | | `FUUL_API_URL` or `NUXT_PUBLIC_FUUL_API_URL` | Optional Fuul API upstream override. | | `INCENTRA_API_URL` or `NUXT_PUBLIC_INCENTRA_API_URL` | Optional Incentra/Brevis API upstream override. | +| `LABELS_SOURCE` | `v3` (default) or `static`; selects the shared label snapshot producer. | +| `STATIC_LABELS_BASE_URL` | Required directory URL for static authoring files; unused by hosted V3 mode. | | `TURTLE_EARN_API_URL` or `NUXT_PUBLIC_TURTLE_EARN_API_URL` | Optional Turtle reward-proof upstream override; defaults to `https://earn.turtle.xyz/v1`. | #### Branding & Feature Flags @@ -95,9 +97,6 @@ These settings use `NUXT_PUBLIC_CONFIG_*` env vars. Branding and announcement va | `NUXT_PUBLIC_CONFIG_APP_DESCRIPTION` | `Lightweight interface for Euler Finance.` | App description | | `NUXT_PUBLIC_CONFIG_LOGO_URL` | — | Custom logo URL (falls back to built-in Euler logo) | | `NUXT_PUBLIC_CONFIG_SOCIAL_IMAGE_URL` | — | Absolute URL to social share image (og:image / twitter:image), 1200×630+ | -| `NUXT_PUBLIC_CONFIG_LABELS_REPO` | `euler-xyz/euler-labels` | GitHub labels repo | -| `NUXT_PUBLIC_CONFIG_LABELS_REPO_BRANCH` | `master` | Branch to fetch labels from | -| `NUXT_PUBLIC_CONFIG_LABELS_BASE_URL` | — | S3/CDN base URL for labels (overrides repo/branch) | | `NUXT_PUBLIC_CONFIG_EULER_CHAINS_URL` | — | Explicit URL for EulerChains.json; takes precedence over the Euler interfaces branch | | `NUXT_PUBLIC_CONFIG_EULER_ABIS_BASE_URL` | — | Base URL serving `{contract}.json` for runtime ABIs; takes precedence over the Euler interfaces branch. | | `NUXT_PUBLIC_CONFIG_DOCS_URL` | — | Documentation link | @@ -219,27 +218,11 @@ The resolution order in `getAssetLogoUrl(address, symbol)`: 2. `logoURI` from the unified token list (Euler SDK token list > DefiLlama > Uniswap > Merkl) 3. Empty string (component shows initials fallback) -#### EulerEarn Vaults +#### Public Labels -If using a custom labels repository, create chain-specific `earn-vaults.json` files to curate which EulerEarn vaults appear: +Vault inventory, products, entities, hosted logos, tags, campaigns, and deprecation metadata come from the versioned Public Labels API at `V3_API_URL`. Lite resolves `version=latest` to one immutable publication before fetching a chain bundle and follows every paginated list through `meta.total`. -``` -your-labels-repo/ -├── 1/earn-vaults.json # Ethereum -├── 42161/earn-vaults.json # Arbitrum -└── 8453/earn-vaults.json # Base -``` - -Each file is a JSON array of vault addresses: - -```json -[ - "0x1234567890123456789012345678901234567890", - "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" -] -``` - -When using the default `euler-xyz/euler-labels` repository, all verified EulerEarn vaults are shown automatically. +Hosted labels, visibility and geo policies come exclusively from V3. Fork deployments can set `LABELS_SOURCE=static` and provide their own authoring files through `STATIC_LABELS_BASE_URL`. See [Static labels](docs/static-labels.md) for the format, failure semantics and on-chain data options. ### 4. Development diff --git a/app.vue b/app.vue index 91891a3de..81d6916c0 100644 --- a/app.vue +++ b/app.vue @@ -14,7 +14,7 @@ let isAnnouncementOpen = false const { loadEulerConfig, chainId } = useEulerAddresses() const { loadVaults, isReady: isVaultsReady, resetVaultsState, refreshVaults, setShowAllLabelEntries } = useVaults() const { loadTokenList, isLoaded: isTokenListLoaded } = useTokenList() -const { loadLabels, retryLabels, isReady: isLabelsReady, isLoading: isLabelsLoading, loadError: labelsLoadError } = useEulerLabels() +const { loadLabels, refreshLabelsIfStale, retryLabels, isReady: isLabelsReady, isLoading: isLabelsLoading, loadError: labelsLoadError } = useEulerLabels() const { loadCountry } = useGeoBlock() const { updateBalances, resetBalances } = useWallets() const { isConnected, address } = useWagmi() @@ -162,6 +162,23 @@ watch([chainId, showAllLabelEntries], () => { }) }, { immediate: true }) +// Refresh live label verdicts for an open tab and after returning from a +// suspended/background tab. The labels store owns freshness and deduplication. +let labelsRefreshInterval: ReturnType | undefined +const refreshVisibleLabels = () => { + if (document.visibilityState === 'visible') void refreshLabelsIfStale() +} +onMounted(() => { + labelsRefreshInterval = setInterval(refreshVisibleLabels, POLL_INTERVAL_60S_MS) + window.addEventListener('focus', refreshVisibleLabels) + document.addEventListener('visibilitychange', refreshVisibleLabels) +}) +onUnmounted(() => { + clearInterval(labelsRefreshInterval) + window.removeEventListener('focus', refreshVisibleLabels) + document.removeEventListener('visibilitychange', refreshVisibleLabels) +}) + // Refresh balances when token list finishes loading (includes new DefiLlama tokens) watch(isTokenListLoaded, (loaded) => { if (loaded && (isConnected.value || isVaultsReady.value)) { diff --git a/components/entities/portfolio/PortfolioNotice.vue b/components/entities/portfolio/PortfolioNotice.vue index 4fe840269..5ba74ced3 100644 --- a/components/entities/portfolio/PortfolioNotice.vue +++ b/components/entities/portfolio/PortfolioNotice.vue @@ -14,7 +14,7 @@ defineProps<{ notice: string }>() name="info-circle" class="!w-20 !h-20 text-accent-600 shrink-0" /> - + ({ data-field="borrow-apy" :data-value="borrowApyWithRewards" > + import type { EulerEarn, EVault, SecuritizeCollateralVault } from '@eulerxyz/euler-v2-sdk' -import { getEulerLabelEntityLogo } from '~/entities/euler/labels' +import { getEulerLabelPointLogo } from '~/entities/euler/labels' import { useModal } from '~/components/ui/composables/useModal' import { VaultPointsModal } from '#components' -const { vault } = defineProps<{ vault: EVault | EulerEarn | SecuritizeCollateralVault }>() +const { vault, campaignType = 'deposit' } = defineProps<{ + vault: EVault | EulerEarn | SecuritizeCollateralVault + campaignType?: 'deposit' | 'borrow' +}>() -const points = useEulerPointsOfVault(vault.address) +const allPoints = useEulerPointsOfVault(vault.address) +const points = computed(() => allPoints.filter(point => + point.type ? point.type === campaignType : campaignType === 'deposit', +)) const modal = useModal() -const onLogoError = (event: Event, logoFileName: string) => { - const img = event.target as HTMLImageElement - if (!img.dataset.triedFallback) { - img.dataset.triedFallback = 'true' - img.src = getEulerLabelEntityLogo(logoFileName) - } -} - const onPointClick = ( point: { name: string, logo: string }, event: MouseEvent | TouchEvent, @@ -27,6 +25,7 @@ const onPointClick = ( props: { pointName: point.name, pointLogo: point.logo, + campaignType, }, }) } @@ -37,18 +36,15 @@ const onPointClick = ( v-if="points.length" class="text-p1 flex items-center gap-0 hover:gap-8 transition-[gap] duration-300 ease-in-out" > - Points entity logo + /> diff --git a/components/entities/vault/VaultPointsModal.vue b/components/entities/vault/VaultPointsModal.vue index 6c2d25cd1..fb68569fe 100644 --- a/components/entities/vault/VaultPointsModal.vue +++ b/components/entities/vault/VaultPointsModal.vue @@ -1,20 +1,13 @@