unstaking

This commit is contained in:
mart1n
2025-01-21 12:56:04 +01:00
committed by r4bbit
parent 4c1decb487
commit 31a8abe078
5 changed files with 529 additions and 28 deletions

View File

@@ -0,0 +1,26 @@
<script lang="ts">
import type { Address } from 'viem';
import { vaultAccounts } from '$lib/viem';
export let vault: Address;
export let vaultId: number;
export let isLocked: boolean;
export let onUnstake: () => void;
export let fullWidth = false;
$: isEmpty = !$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n;
</script>
<button
on:click={onUnstake}
class="rounded-lg bg-white px-2 py-1.5 text-sm font-semibold text-blue-600 shadow-sm ring-1 ring-inset ring-blue-200 hover:bg-blue-50 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white {fullWidth ? 'w-full' : ''}"
disabled={isLocked || isEmpty}
>
{#if isLocked}
Locked
{:else if isEmpty}
Empty
{:else}
Unstake
{/if}
</button>

View File

@@ -0,0 +1,321 @@
<script lang="ts">
import { fade, fly } from 'svelte/transition';
import type { Address } from 'viem';
import { SNT_TOKEN, vaultAccounts, walletAddress, walletClient, publicClient, refreshBalances } from '$lib/viem';
import { formatUnits, parseUnits, type Hash } from 'viem';
export let isOpen = false;
export let onClose: () => void;
export let vaultAddress: Address | undefined;
export let vaultId: number;
let amount = '';
let confirmUnderstand = false;
let showError = false;
let isUnstaking = false;
let unstakeHash: Hash | undefined;
let isCompleted = false;
let unstakeError: string | undefined;
$: maxAmount = vaultAddress && $vaultAccounts[vaultAddress]
? Number(formatUnits($vaultAccounts[vaultAddress].stakedBalance, SNT_TOKEN.decimals))
: 0;
$: percentage = vaultAddress && $vaultAccounts[vaultAddress] && maxAmount > 0
? (Number(amount) / maxAmount) * 100
: 0;
$: mpsToBurn = vaultAddress && $vaultAccounts[vaultAddress]
? (Number(formatMPs(vaultAddress)) * percentage / 100).toFixed(1)
: '0.0';
$: if (vaultAddress && $vaultAccounts[vaultAddress] && !amount) {
// Set initial amount to 10% of total
amount = (maxAmount * 0.1).toFixed(2);
}
function handleAmountInput(e: Event) {
const input = e.target as HTMLInputElement;
const value = input.value;
if (!vaultAddress || !$vaultAccounts[vaultAddress]) return;
const numValue = Number(value);
if (numValue > maxAmount) {
amount = maxAmount.toFixed(2);
} else if (numValue < 1) {
amount = '1.00';
} else {
amount = numValue.toFixed(2);
}
}
function handleSliderInput(e: Event) {
const input = e.target as HTMLInputElement;
amount = Number(input.value).toFixed(2);
}
async function handleUnstake() {
if (!confirmUnderstand) {
showError = true;
return;
}
if (!vaultAddress || !$walletAddress || !$walletClient) {
return;
}
try {
isUnstaking = true;
unstakeError = undefined;
// Convert amount to proper decimals
const amountToUnstake = parseUnits(amount, SNT_TOKEN.decimals);
// Call unstake function
unstakeHash = await $walletClient.writeContract({
chain: publicClient.chain,
account: $walletAddress,
address: vaultAddress,
abi: [{
"inputs": [{"internalType": "uint256","name": "_amount","type": "uint256"}],
"name": "unstake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}],
functionName: 'unstake',
args: [amountToUnstake]
});
// Wait for transaction confirmation
const receipt = await publicClient.waitForTransactionReceipt({
hash: unstakeHash,
confirmations: 1
});
if (receipt.status !== 'success') {
throw new Error('Unstaking transaction failed');
}
// Refresh balances and vault data
await refreshBalances($walletAddress);
isCompleted = true;
} catch (error) {
console.error('Failed to unstake:', error);
unstakeError = error instanceof Error ? error.message : 'Failed to unstake';
} finally {
isUnstaking = false;
}
}
function formatMPs(vault: Address): string {
const account = $vaultAccounts[vault];
if (!account?.mpAccrued) return '0.0';
return Number(formatUnits(account.mpAccrued, SNT_TOKEN.decimals)).toFixed(1);
}
function openTxOnEtherscan(hash: string | undefined) {
if (hash) {
window.open(`https://sepolia.etherscan.io/tx/${hash}`, '_blank');
}
}
</script>
{#if isOpen}
<div
class="fixed inset-0 z-50 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
transition:fade={{ duration: 200 }}
>
<!-- Background overlay -->
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
<!-- Modal panel -->
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div
class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6"
transition:fly={{ y: 20, duration: 200 }}
>
<!-- Close button -->
<div class="absolute right-0 top-0 pr-4 pt-4">
<button
type="button"
class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none"
on:click={onClose}
disabled={isUnstaking}
>
<span class="sr-only">Close</span>
<svg
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="sm:flex sm:items-start">
<div class="mt-3 text-center sm:mt-0 sm:text-left w-full">
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
Unstake from Vault #{vaultId}
</h3>
{#if isUnstaking || isCompleted}
<div class="mt-6 space-y-6">
<div class="flex items-center gap-3">
{#if isUnstaking}
<div class="h-8 w-8 flex items-center justify-center">
<button
class="animate-spin"
on:click={() => openTxOnEtherscan(unstakeHash)}
>
<svg class="h-5 w-5 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</button>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">Unstaking in progress...</p>
</div>
{:else if isCompleted}
<div class="h-8 w-8 flex items-center justify-center">
<svg class="h-8 w-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900">Successfully unstaked {amount} {SNT_TOKEN.symbol}</p>
<button
class="mt-1 text-sm text-blue-600 hover:text-blue-700"
on:click={() => unstakeHash && openTxOnEtherscan(unstakeHash)}
>
View transaction
</button>
</div>
{/if}
</div>
{#if isCompleted}
<div class="mt-8 text-center">
<button
type="button"
class="inline-flex justify-center rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
on:click={onClose}
>
Close
</button>
</div>
{/if}
</div>
{:else}
<div class="mt-6 space-y-6">
<!-- Amount Slider -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
Amount to Unstake
</label>
<input
type="range"
min="1"
max={maxAmount}
step="0.01"
value={amount}
on:input={handleSliderInput}
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
/>
<div class="mt-1 flex justify-between text-xs text-gray-500">
<span>1 {SNT_TOKEN.symbol}</span>
<span>{(maxAmount / 2).toFixed(2)} {SNT_TOKEN.symbol}</span>
<span>{maxAmount.toFixed(2)} {SNT_TOKEN.symbol}</span>
</div>
</div>
<!-- Amount Input -->
<div class="relative">
<input
type="number"
value={amount}
on:input={handleAmountInput}
min="1"
max={maxAmount}
step="0.01"
class="block w-full rounded-lg border-0 py-2 px-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm"
placeholder="0.00"
/>
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
<span class="text-sm text-gray-500">{SNT_TOKEN.symbol}</span>
</div>
</div>
<!-- Warning Box with Checkbox -->
{#if vaultAddress && Number(amount) >= 1}
<div class="rounded-md bg-red-50 p-4">
<div class="flex items-start space-x-3">
<div class="flex-shrink-0 pt-0.5">
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
</svg>
</div>
<div class="flex-1">
<p class="text-sm font-semibold text-red-700">
Unstaking will burn MPs earned in this vault by {percentage.toFixed(0)}% ({mpsToBurn} MPs)
</p>
<div class="mt-3 flex items-center gap-2">
<input
id="confirmation"
type="checkbox"
bind:checked={confirmUnderstand}
on:change={() => showError = false}
class="h-4 w-4 rounded border-red-300 text-red-600 focus:ring-red-600"
/>
<label for="confirmation" class="text-sm text-red-700">
I understand,
</label>
</div>
{#if showError}
<p class="mt-2 text-sm text-red-600">
Please confirm that you understand the consequences of unstaking.
</p>
{/if}
</div>
</div>
</div>
{/if}
<!-- Unstake Button -->
<div class="mt-8">
<button
type="button"
class="w-full rounded-lg bg-blue-600 px-3.5 py-2.5 text-center text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={Number(amount) < 1 || maxAmount === 0}
on:click={handleUnstake}
>
{#if maxAmount === 0}
No tokens to unstake
{:else}
Unstake {amount} {SNT_TOKEN.symbol}
{/if}
</button>
</div>
{#if unstakeError}
<p class="mt-2 text-sm text-red-600 text-center">
{unstakeError}
</p>
{/if}
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
{/if}

View File

@@ -19,6 +19,12 @@
goto('/stake');
}
function handleStakeClick(vault: Address) {
if (!isLocked(vault)) {
goto('/stake?stakeVault=' + vault);
}
}
function shortenAddress(address: string): string {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
@@ -175,6 +181,7 @@
<th class="px-6 py-3.5 text-right text-sm font-semibold text-gray-900">SNT Staked</th>
<th class="px-6 py-3.5 text-right text-sm font-semibold text-gray-900">MPs</th>
<th class="px-6 py-3.5 text-left text-sm font-semibold text-gray-900">Remaining Lock</th>
<th class="w-[52px]"></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
@@ -223,6 +230,20 @@
{formatRemainingLock(vault)}
{/if}
</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
{#if !isLocked(vault)}
<button
on:click={() => handleStakeClick(vault)}
class="rounded-full bg-blue-50 w-8 h-8 flex items-center justify-center text-blue-600 hover:bg-blue-100"
>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</button>
{:else}
<div class="w-8 h-8"></div>
{/if}
</td>
</tr>
{/each}
</tbody>
@@ -287,6 +308,21 @@
</span>
</div>
</div>
<div class="mt-4">
{#if !isLocked(vault)}
<button
on:click={() => handleStakeClick(vault)}
class="w-full rounded-lg bg-blue-50 px-2 py-1.5 text-sm font-semibold text-blue-600 hover:bg-blue-100 flex items-center justify-center gap-2"
>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
Add Stake
</button>
{:else}
<div class="h-[36px]"></div>
{/if}
</div>
</div>
</div>
{/each}

View File

@@ -1,6 +1,12 @@
<script lang="ts">
import { walletAddress, SNT_TOKEN, userVaults, vaultAccounts } from '$lib/viem';
import { formatUnits, type Address } from 'viem';
import UnstakingModal from '$lib/components/UnstakingModal.svelte';
import { goto } from '$app/navigation';
let isUnstakingModalOpen = false;
let selectedVaultAddress: Address | undefined;
let selectedVaultId = 0;
function shortenAddress(address: string): string {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
@@ -14,8 +20,16 @@
return Number(formatUnits(amount, SNT_TOKEN.decimals)).toFixed(2);
}
function handleUnstake(vaultId: number) {
alert(`Unstaking functionality for vault #${vaultId} will be implemented later`);
function handleUnstake(vault: Address, vaultId: number) {
selectedVaultAddress = vault;
selectedVaultId = vaultId;
isUnstakingModalOpen = true;
}
function handleCloseUnstakingModal() {
isUnstakingModalOpen = false;
selectedVaultAddress = undefined;
selectedVaultId = 0;
}
function isLocked(vault: Address): boolean {
@@ -54,6 +68,18 @@
return `${minutes}m`;
}
function handleLockClick(vault: Address) {
if (!isLocked(vault) && $vaultAccounts[vault]?.stakedBalance && $vaultAccounts[vault].stakedBalance > 0n) {
goto('/stake?vault=' + vault);
}
}
function handleStakeClick(vault: Address) {
if (!isLocked(vault)) {
goto('/stake?stakeVault=' + vault);
}
}
</script>
<div class="mx-auto max-w-7xl px-6 lg:px-8">
@@ -103,9 +129,17 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
</svg>
{:else}
<svg class="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 10.5V6.75a4.5 4.5 0 119 0v3.75M3.75 21.75h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H3.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
</svg>
<button
class="hover:text-blue-600"
on:click={() => handleLockClick(vault)}
disabled={!$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
class:opacity-50={!$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
class:cursor-not-allowed={!$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
>
<svg class="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 10.5V6.75a4.5 4.5 0 119 0v3.75M3.75 21.75h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H3.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
</svg>
</button>
{/if}
#{i + 1}
</div>
@@ -143,13 +177,33 @@
{/if}
</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
<button
on:click={() => handleUnstake(i + 1)}
class="rounded-lg bg-white px-2 py-1.5 text-sm font-semibold text-blue-600 shadow-sm ring-1 ring-inset ring-blue-200 hover:bg-blue-50 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white"
disabled={isLocked(vault)}
>
{isLocked(vault) ? 'Locked' : 'Unstake'}
</button>
<div class="flex items-center justify-end gap-2">
<button
on:click={() => handleUnstake(vault, i + 1)}
class="rounded-lg bg-blue-50 px-2 py-1.5 text-sm font-semibold text-blue-600 hover:bg-blue-100 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-blue-50"
disabled={isLocked(vault) || !$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
>
{#if isLocked(vault)}
Locked
{:else if !$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
Empty
{:else}
Unstake
{/if}
</button>
{#if !isLocked(vault)}
<button
on:click={() => handleStakeClick(vault)}
class="rounded-full bg-blue-50 w-8 h-8 flex items-center justify-center text-blue-600 hover:bg-blue-100"
>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</button>
{:else}
<div class="w-8 h-8"></div>
{/if}
</div>
</td>
</tr>
{/each}
@@ -170,9 +224,17 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
</svg>
{:else}
<svg class="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 10.5V6.75a4.5 4.5 0 119 0v3.75M3.75 21.75h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H3.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
</svg>
<button
class="hover:text-blue-600"
on:click={() => handleLockClick(vault)}
disabled={!$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
class:opacity-50={!$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
class:cursor-not-allowed={!$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
>
<svg class="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 10.5V6.75a4.5 4.5 0 119 0v3.75M3.75 21.75h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H3.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
</svg>
</button>
{/if}
<h3 class="text-sm font-medium text-gray-900">Vault #{i + 1}</h3>
</div>
@@ -222,13 +284,33 @@
</div>
</div>
<div class="mt-4">
<button
on:click={() => handleUnstake(i + 1)}
class="w-full rounded-lg bg-white px-2 py-1.5 text-sm font-semibold text-blue-600 shadow-sm ring-1 ring-inset ring-blue-200 hover:bg-blue-50 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white"
disabled={isLocked(vault)}
>
{isLocked(vault) ? 'Locked' : 'Unstake'}
</button>
<div class="flex items-center gap-2">
<button
on:click={() => handleUnstake(vault, i + 1)}
class="flex-1 rounded-lg bg-blue-50 px-2 py-1.5 text-sm font-semibold text-blue-600 hover:bg-blue-100 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-blue-50"
disabled={isLocked(vault) || !$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
>
{#if isLocked(vault)}
Locked
{:else if !$vaultAccounts[vault]?.stakedBalance || $vaultAccounts[vault].stakedBalance === 0n}
Empty
{:else}
Unstake
{/if}
</button>
{#if !isLocked(vault)}
<button
on:click={() => handleStakeClick(vault)}
class="rounded-full bg-blue-50 w-8 h-8 flex items-center justify-center text-blue-600 hover:bg-blue-100"
>
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</button>
{:else}
<div class="w-8 h-8"></div>
{/if}
</div>
</div>
</div>
</div>
@@ -243,4 +325,11 @@
</div>
</div>
{/if}
</div>
</div>
<UnstakingModal
isOpen={isUnstakingModalOpen}
onClose={handleCloseUnstakingModal}
vaultAddress={selectedVaultAddress}
vaultId={selectedVaultId}
/>

View File

@@ -4,6 +4,7 @@
import TransactionModal from '$lib/components/TransactionModal.svelte';
import StakingModal from '$lib/components/StakingModal.svelte';
import LockingModal from '$lib/components/LockingModal.svelte';
import { page } from '$app/stores';
let amount = '';
let selectedVaultId = '';
@@ -45,8 +46,32 @@
});
}
// Handle vault parameter in URL
$: if ($page.url.searchParams.get('vault')) {
const vaultFromUrl = $page.url.searchParams.get('vault') as Address;
if ($userVaults.includes(vaultFromUrl) && !isLocked(vaultFromUrl) && $vaultAccounts[vaultFromUrl]?.stakedBalance && $vaultAccounts[vaultFromUrl].stakedBalance > 0n) {
selectedLockVaultId = vaultFromUrl;
// Scroll to the lock form
setTimeout(() => {
document.getElementById('lockForm')?.scrollIntoView({ behavior: 'smooth' });
}, 100);
}
}
// Handle stakeVault parameter in URL
$: if ($page.url.searchParams.get('stakeVault')) {
const vaultFromUrl = $page.url.searchParams.get('stakeVault') as Address;
if ($userVaults.includes(vaultFromUrl) && !isLocked(vaultFromUrl)) {
selectedVaultId = vaultFromUrl;
// Scroll to the stake form
setTimeout(() => {
document.getElementById('stakeForm')?.scrollIntoView({ behavior: 'smooth' });
}, 100);
}
}
// Helper function to check if vault is locked
function isVaultLocked(vault: Address): boolean {
function isLocked(vault: Address): boolean {
const account = $vaultAccounts[vault];
if (!account?.lockUntil) return false;
return account.lockUntil > currentBlockTimestamp;
@@ -321,6 +346,7 @@
</p>
<form
id="stakeForm"
class="mt-6"
on:submit|preventDefault={async (e) => {
await handleStake();
@@ -341,9 +367,11 @@
>
<option value="">Select a vault</option>
{#each $userVaults as vault, i}
<option value={vault}>
Vault #{i + 1} - {shortenAddress(vault)}
</option>
{#if !isLocked(vault)}
<option value={vault}>
Vault #{i + 1} - {shortenAddress(vault)}
</option>
{/if}
{/each}
</select>
</div>
@@ -402,6 +430,7 @@
{#if $userVaults.some(vault => $vaultAccounts[vault] && $vaultAccounts[vault].stakedBalance > 0n)}
<form
id="lockForm"
class="mt-6"
on:submit|preventDefault={handleLock}
>
@@ -420,7 +449,7 @@
>
<option value="">Select a vault</option>
{#each $userVaults as vault, i}
{#if $vaultAccounts[vault] && $vaultAccounts[vault].stakedBalance > 0n && !isVaultLocked(vault)}
{#if $vaultAccounts[vault] && $vaultAccounts[vault].stakedBalance > 0n && !isLocked(vault)}
<option value={vault}>
Vault #{i + 1} - {shortenAddress(vault)} ({formatAmount($vaultAccounts[vault].stakedBalance)} {SNT_TOKEN.symbol})
</option>