Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d59bca6
fix: bind source-funded repay reviews to snapshots
dglowinski Sep 3, 2026
44c10d6
fix: bind all transaction reviews to action snapshots
dglowinski Sep 3, 2026
82adc03
Merge remote-tracking branch 'origin/development' into fix/review-sna…
dglowinski Sep 3, 2026
3d82647
fix: repair migration batch review
dglowinski Sep 3, 2026
cb76612
chore(deps): bump the all-updates group with 11 updates
dependabot[bot] Sep 7, 2026
36d2c2a
fix: align Wagmi connector override
Seranged Sep 7, 2026
0f7c0ff
Merge pull request #851 from euler-xyz/dependabot/npm_and_yarn/all-up…
Seranged Sep 7, 2026
6ef9692
fix: align environment documentation with current configuration
Seranged Sep 7, 2026
625ec45
fix: clarify announcement configuration deployment scope
Seranged Sep 7, 2026
1f1a0da
Update distroless image
girts512 Sep 8, 2026
feefe82
fix: correct vault verification chain and failure handling
Seranged Sep 8, 2026
1f48297
fix: gate fallback vault policy on verification readiness
Seranged Sep 8, 2026
2e54b25
fix: preserve dismiss-only Earn browsing warning
Seranged Sep 8, 2026
d6a230a
Merge pull request #854 from euler-xyz/fix/earn-vault-verification
Seranged Sep 8, 2026
c32d887
Merge pull request #853 from euler-xyz/chore/update_base_image_version
girts512 Sep 8, 2026
ef3fb19
fix: preserve batch entry identities and savings review sources
dglowinski Sep 8, 2026
ed4a68a
Merge pull request #847 from euler-xyz/fix/review-snapshot-consistency
Seranged Sep 9, 2026
45af546
Merge pull request #852 from euler-xyz/chore/docs-env-cleanup
Seranged Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ NUXT_PUBLIC_SWAP_API_URL="https://swap-dev.euler.finance"
# Pyth Hermes authentication (server-side only, required for Pyth-backed markets)
PYTH_API_KEY=

# Stablewatch intrinsic APY (server-side only, optional)
# If not set, Stablewatch APY data will not be fetched even if tokens are configured.
STABLEWATCH_API_KEY=
# Merkl authentication (server-side only, optional)
# Sends X-API-Key through /api/internal/proxy/merkl for an increased upstream quota.
MERKL_API_KEY=

# Tenderly simulation (server-side only, optional)
TENDERLY_ACCESS_KEY=
Expand Down Expand Up @@ -129,6 +129,8 @@ NUXT_PUBLIC_CONFIG_TELEGRAM_URL=
NUXT_PUBLIC_CONFIG_GITHUB_URL="https://github.com/euler-xyz"
NUXT_PUBLIC_CONFIG_APP_TITLE="Euler Lite"
NUXT_PUBLIC_CONFIG_APP_DESCRIPTION="Lightweight interface for Euler Finance."
# Custom logo URL (optional). LOGO_URL takes precedence at server startup.
NUXT_PUBLIC_CONFIG_LOGO_URL=
# Absolute URL to a social share image (og:image / twitter:image).
# Recommended: 1200x630 or larger (2400x1260), PNG or JPG. Must be publicly
# reachable — X, Slack, Discord fetch it directly. Empty = no preview image.
Expand All @@ -153,6 +155,7 @@ NUXT_PUBLIC_CONFIG_ENABLE_EARN_PAGE="true"
NUXT_PUBLIC_CONFIG_ENABLE_LEND_PAGE="true"
NUXT_PUBLIC_CONFIG_ENABLE_EXPLORE_PAGE="true"
NUXT_PUBLIC_CONFIG_ENABLE_APP_TITLE="true"
NUXT_PUBLIC_CONFIG_ENABLE_POWERED_BY_EULER="true"

# Incentives provider flags — all enabled by default
NUXT_PUBLIC_CONFIG_ENABLE_MERKL="true"
Expand All @@ -161,8 +164,9 @@ NUXT_PUBLIC_CONFIG_ENABLE_FUUL="true"
NUXT_PUBLIC_CONFIG_ENABLE_TURTLE="true"

# One-time announcement modal (optional). Populate any content field to show it
# after onboarding. Short CONFIG_ANNOUNCEMENT_* names are also accepted at
# runtime for Doppler-injected deployments.
# after onboarding. At server startup, short CONFIG_ANNOUNCEMENT_* names take
# precedence over the corresponding NUXT_PUBLIC_CONFIG_ANNOUNCEMENT_* names.
# Clear both naming forms for all four fields to disable the announcement.
NUXT_PUBLIC_CONFIG_ANNOUNCEMENT_TITLE=
NUXT_PUBLIC_CONFIG_ANNOUNCEMENT_BODY=
# Newline-delimited text or JSON array string, e.g. '["First","Second"]'.
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ENV NODE_OPTIONS=--max-old-space-size=4096
RUN npm run build

# ── Production stage (distroless: no shell, no tools, non-root) ──
FROM gcr.io/distroless/nodejs24-debian12:nonroot AS production
FROM gcr.io/distroless/nodejs24-debian13:nonroot AS production

ENV MODE=production
ENV NODE_ENV=production
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Euler Lite uses the [Euler V2 SDK](https://github.com/euler-xyz/euler-sdks) for
| `DEPRECATED_CHAINS` | — | Comma-separated chain IDs shown collapsed in the chain selector and skipped by startup warm-cache cycles. |
| `ONCHAIN_SDK_CHAINS` | — | Comma-separated chain IDs pinned to the onchain SDK adapter config for chain-aware browser reads and the server vault snapshot, bypassing V3. Independent of `DEPRECATED_CHAINS`; list a chain in both to deprecate it and route it onchain. |
| `EVAULT_FETCH_CHUNK_CHAINS` | — | Comma-separated chain IDs whose EVault list reads are split into small sequential SDK calls. Use for RPC/lens endpoints that fail under larger concurrent onchain EVault fetches. |
| `EULER_SDK_EULER_INTERFACES_BRANCH` | `master` | Euler interfaces branch used for runtime ABIs and `EulerChains.json`. When set, it takes precedence over `NUXT_PUBLIC_CONFIG_EULER_CHAINS_URL`. |
| `EULER_SDK_EULER_INTERFACES_BRANCH` | `master` | Euler interfaces branch used for runtime ABIs and `EulerChains.json`. Explicit `NUXT_PUBLIC_CONFIG_EULER_CHAINS_URL` and `NUXT_PUBLIC_CONFIG_EULER_ABIS_BASE_URL` overrides take precedence over the branch for their respective manifests. |

`fallback` uses V3 first and on-chain reads second. If no V3 URL is configured, Lite passes `disableV3: true` to the SDK so fallback reads go straight on-chain.

Expand All @@ -75,18 +75,19 @@ Euler Lite uses the [Euler V2 SDK](https://github.com/euler-xyz/euler-sdks) for
| Variable | Description |
| -------- | ----------- |
| `CORS_ALLOWED_ORIGINS` | Comma-separated allowlist for `/api/*`; falls back to `NUXT_PUBLIC_APP_URL`. |
| `FIRST_PARTY_COOKIE_SECRET` | Optional server-only secret that keeps the internal API marker cookie stable across replicas and deploys. Defaults to a value derived from `NUXT_PUBLIC_APP_URL` or `RAILWAY_PUBLIC_DOMAIN`. |
| `CSP_EXTRA_CONNECT_SRC` | Extra `connect-src` origins for development or staging endpoints. |
| `DEV_GEO_COUNTRY` | Local/preview country fallback when Cloudflare geo headers are absent. Do not set in production behind Cloudflare. |
| `ADDRESS_SCREENING_URI` / `ADDRESS_SCREENING_API_KEY` | Server-side data-v3 compliance endpoint + restricted API key, proxied by `/api/internal/screen-address` (also serves first-party `*.euler.finance` SPAs). Both unset ⇒ screening disabled (all addresses pass) — except in production (`DOPPLER_ENVIRONMENT=prd`), where missing configuration fails closed; only one set ⇒ fails closed everywhere. URI must be https (localhost http allowed for dev). |
| `STABLEWATCH_API_KEY` | Optional server-side Stablewatch key for intrinsic APY data. |
| `MERKL_API_KEY` | Optional server-side Merkl key. The Merkl API works anonymously (10 req/sec shared across all users via `/api/internal/proxy/merkl`); set this to send `X-API-Key` upstream for a higher quota. Server-only — never exposed to the browser. |
| `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. |
| `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

These use Nuxt's `runtimeConfig` and are set via `NUXT_PUBLIC_CONFIG_*` env vars:
These settings use `NUXT_PUBLIC_CONFIG_*` env vars. Branding and announcement values are read by the server at startup and injected into `window.__APP_CONFIG__`; links and feature flags use Nuxt's public `runtimeConfig`. The server proxy handlers resolve the upstream source overrides.

| Variable | Default | Description |
| ------------------------------------------- | ------------------------------------------ | ----------------------------------------------------- |
Expand All @@ -97,7 +98,8 @@ These use Nuxt's `runtimeConfig` and are set via `NUXT_PUBLIC_CONFIG_*` env vars
| `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` | — | URL for EulerChains.json, used when no Euler interfaces branch is configured |
| `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 |
| `NUXT_PUBLIC_CONFIG_STARGATE_URL` | — | Stargate link |
| `NUXT_PUBLIC_CONFIG_TOS_URL` | — | Terms of Service link |
Expand All @@ -115,17 +117,19 @@ These use Nuxt's `runtimeConfig` and are set via `NUXT_PUBLIC_CONFIG_*` env vars
| `NUXT_PUBLIC_CONFIG_ENABLE_ENTITY_BRANDING` | `true` | Show entity branding |
| `NUXT_PUBLIC_CONFIG_ENABLE_VAULT_TYPE` | `true` | Show vault type labels |
| `NUXT_PUBLIC_CONFIG_ENABLE_APP_TITLE` | `true` | Show app title in the navbar |
| `NUXT_PUBLIC_CONFIG_ENABLE_POWERED_BY_EULER` | `true` | Show the Powered by Euler branding in the navbar |
| `NUXT_PUBLIC_CONFIG_ENABLE_MERKL` | `true` | Enable Merkl rewards integration |
| `NUXT_PUBLIC_CONFIG_ENABLE_INCENTRA` | `true` | Enable Incentra rewards integration |
| `NUXT_PUBLIC_CONFIG_ENABLE_FUUL` | `true` | Enable Fuul rewards integration |
| `NUXT_PUBLIC_CONFIG_ENABLE_TURTLE` | `true` | Enable Turtle rewards integration |
| `NUXT_PUBLIC_CONFIG_ANNOUNCEMENT_TITLE` | — | Optional one-time modal title; any announcement content field enables the modal |
| `NUXT_PUBLIC_CONFIG_ANNOUNCEMENT_BODY` | — | Optional one-time modal body text |
| `NUXT_PUBLIC_CONFIG_ANNOUNCEMENT_ITEMS` | — | Optional newline-delimited or JSON-array bullet items |
| `NUXT_PUBLIC_CONFIG_ANNOUNCEMENT_URL` | — | Optional announcement link (`https://`, `http://`, or safe root-relative path) |
| `NUXT_PUBLIC_CONFIG_UNISWAP_TOKEN_LIST_URL` | `https://tokens.uniswap.org` | Uniswap token list for swap selector |
| `NUXT_PUBLIC_CONFIG_DEFILLAMA_TOKEN_LIST_URL` | `https://d3g10bzo9rdluh.cloudfront.net` | DefiLlama token list for swap selector |

Runtime deployments can also set the short `CONFIG_ANNOUNCEMENT_*` names. They are injected through `window.__APP_CONFIG__` at server startup and can change without rebuilding static assets. See [Announcement Modal](./docs/announcement-modal.md) for rollout and dismissal behavior.
At server startup, short `CONFIG_ANNOUNCEMENT_*` names take precedence over the corresponding `NUXT_PUBLIC_CONFIG_ANNOUNCEMENT_*` names. In Nitro-served deployments, both naming forms are read at server startup, so updates require a server restart without rebuilding client assets. Generated/static deployments must rebuild when `NUXT_PUBLIC_CONFIG_ANNOUNCEMENT_*` fallback values change. To disable an announcement, clear both naming forms for all four content fields. See [Announcement Modal](./docs/announcement-modal.md) for rollout and dismissal behavior.

#### Chain Configuration

Expand Down
11 changes: 10 additions & 1 deletion app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 } = useEulerLabels()
const { loadLabels, retryLabels, isReady: isLabelsReady, isLoading: isLabelsLoading, loadError: labelsLoadError } = useEulerLabels()
const { loadCountry } = useGeoBlock()
const { updateBalances, resetBalances } = useWallets()
const { isConnected, address } = useWagmi()
Expand Down Expand Up @@ -217,6 +217,15 @@ onUnmounted(() => {
:class="isMenuVisible ? 'pb-[98px]' : 'pb-16'"
>
<div class="w-full max-w-container mx-16 mobile:px-16 mobile:mx-0">
<UiAlert
v-if="labelsLoadError && !isLabelsReady"
class="mb-16"
title="Vault verification unavailable"
:description="labelsLoadError"
variant="error"
:action-text="isLabelsLoading ? 'Retrying…' : 'Retry'"
@action="retryLabels"
/>
<NuxtLayout>
<NuxtPage
:keepalive="{ include: ['ExplorePage', 'EarnPage', 'LendPage', 'BorrowPage', 'PortfolioPage'] }"
Expand Down
6 changes: 3 additions & 3 deletions components/BatchContents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ const modal = useModal()
const openEntryReview = (entry: BatchEntry) => {
if (!entry.review) return
modal.open(OperationReviewModal, {
// Forward the entry's sub-account so the review shows the same "Position N"
// pill as the batch operations list (entry.review itself doesn't carry it).
props: { ...entry.review, subAccount: entry.subAccount, plan: entryPlans.value[entry.id], hideExecute: true },
// Forward account context so position-funded operations identify both the
// target and the distinct account supplying assets or shares.
props: { ...entry.review, subAccount: entry.subAccount, sourceSubAccount: entry.sourceSubAccount, plan: entryPlans.value[entry.id], hideExecute: true },
})
}

Expand Down
20 changes: 11 additions & 9 deletions components/BatchReviewModal.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { getAddress } from 'viem'
import { flattenBatchEntries, getSubAccountId, type TransactionPlan } from '@eulerxyz/euler-v2-sdk'
import { flattenBatchEntries, type TransactionPlan } from '@eulerxyz/euler-v2-sdk'
import { buildModifiedPositionKeySets, buildRemovedPositionKeySets, filterPositionKeysByOwner, useTxBatch } from '~/composables/useTxBatch'
import { useTokenSymbolResolver } from '~/composables/useTokenSymbolResolver'
import { useVaultRegistry } from '~/composables/useVaultRegistry'
Expand All @@ -18,6 +18,7 @@ import type { PreparedExecutionReview } from '~/composables/useReviewedExecution
import { submissionResultMessage } from '~/features/reviewed-execution/coordinator/coordinator'
import { finalizeSuccessfulSubmission } from '~/features/reviewed-execution/review/submission-completion'
import { useToast } from '~/components/ui/composables/useToast'
import { getPositionTag, getSourcePositionTag } from '~/utils/positionTag'

// Whole-batch review: required approvals, then the operations as rows that roll
// down to their details, the net wallet changes, a Tenderly simulation link,
Expand Down Expand Up @@ -74,15 +75,10 @@ const ownerSubAccountKey = computed(() => {
// Sub-account → tag. Sub-account 0 is the main account (Earn deposits / base
// collateral), labelled "Deposits"; numbered borrow positions are "Position N".
const positionTag = (subAccount?: string): string | undefined => {
if (!subAccount || !owner.value) return undefined
try {
const idx = getSubAccountId(getAddress(owner.value), getAddress(subAccount))
return idx === 0 ? 'Deposits' : `Position ${idx}`
}
catch {
return undefined
}
return getPositionTag(owner.value, subAccount)
}
const sourcePositionTag = (sourceSubAccount?: string, targetSubAccount?: string): string | undefined =>
getSourcePositionTag(owner.value, sourceSubAccount, targetSubAccount)

type ReviewWithSteps = StepDecodingContext & {
displayPlan?: TransactionPlan
Expand Down Expand Up @@ -629,6 +625,12 @@ const onCloseRequested = () => {
:failed="!!layers[index + 1]?.failed"
/>
<BatchOperationLabel :entry="entry" />
<span
v-if="sourcePositionTag(entry.sourceSubAccount, entry.subAccount)"
class="shrink-0 text-h6 text-content-secondary bg-card py-2 px-8 rounded-8 border border-line-default"
>
{{ sourcePositionTag(entry.sourceSubAccount, entry.subAccount) }}
</span>
<span
v-if="positionTag(entry.subAccount)"
class="shrink-0 text-h6 text-content-secondary bg-card py-2 px-8 rounded-8 border border-line-default"
Expand Down
Loading
Loading