// app-modals.jsx — AnarchyFi modal components // Exports to window: BoostedWarningModal, TxModal const { useState, useEffect } = React; /* ── EXIT FEE CHART ─────────────────────────────────────────────────── */ // Exit fee: 25% at deposit → 2.5% floor, LINEAR over 52 weeks (VeGauge.calculate_fee). // Rate is locked in at deposit time and does not change if governance updates the global. function ExitFeeChart({ currentWeek = 0, positionUsd = 0, accent }) { const W = 280, H = 90; const MIN_FEE = 2.5, MAX_FEE = 25.0, WEEKS = 52; const points = Array.from({ length: WEEKS + 1 }, (_, w) => ({ day: w, fee: MAX_FEE - (MAX_FEE - MIN_FEE) * (w / WEEKS), })); const currentDay = currentWeek; const toX = d => (d / WEEKS) * W; const toY = f => H - ((f - MIN_FEE) / (MAX_FEE - MIN_FEE)) * H; const linePts = points.map(p => `${toX(p.day).toFixed(1)},${toY(p.fee).toFixed(1)}`).join(' '); const areaPts = `0,${H} ${linePts} ${W},${H}`; const markerX = toX(currentDay); const markerFee = points[currentDay]?.fee ?? MIN_FEE; const markerY = toY(markerFee); return (
{/* Grid lines */} {[0, 0.25, 0.5, 0.75, 1].map((t, i) => ( ))} {/* Area fill */} {/* Line */} {/* Current position marker */} {currentDay > 0 && ( <> )} {/* Axes */} {/* Axis labels */}
{['WK 0', 'WK 13', 'WK 26', 'WK 39', 'WK 52'].map(l => ( {l} ))}
{['25%','19%','13%','2.5%'].map(l => ( {l} ))}
); } /* ── BOOSTED WARNING MODAL ───────────────────────────────────────────── */ function BoostedWarningModal({ onClose, onConfirm, accent }) { const [agreed, setAgreed] = useState(false); return (
e.stopPropagation()} style={{ position: 'relative', width: 540, background: 'rgba(8,6,5,0.97)', border: '1px solid rgba(255,212,124,0.3)', padding: '32px 32px 28px', maxHeight: '90vh', overflowY: 'auto', }}> {/* Corner brackets — yellow */} {[['top','left'],['top','right'],['bottom','left'],['bottom','right']].map(([v,h]) => (
))} {/* Header */}
BOOSTED STAKE WARNING
{/* Rewards badge */}
1.5×
BOOSTED REWARDS
You earn 1.5× more ANARCHY tokens compared
to standard staking throughout your lock period.
{/* Exit fee warning */}
EXIT FEE — DECAYS LINEARLY OVER 52 WEEKS
The fee starts at 25% and decays in a straight line to 2.5% over 52 weeks. Your rate is locked in at deposit — it will not change if governance updates the global fee.
{/* Chart */}
{/* Fee milestones — in $ cost on a $1,000 position */}
{[ { label:'TODAY', fee:'25.0%', usd:'$250', color:'#ef4444' }, { label:'WEEK 13', fee:'19.4%', usd:'$194', color:'#f97316' }, { label:'WEEK 26', fee:'13.8%', usd:'$138', color:'#ffd47c' }, { label:'WEEK 52', fee:'2.50%', usd:'$25', color:'#4ade80' }, ].map(m => (
{m.label}
{m.fee}
{m.usd}
))}
Dollar figures shown on a $1,000 position. Exiting today costs $250; in 30 days it costs $235.
{/* Additional info */}
{[ '⚡ Boost multiplier applies to all reward epochs during your stake', '🔒 The exit fee is redistributed to remaining stakers', '⏱ Fee countdown begins from the moment you stake', ].map((txt, i) => (
{txt}
))}
{/* Agreement checkbox */} {/* Buttons */}
); } /* ── TX MODAL ────────────────────────────────────────────────────────── */ const TX_STATES = { sending: { icon: null, spinning: true, title: 'BROADCASTING', sub: 'Sending transaction to chain...', color: '#627EEA', }, pending: { icon: null, spinning: false, pulsing: true, title: 'PENDING CONFIRMATION', sub: 'Awaiting block confirmation...', color: '#ffd47c', }, confirmed: { icon: '✓', spinning: false, title: 'CONFIRMED', sub: 'Transaction successfully included.', color: '#4ade80', }, failed: { icon: '✗', spinning: false, title: 'FAILED', sub: 'Transaction did not go through.', color: '#ef4444', }, }; function TxModal({ state, accent, onClose, onRetry, txType, hash, error, stage, explorerUrl }) { const cfg = { ...TX_STATES[state] }; if (state === 'sending' && stage === 'approve') cfg.sub = 'Approve the token spend in your wallet...'; else if (state === 'sending') cfg.sub = 'Confirm in your wallet...'; if (state === 'failed' && error) cfg.sub = error; const [copied, setCopied] = useState(false); const shortHash = hash ? `${hash.slice(0, 10)}…${hash.slice(-8)}` : ''; function copyHash() { navigator.clipboard?.writeText(hash).catch(() => {}); setCopied(true); setTimeout(() => setCopied(false), 1500); } const typeLabel = { swap: 'SWAP', stake: 'STAKE', deposit: 'DEPOSIT', boost: 'BOOSTED STAKE', unstake: 'UNSTAKE', split: 'SPLIT', merge: 'MERGE', reset: 'RESET VOTES', withdraw: 'WITHDRAW', transfer: 'TRANSFER', 'claim-emissions': 'CLAIM LP EMISSIONS', 'claim-bribes': 'CLAIM BRIBES', 'claim-lpfees': 'CLAIM LP FEES', approve: 'APPROVE', setApprovalForAll: 'APPROVE ALL', depositNFT: 'DEPOSIT TO CUSTODY', delegateNFTs: 'DELEGATE', selfVote: 'SELF-VOTE', registerForBenefit: 'REGISTER FEE TIER', resetVotes: 'RESET VOTES', withdrawNFTs: 'WITHDRAW', scheduleWithdrawal: 'SCHEDULE WITHDRAWAL', startUnlock: 'START UNLOCK', forceUnlock: 'FORCE UNLOCK', settleEpoch: 'SETTLE EPOCH', claimRewardAsMember: 'CLAIM EPOCH', claimRewardsForEpochs: 'CLAIM EPOCHS', registerDelegator: 'REGISTER DELEGATOR', delegatorVote: 'GROUP VOTE', reactivateDelegator: 'REACTIVATE', stopDelegator: 'STOP DELEGATOR', unregisterDelegator: 'UNREGISTER', deactivateGroup: 'DEACTIVATE GROUP', claimGroupRewards: 'COLLECT GROUP FEE', matchPosition: 'MATCH POSITION', notifyRewardAmount: 'FUND BRIBE', notifyRewardsMultiEpoch: 'FUND BRIBE · MULTI-EPOCH', vote: 'VOTE', 'boosted-stake': 'BOOSTED STAKE', claimGroupLPEmissions: 'CLAIM GROUP LP EMISSIONS', }[txType] || 'TRANSACTION'; return (
e.stopPropagation()} style={{ position:'relative', width:400, background:'rgba(8,6,5,0.97)', border:`1px solid ${cfg.color}44`, padding:'36px 30px 28px', }}> {/* Corner brackets */} {[['top','left'],['top','right'],['bottom','left'],['bottom','right']].map(([v,h]) => (
))} {/* Type badge */}
{typeLabel}
{/* Icon / spinner */}
{cfg.spinning ? (
) : cfg.pulsing ? (
) : (
{cfg.icon}
)}
{/* Title */}
{cfg.title}
{cfg.sub}
{/* TX Hash */} {hash && (
TX HASH
{shortHash}
)} {/* Progress dots for pending */} {state === 'sending' && (
{[0,1,2].map(i => (
))}
)} {state === 'pending' && (
WAITING FOR THE NEXT BLOCK
)} {/* Actions */} {(state === 'confirmed' || state === 'failed') && (
{state === 'confirmed' && explorerUrl && ( VIEW ON EXPLORER → )} {state === 'failed' && ( )}
)}
); } Object.assign(window, { BoostedWarningModal, TxModal, ExitFeeChart }); /* ── WALLET CONNECT MODAL ────────────────────────────────────────────── */ const KNOWN_WALLETS = { 'io.metamask': { bg:'#F6851B', label:'M' }, 'com.coinbase.wallet':{ bg:'#0052FF', label:'⬤' }, 'com.brave.wallet': { bg:'#FF5000', label:'🦁' }, 'io.safepal': { bg:'#6B4EFF', label:'S' }, 'com.crypto.wallet': { bg:'#1199FA', label:'C' }, 'pro.tokenpocket': { bg:'#2980FE', label:'TP' }, 'io.rabby': { bg:'#7084FF', label:'R' }, 'com.trustwallet.app':{ bg:'#0500FF', label:'T' }, 'com.okex.wallet': { bg:'#000000', label:'OK' }, 'com.binance.wallet': { bg:'#F0B90B', label:'B' }, 'injected': { bg:'#4a4640', label:'W' }, }; function walletStyle(w) { return { id: w.info.rdns, name: w.info.name, icon: w.info.icon, ...(KNOWN_WALLETS[w.info.rdns] || { bg:'#4a4640', label: w.info.name.slice(0,1).toUpperCase() }) }; } function WalletIcon({ wallet, size = 40 }) { if (wallet.icon) { return ; } return (
{wallet.label}
); } function WalletConnectModal({ onClose, onConnect, accent }) { const [connecting, setConnecting] = useState(null); const [error, setError] = useState(null); const [found, setFound] = useState(() => window.Chain.providers()); useEffect(() => { const refresh = () => setFound(window.Chain.providers()); window.addEventListener('eip6963:announceProvider', refresh); window.dispatchEvent(new Event('eip6963:requestProvider')); const t = setTimeout(refresh, 400); return () => { window.removeEventListener('eip6963:announceProvider', refresh); clearTimeout(t); }; }, []); async function handleSelect(w) { if (connecting) return; setConnecting(w.info.rdns); setError(null); try { await onConnect(w); onClose(); } catch (e) { setError(window.Chain.decodeError(e).message); setConnecting(null); } } const wallets = found.map(w => ({ w, style: walletStyle(w) })); const pairs = []; for (let i = 0; i < wallets.length; i += 2) pairs.push([wallets[i], wallets[i+1]]); return (
e.stopPropagation()} style={{ position: 'relative', width: 560, maxWidth:'94vw', background: 'rgba(8,6,5,0.98)', border: `1px solid rgba(232,230,225,0.12)`, padding: '32px 30px 24px', }}> {[['top','left'],['top','right'],['bottom','left'],['bottom','right']].map(([v,h]) => (
))}
CONNECT TO A WALLET
Wallets installed in this browser are listed below. Connecting only shares your address; every transaction is confirmed in the wallet.
{wallets.length === 0 && (
NO WALLET DETECTED
Install MetaMask, Rabby, Coinbase Wallet or another browser wallet, then reload.
)}
{pairs.map(([a, b], ri) => (
{[a, b].map((entry, wi) => { if (!entry) return
; const { w, style } = entry; const isConnecting = connecting === style.id; return ( ); })}
))}
{error && (
{error}
)}
WalletConnect (mobile wallets) is not enabled on this deployment yet.
); } /* ── ACCOUNT MODAL ───────────────────────────────────────────────────── */ function AccountModal({ onClose, onDisconnect, accent, connectedWallet, recentTxs }) { const [copied, setCopied] = useState(false); const chain = useChain(); const ADDR = chain.account || ''; const SHORT = ADDR ? ADDR.slice(0, 6) + '...' + ADDR.slice(-4) : '—'; const wallet = connectedWallet ? walletStyle(connectedWallet) : null; const { data: native } = useLoad(async (c) => c.router.runner.provider.getBalance(chain.account), [chain.blockNumber]); const explorer = chain.explorerAddr(ADDR); function copy() { navigator.clipboard?.writeText(ADDR).catch(()=>{}); setCopied(true); setTimeout(() => setCopied(false), 1800); } return (
e.stopPropagation()} style={{ position:'relative', width:320, background:'rgba(8,6,5,0.98)', border:'1px solid rgba(232,230,225,0.12)', animation:'fadeUp 0.18s forwards', }}> {/* Corner brackets */} {[['top','left'],['top','right'],['bottom','left'],['bottom','right']].map(([v,h]) => (
))} {/* Wallet type + address */}
{wallet && }
{wallet?.name || 'WALLET'}
● CONNECTED
{/* Address */}
{SHORT}
| {explorer ? ( EXPLORER → ) : ( NO EXPLORER )}
{/* Stats */}
{[ { label:'NETWORK', value: chain.chain.name }, { label:'BALANCE', value: `${window.Chain.fmt.units(native, 18, 4)} ${chain.chain.native}` }, ].map((s,i) => (
0?16:0, borderLeft: i>0?'1px solid rgba(232,230,225,0.07)':'none', marginLeft: i>0?16:0, }}>
{s.label}
{s.value}
))}
{/* Recent txs */} {recentTxs && recentTxs.length > 0 && (
RECENT
{recentTxs.slice(-3).reverse().map((tx,i) => (
{tx.type} {tx.hash && chain.explorerTx(tx.hash) ? ( {tx.state === 'failed' ? '✗ FAILED' : '✓ CONFIRMED'} → ) : ( {tx.state === 'failed' ? '✗ FAILED' : '✓ CONFIRMED'} )}
))}
)} {/* Disconnect */}
); } Object.assign(window, { BoostedWarningModal, TxModal, ExitFeeChart, WalletConnectModal, AccountModal });