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>
102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
// Range orders ("sell via LP"): a one-sided CL position parked strictly beyond
|
||
// the current price. As the market trades through the band, the deposit
|
||
// converts into the other token; fully crossed = fully sold, plus swap fees.
|
||
// NOT a resting order: if price retreats back through the band it un-fills,
|
||
// and nothing executes automatically — the owner withdraws to lock in.
|
||
//
|
||
// Closed forms for liquidity L over sqrt bounds [A,B] (verified against
|
||
// CLPool/SqrtPriceMath, see docs/up33-contract-map.md §6.6):
|
||
// all-token0 amount = L·(1/A − 1/B) (price at/below A)
|
||
// all-token1 amount = L·(B − A) (price at/above B)
|
||
// ⇒ full-fill average price (token1 per token0) = A·B — geometric mean of bounds
|
||
import { parseAbi, parseEventLogs, type Address, type TransactionReceipt } from 'viem'
|
||
import { ADDR } from '../config/addresses'
|
||
|
||
export type LimitSide = 'sell0' | 'sell1'
|
||
|
||
/** selling token0 fills as token1/token0 price RISES (band above the market);
|
||
* selling token1 fills as it FALLS (band below) */
|
||
export function limitSideFor(pool: { token0: Address }, sell: Address): LimitSide {
|
||
return sell.toLowerCase() === pool.token0.toLowerCase() ? 'sell0' : 'sell1'
|
||
}
|
||
|
||
/** fraction of the sell amount converted so far, 0..1 (display only) */
|
||
export function limitFillFrac(side: LimitSide, sqrtP: bigint, sqrtA: bigint, sqrtB: bigint): number {
|
||
const P = Number(sqrtP)
|
||
const A = Number(sqrtA)
|
||
const B = Number(sqrtB)
|
||
if (side === 'sell0') {
|
||
// token0 remaining ∝ 1/√P − 1/√B
|
||
if (P <= A) return 0
|
||
if (P >= B) return 1
|
||
return (1 / A - 1 / P) / (1 / A - 1 / B)
|
||
}
|
||
if (P >= B) return 0
|
||
if (P <= A) return 1
|
||
return (B - P) / (B - A)
|
||
}
|
||
|
||
// ---- local bookkeeping: tokenId -> order intent (this frontend only) ----
|
||
|
||
export type LimitTag = {
|
||
sell: Address
|
||
buy: Address
|
||
sellSym: string
|
||
buySym: string
|
||
amountIn: string // raw units of the sell token
|
||
pool: Address
|
||
ts: number
|
||
}
|
||
|
||
const KEY = 'up33.limitOrders.v1'
|
||
|
||
function load(): Record<string, LimitTag> {
|
||
try {
|
||
return JSON.parse(localStorage.getItem(KEY) ?? '{}') as Record<string, LimitTag>
|
||
} catch {
|
||
return {}
|
||
}
|
||
}
|
||
function save(m: Record<string, LimitTag>) {
|
||
try {
|
||
localStorage.setItem(KEY, JSON.stringify(m))
|
||
} catch {
|
||
/* storage unavailable — tags are cosmetic */
|
||
}
|
||
}
|
||
|
||
export function tagLimit(tokenId: bigint, tag: LimitTag) {
|
||
const m = load()
|
||
m[tokenId.toString()] = tag
|
||
save(m)
|
||
}
|
||
export function limitTagOf(tokenId: bigint): LimitTag | null {
|
||
return load()[tokenId.toString()] ?? null
|
||
}
|
||
export function untagLimit(tokenId: bigint) {
|
||
const m = load()
|
||
if (m[tokenId.toString()]) {
|
||
delete m[tokenId.toString()]
|
||
save(m)
|
||
}
|
||
}
|
||
|
||
const erc721TransferAbi = parseAbi([
|
||
'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)',
|
||
])
|
||
|
||
/** tokenId freshly minted to `user` by the CL position manager in this receipt */
|
||
export function mintedTokenId(rcpt: TransactionReceipt, user: Address): bigint | null {
|
||
const logs = parseEventLogs({ abi: erc721TransferAbi, logs: rcpt.logs, eventName: 'Transfer' })
|
||
for (const l of logs) {
|
||
if (
|
||
l.address.toLowerCase() === ADDR.CL_PM.toLowerCase() &&
|
||
l.args.from === '0x0000000000000000000000000000000000000000' &&
|
||
l.args.to?.toLowerCase() === user.toLowerCase()
|
||
) {
|
||
return l.args.tokenId ?? null
|
||
}
|
||
}
|
||
return null
|
||
}
|