diff --git a/src/charts/chart.js b/src/charts/chart.js index 7c2f1d6..0f2321c 100644 --- a/src/charts/chart.js +++ b/src/charts/chart.js @@ -1,7 +1,7 @@ import {useChartOrderStore} from "@/orderbuild.js"; import {invokeCallbacks, prototype} from "@/common.js"; import {DataFeed, defaultSymbol, feelessTickerKey, getAllSymbols, lookupSymbol} from "@/charts/datafeed.js"; -import {intervalToSeconds, SingletonCoroutine} from "@/misc.js"; +import {intervalToSeconds, SingletonCoroutine, toHuman, toPrecision} from "@/misc.js"; import {usePrefStore, useStore} from "@/store/store.js"; import {tvCustomThemes} from "../../theme.js"; @@ -83,51 +83,49 @@ const subscribeEvents = [ */ -let feeDropdown = null +let poolButtonTextElement = null -export function initFeeDropdown(w) { - widget = w - widget.createDropdown( - { - title: 'Fees', - tooltip: 'Choose Fee Tier', - items: [/*{title: 'Automatic Fee Selection', onSelect: () => {log('autofees')}}*/], - icon: ``, - } - ).then(dropdown => { - feeDropdown = dropdown; - updateFeeDropdown() - }) +export function initFeeDropdown() { + const button = widget.createButton() + button.setAttribute('title', 'See Pool Info and Choose Fee'); + button.addEventListener('click', function() { co.showPoolSelection = true }); + button.style.height = '34px'; + button.style.display = 'flex'; + button.style.alignItems = 'center'; + button.addEventListener('mouseover', () => { + button.style.backgroundColor = 'rgb(60,60,60)'; + }); + button.addEventListener('mouseout', () => { + button.style.backgroundColor = ''; + }); + button.id = 'pool-button' + button.style.margin = '2px 0'; + button.style.borderRadius = '4px'; + button.classList.add('pool-button') + + const span = document.createElement('span'); + span.textContent = 'Pool'; + span.style.marginY = 'auto'; + button.appendChild(span); + poolButtonTextElement = span + + updateFeeDropdown() } export function updateFeeDropdown() { - if (feeDropdown === null) return + if (poolButtonTextElement===null) return const symbolItem = useChartOrderStore().selectedSymbol - let items - if (symbolItem === null) - items = [{title: '0.00%'}] - else { - const feeGroup = symbolItem.feeGroup - items = feeGroup.map((p) => { - const [_addr, fee] = p - return { - title: (fee / 10000).toFixed(2) + '%', - onSelect: ()=>{ - if (fee !== symbolItem.fee) - selectPool(fee) - }, - } - }) + let text = 'Pool ' + text += (symbolItem.fee / 10000).toFixed(2) + '%' + const index = symbolItem.feeGroup.findIndex((p) => p[1] === symbolItem.fee) + if (symbolItem.liquiditySymbol) { + const liq = symbolItem.liquidities[index] + if (symbolItem.liquiditySymbol === 'USD') + text += ` $${toHuman(liq)}` + else + text = ` ${toHuman(liq)} ${symbolItem.liquiditySymbol}` } - feeDropdown.applyOptions({items}) -} - -function selectPool(fee) { - const co = useChartOrderStore(); - const s = co.selectedSymbol; - const ticker = feelessTickerKey(s.ticker) + '|' + fee - if (ticker !== s.ticker) - setSymbolTicker(ticker).catch((e)=>console.error('Could not change TV symbol to', ticker)) + poolButtonTextElement.textContent = text } export function initWidget(el) { @@ -145,9 +143,9 @@ export function initWidget(el) { container: el, datafeed: DataFeed, // use this for ohlc locale: "en", - disabled_features: [], - enabled_features: ['saveload_separate_drawings_storage'], - drawings_access: {type: 'white', tools: [],}, // show no tools + disabled_features: ['main_series_scale_menu',], + enabled_features: ['saveload_separate_drawings_storage',], + // drawings_access: {type: 'white', tools: [],}, // show no tools custom_themes: tvCustomThemes, theme: useStore().theme, timezone: prefs.timezone, @@ -155,7 +153,7 @@ export function initWidget(el) { // Chart Overrides // https://www.tradingview.com/charting-library-docs/latest/customization/overrides/chart-overrides overrides: { - // "mainSeriesProperties.priceAxisProperties.log": false, + "mainSeriesProperties.priceAxisProperties.log": false, } }); @@ -168,7 +166,7 @@ export function initWidget(el) { widget.subscribe('onSelectedLineToolChanged', onSelectedLineToolChanged) widget.subscribe('mouse_down', mouseDown) widget.subscribe('mouse_up', mouseUp) - widget.headerReady().then(()=>initFeeDropdown(widget)) + widget.headerReady().then(()=>initFeeDropdown()) widget.onChartReady(initChart) console.log('tv widget initialized') } diff --git a/src/charts/datafeed.js b/src/charts/datafeed.js index 4087d0b..f38c978 100644 --- a/src/charts/datafeed.js +++ b/src/charts/datafeed.js @@ -6,7 +6,9 @@ import {useStore} from "@/store/store.js"; import {subOHLC, unsubOHLC} from "@/blockchain/ohlcs.js"; import {ohlcStart} from "@/charts/chart-misc.js"; -import {timestamp} from "@/common.js"; +import {timestamp, USD_FIAT} from "@/common.js"; +import {erc20Contract} from "@/blockchain/contract.js"; +import {provider} from "@/blockchain/wallet.js"; const DEBUG_LOGGING = false const log = DEBUG_LOGGING ? console.log : ()=>{} @@ -131,9 +133,9 @@ function addSymbol(chainId, p, base, quote, inverted) { else feeGroups[feelessKey] = [[symbolInfo.address, symbolInfo.fee]] symbolInfo.feeGroup = feeGroups[feelessKey] - if (defaultSymbol===null) { - console.log(`invertedDefault(${symbolInfo.base.s}, ${symbolInfo.quote.s})`,invertedDefault(symbolInfo.base.a, symbolInfo.quote.a)) - } + // if (defaultSymbol===null) { + // console.log(`invertedDefault(${symbolInfo.base.s}, ${symbolInfo.quote.s})`,invertedDefault(symbolInfo.base.a, symbolInfo.quote.a)) + // } if (defaultSymbol===null && !invertedDefault(symbolInfo.base.a, symbolInfo.quote.a)) { console.log('setting default symbol', symbolInfo.base.s, symbolInfo.quote.s, symbolInfo.base.a, symbolInfo.quote.a) defaultSymbol = _symbols[ticker] @@ -384,12 +386,45 @@ export const DataFeed = { } const co = useChartOrderStore(); co.selectedSymbol = symbolItem - const feelessKey = feelessTickerKey(symbolItem.ticker) - const symbolsByFee = feeGroups[feelessKey] - symbolsByFee.sort((a,b)=>a.fee-b.fee) - const pool = symbolsByFee[Math.floor((symbolsByFee.length - 1)/2)] // median rounded down - // noinspection JSValidateTypes - co.selectedPool = pool // todo remove + + let ticker = symbolItem.ticker + try { + if (!symbolItem.liquiditySymbol) { + // fetch liquidities and cache on the symbolItem + const inv = invertedDefault(symbolItem.base.a, symbolItem.quote.a) + const markToken = inv ? symbolItem.base : symbolItem.quote + const mark = useStore().markPrice(markToken.a) + const token = await erc20Contract(markToken.a, provider) + const liquidities = await Promise.all(symbolItem.feeGroup.map( + async ([addr, fee]) => await token.balanceOf(addr) + )) + symbolItem.liquidities = liquidities.map(l => Number(l / 10n ** BigInt(markToken.d))) + if (mark) { + symbolItem.liquidities = symbolItem.liquidities.map(l => l * mark) + symbolItem.liquiditySymbol = 'USD' + } else { + symbolItem.liquiditySymbol = symbolItem.quote.s + } + } + const liqsAndFees = [] + for (let i=0; i b[0] - a[0]) + const highestLiquidityFee = liqsAndFees[0][1] + // console.log('liquidities', liqsAndFees) + // console.log('best liquidity', highestLiquidityFee, liqsAndFees[0][0], symbolItem.liquiditySymbol) + ticker = feelessTickerKey(ticker) + '|' + highestLiquidityFee + } + catch (error) { + // use the median fee group instead + console.log('liquidity fetch error', error) + } + // LibrarySymbolInfo // https://www.tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.LibrarySymbolInfo const symbolInfo = { diff --git a/src/charts/shape.js b/src/charts/shape.js index 9227c32..cab0825 100644 --- a/src/charts/shape.js +++ b/src/charts/shape.js @@ -339,7 +339,7 @@ export class Shape { onPoints(points) {} // the control points of an existing shape were changed - onDrag(points) { console.log('shape ondrag'); this.onPoints(points) } + onDrag(points) { this.onPoints(points) } setProps(props) { if (!props || Object.keys(props).length===0) return diff --git a/src/common.js b/src/common.js index b2f4aca..ce73519 100644 --- a/src/common.js +++ b/src/common.js @@ -1,4 +1,5 @@ export const NATIVE_TOKEN = '0x0000000000000000000000000000000000000001' +export const USD_FIAT = '0x0000000000000000000000000000000000000055' // We use 0x55 (ASCII 'U') to indicate the use of fiat USD export function mixin(child, ...parents) { // child is modified directly, assigning fields from parents that are missing in child. parents fields are diff --git a/src/components/CopyButton.vue b/src/components/CopyButton.vue index 4b9345e..e90272e 100644 --- a/src/components/CopyButton.vue +++ b/src/components/CopyButton.vue @@ -1,14 +1,16 @@ - + + + {{ text }} + - {{text}} - + Copied! @@ -17,7 +19,7 @@ + + + + diff --git a/src/components/ScannerButton.vue b/src/components/ScannerButton.vue new file mode 100644 index 0000000..6bcbb11 --- /dev/null +++ b/src/components/ScannerButton.vue @@ -0,0 +1,34 @@ + + + {{addr}} + + + + + + + diff --git a/src/components/chart/ChartPlaceOrder.vue b/src/components/chart/ChartPlaceOrder.vue index da7fa96..2cfe237 100644 --- a/src/components/chart/ChartPlaceOrder.vue +++ b/src/components/chart/ChartPlaceOrder.vue @@ -75,7 +75,7 @@ function changeSymbol(symbol) { console.log('changeSymbol', symbol) os.tokenA = symbol.base os.tokenB = symbol.quote - routeFinder.invoke() + // routeFinder.invoke() } diff --git a/src/layouts/chart/ChartLayout.vue b/src/layouts/chart/ChartLayout.vue index 06cb7ec..4674957 100644 --- a/src/layouts/chart/ChartLayout.vue +++ b/src/layouts/chart/ChartLayout.vue @@ -10,6 +10,7 @@ Signed and sent! Waiting for blockchain confirmation... + @@ -20,6 +21,7 @@ import {computed} from "vue"; import {useWalletStore} from "@/blockchain/wallet.js"; import {TransactionState, TransactionType} from "@/blockchain/transaction.js"; import {FixedNumber} from "ethers"; +import PoolSelectionDialog from "@/components/PoolSelectionDialog.vue"; const s = useStore() const ws = useWalletStore() diff --git a/src/misc.js b/src/misc.js index 2dcc96a..beac44f 100644 --- a/src/misc.js +++ b/src/misc.js @@ -41,7 +41,7 @@ export class SingletonCoroutine { // console.log('invoke', arguments) if (this.timeout === null) // noinspection JSCheckFunctionSignatures - this.timeout = setTimeout(this.onTimeout, this.delay, this) +this.timeout = setTimeout(this.onTimeout, this.delay, this) } async onTimeout(self) { @@ -260,6 +260,24 @@ export function toPrecision(value, significantDigits = 3) { return value.toFixed(decimalsNeeded); // Use toFixed to completely avoid scientific notation } +export function toHuman(value, significantDigits = 2) { + if (!isFinite(value)) return value.toString(); // Handle Infinity and NaN + let suffix = '' + if (value >= 1_000_000_000) { + value /= 1_000_000_000 + suffix = 'B' + } + else if (value >= 1_000_000) { + value /= 1_000_000 + suffix = 'M' + } + else if (value >= 1_000) { + value /= 1_000 + suffix = 'K' + } + return toPrecision(value, significantDigits) + suffix +} + export function errorSuggestsMissingVault(e) { return e.value === '0x' && e.code === 'BAD_DATA' || e.revert === null && e.code === 'CALL_EXCEPTION'; } diff --git a/src/orderbuild.js b/src/orderbuild.js index b3e98e5..c891d86 100644 --- a/src/orderbuild.js +++ b/src/orderbuild.js @@ -45,6 +45,7 @@ function newDefaultOrder() { export const useChartOrderStore = defineStore('chart_orders', () => { const chartReady = ref(false) + const showPoolSelection = ref(false) // if true, the pool information / fee choosing dialog is shown const defaultOrder = newDefaultOrder() const orders = ref([defaultOrder]) // order models in UI format const selectedOrder = ref(null) @@ -96,6 +97,7 @@ export const useChartOrderStore = defineStore('chart_orders', () => { return { chartReady, selectedSymbol, intervalSecs, baseToken, quoteToken, price, orders, drawing, newOrder, removeOrder, resetOrders, meanRange, + showPoolSelection, } })