mirror of
https://github.com/labrinyang/lp-terminal.git
synced 2026-07-27 21:27:43 +00:00
LP TERMINAL — terminal-style LP frontend for Robinhood Chain
Static SPA over UP33 (ve(3,3)) + the official Uniswap v2/v3 deployments: browse and add liquidity across all three, manage CL/v2 positions, one-token ZAP adds, aggregator vs native swaps, and LP-based limit orders. Ships with a zero-dependency pool indexer for uniswap discovery. Browser-wallet signing only; no key material anywhere.
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
// Live-chain smoke test for the read layer: TickMath constants, ABI selectors,
|
||||
// pool discovery, quoter-vs-kyber sanity. Run: npm run smoke
|
||||
// Never prints the RPC URL.
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { createPublicClient, defineChain, http, parseUnits, type Address } from 'viem'
|
||||
import {
|
||||
clFactoryAbi,
|
||||
clGaugeAbi,
|
||||
clPoolAbi,
|
||||
quoterAbi,
|
||||
v2FactoryAbi,
|
||||
v2GaugeAbi,
|
||||
voterAbi,
|
||||
} from '../src/abi'
|
||||
import { ADDR } from '../src/config/addresses'
|
||||
import {
|
||||
getAmountsForLiquidity,
|
||||
getLiquidityForAmounts,
|
||||
getSqrtRatioAtTick,
|
||||
sqrtPriceToPrice,
|
||||
} from '../src/lib/clmath'
|
||||
|
||||
let pass = 0
|
||||
let fail = 0
|
||||
function check(name: string, cond: boolean, detail = '') {
|
||||
if (cond) pass++
|
||||
else fail++
|
||||
console.log(` ${cond ? 'PASS' : 'FAIL'} ${name}${detail ? ' — ' + detail : ''}`)
|
||||
}
|
||||
|
||||
// duplicated rather than imported: src/config/env.ts reads import.meta.env,
|
||||
// which is vite-only and does not load under node/tsx.
|
||||
const PUBLIC_RPC = 'https://rpc.mainnet.chain.robinhood.com'
|
||||
|
||||
/** repo-root .env `RPC` (SECRET — never print it). No .env / no key: public RPC. */
|
||||
const rpc = (() => {
|
||||
const fromEnv = process.env.RPC?.trim()
|
||||
if (fromEnv) return fromEnv
|
||||
try {
|
||||
const text = readFileSync(fileURLToPath(new URL('../.env', import.meta.url)), 'utf8')
|
||||
return text.match(/^\s*RPC\s*=\s*(\S+)\s*$/m)?.[1] ?? PUBLIC_RPC
|
||||
} catch {
|
||||
return PUBLIC_RPC
|
||||
}
|
||||
})()
|
||||
|
||||
const chain = defineChain({
|
||||
id: 4663,
|
||||
name: 'Robinhood Chain',
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
rpcUrls: { default: { http: [rpc] } },
|
||||
contracts: { multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11' } },
|
||||
})
|
||||
const pc = createPublicClient({ chain, transport: http(rpc, { batch: true }) })
|
||||
|
||||
type Res = { status: 'success' | 'failure'; result?: unknown }
|
||||
const ok = <T,>(r: Res | undefined): T | undefined =>
|
||||
r && r.status === 'success' ? (r.result as T) : undefined
|
||||
|
||||
async function main() {
|
||||
console.log('== UP33 TERMINAL smoke test ==')
|
||||
|
||||
// 1. chain id
|
||||
const id = await pc.getChainId()
|
||||
check('chainId == 4663', id === 4663, String(id))
|
||||
|
||||
// 2. TickMath constants vs float reference
|
||||
let maxRel = 0
|
||||
for (const t of [-887200, -200000, -46055, -100, -1, 0, 1, 100, 46054, 200000, 443636, 887200]) {
|
||||
const exact = Number(getSqrtRatioAtTick(t)) / 2 ** 96
|
||||
const approx = Math.sqrt(Math.pow(1.0001, t))
|
||||
const rel = Math.abs(exact - approx) / approx
|
||||
if (rel > maxRel) maxRel = rel
|
||||
}
|
||||
check('TickMath matches float ref (<1e-9 rel)', maxRel < 1e-9, `maxRel=${maxRel.toExponential(2)}`)
|
||||
|
||||
// 3. liquidity math round-trip
|
||||
{
|
||||
const sqrtP = getSqrtRatioAtTick(102000)
|
||||
const sqrtA = getSqrtRatioAtTick(100000)
|
||||
const sqrtB = getSqrtRatioAtTick(104000)
|
||||
const in0 = parseUnits('1', 18)
|
||||
const L = getLiquidityForAmounts(sqrtP, sqrtA, sqrtB, in0, (1n << 255n))
|
||||
const { amount0 } = getAmountsForLiquidity(sqrtP, sqrtA, sqrtB, L)
|
||||
const rel = Number(((in0 - amount0) * 1_000_000n) / in0) / 1e6
|
||||
check('liquidity round-trip (amount0 within 0.01%)', amount0 <= in0 && rel < 1e-4, `loss=${rel}`)
|
||||
}
|
||||
|
||||
// 4. factories enumerable
|
||||
const head = (await pc.multicall({
|
||||
contracts: [
|
||||
{ abi: v2FactoryAbi, address: ADDR.V2_FACTORY, functionName: 'allPoolsLength' },
|
||||
{ abi: clFactoryAbi, address: ADDR.CL_FACTORY, functionName: 'allPoolsLength' },
|
||||
] as never,
|
||||
})) as Res[]
|
||||
const v2Len = Number(ok<bigint>(head[0]) ?? 0n)
|
||||
const clLen = Number(ok<bigint>(head[1]) ?? 0n)
|
||||
check('factories enumerable', v2Len >= 3 && clLen >= 15, `${v2Len} v2 + ${clLen} CL pools`)
|
||||
|
||||
// 5. enumerate CL pools, validate slot0 tick <-> sqrtPrice against our TickMath
|
||||
const clAddrRes = (await pc.multicall({
|
||||
contracts: Array.from({ length: clLen }, (_, i) => ({
|
||||
abi: clFactoryAbi,
|
||||
address: ADDR.CL_FACTORY,
|
||||
functionName: 'allPools',
|
||||
args: [BigInt(i)],
|
||||
})) as never,
|
||||
})) as Res[]
|
||||
const clAddrs = clAddrRes.map((r) => ok<Address>(r)).filter(Boolean) as Address[]
|
||||
const slotRes = (await pc.multicall({
|
||||
contracts: clAddrs.flatMap((p) => [
|
||||
{ abi: clPoolAbi, address: p, functionName: 'slot0' },
|
||||
{ abi: clPoolAbi, address: p, functionName: 'token0' },
|
||||
{ abi: clPoolAbi, address: p, functionName: 'token1' },
|
||||
{ abi: clPoolAbi, address: p, functionName: 'tickSpacing' },
|
||||
{ abi: clPoolAbi, address: p, functionName: 'liquidity' },
|
||||
{ abi: clPoolAbi, address: p, functionName: 'gauge' },
|
||||
]) as never,
|
||||
})) as Res[]
|
||||
let tickOk = 0
|
||||
let tickBad = 0
|
||||
const clPools: {
|
||||
addr: Address
|
||||
sqrtP: bigint
|
||||
tick: number
|
||||
t0: Address
|
||||
t1: Address
|
||||
ts: number
|
||||
liq: bigint
|
||||
gauge?: Address
|
||||
}[] = []
|
||||
clAddrs.forEach((p, i) => {
|
||||
const s0 = ok<readonly [bigint, number, number, number, number, boolean]>(slotRes[i * 6])
|
||||
const t0 = ok<Address>(slotRes[i * 6 + 1])
|
||||
const t1 = ok<Address>(slotRes[i * 6 + 2])
|
||||
const ts = ok<number>(slotRes[i * 6 + 3])
|
||||
const liq = ok<bigint>(slotRes[i * 6 + 4]) ?? 0n
|
||||
const gauge = ok<Address>(slotRes[i * 6 + 5])
|
||||
if (!s0 || !t0 || !t1 || ts === undefined) return
|
||||
clPools.push({ addr: p, sqrtP: s0[0], tick: s0[1], t0, t1, ts, liq, gauge })
|
||||
if (s0[0] === 0n) return
|
||||
const lo = getSqrtRatioAtTick(s0[1])
|
||||
const hi = getSqrtRatioAtTick(s0[1] + 1)
|
||||
if (lo <= s0[0] && s0[0] < hi) tickOk++
|
||||
else tickBad++
|
||||
})
|
||||
check('slot0 sqrtPrice within [tick, tick+1) for all CL pools', tickBad === 0, `${tickOk} ok / ${tickBad} bad`)
|
||||
|
||||
// 6. WETH/UP CL pool: our quoter path vs kyber aggregator ballpark
|
||||
const wethUp = clPools
|
||||
.filter(
|
||||
(p) =>
|
||||
(p.t0.toLowerCase() === ADDR.WETH.toLowerCase() && p.t1.toLowerCase() === ADDR.UP.toLowerCase()) ||
|
||||
(p.t1.toLowerCase() === ADDR.WETH.toLowerCase() && p.t0.toLowerCase() === ADDR.UP.toLowerCase()),
|
||||
)
|
||||
.sort((a, b) => (b.liq > a.liq ? 1 : -1))[0]
|
||||
check('WETH/UP CL pool exists', !!wethUp, wethUp?.addr ?? '')
|
||||
let quoterOut: bigint | undefined
|
||||
if (wethUp) {
|
||||
const oneWeth = parseUnits('1', 18)
|
||||
const qRes = (await pc.multicall({
|
||||
contracts: [
|
||||
{
|
||||
abi: quoterAbi,
|
||||
address: ADDR.CL_QUOTER,
|
||||
functionName: 'quoteExactInputSingle',
|
||||
args: [
|
||||
{
|
||||
tokenIn: ADDR.WETH,
|
||||
tokenOut: ADDR.UP,
|
||||
amountIn: oneWeth,
|
||||
tickSpacing: wethUp.ts,
|
||||
sqrtPriceLimitX96: 0n,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as never,
|
||||
})) as Res[]
|
||||
const q = ok<readonly [bigint, bigint, number, bigint]>(qRes[0])
|
||||
quoterOut = q?.[0]
|
||||
check('quoter quotes via multicall (view-cast ABI works)', quoterOut !== undefined && quoterOut > 0n, `1 WETH -> ${quoterOut} UP wei`)
|
||||
|
||||
// spot price sanity vs quoter (small trade impact expected)
|
||||
const spot = sqrtPriceToPrice(
|
||||
wethUp.sqrtP,
|
||||
wethUp.t0.toLowerCase() === ADDR.WETH.toLowerCase() ? 18 : 18,
|
||||
18,
|
||||
)
|
||||
const upPerWeth = wethUp.t0.toLowerCase() === ADDR.WETH.toLowerCase() ? spot : 1 / spot
|
||||
if (quoterOut) {
|
||||
const outF = Number(quoterOut) / 1e18
|
||||
const rel = Math.abs(outF - upPerWeth) / upPerWeth
|
||||
check('quoter ~ spot price (<30% incl. fee+impact)', rel < 0.3, `spot=${upPerWeth.toFixed(1)} quote=${outF.toFixed(1)}`)
|
||||
}
|
||||
|
||||
try {
|
||||
const r = await fetch(
|
||||
`https://aggregator-api.kyberswap.com/robinhood/api/v1/routes?tokenIn=${ADDR.WETH}&tokenOut=${ADDR.UP}&amountIn=${oneWeth}`,
|
||||
{ headers: { 'x-client-id': 'up33-terminal-smoke' } },
|
||||
)
|
||||
const j: any = await r.json()
|
||||
const aggOut = BigInt(j?.data?.routeSummary?.amountOut ?? '0')
|
||||
if (aggOut > 0n && quoterOut) {
|
||||
const rel = Math.abs(Number(aggOut - quoterOut)) / Number(aggOut)
|
||||
check('kyber agg vs our single-pool quote (<25%)', rel < 0.25, `agg=${aggOut} ours=${quoterOut}`)
|
||||
} else {
|
||||
check('kyber aggregator reachable', aggOut > 0n, j?.message ?? 'no data')
|
||||
}
|
||||
} catch (e) {
|
||||
check('kyber aggregator reachable', false, String(e))
|
||||
}
|
||||
}
|
||||
|
||||
// 7. v2 gauge standard selectors respond (gauge instances are unverified on Blockscout)
|
||||
const v2AddrRes = (await pc.multicall({
|
||||
contracts: Array.from({ length: v2Len }, (_, i) => ({
|
||||
abi: v2FactoryAbi,
|
||||
address: ADDR.V2_FACTORY,
|
||||
functionName: 'allPools',
|
||||
args: [BigInt(i)],
|
||||
})) as never,
|
||||
})) as Res[]
|
||||
const v2Addrs = v2AddrRes.map((r) => ok<Address>(r)).filter(Boolean) as Address[]
|
||||
const gaugeRes = (await pc.multicall({
|
||||
contracts: v2Addrs.map((p) => ({
|
||||
abi: voterAbi,
|
||||
address: ADDR.VOTER,
|
||||
functionName: 'gauges',
|
||||
args: [p],
|
||||
})) as never,
|
||||
})) as Res[]
|
||||
const zero = '0x0000000000000000000000000000000000000000'
|
||||
const v2Gauge = gaugeRes.map((r) => ok<Address>(r)).find((g) => g && g !== zero)
|
||||
if (v2Gauge) {
|
||||
const rnd = '0x00000000000000000000000000000000000000ff' as Address
|
||||
const gRes = (await pc.multicall({
|
||||
contracts: [
|
||||
{ abi: v2GaugeAbi, address: v2Gauge, functionName: 'earned', args: [rnd] },
|
||||
{ abi: v2GaugeAbi, address: v2Gauge, functionName: 'balanceOf', args: [rnd] },
|
||||
{ abi: v2GaugeAbi, address: v2Gauge, functionName: 'rewardRate' },
|
||||
{ abi: v2GaugeAbi, address: v2Gauge, functionName: 'stakingToken' },
|
||||
] as never,
|
||||
})) as Res[]
|
||||
check(
|
||||
'v2 gauge standard selectors respond',
|
||||
gRes.every((r) => r.status === 'success'),
|
||||
v2Gauge,
|
||||
)
|
||||
} else {
|
||||
check('v2 gauge found', false, 'no gauged v2 pool')
|
||||
}
|
||||
|
||||
// 8. CL gauge stakedValues() responds
|
||||
const clGauge = clPools.find((p) => p.gauge && p.gauge !== zero)?.gauge
|
||||
if (clGauge) {
|
||||
const sv = (await pc.multicall({
|
||||
contracts: [
|
||||
{
|
||||
abi: clGaugeAbi,
|
||||
address: clGauge,
|
||||
functionName: 'stakedValues',
|
||||
args: ['0x0eEA30aBa3f07abFA20E4b544F55e0f917d9DFd8'],
|
||||
},
|
||||
] as never,
|
||||
})) as Res[]
|
||||
check('CL gauge stakedValues responds', sv[0].status === 'success', clGauge)
|
||||
} else {
|
||||
check('CL gauge found', false, 'no gauged CL pool')
|
||||
}
|
||||
|
||||
console.log(`\n== ${pass} passed, ${fail} failed ==`)
|
||||
process.exit(fail === 0 ? 0 : 1)
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
// avoid leaking the RPC URL inside error strings
|
||||
console.error('smoke crashed:', String(e).replace(rpc, '<rpc>'))
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,125 @@
|
||||
// Live smoke for the univ3 POOLS browse layer: replicates fetchUniBrowse
|
||||
// (src/lib/uniBrowse.ts) with the REAL abi/address/clmath modules — env.ts
|
||||
// can't load under node, so the flow is mirrored, not imported.
|
||||
// Never prints the RPC URL.
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { createPublicClient, defineChain, encodeFunctionData, getAddress, http, zeroAddress, type Address, type PublicClient } from 'viem'
|
||||
import { uniV3FactoryAbi, uniV3PmAbi, uniV3PoolAbi } from '../src/abi/index'
|
||||
import { ADDR, UNI } from '../src/config/addresses'
|
||||
import { getLiquidityForAmounts, getSqrtRatioAtTick, minAmountsForLiquidity } from '../src/lib/clmath'
|
||||
|
||||
// duplicated rather than imported: src/config/env.ts reads import.meta.env,
|
||||
// which is vite-only and does not load under node/tsx.
|
||||
const PUBLIC_RPC = 'https://rpc.mainnet.chain.robinhood.com'
|
||||
|
||||
/** repo-root .env `RPC` (SECRET — never print it). No .env / no key: public RPC. */
|
||||
const rpc = (() => {
|
||||
const fromEnv = process.env.RPC?.trim()
|
||||
if (fromEnv) return fromEnv
|
||||
try {
|
||||
const text = readFileSync(new URL('../.env', import.meta.url), 'utf8')
|
||||
return text.match(/^\s*RPC\s*=\s*(\S+)\s*$/m)?.[1] ?? PUBLIC_RPC
|
||||
} catch {
|
||||
return PUBLIC_RPC
|
||||
}
|
||||
})()
|
||||
const chain = defineChain({
|
||||
id: 4663,
|
||||
name: 'Robinhood Chain',
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
rpcUrls: { default: { http: [rpc] } },
|
||||
contracts: { multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11' } },
|
||||
})
|
||||
const pc = createPublicClient({ chain, transport: http(rpc, { batch: true }) }) as PublicClient
|
||||
|
||||
let fails = 0
|
||||
function check(name: string, cond: boolean, detail = '') {
|
||||
console.log(`${cond ? 'ok ' : 'FAIL'} ${name}${detail ? ' — ' + detail : ''}`)
|
||||
if (!cond) fails++
|
||||
}
|
||||
|
||||
type DsPair = { chainId?: string; dexId?: string; labels?: string[]; pairAddress?: string; volume?: { h24?: number }; liquidity?: { usd?: number } }
|
||||
const v3PairsOf = (json: unknown): DsPair[] => {
|
||||
const arr = Array.isArray(json) ? (json as DsPair[]) : ((json as { pairs?: DsPair[] })?.pairs ?? [])
|
||||
return arr.filter((p) => p?.chainId === 'robinhood' && p?.dexId === 'uniswap' && (p?.labels ?? []).includes('v3'))
|
||||
}
|
||||
type McRes = { status: string; result?: unknown }
|
||||
const ok = <T,>(r: McRes | undefined): T | undefined => (r && r.status === 'success' ? (r.result as T) : undefined)
|
||||
|
||||
const FEE_TS: Record<number, number> = { 100: 1, 500: 10, 3000: 60, 10000: 200 }
|
||||
const KNOWN_POOL = '0xa9188730fe85be88ad499d7d52b099e800fb0334' // WETH/USDG 0.3% (verified earlier)
|
||||
|
||||
async function main() {
|
||||
// 1. token-pairs discovery for WETH (the default browse query)
|
||||
const tp = await (await fetch(`https://api.dexscreener.com/token-pairs/v1/robinhood/${ADDR.WETH}`)).json()
|
||||
const cands = v3PairsOf(tp)
|
||||
check('dexscreener token-pairs finds v3 WETH pools', cands.length >= 1, `${cands.length} candidates`)
|
||||
// token-pairs caps at ~30 pairs/token (activity-ordered) — a pool missing from
|
||||
// one token's list is reachable via its OTHER token; assert exactly that:
|
||||
const tpU = await (await fetch(`https://api.dexscreener.com/token-pairs/v1/robinhood/${ADDR.USDG}`)).json()
|
||||
check('known USDG/WETH 0.3% pool reachable via USDG query', v3PairsOf(tpU).some((p) => p.pairAddress?.toLowerCase() === KNOWN_POOL))
|
||||
|
||||
// 2. rank by TVL + cap (same as the lib)
|
||||
const seen = new Map<string, DsPair>()
|
||||
for (const p of cands) { const a = p.pairAddress?.toLowerCase(); if (a && !seen.has(a)) seen.set(a, p) }
|
||||
const picks = [...seen.values()].sort((a, b) => (b.liquidity?.usd ?? 0) - (a.liquidity?.usd ?? 0)).slice(0, 30)
|
||||
console.log(` top pools by TVL: ${picks.slice(0, 5).map((p) => `${p.pairAddress?.slice(0, 8)}($${Math.round(p.liquidity?.usd ?? 0)})`).join(' ')}`)
|
||||
|
||||
// 3. hydrate from the pool contracts
|
||||
const addrs = picks.map((p) => getAddress(p.pairAddress!))
|
||||
const det = (await pc.multicall({
|
||||
contracts: addrs.flatMap((a) => [
|
||||
{ abi: uniV3PoolAbi, address: a, functionName: 'token0' },
|
||||
{ abi: uniV3PoolAbi, address: a, functionName: 'token1' },
|
||||
{ abi: uniV3PoolAbi, address: a, functionName: 'fee' },
|
||||
{ abi: uniV3PoolAbi, address: a, functionName: 'tickSpacing' },
|
||||
{ abi: uniV3PoolAbi, address: a, functionName: 'slot0' },
|
||||
{ abi: uniV3PoolAbi, address: a, functionName: 'liquidity' },
|
||||
]) as never,
|
||||
})) as McRes[]
|
||||
type Hyd = { addr: Address; token0: Address; token1: Address; fee: number; ts: number; sqrtP: bigint; tick: number; liq: bigint }
|
||||
const hyd: Hyd[] = []
|
||||
addrs.forEach((a, i) => {
|
||||
const token0 = ok<Address>(det[i * 6]); const token1 = ok<Address>(det[i * 6 + 1])
|
||||
const fee = ok<number>(det[i * 6 + 2]); const ts = ok<number>(det[i * 6 + 3])
|
||||
const s0 = ok<readonly [bigint, number]>(det[i * 6 + 4]); const liq = ok<bigint>(det[i * 6 + 5])
|
||||
if (!token0 || !token1 || fee === undefined || ts === undefined || !s0) return
|
||||
hyd.push({ addr: a, token0, token1, fee, ts, sqrtP: s0[0], tick: s0[1], liq: liq ?? 0n })
|
||||
})
|
||||
check('all candidates hydrate on-chain', hyd.length === addrs.length, `${hyd.length}/${addrs.length}`)
|
||||
check('fee↔tickSpacing mapping consistent', hyd.every((h) => FEE_TS[h.fee] === h.ts))
|
||||
|
||||
// 4. factory.getPool authenticity round-trip
|
||||
const gp = (await pc.multicall({
|
||||
contracts: hyd.map((h) => ({ abi: uniV3FactoryAbi, address: UNI.V3_FACTORY, functionName: 'getPool', args: [h.token0, h.token1, h.fee] })) as never,
|
||||
})) as McRes[]
|
||||
const verified = hyd.filter((h, i) => {
|
||||
const m = ok<Address>(gp[i])
|
||||
return !!m && m !== zeroAddress && m.toLowerCase() === h.addr.toLowerCase()
|
||||
})
|
||||
check('factory.getPool verifies every pool', verified.length === hyd.length, `${verified.length}/${hyd.length} (drops would be spoofs)`)
|
||||
|
||||
// 5. symbol search path
|
||||
const sr = await (await fetch('https://api.dexscreener.com/latest/dex/search?q=USDG')).json()
|
||||
check('symbol search returns robinhood v3 pairs', v3PairsOf(sr).length >= 1, `${v3PairsOf(sr).length} matches`)
|
||||
|
||||
// 6. mint calldata: canonical univ3 selector + slippage mins sane on live state
|
||||
const h0 = verified.find((h) => h.addr.toLowerCase() === KNOWN_POOL) ?? verified[0]
|
||||
const lower = Math.floor((h0.tick - 600) / h0.ts) * h0.ts
|
||||
const upper = Math.ceil((h0.tick + 600) / h0.ts) * h0.ts
|
||||
const amt0 = 10n ** 15n
|
||||
const liq = getLiquidityForAmounts(h0.sqrtP, getSqrtRatioAtTick(lower), getSqrtRatioAtTick(upper), amt0, 2n ** 120n)
|
||||
const mins = minAmountsForLiquidity(h0.sqrtP, getSqrtRatioAtTick(lower), getSqrtRatioAtTick(upper), liq, 100)
|
||||
const data = encodeFunctionData({
|
||||
abi: uniV3PmAbi,
|
||||
functionName: 'mint',
|
||||
args: [{ token0: h0.token0, token1: h0.token1, fee: h0.fee, tickLower: lower, tickUpper: upper, amount0Desired: amt0, amount1Desired: 2n ** 120n, amount0Min: mins.amount0Min, amount1Min: mins.amount1Min, recipient: '0x0000000000000000000000000000000000000001', deadline: 2n ** 40n }],
|
||||
})
|
||||
check('mint selector == canonical 0x88316456', data.slice(0, 10) === '0x88316456', data.slice(0, 10))
|
||||
check('band-edge mins nonzero for in-range band', mins.amount0Min > 0n && mins.amount1Min > 0n, `${mins.amount0Min}/${mins.amount1Min}`)
|
||||
|
||||
console.log(fails === 0 ? '\nALL UNI-BROWSE SMOKE CHECKS PASSED' : `\n${fails} CHECKS FAILED`)
|
||||
process.exit(fails === 0 ? 0 : 1)
|
||||
}
|
||||
|
||||
void main()
|
||||
Reference in New Issue
Block a user