Add more test for the daily volumes and add button to set the UIFee Factor
This commit is contained in:
@@ -800,4 +800,179 @@ public class PlatformSummaryMetricsTests
|
||||
result.PositionCountByAsset.Should().HaveCount(1);
|
||||
result.TotalPlatformVolume.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateDailySnapshotFromPositions_WithDateFiltering_CalculatesCumulativeMetricsCorrectly()
|
||||
{
|
||||
// Arrange - Create positions with different dates spanning multiple days
|
||||
var baseDate = TestDate.Date;
|
||||
var positions = new List<Position>
|
||||
{
|
||||
// Day 1: Two positions (one finished, one filled)
|
||||
CreateFinishedPosition(50000m, 0.1m, TradeDirection.Long, 1m, 51000m, false),
|
||||
CreateFilledPosition(40000m, 0.2m, TradeDirection.Short, 1m),
|
||||
|
||||
// Day 2: One finished position
|
||||
CreateFinishedPosition(60000m, 0.05m, TradeDirection.Long, 2m, 61200m, false),
|
||||
|
||||
// Day 3: Two positions (future dates - should be excluded)
|
||||
};
|
||||
|
||||
// Set specific dates for positions
|
||||
positions[0].Date = baseDate.AddHours(10); // Day 1, 10 AM
|
||||
positions[1].Date = baseDate.AddHours(14); // Day 1, 2 PM
|
||||
positions[2].Date = baseDate.AddDays(1).AddHours(11); // Day 2, 11 AM
|
||||
|
||||
// Future positions (should be excluded)
|
||||
var futurePositions = new List<Position>
|
||||
{
|
||||
CreateFinishedPosition(30000m, 0.15m, TradeDirection.Short, 1m, 29100m, false),
|
||||
CreateFilledPosition(70000m, 0.08m, TradeDirection.Long, 1m)
|
||||
};
|
||||
futurePositions[0].Date = baseDate.AddDays(2).AddHours(9); // Day 3, 9 AM
|
||||
futurePositions[1].Date = baseDate.AddDays(2).AddHours(16); // Day 3, 4 PM
|
||||
|
||||
var allPositions = positions.Concat(futurePositions).ToList();
|
||||
|
||||
// Act - Calculate snapshot for Day 2 (should include Day 1 and Day 2 positions only)
|
||||
var targetDate = baseDate.AddDays(1); // Day 2
|
||||
var filteredPositions = allPositions.Where(p => p.Date.Date <= targetDate).ToList();
|
||||
var metrics = TradingBox.CalculatePlatformSummaryMetrics(filteredPositions);
|
||||
|
||||
// Assert - Should include positions from Day 1 and Day 2 only
|
||||
metrics.TotalLifetimePositionCount.Should().Be(3); // 2 from Day 1 + 1 from Day 2
|
||||
|
||||
// Calculate expected volume: Day 1 positions + Day 2 position
|
||||
// Default takeProfitPercentage is 0.04m (4%)
|
||||
var day1Volume = (50000m * 0.1m * 1m + 52000m * 0.1m * 1m) + // Finished long: 5000 + 5200 = 10200
|
||||
(40000m * 0.2m * 1m); // Open short: 8000 (not finished, no close volume)
|
||||
var day2Volume = (60000m * 0.05m * 2m + 62400m * 0.05m * 2m); // Finished long with 2x leverage: 6000 + 6240 = 12240
|
||||
var expectedTotalVolume = day1Volume + day2Volume; // 10200 + 8000 + 12240 = 30440
|
||||
|
||||
metrics.TotalPlatformVolume.Should().Be(expectedTotalVolume);
|
||||
metrics.OpenInterest.Should().Be(40000m * 0.2m * 1m); // Only the open short position from Day 1
|
||||
|
||||
// Should have both BTC and position counts
|
||||
metrics.VolumeByAsset.Should().ContainKey("BTC");
|
||||
metrics.PositionCountByAsset["BTC"].Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateDailySnapshotFromPositions_WithNoPositionsForDate_ReturnsEmptyMetrics()
|
||||
{
|
||||
// Arrange - Create positions all after the target date
|
||||
var baseDate = TestDate.Date;
|
||||
var positions = new List<Position>
|
||||
{
|
||||
CreateFinishedPosition(50000m, 0.1m, TradeDirection.Long, 1m, 51000m, false),
|
||||
CreateFilledPosition(40000m, 0.2m, TradeDirection.Short, 1m)
|
||||
};
|
||||
|
||||
// Set dates after target date
|
||||
positions[0].Date = baseDate.AddDays(1).AddHours(10);
|
||||
positions[1].Date = baseDate.AddDays(2).AddHours(14);
|
||||
|
||||
// Act - Calculate snapshot for a date before all positions
|
||||
var targetDate = baseDate; // Today, before all positions
|
||||
var filteredPositions = positions.Where(p => p.Date.Date <= targetDate).ToList();
|
||||
var metrics = TradingBox.CalculatePlatformSummaryMetrics(filteredPositions);
|
||||
|
||||
// Assert - Should have no positions for this date
|
||||
metrics.TotalLifetimePositionCount.Should().Be(0);
|
||||
metrics.TotalPlatformVolume.Should().Be(0);
|
||||
metrics.TotalPlatformFees.Should().Be(0);
|
||||
metrics.TotalPlatformPnL.Should().Be(0);
|
||||
metrics.NetPnL.Should().Be(0);
|
||||
metrics.OpenInterest.Should().Be(0);
|
||||
metrics.VolumeByAsset.Should().BeEmpty();
|
||||
metrics.PositionCountByAsset.Should().BeEmpty();
|
||||
metrics.PositionCountByDirection.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateDailySnapshotFromPositions_WithMixedAssetsAndDirections_CalculatesAssetBreakdownsCorrectly()
|
||||
{
|
||||
// Arrange - Create positions with different assets and directions over multiple days
|
||||
var baseDate = TestDate.Date;
|
||||
var positions = new List<Position>
|
||||
{
|
||||
// Day 1: BTC Long (finished) and ETH Short (open)
|
||||
CreateFinishedPosition(50000m, 0.1m, TradeDirection.Long, 1m, 51000m, false), // BTC finished
|
||||
CreateFilledPosition(3000m, 1m, TradeDirection.Short, 1m), // ETH open
|
||||
|
||||
// Day 2: BTC Short (finished) and ETH Long (finished)
|
||||
CreateFinishedPosition(40000m, 0.2m, TradeDirection.Short, 2m, 39200m, false), // BTC finished
|
||||
CreateFinishedPosition(3100m, 0.5m, TradeDirection.Long, 1m, 3180m, false) // ETH finished
|
||||
};
|
||||
|
||||
// Set dates
|
||||
positions[0].Date = baseDate.AddHours(10); // Day 1 BTC
|
||||
positions[1].Date = baseDate.AddHours(14); // Day 1 ETH
|
||||
positions[2].Date = baseDate.AddDays(1).AddHours(11); // Day 2 BTC
|
||||
positions[3].Date = baseDate.AddDays(1).AddHours(15); // Day 2 ETH
|
||||
|
||||
// Set ETH tickers for ETH positions
|
||||
positions[1].Ticker = Ticker.ETH;
|
||||
positions[3].Ticker = Ticker.ETH;
|
||||
|
||||
// Act - Calculate snapshot for Day 2 (includes all positions)
|
||||
var targetDate = baseDate.AddDays(1);
|
||||
var filteredPositions = positions.Where(p => p.Date.Date <= targetDate).ToList();
|
||||
var metrics = TradingBox.CalculatePlatformSummaryMetrics(filteredPositions);
|
||||
|
||||
// Assert - Should include all 4 positions
|
||||
metrics.TotalLifetimePositionCount.Should().Be(4);
|
||||
metrics.VolumeByAsset.Should().HaveCount(2); // BTC and ETH
|
||||
metrics.PositionCountByAsset.Should().HaveCount(2);
|
||||
metrics.PositionCountByAsset["BTC"].Should().Be(2);
|
||||
metrics.PositionCountByAsset["ETH"].Should().Be(2);
|
||||
|
||||
// Should have only one direction (one open short ETH, rest are finished)
|
||||
metrics.PositionCountByDirection.Should().HaveCount(1);
|
||||
metrics.PositionCountByDirection[TradeDirection.Short].Should().Be(1); // Only the open ETH short position
|
||||
|
||||
// Open interest should only include the open ETH short position
|
||||
metrics.OpenInterest.Should().Be(3000m * 1m * 1m); // ETH open short: 3000
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateDailySnapshotFromPositions_WithPositionSpanningMultipleDays_CalculatesVolumePerDayCorrectly()
|
||||
{
|
||||
// Arrange - Create positions representing the same position at different stages
|
||||
var baseDate = TestDate.Date;
|
||||
|
||||
// Position as it appears on Day 1 (still open)
|
||||
var positionDay1 = CreateFilledPosition(50000m, 0.1m, TradeDirection.Long, 1m);
|
||||
positionDay1.Date = baseDate.AddHours(12); // Day 1, noon
|
||||
|
||||
// Position as it appears on Day 2 (now closed)
|
||||
var positionDay2 = CreateFinishedPosition(50000m, 0.1m, TradeDirection.Long, 1m, 52000m, false);
|
||||
positionDay2.Date = baseDate.AddHours(12); // Same open date
|
||||
positionDay2.TakeProfit1.Date = baseDate.AddDays(1).AddHours(10); // Closed on Day 2
|
||||
|
||||
// Act - Calculate snapshot for Day 1 (position opened but not closed yet)
|
||||
var day1Positions = new List<Position> { positionDay1 };
|
||||
var day1Metrics = TradingBox.CalculatePlatformSummaryMetrics(day1Positions);
|
||||
|
||||
// Calculate snapshot for Day 2 (position opened and closed)
|
||||
var day2Positions = new List<Position> { positionDay2 };
|
||||
var day2Metrics = TradingBox.CalculatePlatformSummaryMetrics(day2Positions);
|
||||
|
||||
// Assert - Day 1: Only opening volume (position still open)
|
||||
var expectedDay1Volume = 50000m * 0.1m * 1m; // Open: 5000
|
||||
day1Metrics.TotalPlatformVolume.Should().Be(expectedDay1Volume);
|
||||
day1Metrics.TotalLifetimePositionCount.Should().Be(1);
|
||||
day1Metrics.OpenInterest.Should().Be(expectedDay1Volume); // Position still open
|
||||
|
||||
// Assert - Day 2: Opening volume + closing volume (position now closed)
|
||||
var expectedDay2Volume = 50000m * 0.1m * 1m + 52000m * 0.1m * 1m; // Open: 5000 + Close: 5200 = 10200
|
||||
day2Metrics.TotalPlatformVolume.Should().Be(expectedDay2Volume);
|
||||
day2Metrics.TotalLifetimePositionCount.Should().Be(1);
|
||||
day2Metrics.OpenInterest.Should().Be(0m); // Position now closed
|
||||
|
||||
// Assert - Volume increased from Day 1 to Day 2 (closing volume added)
|
||||
day2Metrics.TotalPlatformVolume.Should().BeGreaterThan(day1Metrics.TotalPlatformVolume);
|
||||
var volumeIncrease = day2Metrics.TotalPlatformVolume - day1Metrics.TotalPlatformVolume;
|
||||
volumeIncrease.Should().Be(5200m); // The closing volume added on Day 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +222,106 @@ export const useClaimUiFees = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ABI for the setUiFeeFactor function
|
||||
const SET_UI_FEE_FACTOR_ABI = parseAbi([
|
||||
'function setUiFeeFactor(uint256 uiFeeFactor) external',
|
||||
])
|
||||
|
||||
// UI Fee Receiver contract address on Arbitrum (from Arbiscan)
|
||||
const UI_FEE_RECEIVER_ADDRESS = '0x602b805EedddBbD9ddff44A7dcBD46cb07849685'
|
||||
|
||||
/**
|
||||
* Custom hook to update UI fee factor on GMX ExchangeRouter contract
|
||||
*/
|
||||
export const useUpdateUiFee = () => {
|
||||
const { address, isConnected } = useAccount()
|
||||
const chainId = useChainId()
|
||||
const { switchChainAsync } = useSwitchChain()
|
||||
const { writeContractAsync } = useWriteContract()
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [txHash, setTxHash] = useState<string | null>(null)
|
||||
|
||||
/**
|
||||
* Updates the UI fee factor by calling the ExchangeRouter contract
|
||||
* @param percentage The fee percentage (e.g., 0.1 for 0.1%)
|
||||
*/
|
||||
const updateUiFeeFactor = async (percentage: number) => {
|
||||
if (!isConnected || !address) {
|
||||
throw new Error('Wallet not connected')
|
||||
}
|
||||
|
||||
if (percentage < 0 || percentage > 100) {
|
||||
throw new Error('Fee percentage must be between 0 and 100')
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
setTxHash(null)
|
||||
|
||||
try {
|
||||
// Switch to Arbitrum if not already on it
|
||||
if (chainId !== arbitrum.id) {
|
||||
await switchChainAsync({ chainId: arbitrum.id })
|
||||
}
|
||||
|
||||
// Convert percentage to basis points (1% = 100 basis points)
|
||||
// GMX typically uses factors with 30 decimal places (10^30 total precision)
|
||||
const basisPoints = Math.round(percentage * 100) // Convert to basis points
|
||||
const feeFactor = BigInt(basisPoints) * BigInt(10) ** BigInt(26) // Multiply by 10^26 to get the factor (10^30 - 10^4 for basis points)
|
||||
|
||||
console.log('Updating UI fee factor:')
|
||||
console.log(`Percentage: ${percentage}%`)
|
||||
console.log(`Basis points: ${basisPoints}`)
|
||||
console.log(`Fee factor: ${feeFactor.toString()}`)
|
||||
|
||||
// Call the setUiFeeFactor function on the contract
|
||||
const hash = await writeContractAsync({
|
||||
address: UI_FEE_RECEIVER_ADDRESS,
|
||||
abi: SET_UI_FEE_FACTOR_ABI,
|
||||
functionName: 'setUiFeeFactor',
|
||||
args: [feeFactor],
|
||||
})
|
||||
|
||||
setTxHash(hash)
|
||||
return hash
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'
|
||||
setError(errorMessage)
|
||||
throw new Error(`Failed to update UI fee factor: ${errorMessage}`)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
updateUiFeeFactor,
|
||||
isLoading,
|
||||
error,
|
||||
txHash,
|
||||
isConnected,
|
||||
address,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to monitor UI fee update transaction status
|
||||
*/
|
||||
export const useUpdateUiFeeTransaction = (txHash: string | null) => {
|
||||
const { data, isLoading, isSuccess, isError } = useWaitForTransactionReceipt({
|
||||
hash: txHash as `0x${string}` | undefined,
|
||||
chainId: arbitrum.id,
|
||||
})
|
||||
|
||||
return {
|
||||
transactionData: data,
|
||||
isConfirming: isLoading,
|
||||
isConfirmed: isSuccess,
|
||||
isError,
|
||||
}
|
||||
}
|
||||
|
||||
// Export the allowed tickers for use in other components
|
||||
export { ALLOWED_TICKERS, type AllowedTicker }
|
||||
|
||||
|
||||
@@ -5,7 +5,13 @@ import LogIn from '../../components/mollecules/LogIn/LogIn'
|
||||
import useCookie from '../../hooks/useCookie'
|
||||
import {useEffect, useState} from 'react'
|
||||
import {useAuthStore} from '../../app/store/accountStore'
|
||||
import {ALLOWED_TICKERS, useClaimUiFees, useClaimUiFeesTransaction} from '../../hooks/useClaimUiFees'
|
||||
import {
|
||||
ALLOWED_TICKERS,
|
||||
useClaimUiFees,
|
||||
useClaimUiFeesTransaction,
|
||||
useUpdateUiFee,
|
||||
useUpdateUiFeeTransaction
|
||||
} from '../../hooks/useClaimUiFees'
|
||||
import Toast from '../../components/mollecules/Toast/Toast'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
|
||||
@@ -86,10 +92,18 @@ export const Auth = ({ children }: any) => {
|
||||
const [claimAccountAddress, setClaimAccountAddress] = useState('')
|
||||
const [showClaimSection, setShowClaimSection] = useState(false)
|
||||
|
||||
// Update UI Fee state
|
||||
const [uiFeePercentage, setUiFeePercentage] = useState('')
|
||||
const [showUpdateUiFeeSection, setShowUpdateUiFeeSection] = useState(false)
|
||||
|
||||
// Claim UI fees hook
|
||||
const { claimUiFees, isLoading: isClaimingFees, txHash, error: claimError } = useClaimUiFees()
|
||||
const { isConfirming, isConfirmed, isError: txError } = useClaimUiFeesTransaction(txHash)
|
||||
|
||||
// Update UI fee hook
|
||||
const { updateUiFeeFactor, isLoading: isUpdatingFee, txHash: updateTxHash, error: updateFeeError } = useUpdateUiFee()
|
||||
const { isConfirming: isUpdateConfirming, isConfirmed: isUpdateConfirmed, isError: updateTxError } = useUpdateUiFeeTransaction(updateTxHash)
|
||||
|
||||
useEffect(() => {
|
||||
if (ready) {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -123,6 +137,23 @@ export const Auth = ({ children }: any) => {
|
||||
}
|
||||
}, [txError])
|
||||
|
||||
// Handle successful UI fee update
|
||||
useEffect(() => {
|
||||
if (isUpdateConfirmed) {
|
||||
const toast = new Toast('Success')
|
||||
toast.update('success', 'UI fee factor updated successfully!')
|
||||
setUiFeePercentage('') // Clear the input
|
||||
}
|
||||
}, [isUpdateConfirmed])
|
||||
|
||||
// Handle UI fee update transaction error
|
||||
useEffect(() => {
|
||||
if (updateTxError) {
|
||||
const toast = new Toast('Error')
|
||||
toast.update('error', 'UI fee update transaction failed')
|
||||
}
|
||||
}, [updateTxError])
|
||||
|
||||
const handleClaimUiFees = async () => {
|
||||
if (!isConnected) {
|
||||
const toast = new Toast('Error')
|
||||
@@ -149,6 +180,33 @@ export const Auth = ({ children }: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateUiFee = async () => {
|
||||
if (!isConnected) {
|
||||
const toast = new Toast('Error')
|
||||
toast.update('error', 'Please connect your wallet first')
|
||||
return
|
||||
}
|
||||
|
||||
const percentage = parseFloat(uiFeePercentage)
|
||||
if (isNaN(percentage) || percentage < 0 || percentage > 100) {
|
||||
const toast = new Toast('Error')
|
||||
toast.update('error', 'Please enter a valid percentage between 0 and 100')
|
||||
return
|
||||
}
|
||||
|
||||
const toast = new Toast('Updating UI fee factor...')
|
||||
|
||||
try {
|
||||
toast.update('info', 'Submitting transaction to update UI fee factor...')
|
||||
await updateUiFeeFactor(percentage)
|
||||
toast.update('info', 'Transaction submitted, waiting for confirmation...')
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'
|
||||
toast.update('error', `Failed to update UI fee factor: ${errorMessage}`)
|
||||
console.error('Error updating UI fee factor:', err)
|
||||
}
|
||||
}
|
||||
|
||||
if (!ready || isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
@@ -288,6 +346,125 @@ export const Auth = ({ children }: any) => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Update UI Fee Section */}
|
||||
<div style={{ textAlign: 'center', marginTop: '20px' }}>
|
||||
<button
|
||||
onClick={() => setShowUpdateUiFeeSection(!showUpdateUiFeeSection)}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: showUpdateUiFeeSection ? '#6B7280' : '#8B5CF6',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px'
|
||||
}}
|
||||
>
|
||||
{showUpdateUiFeeSection ? 'Hide' : 'Update UI Fee'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showUpdateUiFeeSection && (
|
||||
<div style={{
|
||||
padding: '20px',
|
||||
backgroundColor: '#F3F4F6',
|
||||
borderRadius: '8px',
|
||||
maxWidth: '400px',
|
||||
width: '100%',
|
||||
marginTop: '20px'
|
||||
}}>
|
||||
<h3 style={{ margin: '0 0 15px 0', fontSize: '18px', color: '#111827' }}>
|
||||
Update GMX UI Fee Factor
|
||||
</h3>
|
||||
<p style={{ margin: '0 0 10px 0', fontSize: '14px', color: '#6B7280' }}>
|
||||
Set the UI fee factor percentage for GMX trading. This will be automatically formatted for the smart contract.
|
||||
</p>
|
||||
<details style={{ marginBottom: '15px', fontSize: '12px', color: '#6B7280' }}>
|
||||
<summary style={{ cursor: 'pointer', fontWeight: '500' }}>
|
||||
How it works
|
||||
</summary>
|
||||
<div style={{
|
||||
marginTop: '8px',
|
||||
padding: '8px',
|
||||
backgroundColor: '#E5E7EB',
|
||||
borderRadius: '4px',
|
||||
lineHeight: '1.6'
|
||||
}}>
|
||||
Enter a percentage (e.g., 0.1 for 0.1%). This gets converted to basis points and formatted for the GMX contract.
|
||||
The fee factor determines the percentage of trading fees that go to the UI/interface.
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{isConnected && walletAddress && (
|
||||
<div style={{ marginBottom: '15px', fontSize: '12px', color: '#059669' }}>
|
||||
✓ Wallet connected: {walletAddress.slice(0, 6)}...{walletAddress.slice(-4)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isConnected && (
|
||||
<div style={{ marginBottom: '15px', fontSize: '12px', color: '#DC2626' }}>
|
||||
⚠ Please connect your wallet first
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
type="number"
|
||||
placeholder="Enter fee percentage (e.g., 0.1)"
|
||||
value={uiFeePercentage}
|
||||
onChange={(e) => setUiFeePercentage(e.target.value)}
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
marginBottom: '15px',
|
||||
border: '1px solid #D1D5DB',
|
||||
borderRadius: '4px',
|
||||
fontSize: '14px',
|
||||
boxSizing: 'border-box'
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={handleUpdateUiFee}
|
||||
disabled={!isConnected || isUpdatingFee || isUpdateConfirming || !uiFeePercentage.trim()}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 20px',
|
||||
backgroundColor: (!isConnected || isUpdatingFee || isUpdateConfirming || !uiFeePercentage.trim()) ? '#9CA3AF' : '#8B5CF6',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: (!isConnected || isUpdatingFee || isUpdateConfirming || !uiFeePercentage.trim()) ? 'not-allowed' : 'pointer',
|
||||
fontSize: '16px'
|
||||
}}
|
||||
>
|
||||
{isUpdatingFee ? 'Signing...' : isUpdateConfirming ? 'Confirming...' : isUpdateConfirmed ? 'Updated!' : 'Update UI Fee Factor'}
|
||||
</button>
|
||||
|
||||
{(updateFeeError) && (
|
||||
<div style={{ marginTop: '10px', fontSize: '12px', color: '#DC2626' }}>
|
||||
{updateFeeError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{updateTxHash && (
|
||||
<div style={{ marginTop: '10px', fontSize: '12px' }}>
|
||||
<p style={{ color: '#059669', margin: '0 0 5px 0' }}>Transaction Hash:</p>
|
||||
<a
|
||||
href={`https://arbiscan.io/tx/${updateTxHash}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: '#8B5CF6', wordBreak: 'break-all' }}
|
||||
>
|
||||
{updateTxHash}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
} else if (!token) {
|
||||
|
||||
Reference in New Issue
Block a user