Skip to content

chore: promote development to master - #806

Merged
Seranged merged 153 commits into
masterfrom
development
Aug 20, 2026
Merged

Seranged merged 153 commits into
masterfrom
development

Conversation

@Seranged

@Seranged Seranged commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Features

Fixes

Documentation

Dependencies

cursoragent and others added 30 commits July 17, 2026 07:33
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.
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
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / development August 18, 2026 14:06 Inactive
…on-updates-19f9

docs: earn uncovered losses and gasless signature preference
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / development August 18, 2026 14:07 Inactive
# Conflicts:
#	docs/transaction-building.md
…on-updates-0202

docs: Safe wallets, external migration, and batch plugin layers
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / development August 18, 2026 14:13 Inactive

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

Comment thread composables/useEulerTx.ts
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / pr-816 August 19, 2026 09:01 Inactive
kasperpawlowski and others added 3 commits August 19, 2026 11:44
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
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / development August 19, 2026 10:11 Inactive
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / development August 20, 2026 09:16 Inactive
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / development August 20, 2026 09:39 Inactive

@LeonardEulerXYZ LeonardEulerXYZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the final developmentmaster 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/master is now an ancestor, with no master-only composition branch remaining.
  • The master integration changes only package.json / package-lock.json relative to the prior reviewed head and preserves @eulerxyz/euler-v2-sdk@2.0.0 while carrying the master audit overrides.
  • Clean install with the repo-pinned Node v24.14.0 and npm 11.10.0 succeeded; 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/migrate from 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.

@LeonardEulerXYZ

Copy link
Copy Markdown
Contributor

Follow-up after the delayed independent lifecycle/security passes completed, rechecked against current exact head 9c487df5c265b90df5786381733b8b509f31a7d5:

  • No new P0/P1 blocker. Approval stands.
  • The deployment-manifest cache-admission concern raised by one pass does not reproduce on either the prior immutable head or the current head. server/api/internal/euler-chains.get.ts already validates positive chain IDs, required coreAddrs / lensAddrs, and EVM address shape before cache admission; an invalid enabled-chain entry throws so stale last-known-good data remains available. The focused EulerChains route suite passed earlier in the final-head run.
  • One additional transaction-lifecycle residual is worth recording: the private pending-add queue is not generation-owned by the review ceremony and is not included in canExecuteBatch. With an existing cart, a user could begin a slow second add and open review during that narrow in-flight window; an older bundled preparation can republish after cart invalidation. The stale proposal would omit the late operation (rather than introduce unseen calldata) and successful execution would then clear the current cart. Production add handlers await addBatchEntry() before redirect/reset, so reaching this requires an existing cart plus overlapping user actions and slow asynchronous planning. Under the stated promotion policy, I classify this as an accepted P2 timing edge case, alongside the already-recorded calldata-export and detached-Safe retry residuals.

Browser-storage cross-tab replay coordination remains explicitly outside this parity-scoped release.

@Seranged
Seranged merged commit a9b7f5c into master Aug 20, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants