243 lines
6.9 KiB
JavaScript
243 lines
6.9 KiB
JavaScript
import {FixedNumber} from "ethers";
|
|
import {usePrefStore, useStore} from "@/store/store.js";
|
|
import {token} from "@/blockchain/token.js";
|
|
import Color from "color";
|
|
|
|
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 = 4294967295n
|
|
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`
|
|
}
|
|
|
|
const _dateFormat = new Intl.DateTimeFormat(undefined, {dateStyle: 'medium', timeStyle: 'short'})
|
|
|
|
export function dateString(seconds) {
|
|
const date = new Date(seconds * 1000)
|
|
return _dateFormat.format(date)
|
|
}
|
|
|
|
|
|
export function timestamp(date = null) {
|
|
if (date === null)
|
|
date = new Date()
|
|
return Math.round(date.getTime() / 1000)
|
|
}
|
|
|
|
|
|
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];
|
|
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
|
|
}
|
|
// console.log('inversion preference', base, quote, prefs.inverted[key], inputInverted)
|
|
return prefs.inverted[key] !== inputInverted
|
|
}
|
|
|
|
|
|
export function pairPrice(chainId, baseToken, quoteToken, price) {
|
|
if (price === null || price === undefined)
|
|
return null
|
|
const decimals = quoteToken.d - baseToken.d
|
|
// console.log('pairPrice', chainId, baseToken, quoteToken, price, decimals)
|
|
if (decimals >= 0)
|
|
price /= 10 ** decimals
|
|
else
|
|
price *= 10 ** -decimals
|
|
// console.log('adjusted pairPrice', price)
|
|
if (inversionPreference(chainId, baseToken, quoteToken))
|
|
price = 1 / price
|
|
// console.log('inverted?', price)
|
|
return price
|
|
}
|
|
|
|
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().lightness(lightness).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) {
|
|
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')
|
|
return 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
|
|
}
|
|
|