diff --git a/cypress.config.js b/cypress.config.js
index bfc84190..553377ac 100644
--- a/cypress.config.js
+++ b/cypress.config.js
@@ -13,4 +13,5 @@ export default defineConfig({
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1',
viewportHeight: 800,
viewportWidth: 400,
+ defaultCommandTimeout: 20000,
});
diff --git a/src/components/ConfirmSwapModal.vue b/src/components/ConfirmSwapModal.vue
index 41080d3b..02bb7d65 100644
--- a/src/components/ConfirmSwapModal.vue
+++ b/src/components/ConfirmSwapModal.vue
@@ -44,7 +44,7 @@
{{ slippage }}%
diff --git a/src/components/explore/PairTable.vue b/src/components/explore/PairTable.vue
index 102821d1..39760831 100644
--- a/src/components/explore/PairTable.vue
+++ b/src/components/explore/PairTable.vue
@@ -58,15 +58,15 @@ export default {
value: pair.tvlUsd,
},
volumeDay: {
- text: formatUsdPretty(pair.volumeUsdDay, 0),
+ text: formatUsdPretty(pair.volumeUsdDay || 0, 0),
value: pair.volumeUsdDay,
},
volumeMonth: {
- text: formatUsdPretty(pair.volumeUsdMonth, 0),
+ text: formatUsdPretty(pair.volumeUsdMonth || 0, 0),
value: pair.volumeUsdMonth,
},
volumeAll: {
- text: formatUsdPretty(pair.volumeUsdAll, 0),
+ text: formatUsdPretty(pair.volumeUsdAll || 0, 0),
value: pair.volumeUsdAll,
},
}));
diff --git a/src/components/explore/PriceHistoryGraph.vue b/src/components/explore/PriceHistoryGraph.vue
index d90f8212..dacdfa37 100644
--- a/src/components/explore/PriceHistoryGraph.vue
+++ b/src/components/explore/PriceHistoryGraph.vue
@@ -4,10 +4,10 @@
v-for="label in labels"
:key="label"
class="p-16 hidden md:block"
- :fill="label === selectedChart ? 'light' : 'transparent'"
+ :fill="label.type === selectedChart.type ? 'light' : 'transparent'"
@click="changeChartContent(label)"
>
- {{ label }}
+ {{ label.text }}
@@ -18,7 +18,7 @@
@change="changeChartContent($event.target.value)"
>
@@ -71,7 +71,6 @@ import {
import { Line, Bar } from 'vue-chartjs';
import 'chartjs-adapter-date-fns';
import ButtonDefault from '@/components/ButtonDefault.vue';
-import BigNumber from 'bignumber.js';
const TIME_FRAMES = {
'1H': 1,
@@ -102,18 +101,27 @@ export default {
Bar,
},
props: {
- datasets: { type: Array, required: true },
- x: { type: Array, required: true },
- initialChart: { type: String, default: 'Volume' },
+ availableGraphTypes: { type: Array, required: true },
+ initialChart: { type: Object, required: true },
initialTimeFrame: { type: String, default: 'MAX' },
- loading: { type: Boolean, default: false },
+ pairId: { type: String, default: null },
+ tokenId: { type: String, default: null },
},
data() {
return {
selectedTimeFrame: null,
- selectedChart: null,
+ selectedChart: {
+ type: null,
+ text: null,
+ },
colors: ['red', 'green', 'blue', 'purple', 'orange'],
timeFrames: TIME_FRAMES,
+ graph: {
+ labels: [],
+ data: [],
+ graphType: null,
+ },
+ loading: false,
};
},
computed: {
@@ -137,7 +145,7 @@ export default {
offset: true,
min: Math.max(
Date.now() - 1000 * 60 * 60 * this.timeFrames[this.selectedTimeFrame],
- this.filteredData.filteredTime[0],
+ this.graph.labels[0],
),
max: Date.now(),
},
@@ -145,152 +153,22 @@ export default {
ticks: {
// Include a dollar sign in the ticks
callback: (value) =>
- ['TVL', 'Fees', 'Volume'].includes(this.selectedChart) ? `$${value}` : value,
+ ['TVL', 'Fees', 'Volume'].includes(this.selectedChart.type) ? `$${value}` : value,
},
},
},
};
},
labels() {
- return this.datasets.map((d) => d.label);
- },
- filteredData() {
- const selectedDataSet = this.datasets.find((d) => d.label === this.selectedChart);
- const minTime =
- this.selectedTimeFrame === 'MAX'
- ? Math.min(...this.x)
- : Date.now() - 1000 * 60 * 60 * this.timeFrames[this.selectedTimeFrame];
-
- const data = {
- filteredData: selectedDataSet.data
- .filter((_, i) => this.x[i] >= minTime)
- .filter((d) => !new BigNumber(d).isNaN()),
- excludedData: selectedDataSet.data
- .filter((_, i) => this.x[i] < minTime)
- .filter((d) => !new BigNumber(d).isNaN()),
- filteredTime: this.x
- .filter((_, i) => !new BigNumber(selectedDataSet.data[i]).isNaN())
- .filter((d) => d >= minTime)
- .map((d) => Number(d)),
- excludedTime: this.x
- .filter((_, i) => !new BigNumber(selectedDataSet.data[i]).isNaN())
- .filter((d) => d < minTime)
- .map((d) => Number(d)),
- };
-
- // interpolate data to show full frame
- if (
- (['TVL', 'Locked'].includes(this.selectedChart) || this.selectedChart.includes('Price')) &&
- data.excludedData.length > 0
- ) {
- // all of these are aggregated and summed, so we need to have baseline
- data.filteredData.unshift(data.excludedData.pop());
- data.filteredTime.unshift(minTime);
- data.excludedTime.pop();
- }
-
- if (['Volume', 'Fees'].includes(this.selectedChart) && data.filteredData.length > 0) {
- // these just show the last value, so we need to have a baseline for the graph time but no value
- // if there is no data, we do not need to add anything as we can show "no data"
- data.filteredData.unshift(0);
- data.filteredTime.unshift(minTime);
- }
-
- if (this.selectedChart.includes('Price')) {
- // as these always have current value, we need to add it to the end
- // theoretically this is also required for TVL and locked, but we interpolate those in the graph based
- // on the last value, so we don't need to add it here
- data.filteredData.push(data.filteredData[data.filteredData.length - 1]);
- data.filteredTime.push(Date.now());
- }
-
- return {
- ...data,
- selectedDataSet,
- minTime,
- };
+ return this.availableGraphTypes;
},
graphData() {
- // filter data based on selected time
- const { filteredTime, filteredData, selectedDataSet, minTime } = this.filteredData;
- if (
- (filteredData.length === 0 || filteredTime.length === 0 || !selectedDataSet) &&
- (this.selectedChart === 'Fees' || this.selectedChart === 'Volume')
- ) {
- return {
- labels: [],
- datasets: [],
- };
- }
-
- // aggregate data based on selected time
- // retrieve min time from data or default to selected view
- if (['TVL', 'Volume', 'Fees', 'Locked'].includes(this.selectedChart)) {
- // these three charts are bar charts, so we need to calculate buckets
- const bucketSize = (Date.now() - minTime) / 30;
-
- // seed empty buckets
- const emptyBuckets = Object.fromEntries(
- Array.from({ length: 31 }).map((_, i) => {
- const key = minTime + i * bucketSize;
- return [key, []];
- }),
- );
-
- const aggregatedData = filteredData.reduce((acc, d, i) => {
- const time = filteredTime[i];
- const bucketIndex = Math.floor((time - minTime) / bucketSize);
- const key = minTime + bucketIndex * bucketSize;
- acc[key].push(d);
- return acc;
- }, emptyBuckets);
- let bucketedData;
- // interpolate TVL
- if (['TVL', 'Locked'].includes(this.selectedChart)) {
- // average TVL
- let prevArr = [];
- bucketedData = Object.fromEntries(
- Object.entries(aggregatedData).map(([time, bucketArr], index) => {
- let aggregatedValue = bucketArr
- .reduce((acc, v) => acc.plus(v), new BigNumber(0))
- .div(bucketArr.length);
- // interpolate TVL by filling in missing data with latest value from previous bucket
- if (index > 0 && aggregatedValue.isNaN()) {
- aggregatedValue = prevArr[prevArr.length - 1];
- } else {
- prevArr = [...bucketArr];
- }
- return [time, aggregatedValue];
- }),
- );
- } else {
- // sum fees and volume
- bucketedData = Object.fromEntries(
- Object.entries(aggregatedData).map(([time, bucketArr]) => [
- time,
- bucketArr.reduce((acc, v) => acc.plus(v), new BigNumber(0)),
- ]),
- );
- }
- return {
- labels: Object.keys(bucketedData).map((x) => Number(x)),
- datasets: [
- {
- label: selectedDataSet.label,
- data: Object.values(bucketedData).map((y) => Number(y)),
- borderColor: 'rgb(0 255 157 / 80%)',
- backgroundColor: 'rgb(0 255 157 / 80%)',
- },
- ],
- };
- }
-
return {
- labels: filteredTime.map((x) => Number(x)),
+ labels: this.graph.labels.map((l) => Number(l)),
datasets: [
{
- label: selectedDataSet.label,
- data: filteredData.map((y) => Number(y)),
+ label: this.graph.graphType,
+ data: this.graph.data?.map((n) => Number(n)),
borderColor: 'rgb(0 255 157 / 80%)',
backgroundColor: 'rgb(0 255 157 / 80%)',
},
@@ -298,9 +176,12 @@ export default {
};
},
showBar() {
- return ['TVL', 'Volume', 'Fees', 'Locked'].includes(this.selectedChart);
+ return ['TVL', 'Volume', 'Fees', 'Locked'].includes(this.selectedChart.type);
},
},
+ async mounted() {
+ await this.fetchData();
+ },
created() {
this.selectedTimeFrame = this.initialTimeFrame;
this.selectedChart = this.initialChart;
@@ -308,9 +189,27 @@ export default {
methods: {
changeTimeFrame(newTimeFrame) {
this.selectedTimeFrame = newTimeFrame;
+ this.fetchData();
},
changeChartContent(newChart) {
this.selectedChart = newChart;
+ this.fetchData();
+ },
+ async fetchData() {
+ this.loading = true;
+ this.graph.data = [];
+ let options = {
+ graphType: this.selectedChart.type,
+ timeFrame: this.selectedTimeFrame,
+ };
+ if (this.pairId) {
+ options = { ...options, pairAddress: this.pairId };
+ }
+ if (this.tokenId) {
+ options = { ...options, tokenAddress: this.tokenId };
+ }
+ this.graph = await this.$store.dispatch('backend/fetchGraph', options);
+ this.loading = false;
},
},
};
diff --git a/src/components/explore/TokenTable.vue b/src/components/explore/TokenTable.vue
index e04b193e..85a2319d 100644
--- a/src/components/explore/TokenTable.vue
+++ b/src/components/explore/TokenTable.vue
@@ -74,15 +74,15 @@ export default {
value: token.priceChangeMonth,
},
volumeDay: {
- text: formatUsdPretty(token.volumeUsdDay, 0),
+ text: formatUsdPretty(token.volumeUsdDay || 0, 0),
value: token.volumeUsdDay,
},
volumeMonth: {
- text: formatUsdPretty(token.volumeUsdMonth, 0),
+ text: formatUsdPretty(token.volumeUsdMonth || 0, 0),
value: token.volumeUsdMonth,
},
volumeAll: {
- text: formatUsdPretty(token.volumeUsdAll, 0),
+ text: formatUsdPretty(token.volumeUsdAll || 0, 0),
value: token.volumeUsdAll,
},
}));
diff --git a/src/lib/swapUtils.js b/src/lib/swapUtils.js
index aeedbff8..723c4234 100644
--- a/src/lib/swapUtils.js
+++ b/src/lib/swapUtils.js
@@ -119,7 +119,7 @@ const getPriceImpactForPairReserves = (pairReserves, amountA) => {
const marketPrice = BigNumber(1).div(ratioFromPairReserves(pairReserves));
const newPrice = BigNumber(amountA).div(receivedB);
- return newPrice.minus(marketPrice).times(100).div(marketPrice).toNumber();
+ return -newPrice.minus(marketPrice).times(100).div(newPrice).toNumber();
};
/**
diff --git a/src/locales/en.json b/src/locales/en.json
index fd8d73b9..382b50ff 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -50,7 +50,7 @@
"to": "To",
"transactionDetails": "Transaction Details",
"liquidityProviderFee": "Liquidity Provider Fee",
- "priceImpact": "Price Impact",
+ "priceImpact": "Price Impact on {token}",
"allowedSlippage": "Allowed Slippage",
"minReceived": "Minimum received",
"maxSpent": "Maximum spent",
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 1f7b00dd..5c1092f1 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -42,7 +42,7 @@
"to": "Pour",
"transactionDetails": "détails de la transaction",
"liquidityProviderFee": "Frais de fournisseur de liquidité",
- "priceImpact": "Incidence sur les prix",
+ "priceImpact": "Incidence sur les prix de {token}",
"allowedSlippage": "Glissement autorisé",
"minReceived": "Minimum reçu",
"maxSpent": "Dépense maximale",
diff --git a/src/locales/ru.json b/src/locales/ru.json
index ca1d72ea..3d6cdecb 100644
--- a/src/locales/ru.json
+++ b/src/locales/ru.json
@@ -36,7 +36,7 @@
"to": "Получаете",
"transactionDetails": "Детали транзакции",
"liquidityProviderFee": "Комиссия поставщика ликвидности",
- "priceImpact": "Влияние на цену",
+ "priceImpact": "Влияние на цену {token}",
"allowedSlippage": "Допустимое проскальзывание",
"minReceived": "Минимум к получению",
"maxSpent": "Максимум к продаже",
diff --git a/src/locales/zh-cn.json b/src/locales/zh-cn.json
index 6ddf476c..c4f6e0fb 100644
--- a/src/locales/zh-cn.json
+++ b/src/locales/zh-cn.json
@@ -49,7 +49,7 @@
"to": "到",
"transactionDetails": "交换详情",
"liquidityProviderFee": "Liquidity Provider Fee",
- "priceImpact": "价格影响",
+ "priceImpact": "价格影响针对 {token}",
"allowedSlippage": "滑点",
"minReceived": "最小收益",
"maxSpent": "最多支付",
diff --git a/src/store/modules/dexBackend.js b/src/store/modules/dexBackend.js
index 0c590b96..cc4290e8 100644
--- a/src/store/modules/dexBackend.js
+++ b/src/store/modules/dexBackend.js
@@ -128,7 +128,7 @@ export default {
if (!pair) return null;
}
const resp = await dispatch('safeFetch', {
- url: `pairs/by-address/${pairAddress || pair.address}`,
+ url: `pairs/${pairAddress || pair.address}`,
});
return (
resp && {
@@ -144,7 +144,7 @@ export default {
},
async fetchSwapRoutes({ dispatch }, { tokenA, tokenB }) {
- return dispatch('safeFetch', { url: `pairs/swap-routes/${tokenA}/${tokenB}` });
+ return dispatch('safeFetch', { url: `swap-routes/${tokenA}/${tokenB}` });
},
async getListedTokens({ dispatch }) {
@@ -156,6 +156,10 @@ export default {
}));
},
+ async getTokenWithUsd({ dispatch }, tokenId) {
+ return dispatch('safeFetch', { url: `tokens/${tokenId}` });
+ },
+
async getAllTokens({ dispatch }) {
return dispatch('safeFetch', { url: 'tokens' });
},
@@ -194,11 +198,16 @@ export default {
},
async fetchPairsByToken({ dispatch }, tokenId) {
- return dispatch('safeFetch', { url: `tokens/by-address/${tokenId}/pairs` });
+ return dispatch('safeFetch', { url: `tokens/${tokenId}/pairs` });
},
async fetchPairsByTokenUsd({ dispatch }, tokenId) {
return dispatch('safeFetch', { url: `pairs?token=${tokenId}` });
},
+
+ async fetchGraph({ dispatch }, options) {
+ const queryString = new URLSearchParams(options).toString();
+ return dispatch('safeFetch', { url: `graph?${queryString}` });
+ },
},
};
diff --git a/src/views/AddLiquidity.vue b/src/views/AddLiquidity.vue
index e87bb495..1e89fc29 100644
--- a/src/views/AddLiquidity.vue
+++ b/src/views/AddLiquidity.vue
@@ -153,7 +153,10 @@ export default {
const amountTokenB = expandDecimals(this.amountTokenB, this.tokenB.decimals);
const amount = amountTokenA * amountTokenB;
const reserve = this.reserveTokenB * this.reserveTokenA;
- return BigNumber(amount).times(100).div(reserve).toNumber();
+ return BigNumber(amount)
+ .times(100)
+ .div(reserve + amount)
+ .toNumber();
},
ratio() {
if (!this.reserveTokenA || !this.reserveTokenB || !this.tokenA || !this.tokenB) {
diff --git a/src/views/ExploreView.vue b/src/views/ExploreView.vue
index fa30d11d..8d7aec7e 100644
--- a/src/views/ExploreView.vue
+++ b/src/views/ExploreView.vue
@@ -4,21 +4,15 @@
@@ -74,7 +68,6 @@
import { defineComponent } from 'vue';
import ExploreWrapper from '@/components/explore/ExploreWrapper.vue';
import { mapGetters } from 'vuex';
-import BigNumber from 'bignumber.js';
import PriceHistoryGraph from '@/components/explore/PriceHistoryGraph.vue';
import PairTable from '@/components/explore/PairTable.vue';
import TransactionTable from '@/components/explore/TransactionTable.vue';
@@ -97,17 +90,10 @@ export default defineComponent({
history: [],
tokenMap: new Map(),
activeTab: 'Tokens',
- loading: false,
};
},
computed: {
...mapGetters(['activeNetwork']),
- tvl() {
- return [this.graphData.datasets[0]];
- },
- volume() {
- return [this.graphData.datasets[1]];
- },
pairTable() {
return this.pairs.map((pair) => ({
...pair,
@@ -125,57 +111,14 @@ export default defineComponent({
tokenTable() {
return [...this.tokenMap.values()];
},
- graphData() {
- let tvl = new BigNumber(0);
- return this.history.reduce(
- (acc, tx) => {
- // TVL
- // deltaUsdValue is already calculated but absolute, so we need to check the deltaReserve to get the sign
- const delta0 = new BigNumber(tx.delta0UsdValue).times(Math.sign(tx.deltaReserve0));
- const delta1 = new BigNumber(tx.delta1UsdValue).times(Math.sign(tx.deltaReserve1));
- tvl = tvl.plus(delta0.isNaN() ? 0 : delta0).plus(delta1.isNaN() ? 0 : delta1);
- acc.datasets[0].data = [...acc.datasets[0].data, tvl.toString()].map((d) => d || 0);
-
- // VOLUME
- if (tx.type === 'SwapTokens') {
- acc.datasets[1].data = [
- ...acc.datasets[1].data,
- new BigNumber(tx.delta0UsdValue).plus(tx.delta1UsdValue).toString(),
- ].map((d) => d || 0);
- } else {
- acc.datasets[1].data = [...acc.datasets[1].data, 0].map((d) => d || 0);
- }
- acc.x = [...acc.x, tx.microBlockTime];
- return acc;
- },
- {
- x: [],
- datasets: [
- {
- label: 'TVL',
- data: [],
- },
- {
- label: 'Volume',
- data: [],
- },
- ],
- },
- );
- },
},
async mounted() {
- this.loading = true;
// fetch all tokens
const tokens = await this.$store.dispatch('backend/getAllTokens');
this.tokenMap = new Map(tokens.map((token) => [token.address, detectAndModifyWAE(token)]));
// fetch all pairs
const fetchResult = await this.$store.dispatch('backend/fetchPairs');
this.pairs = Object.values(fetchResult);
-
- // fetch all history
- this.history = await this.$store.dispatch('backend/fetchHistory');
- this.loading = false;
},
methods: {
pairToToken(pairAddress) {
diff --git a/src/views/PoolDetailView.vue b/src/views/PoolDetailView.vue
index b254eb22..ac531221 100644
--- a/src/views/PoolDetailView.vue
+++ b/src/views/PoolDetailView.vue
@@ -3,18 +3,35 @@