mirror of
https://github.com/labrinyang/lp-terminal.git
synced 2026-07-27 21:27:43 +00:00
bca538e7e3
One commit because the pieces do not compile apart: the shared copy, the tab shell and the data layer all changed together, and splitting them further would mean inventing intermediate states that never existed. SHEEP CHOICE — the terminal's own swap. Quotes come from a solver that splits one trade across several pools instead of forcing it down a single path, and returns a ready-to-sign Settler transaction; the UI draws the split leg by leg and scores every venue against one shared fee-free baseline, so the card that says it pays most actually does. The Kyber transaction path is gone — Kyber is read-only USD valuation now, and there is no Kyber calldata to sign. BRIDGE — deposits from other chains over Relay, Across and the native portal, priced side by side and sorted by what actually reaches you. No fee on any of them. In-flight transfers get a countdown and survive a reload. ZAP — add liquidity holding neither side of the pair; whatever needs swapping is done in the same flow, with an optional stake-after step. Uniswap V2 liquidity now shows up under POSITIONS. Swaps in flight are persisted, so a refresh mid-swap no longer loses the transaction. Pair labels copy their token and pool addresses, and jump to DexScreener. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
61 lines
1.7 KiB
TypeScript
61 lines
1.7 KiB
TypeScript
import { useEffect, useRef, useSyncExternalStore } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { EXPLORER } from '../config/addresses'
|
|
import { txlog, type LogLine } from '../lib/txlog'
|
|
|
|
function glyph(l: LogLine): string {
|
|
switch (l.kind) {
|
|
case 'ok':
|
|
return '✓'
|
|
case 'err':
|
|
return '✗'
|
|
case 'pending':
|
|
return '⧗'
|
|
default:
|
|
return '>'
|
|
}
|
|
}
|
|
|
|
export function TxLogPanel() {
|
|
const { t } = useTranslation()
|
|
const lines = useSyncExternalStore(txlog.subscribe, txlog.get)
|
|
const ref = useRef<HTMLDivElement>(null)
|
|
useEffect(() => {
|
|
const el = ref.current
|
|
if (el) el.scrollTop = el.scrollHeight
|
|
}, [lines])
|
|
|
|
return (
|
|
<div className="logpanel" ref={ref}>
|
|
{lines.length === 0 && <div className="logline dim">{t('log.ready')}</div>}
|
|
{lines.length > 2 && (
|
|
<div className="logline dim">
|
|
<span className="t">--------</span>
|
|
<span className="txt">{t('log.events', { n: lines.length })}</span>
|
|
<button className="chip logaction" onClick={() => txlog.clear()}>
|
|
{t('log.clear')}
|
|
</button>
|
|
</div>
|
|
)}
|
|
{lines.map((l) => (
|
|
<div key={l.id} className={`logline ${l.kind}`}>
|
|
<span className="t">{new Date(l.ts).toLocaleTimeString('en-GB')}</span>
|
|
<span className="txt">
|
|
{glyph(l)} {l.text}
|
|
</span>
|
|
{l.hash && (
|
|
<a href={l.href ?? `${EXPLORER}/tx/${l.hash}`} target="_blank" rel="noreferrer">
|
|
tx↗
|
|
</a>
|
|
)}
|
|
{l.action && (
|
|
<button className="chip logaction" onClick={l.action.onClick}>
|
|
{l.action.label}
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|