From d99c2ed3f84fbda4b5f41f378e5bdc43ec1ff1fb Mon Sep 17 00:00:00 2001 From: kasperpawlowski Date: Mon, 10 Aug 2026 14:08:51 +0200 Subject: [PATCH 1/7] feat: show Safe multisig badge on governance addresses and oracle router governor --- abis/oracle.ts | 11 ++ abis/safe.ts | 33 +++++ assets/sprite/svg/safe.svg | 1 + components/entities/safe/SafeAccountBadge.vue | 38 +++++ .../overview/SecuritizeVaultOverview.vue | 73 +-------- .../overview/VaultOverviewAddressValue.vue | 50 +++++++ .../overview/VaultOverviewBlockAddresses.vue | 60 ++++---- .../earn/VaultOverviewEarnBlockAddresses.vue | 35 +---- .../earn/VaultOverviewEarnBlockManagement.vue | 34 +---- composables/useOracleRouterGovernor.ts | 73 +++++++++ composables/useSafeAddressInfo.ts | 100 +++++++++++++ .../useOracleRouterGovernor.test.ts | 80 ++++++++++ tests/composables/useSafeAddressInfo.test.ts | 139 ++++++++++++++++++ tests/utils/onchain-lookup-cache.test.ts | 79 ++++++++++ tests/utils/safe-account.test.ts | 52 +++++++ utils/onchain-lookup-cache.ts | 58 ++++++++ utils/safe-account.ts | 66 +++++++++ 17 files changed, 823 insertions(+), 159 deletions(-) create mode 100644 abis/safe.ts create mode 100644 assets/sprite/svg/safe.svg create mode 100644 components/entities/safe/SafeAccountBadge.vue create mode 100644 components/entities/vault/overview/VaultOverviewAddressValue.vue create mode 100644 composables/useOracleRouterGovernor.ts create mode 100644 composables/useSafeAddressInfo.ts create mode 100644 tests/composables/useOracleRouterGovernor.test.ts create mode 100644 tests/composables/useSafeAddressInfo.test.ts create mode 100644 tests/utils/onchain-lookup-cache.test.ts create mode 100644 tests/utils/safe-account.test.ts create mode 100644 utils/onchain-lookup-cache.ts create mode 100644 utils/safe-account.ts diff --git a/abis/oracle.ts b/abis/oracle.ts index 264517081..b240d30f9 100644 --- a/abis/oracle.ts +++ b/abis/oracle.ts @@ -1,3 +1,14 @@ +/** `governor()` getter from euler-price-oracle's Governable (EulerRouter). */ +export const governableGovernorAbi = [ + { + type: 'function', + name: 'governor', + inputs: [], + outputs: [{ name: 'governor', type: 'address' }], + stateMutability: 'view', + }, +] as const + export const priceOracleAbi = [ { type: 'function', diff --git a/abis/safe.ts b/abis/safe.ts new file mode 100644 index 000000000..7aaec779c --- /dev/null +++ b/abis/safe.ts @@ -0,0 +1,33 @@ +/** + * Minimal fragments for probing Safe (ex Gnosis Safe) smart accounts. + * + * `masterCopy()` is not a regular function on the Safe singleton — Safe proxy + * contracts (v1.1.1+) special-case the `0xa619486e` selector in their fallback + * and return the singleton address stored at slot 0 without delegating. An + * `eth_call` against any Safe proxy therefore answers it, while EOAs return + * empty data and non-Safe contracts revert or return garbage that fails + * decoding. + */ +export const safeAccountAbi = [ + { + type: 'function', + name: 'masterCopy', + inputs: [], + outputs: [{ name: 'masterCopy', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getThreshold', + inputs: [], + outputs: [{ name: 'threshold', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getOwners', + inputs: [], + outputs: [{ name: 'owners', type: 'address[]' }], + stateMutability: 'view', + }, +] as const diff --git a/assets/sprite/svg/safe.svg b/assets/sprite/svg/safe.svg new file mode 100644 index 000000000..c0dd53f94 --- /dev/null +++ b/assets/sprite/svg/safe.svg @@ -0,0 +1 @@ + diff --git a/components/entities/safe/SafeAccountBadge.vue b/components/entities/safe/SafeAccountBadge.vue new file mode 100644 index 000000000..b1286b300 --- /dev/null +++ b/components/entities/safe/SafeAccountBadge.vue @@ -0,0 +1,38 @@ + + + diff --git a/components/entities/vault/overview/SecuritizeVaultOverview.vue b/components/entities/vault/overview/SecuritizeVaultOverview.vue index 5febee351..8b4df7d51 100644 --- a/components/entities/vault/overview/SecuritizeVaultOverview.vue +++ b/components/entities/vault/overview/SecuritizeVaultOverview.vue @@ -5,10 +5,8 @@ import { getProductByVault, getProductKeyByVault, isVaultGovernanceLimited } fro import { getEulerLabelEntityLogo } from '~/entities/euler/labels' import { isVaultBlockedByCountry } from '~/composables/useGeoBlock' import { autoLink } from '~/utils/autoLink' -import { getExplorerLink } from '~/utils/block-explorer' -import { getSpecialAddressLabel } from '~/utils/special-addresses' import { formatAssetValue } from '~/utils/sdk-prices' -import { formatNumber, compactNumber, formatUsdValue, formatCompactUsdValue, shortenAddress } from '~/utils/string-utils' +import { formatNumber, compactNumber, formatUsdValue, formatCompactUsdValue } from '~/utils/string-utils' import { nanoToValue } from '~/utils/crypto-utils' import { formatMarketAvailability } from '~/utils/vault-display' import { VaultApyModal } from '#components' @@ -23,7 +21,6 @@ const emit = defineEmits<{ const route = useRoute() const { enableEntityBranding: enableEntityBrandingDisplay, enableVaultType: enableVaultTypeDisplay } = useDeployConfig() -const { chainId } = useEulerAddresses() const { borrowList: _borrowList, isVaultGovernorVerified } = useVaults() const { settings } = useUserSettings() const enableIntrinsicApy = computed(() => settings.value.enableIntrinsicApy) @@ -45,14 +42,6 @@ const isDeprecated = computed(() => { const deprecationReason = computed(() => isDeprecated.value ? product.deprecationReason || '' : '') const isRestricted = computed(() => isVaultBlockedByCountry(vault.address)) -const { copyToClipboard } = useClipboardCopy() - -const onCopyClick = (address: string) => { - copyToClipboard(address).catch(() => {}) -} - -const getExplorerAddressLink = (address: string) => getExplorerLink(address, chainId.value, true) - // Count markets where this can be borrowed (securitize vaults cannot be borrow destinations) const borrowCount = computed(() => 0) @@ -330,71 +319,23 @@ const supplyCapPercentageDisplay = computed(() => { :label="`${vault.asset.symbol} token`" orientation="horizontal" > -
- - {{ getSpecialAddressLabel(vault.asset.address) || shortenAddress(vault.asset.address) }} - - -
+ -
- - {{ getSpecialAddressLabel(vault.address) || shortenAddress(vault.address) }} - - -
+
-
- - {{ getSpecialAddressLabel(vault.governor) || shortenAddress(vault.governor) }} - - -
+
diff --git a/components/entities/vault/overview/VaultOverviewAddressValue.vue b/components/entities/vault/overview/VaultOverviewAddressValue.vue new file mode 100644 index 000000000..e22faee47 --- /dev/null +++ b/components/entities/vault/overview/VaultOverviewAddressValue.vue @@ -0,0 +1,50 @@ + + + diff --git a/components/entities/vault/overview/VaultOverviewBlockAddresses.vue b/components/entities/vault/overview/VaultOverviewBlockAddresses.vue index db98148f5..e014ca6db 100644 --- a/components/entities/vault/overview/VaultOverviewBlockAddresses.vue +++ b/components/entities/vault/overview/VaultOverviewBlockAddresses.vue @@ -1,15 +1,10 @@ -
- - {{ getSpecialAddressLabel(infoItem.address) || shortenAddress(infoItem.address) }} - - -
+ diff --git a/components/entities/vault/overview/earn/VaultOverviewEarnBlockAddresses.vue b/components/entities/vault/overview/earn/VaultOverviewEarnBlockAddresses.vue index be700b9f7..af0d531de 100644 --- a/components/entities/vault/overview/earn/VaultOverviewEarnBlockAddresses.vue +++ b/components/entities/vault/overview/earn/VaultOverviewEarnBlockAddresses.vue @@ -1,11 +1,7 @@ diff --git a/components/entities/vault/overview/earn/VaultOverviewEarnBlockManagement.vue b/components/entities/vault/overview/earn/VaultOverviewEarnBlockManagement.vue index 07bc1a585..2e58b4cad 100644 --- a/components/entities/vault/overview/earn/VaultOverviewEarnBlockManagement.vue +++ b/components/entities/vault/overview/earn/VaultOverviewEarnBlockManagement.vue @@ -1,12 +1,8 @@