Skip to content

feat: Safe multisig badge on governance addresses + oracle router governor row - #796

Merged
kanvgupta merged 7 commits into
developmentfrom
feature/lite-94-discuss-safe-compatibility
Aug 12, 2026
Merged

kanvgupta merged 7 commits into
developmentfrom
feature/lite-94-discuss-safe-compatibility

Conversation

@kasperpawlowski

@kasperpawlowski kasperpawlowski commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Detect whether governance-related addresses are Safe multisigs and show a small Safe icon with the signer configuration, e.g. (3/7) = threshold / owners (LITE-94)
  • Add an Oracle governor row to the vault addresses section, read on-chain from the EulerRouter (governor()); renounced governance renders as "None"
  • Extract the copy-pasted address row markup from the four overview blocks into a shared VaultOverviewAddressValue component

Implementation

  • Detection is fully on-chain with no Safe API dependency, so it works on every enabled chain: masterCopy() (special-cased in every Safe proxy since v1.1.1) is validated against the canonical singleton list from safe-deployments (v1.1.1–v1.5.0 incl. eip155 variants), then getThreshold()/getOwners() provide the badge data; Safe threshold/owner invariants are enforced so lookalikes are rejected
  • useSafeAddressInfo / useOracleRouterGovernor composables cache per chainId:address (5-min TTL, in-flight dedup, transient RPC failures not cached) via a new reusable createOnchainLookupCache util; the three probe reads fire concurrently and coalesce into one RPC request via transport batching
  • Probes run client-side only and only for governance rows (check-safe prop): risk manager, fee receivers, oracle governor, Earn owner/curator/guardian, Securitize governor
  • Badge tooltip shows the Safe version, an explainer, and the shortened owner list
  • The same detection primitive was added to the SDK as safeAccountService (feat: add safeAccountService for Safe smart-account detection euler-sdks#94); this app-side probe is intentionally self-contained and can swap to sdk.safeAccountService.fetchSafeAccount once a release ships with it

Test plan

  • Unit tests: singleton recognition + Safe invariant validation (tests/utils/safe-account.test.ts)
  • Unit tests: cache TTL / dedup / failure semantics (tests/utils/onchain-lookup-cache.test.ts)
  • Composable tests: detection, non-Safe/EOA nulls, sentinel-address skip, cross-instance caching (tests/composables/useSafeAddressInfo.test.ts, tests/composables/useOracleRouterGovernor.test.ts)
  • Full suite: 1635 passing incl. golden/parity; nuxt typecheck and ESLint clean
  • Visual check on a vault with a Safe governor (badge placement, tooltip, owners list)
  • Check a vault with renounced router governance shows "None"

Summary by CodeRabbit

  • New Features

    • Added Safe multisig badges showing Safe version, signature threshold, and owner count.
    • Added explorer links, shortened labels, special address labels, and copy-to-clipboard actions for vault addresses.
    • Added Oracle governor visibility for applicable borrowable vaults.
    • Added Safe address detection for relevant vault roles.
    • Standardized address presentation across vault overview sections.
  • Bug Fixes

    • Improved handling of unavailable, invalid, or unsupported on-chain address data.

@railway-app

railway-app Bot commented Aug 10, 2026

Copy link
Copy Markdown

🚅 Deployed to the euler-lite-pr-796 environment in euler-lite(dev,PR previews)

Service Status Web Updated (UTC)
dev-build ✅ Success (View Logs) Web Aug 11, 2026 at 3:26 pm

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added Safe account detection, cached on-chain lookups, oracle governor discovery, and shared vault address rendering. Vault overviews now display Safe badges and oracle governor addresses when available.

Changes

Safe and governor metadata

Layer / File(s) Summary
Metadata contracts and validation
abis/oracle.ts, abis/safe.ts, utils/safe-account.ts, tests/utils/safe-account.test.ts
Added ABI definitions and Safe singleton/version validation utilities with unit coverage.
Cached on-chain lookup composables
utils/onchain-lookup-cache.ts, composables/useSafeAddressInfo.ts, composables/useOracleRouterGovernor.ts, tests/utils/*, tests/composables/*
Added five-minute, chain/address-keyed lookup caching, concurrent request deduplication, Safe probing, oracle governor reads, and related tests.
Vault address rendering and integration
components/entities/safe/SafeAccountBadge.vue, components/entities/vault/overview/VaultOverviewAddressValue.vue, components/entities/vault/overview/*
Centralized address links and copy actions. Added optional Safe badges, Safe checks, and oracle governor display across vault overview components.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: vsbdev

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: Safe multisig badges for governance addresses and an Oracle router governor row.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/lite-94-discuss-safe-compatibility

Comment @coderabbitai help to get the list of available commands.

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

Review summary

Display-only PR for Safe multisig badges on governance addresses and a live EulerRouter governor() row. I traced the probe → cache → overview address rows path (including how createOnchainLookupCache treats successes vs throws) and cross-checked oracle/governor helpers already in the repo. One warning stands out; nothing here moves funds or bypasses guards.

Reviewed: useSafeAddressInfo, useOracleRouterGovernor, safe-account / onchain-lookup-cache, overview address components, ABIs · Traced beyond the diff: getPublicClient (retryCount 0 + batching), getSpecialAddressLabel, getEulerRouterGovernor / EVault oracle shape, UiHoverPreviewTooltip · Protocol skills consulted: euler-vaults, euler-irm-oracles

Findings

⚠️ Warnings

composables/useSafeAddressInfo.ts / composables/useOracleRouterGovernor.ts — RPC failures cached as definitive negatives for 5 minutes

createOnchainLookupCache correctly skips caching when a probe throws, but both probes swallow failures and return null instead:

  • Safe: Promise.allSettled + resolveSafeAccountInfo(...)null when any/all reads reject (transport error looks the same as an EOA).
  • Governor: bare catch { return null } treats RPC/proxy failures like “not Governable”.

Those nulls are then cached under ${chainId}:${address} for CACHE_TTL_MS (5 min). The shared public client also sets retryCount: 0, so a single 502/timeout from /api/internal/rpc/{chainId} sticks.

Trigger: Open a vault Addresses section while the internal RPC proxy is briefly unhealthy (this environment already sees intermittent upstream 502s). Refreshing within the TTL still serves the cached null and does not re-probe.

Consequence: Real Safe risk managers / fee receivers / Earn roles lose the (threshold/owners) badge for up to five minutes, and EulerRouter governor rows disappear even when governance is live — including across every vault that shares the same router or Safe address, because the cache is module-scoped.

Fix direction: Rethrow transport / HTTP errors from the probes (so the cache’s existing failure path applies), and only return null for definitive on-chain negatives (empty calldata / execution revert / unknown singleton). Optionally treat “all three Safe reads rejected” as a throw rather than null.

Not flagged

  • No transaction / geo / screening / approval surface — badges and address rows only.
  • Renounced router governance (0x0 → “None” via getSpecialAddressLabel) behaves as described; Safe probes correctly skip sentinels.
  • Reusing getEulerRouterGovernor is not practical here: EVault.oracle is only { oracle, name }; lens oracleDetailedInfo is not on the vault object the overview receives, so the on-chain governor() read is justified.
  • Hardcoded Safe singleton list matches canonical 1.1.1–1.5.0 deployments; false-positive risk is low given singleton + threshold/owner invariant checks.
Open in Web View Automation 

Sent by Cursor Automation: Lite PR Reviewer

Comment thread composables/useSafeAddressInfo.ts Outdated
Comment thread composables/useOracleRouterGovernor.ts Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@composables/useOracleRouterGovernor.ts`:
- Around line 24-26: Update the readContract error handling in
useOracleRouterGovernor so it returns null only when the error chain contains
viem’s ContractFunctionZeroDataError; rethrow transport and request failures so
createOnchainLookupCache does not cache them. Add coverage for zero-data
returning null and for a subsequent successful lookup after a transport failure.

In `@utils/safe-account.ts`:
- Around line 57-59: Update resolveSafeAccountInfo to validate owners before
returning metadata: reject duplicate addresses, the zero address, and the Safe
owner sentinel address, while preserving the existing threshold checks. Add test
cases covering each invalid owner-list condition.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: euler-xyz/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a0a923cf-f1aa-4830-a8d2-979f1e965095

📥 Commits

Reviewing files that changed from the base of the PR and between 60cc27b and d99c2ed.

⛔ Files ignored due to path filters (1)
  • assets/sprite/svg/safe.svg is excluded by !**/*.svg
📒 Files selected for processing (16)
  • abis/oracle.ts
  • abis/safe.ts
  • components/entities/safe/SafeAccountBadge.vue
  • components/entities/vault/overview/SecuritizeVaultOverview.vue
  • components/entities/vault/overview/VaultOverviewAddressValue.vue
  • components/entities/vault/overview/VaultOverviewBlockAddresses.vue
  • components/entities/vault/overview/earn/VaultOverviewEarnBlockAddresses.vue
  • components/entities/vault/overview/earn/VaultOverviewEarnBlockManagement.vue
  • composables/useOracleRouterGovernor.ts
  • composables/useSafeAddressInfo.ts
  • tests/composables/useOracleRouterGovernor.test.ts
  • tests/composables/useSafeAddressInfo.test.ts
  • tests/utils/onchain-lookup-cache.test.ts
  • tests/utils/safe-account.test.ts
  • utils/onchain-lookup-cache.ts
  • utils/safe-account.ts

Comment thread composables/useOracleRouterGovernor.ts Outdated
Comment thread utils/safe-account.ts
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / euler-lite-pr-796 August 10, 2026 12:27 Destroyed
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.
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / euler-lite-pr-796 August 10, 2026 12:37 Destroyed
@kasperpawlowski

Copy link
Copy Markdown
Contributor Author

Review findings addressed in 7bdd0f2:

  • Transport failures cached as negatives (both probes): transport-level errors (classified via utils/viem-errors) now rethrow out of the probe, so createOnchainLookupCache skips caching and the next mount retries; contract-level failures (empty data / revert) remain definitive negatives. Added retry tests for both composables.
  • Owner invariants: resolveSafeAccountInfo rejects zero, sentinel, and duplicate owners, mirroring Safe's OwnerManager; badge detection is also documented as a display heuristic.

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

The visual change is clean in the Railway preview, and the earlier transport/owner-invariant findings are fixed on this head. One merge blocker remains: npm run typecheck fails at tests/utils/safe-account.test.ts:58 because toUpperCase().replace(...) widens the second owner from Address to string (TS2322). The focused feature suite passes (26/26), lint has no errors, and git diff --check is clean.

Deployment note, separate from the source blocker: GitHub marks Railway deployment 5832107876 successful for this exact Lite SHA, but the currently served SDK bundle still contains the pre-fix safeAccountService probe (immediate null on rejected masterCopy, without the transport-failure and owner-invariant handling now present at SDK PR #94 head 5ebab4bc). Lite itself does not reference safeAccountService, so the screenshots validate the local Lite implementation and visual delta, not integration with the latest SDK head.

Comment thread tests/utils/safe-account.test.ts Outdated

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

Two non-CI points from the earlier-head review remain on this exact head: one control-boundary wording correction and one accessibility improvement.

Comment thread components/entities/safe/SafeAccountBadge.vue Outdated
Comment thread components/entities/safe/SafeAccountBadge.vue
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / euler-lite-pr-796 August 10, 2026 13:14 Destroyed
@kasperpawlowski

Copy link
Copy Markdown
Contributor Author

Re: review 4897002255 — both findings are now fixed:

The SDK-bundle caveat is expected: Lite deliberately uses its local probe until an SDK release ships safeAccountService, at which point useSafeAddressInfo swaps to sdk.safeAccountService.fetchSafeAccount in one place.

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

The CI/typecheck blocker is resolved on this exact head: local npm run typecheck, the focused owner-list test, and all three GitHub CI jobs pass. I am not re-approving yet because the separate control-boundary wording finding remains unchanged at SafeAccountBadge.vue:9: Safe modules can execute through execTransactionFromModule without owner confirmations, so the tooltip must describe the configured X-of-Y owner threshold rather than claim every transaction requires those signatures. The accessibility aria-label point remains non-blocking.

@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / euler-lite-pr-796 August 10, 2026 13:59 Destroyed
@kasperpawlowski

Copy link
Copy Markdown
Contributor Author

Re: review 4897209489 — both points addressed in b5d8cd3:

  • Control-boundary wording: the tooltip now describes the configuration only — "…configured with a 3-of-7 owner threshold" — with a code comment noting that enabled modules can execute via execTransactionFromModule without owner confirmations and the probe does not inspect them.
  • Accessibility (non-blocking, taken anyway): the badge now passes a dynamic aria-label ("Safe multisig: 3 of 7 owner threshold") so the accessible name carries the same status as the visible text.

@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 exact head b5d8cd38306b99479f0cb0673a91f1af6f715614.

The previous control-boundary blocker is resolved. The tooltip now describes only the configured X-of-Y owner threshold, explicitly avoids claiming that every Safe execution requires owner confirmations, and the badge exposes the same status through a dynamic accessible name.

Validation:

  • npm run typecheck — passed
  • npx vitest run tests/utils/safe-account.test.ts — 7/7 passed
  • npm run build — passed
  • GitHub lint, test, and typecheck checks — green on this head
  • git diff --check — clean

No remaining blockers found.

Mirrors OwnerManager GS203 (owner != address(this)) — a lookalike listing itself as owner no longer passes.
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / euler-lite-pr-796 August 10, 2026 14:40 Destroyed

@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 exact head 6adf4f999d1059122ab4a8d7164892ffb8f814b9.

The self-ownership parity fix is correct: the local Safe probe now rejects a recognized-singleton lookalike that lists its own address as an owner, matching Safe OwnerManager's GS203 invariant. The regression test covers it alongside zero, sentinel, duplicate-owner, threshold, and malformed-return cases.

Validation:

  • npx vitest run tests/utils/safe-account.test.ts: 8/8 passed
  • npm run typecheck: passed
  • npm run build: passed
  • git diff --check origin/development...HEAD: clean
  • GitHub lint/typecheck/test checks: green

Non-blocking: the internal SafeAccountInfo.threshold JSDoc still says "required to execute a transaction"; it would be more exact to mirror the badge/SDK wording and scope this to owner-authorized transactions, since modules can execute without owner signatures. This does not affect the rendered badge copy.

No blockers.

kanvgupta
kanvgupta previously approved these changes Aug 11, 2026
Use the published package and drop unused deployment fields removed from the SDK surface.
@railway-app
railway-app Bot temporarily deployed to euler-lite(dev,PR previews) / euler-lite-pr-796 August 11, 2026 15:08 Destroyed

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

Re-reviewed the delta since the prior approved head and verified current head 0b1bd5015b7119bde6c1b235d030beb4eaaab978.

Local verification:

  • clean npm ci
  • npm run test:run: 1,639 passed, 1 skipped
  • npm run typecheck
  • npm run build
  • built-server smoke: GET / returned 200 with the Euler Lite page

No blocking findings.

@kanvgupta
kanvgupta merged commit ca47a67 into development Aug 12, 2026
5 checks passed
@kanvgupta
kanvgupta deleted the feature/lite-94-discuss-safe-compatibility branch August 12, 2026 11:39
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.

4 participants