Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion components/BatchReviewModal.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { encodeFunctionData, getAddress } from 'viem'
import { flattenBatchEntries, getSubAccountId, type TransactionPlan } from '@eulerxyz/euler-v2-sdk'
import { getEulerSdk } from '~/composables/useEulerSdk'
Expand Down Expand Up @@ -213,6 +213,19 @@ const openId = ref<string | null>(null)
const toggle = (id: string) => {
openId.value = openId.value === id ? null : id
}
const nowMs = ref(Date.now())
const staleQuoteThresholdMs = 3 * 60 * 1000
let nowTimer: ReturnType<typeof setInterval> | undefined

const getQuoteFetchedAt = (entry: { review?: Record<string, unknown> }): number | null => {
const value = entry.review?.quoteFetchedAt
return typeof value === 'number' ? value : null
}
const isEntryQuoteStale = (entry: { review?: Record<string, unknown> }) => {
const fetchedAt = getQuoteFetchedAt(entry)
return typeof fetchedAt === 'number' && nowMs.value - fetchedAt > staleQuoteThresholdMs
}
const hasStaleQuoteEntries = computed(() => entries.value.some(isEntryQuoteStale))

// Approvals the user will be asked to sign, decoded from the prepared plan.
interface ResolvedApproval { type: string, token: string }
Expand All @@ -226,6 +239,9 @@ const hasPermit2Approval = computed(() =>
)

onMounted(async () => {
nowTimer = setInterval(() => {
nowMs.value = Date.now()
}, 1000)
void fetchTenderlyEnabled()
isPreparing.value = true
prepareError.value = ''
Expand All @@ -252,6 +268,12 @@ onMounted(async () => {
}
})

onUnmounted(() => {
if (nowTimer) {
clearInterval(nowTimer)
}
})

// Copy the exact batch calldata (one entry per on-chain tx: approvals + the EVC
// batch), matching the per-operation review modals. This requires the prepared
// plan so approval txs are included.
Expand Down Expand Up @@ -361,6 +383,14 @@ const handleClose = () => {
description="You are granting the Permit2 contract an unlimited token allowance. Permit2 is a Uniswap contract that lets you approve once, then sign per-action permissions without new onchain approvals."
/>

<UiAlert
v-if="hasStaleQuoteEntries"
variant="warning"
size="compact"
title="Stale swap quote"
description="This batch includes a swap quote that is more than 3 minutes old. Remove and re-add affected operations before executing to refresh quotes."
/>

<!-- Operations -->
<div>
<p class="text-p3 text-content-tertiary uppercase tracking-[0.04em] mb-8">
Expand Down Expand Up @@ -410,6 +440,14 @@ const handleClose = () => {
:market="marketByEntryId[entry.id]"
class="mt-6 px-12"
/>
<UiAlert
v-if="isEntryQuoteStale(entry)"
class="mt-6"
variant="warning"
size="compact"
title="Stale swap quote"
description="This operation uses a swap quote that is more than 3 minutes old."
/>
<!-- Same decoded operation steps the per-op review modal shows
(the builder row's (i) icon). -->
<div
Expand Down
6 changes: 3 additions & 3 deletions components/entities/asset/AssetInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ watch(() => props.collateralOptions, (options) => {
const firstEnabled = options.findIndex(o => !o.disabled)
if (firstEnabled >= 0) {
selectedIdx.value = firstEnabled
emits('change-collateral', firstEnabled)
emits('change-collateral', firstEnabled, options[firstEnabled])
}
}
})
Expand Down Expand Up @@ -184,9 +184,9 @@ const openChooseCollateralModal = () => {
selected: selectedIdx.value,
title: props.collateralModalTitle,
apyLabel: props.collateralModalApyLabel,
onSave: (selectedIndex: number) => {
onSave: (selectedIndex: number, selectedOption: CollateralOption) => {
selectedIdx.value = selectedIndex
emits('change-collateral', selectedIndex)
emits('change-collateral', selectedIndex, selectedOption)
modal.close()
},
},
Expand Down
11 changes: 8 additions & 3 deletions components/entities/operation/OperationReviewModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ interface REULUnlockInfo {
daysUntilMaturity: number
}

const { type, asset, assetIconUrl, reulUnlockInfo, amount, onConfirm, plan, prepared, swapToAsset, swapToAmount, swapMode, swapEstimatedSide, supplyingAssetForBorrow, supplyingAmount, transferAmounts, submittingLabel, quoteFetchedAt, hideExecute, subAccount, marketLabel } = defineProps<{
type?: 'supply' | 'withdraw' | 'borrow' | 'repay' | 'swap' | 'transfer' | 'reward' | 'brevis-reward' | 'fuul-reward' | 'reul-unlock' | 'disableCollateral' | 'swap-supply' | 'swap-withdraw' | 'swap-borrow'
const { type, asset, assetIconUrl, reulUnlockInfo, amount, onConfirm, plan, prepared, swapFromAsset, swapFromAmount, swapToAsset, swapToAmount, swapMode, swapEstimatedSide, supplyingAssetForBorrow, supplyingAmount, transferAmounts, vaultAmounts, submittingLabel, quoteFetchedAt, hideExecute, subAccount, marketLabel } = defineProps<{
type?: 'supply' | 'withdraw' | 'borrow' | 'repay' | 'swap' | 'transfer' | 'refinance' | 'reward' | 'brevis-reward' | 'fuul-reward' | 'reul-unlock' | 'disableCollateral' | 'swap-supply' | 'swap-withdraw' | 'swap-borrow'
asset: VaultAsset
assetIconUrl?: string
amount: number | string
Expand All @@ -35,6 +35,8 @@ const { type, asset, assetIconUrl, reulUnlockInfo, amount, onConfirm, plan, prep
prepared?: TransactionPlanPrepared
supplyingAssetForBorrow?: VaultAsset
supplyingAmount?: number | string
swapFromAsset?: VaultAsset
swapFromAmount?: number | string
swapToAsset?: VaultAsset
swapToAmount?: number | string
swapMode?: SwapperMode
Expand All @@ -44,6 +46,7 @@ const { type, asset, assetIconUrl, reulUnlockInfo, amount, onConfirm, plan, prep
subAccount?: string
hasBorrows?: boolean
transferAmounts?: Record<string, string>
vaultAmounts?: Record<string, string>
submittingLabel?: string
/** Milliseconds since epoch when the active swap quote was fetched */
quoteFetchedAt?: number | null
Expand Down Expand Up @@ -200,7 +203,7 @@ const displaySteps = computed((): DisplayStep[] => {
const ctx: StepDecodingContext = {
type, asset, assetIconUrl, amount,
supplyingAssetForBorrow, supplyingAmount,
swapToAsset, swapToAmount, swapMode, swapEstimatedSide, transferAmounts,
swapFromAsset, swapFromAmount, swapToAsset, swapToAmount, swapMode, swapEstimatedSide, transferAmounts, vaultAmounts,
}
return buildTransactionPlanDisplaySteps(currentPlan, ctx, getVault, getAssetLogoUrl)
})
Expand Down Expand Up @@ -293,6 +296,8 @@ const btnLabel = computed(() => {
return 'Swap'
case 'transfer':
return 'Transfer'
case 'refinance':
return 'Refinance'
case 'reul-unlock':
return 'Unlock'
case 'reward':
Expand Down
Loading