From 1fd59edacbea8dbfef6429962c03b3e5ce0f194c Mon Sep 17 00:00:00 2001
From: Seranged <80223622+Seranged@users.noreply.github.com>
Date: Tue, 11 Aug 2026 12:42:47 +0100
Subject: [PATCH 1/4] fix: decode flow cap activity details
---
.../entities/activity/ActivityEventRow.vue | 7 +-
tests/utils/activity-display.test.ts | 46 ++++++++
utils/activity-display.ts | 104 +++++++++++++++++-
3 files changed, 153 insertions(+), 4 deletions(-)
diff --git a/components/entities/activity/ActivityEventRow.vue b/components/entities/activity/ActivityEventRow.vue
index b4c348ae4..6ce363ed6 100644
--- a/components/entities/activity/ActivityEventRow.vue
+++ b/components/entities/activity/ActivityEventRow.vue
@@ -208,6 +208,7 @@ const changes = computed(() => {
valueTitle: entry.value,
summary: entry.summary,
addresses: entry.addresses,
+ addressDetails: entry.addressDetails,
avatarAssets: entry.field === 'asset_pair'
|| (event.type === 'set_resolved_vault' && entry.field === 'asset')
? entry.addresses?.map(address => ({
@@ -468,7 +469,7 @@ const vaultDisplay = computed(() => {
{
:vault-type="address.vaultType"
compact-vault
/>
+ {{ detail.addressDetails[addressIndex] }}
diff --git a/tests/utils/activity-display.test.ts b/tests/utils/activity-display.test.ts
index 7a8e19e79..6050b83dc 100644
--- a/tests/utils/activity-display.test.ts
+++ b/tests/utils/activity-display.test.ts
@@ -21,6 +21,7 @@ import {
getActivityChangeEntries,
getActivityEventIcon,
getActivityLiquidationDisplayDetails,
+ getActivityResolvableVaultAddresses,
getPortfolioActivityPositionParticipant,
getActivityTransferDirection,
getActivityTransactionGroupLabel,
@@ -714,6 +715,51 @@ describe('activity display helpers', () => {
{ field: 'new_supply_cap', label: 'New supply cap', value: '155M USDC' },
])
+ const flowCapsConfig = JSON.stringify([
+ { id: OTHER_VAULT, caps: { maxIn: '10000000000', maxOut: '2500000000' } },
+ { id: SHARES, caps: { maxIn: '0', maxOut: '10000000000' } },
+ ])
+ expect(getActivityChangeEntries({
+ type: 'set_flow_caps',
+ vault: VAULT,
+ vaultType: 'earn',
+ change: { fields: { config: flowCapsConfig } },
+ }, getVaultMetadata)).toEqual([{
+ field: 'config',
+ label: 'Strategies',
+ summary: '2 strategies',
+ addresses: [
+ {
+ address: OTHER_VAULT,
+ label: 'Collateral vault',
+ linkKind: 'vault',
+ vaultType: 'evk',
+ },
+ { address: SHARES, linkKind: 'explorer' },
+ ],
+ addressDetails: [
+ 'Max in 10K USDC · Max out 2.5K USDC',
+ 'Max in 0 USDC · Max out 10K USDC',
+ ],
+ }])
+ expect(getActivityResolvableVaultAddresses({
+ type: 'set_flow_caps',
+ vault: VAULT,
+ change: { fields: { config: flowCapsConfig } },
+ })).toEqual([VAULT, OTHER_VAULT, SHARES])
+
+ const malformedFlowCapsConfig = '[{"id":"not-an-address"}]'
+ expect(getActivityChangeEntries({
+ type: 'set_flow_caps',
+ vault: VAULT,
+ vaultType: 'earn',
+ change: { fields: { config: malformedFlowCapsConfig } },
+ }, getVaultMetadata)).toEqual([{
+ field: 'config',
+ label: 'Config',
+ value: malformedFlowCapsConfig,
+ }])
+
expect(getActivityChangeEntries({
type: 'set_oracle_config',
vault: VAULT,
diff --git a/utils/activity-display.ts b/utils/activity-display.ts
index 1df1af2b6..a31ca2053 100644
--- a/utils/activity-display.ts
+++ b/utils/activity-display.ts
@@ -9,7 +9,7 @@ import type {
ActivityVaultType,
LiquidationRecord,
} from '@eulerxyz/euler-v2-sdk'
-import { formatUnits, isAddress, maxUint256, zeroAddress, type Address } from 'viem'
+import { formatUnits, getAddress, isAddress, maxUint256, zeroAddress, type Address } from 'viem'
import { compactNumber, formatCompactUsdValue, formatSmartAmount, shortenAddress } from '~/utils/string-utils'
import { CFG_DONT_SOCIALIZE_DEBT } from '~/entities/constants'
import { decodeHookedOperationsMask, getHookedOperationMetas } from '~/utils/vault-hooks'
@@ -76,6 +76,7 @@ const CATEGORY_LABELS: Record = {
governance: 'Governance',
}
+const UINT128_MAX = 2n ** 128n - 1n
const UINT136_MAX = 2n ** 136n - 1n
const ACCOUNT_ACTIVITY_EVENT_TYPES = [
@@ -832,6 +833,7 @@ export interface ActivityChangeEntry {
value?: string
summary?: string
addresses?: ActivityChangeAddress[]
+ addressDetails?: string[]
}
type ActivityChangeEventSource = Pick
@@ -869,6 +871,56 @@ const parseActivityInteger = (value: ActivityChangeValue): bigint | null => {
}
}
+interface ActivityFlowCapsConfig {
+ id: Address
+ maxIn: string
+ maxOut: string
+}
+
+const isActivityRecord = (value: unknown): value is Record =>
+ typeof value === 'object' && value !== null && !Array.isArray(value)
+
+const isActivityUint128 = (value: unknown): value is string =>
+ typeof value === 'string'
+ && value.length <= UINT128_MAX.toString().length
+ && /^\d+$/.test(value)
+ && BigInt(value) <= UINT128_MAX
+
+/**
+ * The activity API serializes FlowCapsConfig[] into a string because the SDK
+ * change-value contract intentionally accepts only scalars and string arrays.
+ * Keep this parser event-specific and fail closed to the generic raw display.
+ */
+const parseActivityFlowCapsConfig = (
+ value: ActivityChangeValue | undefined,
+): ActivityFlowCapsConfig[] | null => {
+ if (typeof value !== 'string') return null
+
+ try {
+ const parsed: unknown = JSON.parse(value)
+ if (!Array.isArray(parsed)) return null
+
+ const configs: ActivityFlowCapsConfig[] = []
+ for (const item of parsed) {
+ if (
+ !isActivityRecord(item)
+ || typeof item.id !== 'string'
+ || !isAddress(item.id)
+ || !isActivityRecord(item.caps)
+ ) {
+ return null
+ }
+ const { maxIn, maxOut } = item.caps
+ if (!isActivityUint128(maxIn) || !isActivityUint128(maxOut)) return null
+ configs.push({ id: getAddress(item.id), maxIn, maxOut })
+ }
+ return configs
+ }
+ catch {
+ return null
+ }
+}
+
/** Decodes the EVK AmountCap uint16 encoding into underlying token units. */
export const decodeEvkAmountCap = (value: ActivityChangeValue): bigint | null => {
const raw = parseActivityInteger(value)
@@ -1094,14 +1146,51 @@ const resolveOracleAssetPair = (
}
}
+const resolveFlowCapsConfig = (
+ event: ActivityChangeEventSource,
+ getVaultMetadata: ActivityVaultMetadataLookup | undefined,
+ getTokenSymbol: ActivityAddressLabelLookup | undefined,
+): ActivityChangeEntry | null => {
+ if (event.type !== 'set_flow_caps') return null
+ const configs = parseActivityFlowCapsConfig(event.change?.fields.config)
+ if (configs === null) return null
+ if (configs.length === 0) {
+ return { field: 'config', label: 'Strategies', value: 'None' }
+ }
+
+ const addresses = resolveChangeAddresses(
+ event,
+ 'strategy',
+ configs.map(config => config.id),
+ getVaultMetadata,
+ getTokenSymbol,
+ )
+ if (!addresses) return null
+
+ const asset = event.vault ? getVaultMetadata?.(event.vault)?.asset : undefined
+ const formatCap = (value: string) =>
+ formatActivityTokenAmount(value, asset, true) ?? value
+ return {
+ field: 'config',
+ label: configs.length === 1 ? 'Strategy' : 'Strategies',
+ ...(configs.length > 1 ? { summary: `${configs.length} strategies` } : {}),
+ addresses,
+ addressDetails: configs.map(config =>
+ `Max in ${formatCap(config.maxIn)} · Max out ${formatCap(config.maxOut)}`),
+ }
+}
+
export const getActivityChangeEntries = (
event: ActivityChangeEventSource,
getVaultMetadata?: ActivityVaultMetadataLookup,
getTokenSymbol?: ActivityAddressLabelLookup,
): ActivityChangeEntry[] => {
const assetPair = resolveOracleAssetPair(event, getTokenSymbol)
+ const flowCapsConfig = resolveFlowCapsConfig(event, getVaultMetadata, getTokenSymbol)
const fields = orderedActivityChangeFields(event)
- .filter(([field]) => !assetPair || (field !== 'asset0' && field !== 'asset1'))
+ .filter(([field]) =>
+ (!assetPair || (field !== 'asset0' && field !== 'asset1'))
+ && (!flowCapsConfig || field !== 'config'))
const entries = fields.map(([field, value]): ActivityChangeEntry => {
// The zero address reads better as an explicit "None" than as a linked,
@@ -1179,7 +1268,11 @@ export const getActivityChangeEntries = (
value: formatted ?? formatActivityChangeValue(value),
}
})
- return assetPair ? [assetPair, ...entries] : entries
+ return [
+ ...(assetPair ? [assetPair] : []),
+ ...(flowCapsConfig ? [flowCapsConfig] : []),
+ ...entries,
+ ]
}
interface ActivityParticipantSource {
@@ -1235,6 +1328,11 @@ export const getActivityResolvableVaultAddresses = (
if (typeof item === 'string' && isAddress(item)) addresses.add(item as Address)
}
}
+ if (event.type === 'set_flow_caps') {
+ for (const config of parseActivityFlowCapsConfig(event.change?.fields.config) ?? []) {
+ addresses.add(config.id)
+ }
+ }
return [...addresses]
}
From 4e95c0b4be709c22a83d84a31053a74377be29ee Mon Sep 17 00:00:00 2001
From: Seranged <80223622+Seranged@users.noreply.github.com>
Date: Tue, 11 Aug 2026 13:23:38 +0100
Subject: [PATCH 2/4] fix: label flow caps as strategy caps
---
tests/utils/activity-display.test.ts | 1 +
utils/activity-display.ts | 1 +
2 files changed, 2 insertions(+)
diff --git a/tests/utils/activity-display.test.ts b/tests/utils/activity-display.test.ts
index 6050b83dc..50ef31550 100644
--- a/tests/utils/activity-display.test.ts
+++ b/tests/utils/activity-display.test.ts
@@ -202,6 +202,7 @@ describe('activity display helpers', () => {
expect(formatActivityEventLabel({ type: 'set_interest_rate_model' })).toBe('Interest rate model updated')
expect(formatActivityEventLabel({ type: 'set_liquidation_cool_off_time' })).toBe('Liquidation cool-off time updated')
expect(formatActivityEventLabel({ type: 'set_is_allocator' })).toBe('Allocator status updated')
+ expect(formatActivityEventLabel({ label: 'Flow caps updated', type: 'set_flow_caps' })).toBe('Strategy caps updated')
expect(formatActivityEventLabel({ type: 'set_oracle_config' })).toBe('Oracle route updated')
expect(formatActivityEventLabel({ type: 'set_fallback_oracle' })).toBe('Fallback oracle updated')
expect(formatActivityEventLabel({ type: 'set_resolved_vault' })).toBe('Resolved vault updated')
diff --git a/utils/activity-display.ts b/utils/activity-display.ts
index a31ca2053..dfcb9b7f8 100644
--- a/utils/activity-display.ts
+++ b/utils/activity-display.ts
@@ -448,6 +448,7 @@ export const getActivityTransferDirection = (
export const formatActivityEventLabel = (
event: ActivityEventLabelSource,
): string => {
+ if (event.type === 'set_flow_caps') return 'Strategy caps updated'
const sourceLabel = event.label?.trim()
if (sourceLabel) return sourceLabel
const normalizedLabel = {
From 8717c995d59571a09861b71caea30a132327c7bb Mon Sep 17 00:00:00 2001
From: Seranged <80223622+Seranged@users.noreply.github.com>
Date: Tue, 11 Aug 2026 13:41:29 +0100
Subject: [PATCH 3/4] fix: use public allocator flow cap terminology
---
tests/utils/activity-display.test.ts | 2 +-
utils/activity-display.ts | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/tests/utils/activity-display.test.ts b/tests/utils/activity-display.test.ts
index 50ef31550..02f2e6f69 100644
--- a/tests/utils/activity-display.test.ts
+++ b/tests/utils/activity-display.test.ts
@@ -202,7 +202,7 @@ describe('activity display helpers', () => {
expect(formatActivityEventLabel({ type: 'set_interest_rate_model' })).toBe('Interest rate model updated')
expect(formatActivityEventLabel({ type: 'set_liquidation_cool_off_time' })).toBe('Liquidation cool-off time updated')
expect(formatActivityEventLabel({ type: 'set_is_allocator' })).toBe('Allocator status updated')
- expect(formatActivityEventLabel({ label: 'Flow caps updated', type: 'set_flow_caps' })).toBe('Strategy caps updated')
+ expect(formatActivityEventLabel({ type: 'set_flow_caps' })).toBe('Flow caps updated')
expect(formatActivityEventLabel({ type: 'set_oracle_config' })).toBe('Oracle route updated')
expect(formatActivityEventLabel({ type: 'set_fallback_oracle' })).toBe('Fallback oracle updated')
expect(formatActivityEventLabel({ type: 'set_resolved_vault' })).toBe('Resolved vault updated')
diff --git a/utils/activity-display.ts b/utils/activity-display.ts
index dfcb9b7f8..a31ca2053 100644
--- a/utils/activity-display.ts
+++ b/utils/activity-display.ts
@@ -448,7 +448,6 @@ export const getActivityTransferDirection = (
export const formatActivityEventLabel = (
event: ActivityEventLabelSource,
): string => {
- if (event.type === 'set_flow_caps') return 'Strategy caps updated'
const sourceLabel = event.label?.trim()
if (sourceLabel) return sourceLabel
const normalizedLabel = {
From c26bb5c623489956ac912d4fba190401c6a5e422 Mon Sep 17 00:00:00 2001
From: Seranged <80223622+Seranged@users.noreply.github.com>
Date: Tue, 11 Aug 2026 13:42:08 +0100
Subject: [PATCH 4/4] fix: clarify public allocator limit activity
---
tests/utils/activity-display.test.ts | 2 +-
utils/activity-display.ts | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/tests/utils/activity-display.test.ts b/tests/utils/activity-display.test.ts
index 02f2e6f69..bef5c755d 100644
--- a/tests/utils/activity-display.test.ts
+++ b/tests/utils/activity-display.test.ts
@@ -202,7 +202,7 @@ describe('activity display helpers', () => {
expect(formatActivityEventLabel({ type: 'set_interest_rate_model' })).toBe('Interest rate model updated')
expect(formatActivityEventLabel({ type: 'set_liquidation_cool_off_time' })).toBe('Liquidation cool-off time updated')
expect(formatActivityEventLabel({ type: 'set_is_allocator' })).toBe('Allocator status updated')
- expect(formatActivityEventLabel({ type: 'set_flow_caps' })).toBe('Flow caps updated')
+ expect(formatActivityEventLabel({ label: 'Flow caps updated', type: 'set_flow_caps' })).toBe('Public allocator limits updated')
expect(formatActivityEventLabel({ type: 'set_oracle_config' })).toBe('Oracle route updated')
expect(formatActivityEventLabel({ type: 'set_fallback_oracle' })).toBe('Fallback oracle updated')
expect(formatActivityEventLabel({ type: 'set_resolved_vault' })).toBe('Resolved vault updated')
diff --git a/utils/activity-display.ts b/utils/activity-display.ts
index a31ca2053..e5e27fc3d 100644
--- a/utils/activity-display.ts
+++ b/utils/activity-display.ts
@@ -448,6 +448,7 @@ export const getActivityTransferDirection = (
export const formatActivityEventLabel = (
event: ActivityEventLabelSource,
): string => {
+ if (event.type === 'set_flow_caps') return 'Public allocator limits updated'
const sourceLabel = event.label?.trim()
if (sourceLabel) return sourceLabel
const normalizedLabel = {