Files
lp-terminal_github/src/lib/limit.ts
T
labrinyang bca538e7e3 feat: SHEEP CHOICE routing, cross-chain deposits, and any-token ZAP
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>
2026-07-23 00:01:52 +08:00

102 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}