import {FixedNumber} from "ethers"; import {usePrefStore, useStore} from "@/store/store.js"; import {token} from "@/blockchain/token.js"; import Color from "color"; import {DateTime} from "luxon"; import router from "@/router/index.js"; import {dateString} from "@/common.js"; export function nav(name) { // noinspection JSIgnoredPromiseFromCall router.push({name}) } const QUOTE_SYMBOLS = [ // in order of preference // todo put this in metadata.json using addrs 'USDT', 'USDC', 'USDC.e', 'BTC', 'WBTC', 'ETH', 'WETH', 'ARB', ] export class SingletonCoroutine { constructor(f, delay = 10) { this.f = f this.delay = delay this.timeout = null this.args = null } pending() { return this.timeout !== null } invoke(/*arguments*/) { this.args = arguments // console.log('invoke', arguments) if (this.timeout === null) // noinspection JSCheckFunctionSignatures this.timeout = setTimeout(this.onTimeout, this.delay, this) } async onTimeout(self) { try { await self.f(...self.args) } catch (e) { if (self.retry) { console.log('retrying', this.f, 'due to error', e) self.timeout = null self.invoke(self.args) } else console.error(e) } finally { self.timeout = null } } } export const vAutoSelect = { beforeMount: (el) => { const input = el.querySelector('input') input.onfocus = () => setTimeout(() => input.select(), 0) } } export const uint32max = 4294967295 export const uint64max = 18446744073709551615n export function tokenNumber(token, balance) { return FixedNumber.fromValue(balance, token.decimals, {decimals: token.decimals, width: 256}) } export function tokenFloat(token, balance) { return tokenNumber(token, balance).toUnsafeFloat() } export function routeInverted(route) { const s = useStore() return route && (route.token0 === s.tokenA) === s.inverted } export function intervalString(seconds) { if (seconds < 1) return 'now' else if (seconds < 60) return `${seconds} seconds` else if (seconds < 3600) return `${(seconds / 60).toFixed(1)} minutes` else if (seconds < 86400) return `${(seconds / 3600).toFixed(1)} hours` else return `${(seconds / 86400).toFixed(1)} days` } export function timestampString(seconds) { const date = DateTime.fromSeconds(seconds).setZone(usePrefStore().timezone) return dateString(date) } export function pairKey(chainId, tokenA, tokenB) { const token0 = tokenA.a < tokenB.a ? tokenA.a : tokenB.a const token1 = tokenA.a > tokenB.a ? tokenA.a : tokenB.a return [chainId, token0, token1]; } export function pairPriceAddr(chainId, baseTokenAddr, quoteTokenAddr, price) { const baseToken = token(chainId, baseTokenAddr) const quoteToken = token(chainId, quoteTokenAddr) if (!baseToken || !quoteToken) return null return pairPrice(chainId, baseToken, quoteToken, price) } export function flipInversionPreference(chainId, base, quote) { const inverted = base.a > quote.a const token0 = !inverted ? base.a : quote.a const token1 = inverted ? base.a : quote.a const k = [chainId, token0, token1]; const prefs = usePrefStore() prefs.inverted[k] = !prefs.inverted[k] } export function inversionPreference(chainId, base, quote) { // returns the user preference for whether to invert this pair or not const inputInverted = base.a > quote.a const token0 = !inputInverted ? base.a : quote.a const token1 = inputInverted ? base.a : quote.a const key = [chainId, token0, token1]; // todo chainId shouldn't matter const prefs = usePrefStore() if (!(key in prefs.inverted)) { // todo prefer stablecoins as the quote asset let preferInverted = false; for (const q of QUOTE_SYMBOLS) { if (quote.s === q) break // definitely not inverted if (base.s === q) { preferInverted = true break // definitely inverted } } prefs.inverted[key] = preferInverted } return prefs.inverted[key] !== inputInverted } export const sleep = ms => new Promise(r => setTimeout(r, ms)) export function uuid() { // noinspection JSUnresolvedReference return crypto.randomUUID(); } export function lightenColor(color, lightness = 85, alpha = null) { let c = new Color(color).hsl() if (useStore().theme === 'light') c = c.lightness(lightness) else c = c.lightness(100-lightness) c = c.rgb() if (alpha !== null) c = c.alpha(alpha) return c.string() } export function lightenColor2(color, alpha = null) { return lightenColor(color, 98, alpha) } const colorRanges = { buy: ['#00CC33', '#3300CC'], sell: ['#CC0033', '#CCCC33'], } export function sideColor(buy, index=0) { const range = buy ? colorRanges.buy : colorRanges.sell const a = new Color(range[0]).rgb() const b = new Color(range[1]).rgb() // const z2 = Math.PI * Math.PI / 6 - 1 // ζ(2) modulo 1 const z2 = 1 - 1/Math.E // ζ(2) modulo 1 const kk = index * z2; const k = kk - Math.trunc(kk) const mix = (x, y) => (1 - k) * x + k * y const c = new Color([mix(a.red(), b.red()), mix(a.green(), b.green()), mix(a.blue(), b.blue())]) // console.log('default color', c.string()) return c.string() } export function unique(arr) { const u = {}, a = []; for(let i = 0, l = arr.length; i < l; ++i){ if(!u.hasOwnProperty(arr[i])) { a.push(arr[i]); u[arr[i]] = 1; } } return a; } export function linspace(a, b, n) { if (n===1) return [(a+b)/2] // single line const spacing = (b - a) / (n - 1) // console.log('spacing', a, b) const result = [] for (let i = 0; i < n; i++) result.push(a + i * spacing) return result; } export function intervalToSeconds(interval) { if (interval.endsWith('T')) throw Error('Tick intervals not supported') const unit = interval.endsWith('M') ? 30 * 24 * 60 * 60 : interval.endsWith('W') ? 7 * 24 * 60 * 60 : interval.endsWith('D') ? 24 * 60 * 60 : interval.endsWith('S') ? 1 : 60 // if no unit char, then it's minutes const result = parseInt(/\d+/.exec(interval)[0]) * unit // console.log('intervalToSeconds', interval, result) return result } export function interpolate(a, b, zeroToOne) { const d = (b-a) return a + d * zeroToOne } export function computeInterceptSlope(time0, price0, time1, price1) { if (!time0 || !price0 && price0 !== 0 || !time1 || !price1 && price1 !== 0) throw Error(`invalid line points data ${time0} ${price0} ${time1} ${price1}`) const t0 = time0 const t1 = time1 if (t0 === t1) throw Error("line points' times must be different") const slope = (price1 - price0) / (t1 - t0) const intercept = price1 - slope * t1 return [intercept, slope] } export function defined(v) { return v !== undefined && v !== null } export function toPrecision(value, significantDigits = 3) { if (!isFinite(value)) return value.toString(); // Handle Infinity and NaN if (value === 0) return "0"; // Special case for 0 const magnitude = Math.floor(Math.log10(Math.abs(value))); const decimalsNeeded = Math.max(0, significantDigits - 1 - magnitude); return value.toFixed(decimalsNeeded); // Use toFixed to completely avoid scientific notation }