chore: promote development to master - #806
Conversation
The path-shape handler backs the SDK's default `eulerLabelsBaseUrl` template, but it called `refreshLabelFile` — the force-refresh primitive reserved for warm-cache cycles. That sent every SDK label read upstream and left the warm-cache prewarm useful only as a stale fallback, despite the handler comment, docs/server-side-caching.md, and docs/vault-labels-and-verification.md all describing a shared read-through cache. Export the existing `getOrRefresh` read-through helper and use it in both handlers. Warm callers keep calling `refreshLabelFile` directly so each cycle rewrites the entry before it expires.
`freshPlanContext()` fetches through the shared QueryClient without invalidating first, and `queryAccountVaults` carries `formStaleTimeMs: MINUTE`, so the plan-time account is onchain-backed but can be up to a minute old. Pinning the adapters selects the data source, not the cache. Describe the actual bound in sdk-integration.md and in the matching comments in useEulerSdk.ts, useEulerTx.ts, and sdk-query-policy.ts, and note that post-tx eviction is the only unconditional refetch in the flow. Also correct three smaller drifts in the same document: the SDK instance key includes a `keyring:` segment, no policy row uses `formStaleTimeMs: 0` any more, and `invalidateSdkQueries` has callers beyond the two post-tx ones (none of which run at form mount).
vaults-cache.ts calls the force-refresh primitive too, not just the warm-cache plugin.
Revert the non-documentation changes made while responding to review: the path-shape labels handler, the read-through helper export, the new route test, and the comment edits in useEulerSdk.ts, useEulerTx.ts, and sdk-query-policy.ts. Those files are byte-identical to their previous state again. Instead of changing behavior, describe it. The path-shape labels route backs the SDK's default `eulerLabelsBaseUrl` template and calls `refreshLabelFile`, the force-refresh primitive, so it bypasses the fresh-entry check: every request reaches upstream unless it coincides with an in-flight fetch for the same key, and the warm-cache entry acts as a stale fallback rather than a read-through cache. Only the query-shape handler short-circuits on a fresh entry. vault-labels-and-verification.md now contrasts the two handlers in a table, server-side-caching.md scopes its read-through claim to the handlers that actually short-circuit, and the warm-cache section's "requests read the still-fresh previous entry" note is qualified the same way. The divergence is flagged as such rather than presented as design.
useEulerSdk.ts and useEulerTx.ts both describe a zero stale time on plan-critical reads, and sdk-query-policy.ts says invalidateAfterTx evicts at form mount. Neither is true any more, and this PR is docs-only so the comments stay as they are; point the reader at the document instead of leaving a silent contradiction.
…tes-7de8 Signed-off-by: kanv <85568419+kanvgupta@users.noreply.github.com>
The guide describes plan-time reads as bounded by FORM_STALE_TIMES with no form-mount invalidation, but several source comments still promised something stronger. Comment-only edits; no executable line changes. - useEulerSdk.ts: getEulerSdkFresh() no longer claims a zero stale time or that planning reflects the latest block. Pinning the adapters selects the data source; the windows are 1 min / 15 s per row. Also drops queryPythUpdateData from the list, which has no formStaleTimeMs and inherits its 30 s staleTimeMs. - useEulerTx.ts: freshPlanContext() no longer claims a live latest-block account. The fetch resolves through the shared QueryClient at each row's form stale time. - sdk-query-policy.ts: formStaleTimeMs is a stale time, not a forced refetch, and no row uses 0. invalidateAfterTx evicts after a successful tx only, naming the three call sites; nothing evicts at form mount. Two more instances of the same drift, beyond the three flagged: - tests/utils/sdk-query-policy.test.ts repeated the form-mount claim. - useEulerAccount.ts said portfolio reads via the fresh instance "always reflect the latest block", which is the same conflation of data source with freshness. sdk-integration.md drops the note recording the contradiction, now that there is no contradiction to record.
Remaining stale comments: - useEulerSdk.ts: the short doc comment directly above getEulerSdkFresh() still said "zero stale-time on plan-critical queries". It now names the FORM_STALE_TIMES windows and points at the fuller entry-point notes. - sdk-query-policy.ts: the balances/allowances section header said "always-fresh in form context"; it now says 15 s and spells out that a read inside that window still comes from cache. The INVALIDATE_AFTER_TX export comment no longer claims form-mount eviction. Comment-only; no executable lines changed. docs/server-side-caching.md conflicted with #720, which independently corrected the same Labels and warm-cache paragraphs. Adopt #720's wording verbatim for both regions so the two branches make byte-identical edits. Git then merges them without a conflict in either order, and the guide keeps a single consolidated explanation instead of two overlapping ones.
Capture the post-#778 path that formats vault.lostAssets directly, including the lens/SDK coverage-normalization contract and the race-guarded off-chain USD formatting pitfalls.
Replace the incorrect Incentra wording with the real Settings toggle, storage-key migration from permit2-enabled, useEulerTx usePermit2 wiring, and the typedData vs transaction migration authorization path.
Transport-level RPC failures now rethrow instead of being cached as negative detections for the TTL. Owner lists violating Safe's OwnerManager invariants (zero, sentinel, duplicate owners) are rejected as lookalikes.
Safe multisigs get the approval flow forced on (signatures preference is pinned off while connected, stored preference untouched) and approve + EVC batch submit as a single EIP-5792 wallet_sendCalls bundle, which Safe turns into one MultiSend proposal. Falls back to the sequential path for single-call plans and anything not bundleable.
…ature requirement
Mirrors OwnerManager GS203 (owner != address(this)) — a lookalike listing itself as owner no longer passes.
Reverted bundle receipts now throw instead of finalizing as success. sendCalls is pinned to the connector whose Safe provider was detected so a same-account connector switch cannot split submission and polling. Safe detection exposes a resolved flag and the signature preference fails closed while detection is pending. A permit2-resolved prepared envelope is re-resolved with permit2 off before bundling, and the repaired envelope also drives the sequential fallback. Review display derives bundled-approval labels from actual bundle eligibility instead of Safe detection alone.
On a Safe-to-EOA connector switch, isSafeWallet dropped only after the new connector's detection resolved, so review modals could transiently label direct EOA approvals as bundled. The flag now clears synchronously when a transition begins; execution was already fail-closed via the resolved flag.
A connector that is identifiably Safe by id/name stays classified as a Safe even when provider acquisition fails, so signatures cannot silently re-enable. Prepared envelopes executed for a detected Safe are also unconditionally normalized to usePermit2: false.
The permit2 normalization/repair block was gated on the acquired Safe provider, so a connector that identifies as Safe but whose getProvider() rejects executed prepared envelopes with usePermit2: true through the sequential path. Normalization now keys on isKnownSafe (provider OR connector identity); the bundle attempt still requires the provider. executePlan's sequential fallback gets the same treatment.
CoW orders require a recoverable ECDSA signature the SDK's cowExecutor enforces after approvals have already been sent — a Safe fails mid-flow with stranded approvals (and a burned EVC permit nonce on close-position). New useCowSwapEligibility gate (fail-closed while Safe detection is pending) is ANDed into the three includeCowSwap call sites, so Safe wallets route to on-chain swap providers; the existing eviction watcher removes on-screen CoW cards on mid-session Safe connection. Defence-in-depth throw in useCowSwapExecutionCore before any transaction is sent.
The eviction watcher only removes displayed cards; a CoW request already in flight when the gate flipped kept its current sweep generation and reinserted on resolution. upsertQuote now re-checks CoW eligibility at acceptance time.
Transaction-kind migrations for Safe wallets submit grants + migration EVC batch + revocations as a single atomic wallet_sendCalls bundle instead of two-to-three sequential Safe proposals. The bundled plan comes from the SDK's simulation variant, which is byte-identical to the execution plan for transaction-kind authorizations but validates the grant instead of reading the live allowance, so nothing needs to mine before the bundle is built; the pre-bundle simulation runs with the SDK's authorization state overrides threaded through simulatePreparedPlan. Atomicity removes the dangling-authorization window and all unwind bookkeeping on this path. The sequential grant flow remains the fallback for regular wallets and degraded Safes.
Wrapper grant/revoke calls could satisfy the bundle's minimum-call guard on their own, submitting [grant, revoke] around an empty plan and finalizing a no-op migration as success. An empty plan now throws when wrapper calls are present; without wrappers it keeps falling back to sequential execution.
- Planning helpers take no gasless-signature flag; usePermit2 enters at prepareTransactionPlan/executePlan, which also add the Safe pin. - The signatures toggle covers approval and migration-authorization signatures only. CoW order and CoW EVC-permit signing ignores it and is gated separately by cowSwapForcedOff. - Only the queuing of failed restorations is sequential-only; the restorePendingBeforeRetry gate runs before the bundled branch and so blocks bundled Safe migrations too. - Label the lens convertToAssets(balanceOf(address(1))) subtraction as an upstream on-chain invariant that cannot be verified in this repo, and state what can be: the SDK adapters pass lostAssets through unchanged. - Name createRaceGuard alongside runGuarded in the race-guard reference. - Drop the stale 'unavailable' return from the sendMigrationAsSafeBundle docstring; the function throws rather than falling back to sequential grants.
external-migration.md: - discovery is started from app root, not on Portfolio load; Portfolio pages consume the shared state - Aave reads are direct viem multicalls against local ABIs; the SDK connector supplies only the pool address, and aave.post.ts is the connector's GraphQL endpoint rather than a discovery dependency - show the real MORPHO_MIGRATION_SUPPORTED_CHAIN_IDS declaration (ReadonlySet) - borrow pairs require nonzero combined variable + stable debt - supply-only rows omit the Debt cell; "supply only" is aria-label only - name the second whole-source error (invalid reserve data) - state the actual allSettled rule: a rejection plus zero surviving positions errors the load, so a healthy-but-empty source does not protect it - refresh is four staggered bumps at [0, 5s, 15s, 30s]; migrate pages schedule them unconditionally while batch execution requires the cart-entry flag safe-wallets.md: - attribute the empty-plan and single-call rules to executePlanAsSafeBundle in useEulerTx, keeping PlanNotBundleableError with transaction-plan-calls - SAFE_STATUS_POLL_TIMEOUT_MS is module-private: describe it as the poller's default timeoutMs and note the AbortSignal early stop - SafeAccountBadge renders on vault overview address rows generally
docs referenced utils/keyring-injection.ts and utils/tos-injection.ts, which no longer exist: plan transformation moved from Lite-side guard transformers into SDK EulerPlugins (createKeyringPlugin configured via utils/sdk-keyring.ts, createLiteTosPlugin in utils/sdk-tos.ts), both registered in composables/useEulerSdk.ts. Update transaction-building.md, keyring-hooks.md, and tos-signing.md to describe the plugin pipeline, the credential/signature stores, and the guard registry's remaining blocker + metadata role, and replace the removed preparePlanForReview/applyOperationGuards flow with the current prepareTransactionPlan path.
The SDK doc references were bare monorepo paths that do not resolve from this repo, and the keyring plugin behavior described in the guard docs lives in the SDK source. Link both to the public euler-sdks repository so readers can follow them.
The TOS and Keyring plugins fail open: TOS injection requires a published signed message and a deployed termsOfUseSigner address, and Keyring injection requires a gated hook target, no valid on-chain credential (re-checked at submit), and a current credential from the store. Note that submit blockers, not the plugins, enforce signing and verification when that data is unavailable.
docs: point guard docs at the SDK plugin implementation
…on-updates-19f9 docs: earn uncovered losses and gasless signature preference
# Conflicts: # docs/transaction-building.md
…on-updates-0202 docs: Safe wallets, external migration, and batch plugin layers
There was a problem hiding this comment.
Stale comment
Agentic security review of the development \u2192 master promotion. One medium finding on Safe proposal retry after confirmation-poll timeout; other candidate issues were triaged out (re-planning at confirm is gated on reviewed input and authorization payload).
Sent by Cursor Security Agent: Security Reviewer
A product whose every member vault is hidden on both the lend and borrow side (e.g. issuer-governed Securitize collateral wrappers flagged notExplorableLend) is not a market: it gets no Explore card and no filter-option entries. Its addresses stay assigned so the members do not fall into orphan clustering, they still resolve as externalCollateral in other groups' graphs, and direct market URLs keep loading through fetchMarketGroupOnDemand. The show-all-label-entries toggle bypasses the rule like it does every other explorability filter.
fetchMarketGroupOnDemand routed every member address through eVaultService, which cannot load non-EVault members — a direct URL to a product of Securitize collateral wrappers rendered as market-not-found once the group stopped being listed. Members now come from the vault registry first (wrappers are hydrated there at startup); only the remainder is fetched through the EVault lens. Also: dedupe getVaultAddress via utils/discoveryCalculations, cover the buildProductGroups wiring (assigned-addresses invariant and the show-all-label-entries bypass), and update the discovery filtering doc table for the collateral-only product rule.
fix: hide collateral-only products from market discovery
fix: include Securitize intrinsic APY on lend pages
Master<>Dev Sync
LeonardEulerXYZ
left a comment
There was a problem hiding this comment.
Reviewed the final development → master promotion at exact head 9c487df5c265b90df5786381733b8b509f31a7d5.
Verdict
Approved for production promotion with the explicitly accepted P2 transaction-lifecycle residuals below. I found no P0/P1 blocker on the current head.
Promotion shape and validation
- PR head exactly matches
origin/development;origin/masteris now an ancestor, with no master-only composition branch remaining. - The master integration changes only
package.json/package-lock.jsonrelative to the prior reviewed head and preserves@eulerxyz/euler-v2-sdk@2.0.0while carrying the master audit overrides. - Clean install with the repo-pinned Node
v24.14.0and npm11.10.0succeeded; npm audit reported 0 vulnerabilities. - Focused current-head lifecycle/server/recent-change suite: 202/202 passed across Safe batch execution/detachment/status polling, external migration partial results, EulerRouter governor verification, market-group visibility, intrinsic APY, ABI proxy, and EulerChains proxy.
- Exact-head GitHub CI lint, typecheck, build/full test job, Cursor security review, and Railway preview are green.
- Headed Chromium under managed Xvfb loaded
/,/explore,/lend,/borrow,/earn,/portfolio, and/portfolio/migratefrom the exact-head dev preview with HTTP 200, no Cloudflare challenge, and no console errors. - Deployed internal API probes returned valid JSON for EulerChains, AccountLens/UtilsLens/VaultLens ABIs, V3 token/EVault/Earn paths, and oracle adapters/routers.
- Whole-diff review covered the Safe/Permit2/CoW/migration execution paths, transaction-plan encoding and review summaries, cached manifest/ABI routes and override precedence, SDK 2.0 lock integrity, external migration discovery, governor verification, market visibility, intrinsic APY, env/CSP changes, and dependency/supply-chain surfaces.
Accepted P2 residual risk
- Safe confirmation tracking times out after five minutes and reports status as unknown. The local tracking gate is then released, so a user who retries without checking Safe could create another proposal. The warning is explicit and this is treated as a recovery/retry edge case, not a P0/P1 promotion blocker.
- In the bundled migration edge case where authorization state changes between add-time preview and review-open latching, Copy calldata can still encode the older preview core plan while Confirm executes the freshly latched core plan. Execution remains fail-closed and consumes the latched ceremony; the mismatch is confined to the optional export/review artifact and is accepted here as P2.
- Browser-storage cross-tab exactly-once/replay coordination remains deliberately outside this parity-scoped release.
No additional blocking finding was identified in the current-head challenge pass.
|
Follow-up after the delayed independent lifecycle/security passes completed, rechecked against current exact head
Browser-storage cross-tab replay coordination remains explicitly outside this parity-scoped release. |


Features
Fixes
Batch simulation not loaded(fix: batch simulation dies with 'not loaded' when ToS registration rides in the batch #802) @kasperpawlowskiDocumentation
Dependencies
@eulerxyz/euler-v2-sdkfrom 1.2.5 to 2.0.0 across the Safe and governance work (feat: Safe multisig badge on governance addresses + oracle router governor row #796, feat: disable Permit2 and batch approval+EVC into one tx for Safe wallets #797) @kasperpawlowski