diff --git a/Jenkinsfile b/Jenkinsfile index 1e42ffb4f..da3bdcfe7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -34,7 +34,7 @@ def pipeline = new org.js.AppPipeline(steps: this, noIndex: true, sonarSrcPath: 'src', sonarTestsPath: 'tests', - dojoProductType: 'sora', + dojoProductType: 'polkaswap', movingFiles: [ "*":"./", ".well-known/":"./"] ) pipeline.runPipeline() diff --git a/env.json b/env.json index 82b24c258..df6b2593e 100644 --- a/env.json +++ b/env.json @@ -12,7 +12,8 @@ "moonpay": true, "x1ex": false, "charts": true, - "soraCard": false + "soraCard": false, + "orderBook": false }, "SUBQUERY_ENDPOINT": "https://api.subquery.network/sq/sora-xor/sora-prod-sub4", "SUBSQUID_ENDPOINT": "", diff --git a/package.json b/package.json index 6f69df475..01070ec9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "polkaswap-exchange-web", - "version": "1.28.3", + "version": "1.29.0", "repository": { "type": "git", "url": "https://github.com/sora-xor/polkaswap-exchange-web.git" @@ -24,14 +24,14 @@ }, "dependencies": { "@metamask/detect-provider": "^2.0.0", - "@soramitsu/soraneo-wallet-web": "1.28.0", + "@soramitsu/soraneo-wallet-web": "1.29.4", "@walletconnect/ethereum-provider": "^2.11.0", "@walletconnect/modal": "^2.6.2", "core-js": "^3.33.2", "country-code-emoji": "^2.3.0", "country-flag-emoji-polyfill": "^0.1.4", "direct-vuex": "^0.12.1", - "echarts": "^5.4.1", + "echarts": "^5.4.3", "email-validator": "^2.0.4", "ethers": "6.8.0", "jwt-decode": "^3.1.2", @@ -40,10 +40,10 @@ "vue": "2.7.14", "vue-class-component": "^7.2.6", "vue-echarts": "^6.3.3", - "vue-i18n": "^8.11.2", + "vue-i18n": "^8.28.2", "vue-property-decorator": "^9.1.2", "vue-router": "^3.6.5", - "vuex": "^3.1.3" + "vuex": "^3.6.2" }, "devDependencies": { "@babel/runtime": "^7.23.2", @@ -65,29 +65,29 @@ "@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-standard": "^6.1.0", "@vue/eslint-config-typescript": "^8.0.0", - "@vue/test-utils": "^1.2.2", - "@vue/vue2-jest": "^27.0.0-alpha.2", + "@vue/test-utils": "^1.3.6", + "@vue/vue2-jest": "^27.0.0", "babel-plugin-require-context-hook": "^1.0.0", "css-unicode-loader": "^1.0.3", "electron": "^13.0.0", - "electron-devtools-installer": "^3.1.0", - "eslint": "^7.29.0", + "electron-devtools-installer": "^3.2.0", + "eslint": "^7.32.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^5.2.0", "eslint-plugin-standard": "^5.0.0", "eslint-plugin-vue": "^7.20.0", - "jest": "^27.2.2", - "jsdom": "^16.6.0", + "jest": "^27.5.1", + "jsdom": "^16.7.0", "lint-staged": "^9.5.0", "node-polyfill-webpack-plugin": "^2.0.1", - "prettier": "^2.2.1", + "prettier": "^2.8.8", "sass": "^1.66.1", "sass-loader": "^13.3.2", - "ts-jest": "^27.0.5", + "ts-jest": "^27.1.5", "typescript": "~5.2.2", - "vue-cli-plugin-electron-builder": "^3.0.0-alpha.3", + "vue-cli-plugin-electron-builder": "^3.0.0-alpha.4", "vue-cli-plugin-test-attrs": "^0.1.5", "vue-template-compiler": "2.7.14" }, diff --git a/public/env.json b/public/env.json index c5029c8dc..7386257fd 100644 --- a/public/env.json +++ b/public/env.json @@ -12,7 +12,8 @@ "moonpay": true, "x1ex": false, "charts": true, - "soraCard": true + "soraCard": false, + "orderBook": true }, "FAUCET_URL": "https://faucet.dev.sora2.tachi.soramitsu.co.jp/", "DEFAULT_NETWORKS": [ @@ -34,7 +35,7 @@ "address": "wss://ws.framenode-3.r0.dev.sora2.soramitsu.co.jp" } ], - "SUBQUERY_ENDPOINT": "https://subquery.sq1.dev.sora2.soramitsu.co.jp", + "SUBQUERY_ENDPOINT": "https://api.subquery.network/sq/sora-xor/sora-dev", "SUBSQUID_ENDPOINT": "https://squid.subsquid.io/sora-dev/v/v1/graphql", "NETWORK_TYPE": "Dev", "CHAIN_GENESIS_HASH": "", diff --git a/public/marketing.json b/public/marketing.json index 96351c738..95504f8fa 100644 --- a/public/marketing.json +++ b/public/marketing.json @@ -1,9 +1,4 @@ [ - { - "title": "ROADMAP 2024 SURVEY", - "img": "/marketing/survey.png", - "link": "https://form.typeform.com/to/Mb6p2Kpy" - }, { "title": "GET SORA CARD", "img": "/marketing/card.png", diff --git a/public/marketing/survey.png b/public/marketing/survey.png deleted file mode 100644 index 1c3784620..000000000 Binary files a/public/marketing/survey.png and /dev/null differ diff --git a/src/App.vue b/src/App.vue index a9e1dcfc9..493844371 100644 --- a/src/App.vue +++ b/src/App.vue @@ -66,8 +66,8 @@ import type { ConnectToNodeOptions, Node } from './types/nodes'; import type { History, HistoryItem } from '@sora-substrate/util'; import type { WhitelistArrayItem } from '@sora-substrate/util/build/assets/types'; import type { EvmNetwork } from '@sora-substrate/util/build/bridgeProxy/evm/types'; -import type DesignSystem from '@soramitsu/soramitsu-js-ui/lib/types/DesignSystem'; -import type Theme from '@soramitsu/soramitsu-js-ui/lib/types/Theme'; +import type DesignSystem from '@soramitsu-ui/ui-vue2/lib/types/DesignSystem'; +import type Theme from '@soramitsu-ui/ui-vue2/lib/types/Theme'; @Component({ components: { @@ -392,6 +392,8 @@ html { font-size: var(--s-font-size-small); line-height: var(--s-line-height-base); letter-spacing: var(--s-letter-spacing-small); + background-color: var(--s-color-utility-body); + scrollbar-color: transparent transparent; } ul ul { @@ -550,6 +552,18 @@ i.icon-divider { @include icon-styles; } +.app-main--orderbook { + .app-menu { + position: absolute; + right: initial; + } + + .app-content { + display: flex; + justify-content: center; + } +} + @include desktop { .app-main { &.app-main--swap.app-main--has-charts { diff --git a/src/assets/fonts/polkaswap_icons.ttf b/src/assets/fonts/polkaswap_icons.ttf index 59aa1dc04..fe862b8c1 100644 Binary files a/src/assets/fonts/polkaswap_icons.ttf and b/src/assets/fonts/polkaswap_icons.ttf differ diff --git a/src/components/App/Footer/AppFooter.vue b/src/components/App/Footer/AppFooter.vue index ed1af754d..6f916ece8 100644 --- a/src/components/App/Footer/AppFooter.vue +++ b/src/components/App/Footer/AppFooter.vue @@ -65,8 +65,8 @@ @@ -204,6 +211,10 @@ export default class AppMenu extends Mixins(TranslationMixin) { } } + .collapse-button { + pointer-events: none; + } + &:hover, &:focus { box-shadow: 20px 20px 60px 0px #0000001a; @@ -213,6 +224,10 @@ export default class AppMenu extends Mixins(TranslationMixin) { display: initial; } } + + .collapse-button { + pointer-events: all; + } } } } @@ -286,9 +301,6 @@ export default class AppMenu extends Mixins(TranslationMixin) { &:focus { background-color: unset !important; } - i.el-icon-bank-card { - width: 28px; // to avoid issue with paddings - } } } @@ -346,6 +358,7 @@ export default class AppMenu extends Mixins(TranslationMixin) { @include large-mobile(true) { position: fixed; + right: 0; &.visible { visibility: visible; @@ -368,10 +381,6 @@ export default class AppMenu extends Mixins(TranslationMixin) { } } - @include large-mobile(true) { - right: 0; - } - @include large-mobile { visibility: visible; position: relative; @@ -409,6 +418,8 @@ export default class AppMenu extends Mixins(TranslationMixin) { flex: 1; flex-flow: column nowrap; justify-content: space-between; + max-width: $sidebar-max-width; + padding-right: $inner-spacing-mini; // for shadow } } } @@ -480,4 +491,3 @@ export default class AppMenu extends Mixins(TranslationMixin) { } } -@/modules/staking/demeter/consts diff --git a/src/components/mixins/ChartSpecMixin.ts b/src/components/mixins/ChartSpecMixin.ts index a673a0af0..7c9121aa8 100644 --- a/src/components/mixins/ChartSpecMixin.ts +++ b/src/components/mixins/ChartSpecMixin.ts @@ -18,7 +18,6 @@ const AXIS_LABEL_CSS = { export default class ChartSpecMixin extends Mixins(ThemePaletteMixin, TranslationMixin) { gridSpec(options: any = {}) { return merge({ - top: 20, left: 0, right: 0, bottom: 20 + AXIS_OFFSET, diff --git a/src/components/mixins/ExplorePageMixin.ts b/src/components/mixins/ExplorePageMixin.ts index 3ade24223..236874e65 100644 --- a/src/components/mixins/ExplorePageMixin.ts +++ b/src/components/mixins/ExplorePageMixin.ts @@ -1,23 +1,16 @@ -import SScrollbar from '@soramitsu/soramitsu-js-ui/lib/components/Scrollbar'; -import { SortDirection } from '@soramitsu/soramitsu-js-ui/lib/components/Table/consts'; -import { mixins, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; -import Vue from 'vue'; -import { Component, Mixins, Prop, Ref, Watch } from 'vue-property-decorator'; +import { KnownAssets } from '@sora-substrate/util/build/assets/consts'; +import { SortDirection } from '@soramitsu-ui/ui-vue2/lib/components/Table/consts'; +import { Component, Mixins, Prop, Watch } from 'vue-property-decorator'; import { getter } from '@/store/decorators'; -import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; +import ScrollableTableMixin from './ScrollableTableMixin'; +import TranslationMixin from './TranslationMixin'; + +import type { Asset, RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; @Component -export default class ExplorePageMixin extends Mixins( - mixins.LoadingMixin, - mixins.PaginationSearchMixin, - mixins.FormattedAmountMixin -) { - readonly FontSizeRate = WALLET_CONSTS.FontSizeRate; - readonly FontWeightRate = WALLET_CONSTS.FontWeightRate; - - @Ref('table') readonly tableComponent!: any; +export default class ExplorePageMixin extends Mixins(ScrollableTableMixin, TranslationMixin) { @Prop({ default: '', type: String }) readonly exploreQuery!: string; @Prop({ default: false, type: Boolean }) readonly isAccountItemsOnly!: boolean; @Watch('exploreQuery') @@ -25,16 +18,25 @@ export default class ExplorePageMixin extends Mixins( this.currentPage = 1; } - @getter.wallet.account.isLoggedIn isLoggedIn!: boolean; + @getter.wallet.account.isLoggedIn public isLoggedIn!: boolean; @getter.assets.assetDataByAddress public getAsset!: (addr?: string) => Nullable; + @getter.assets.whitelistAssets public whitelistAssets!: Array; - order = ''; - property = ''; + order = SortDirection.DESC; + property = 'tvl'; get loadingState(): boolean { return this.parentLoading || this.loading; } + get allowedAssets(): Array { + // if whitelist is not available, use KnownAssets + if (!this.whitelistAssets.length) { + return [...KnownAssets]; + } + return this.whitelistAssets; + } + get pricesAvailable(): boolean { return Object.keys(this.fiatPriceObject).length > 0; } @@ -43,26 +45,23 @@ export default class ExplorePageMixin extends Mixins( return !(this.order && this.property); } - get preparedItems(): any[] { - console.warn('[ExplorePageMixin]: "preparedItems" computed property is not implemented'); + // items -> prefilteredItems -> filteredItems -> preparedItems + get prefilteredItems(): any[] { + console.warn('[ExplorePageMixin]: "prefilteredItems" computed property is not implemented'); return []; } - get total(): number { - return this.filteredItems.length; - } - get filteredItems() { const search = this.exploreQuery.toLowerCase().trim(); - if (!search) return this.preparedItems; + if (!search) return this.prefilteredItems; const filterAsset = (asset): boolean => asset?.name?.toLowerCase?.()?.includes?.(search) || asset?.symbol?.toLowerCase?.()?.includes?.(search) || asset?.address?.toLowerCase?.() === search; - return this.preparedItems.filter( + return this.prefilteredItems.filter( (item: any) => filterAsset(item) || filterAsset(item.poolAsset) || @@ -72,7 +71,7 @@ export default class ExplorePageMixin extends Mixins( ); } - get sortedItems() { + get preparedItems() { if (this.isDefaultSort) return this.filteredItems; const isAscending = this.order === SortDirection.ASC; @@ -87,19 +86,11 @@ export default class ExplorePageMixin extends Mixins( }); } - get tableItems() { - return this.getPageItems(this.sortedItems); - } - async mounted(): Promise { - await this.$nextTick(); - - this.initScrollbar(); - await this.updateExploreData(); } - changeSort({ order = '', property = '' } = {}): void { + changeSort({ order = SortDirection.DESC, property = '' } = {}): void { this.order = order; this.property = property; } @@ -111,54 +102,4 @@ export default class ExplorePageMixin extends Mixins( async updateExploreData(): Promise { console.warn('[ExplorePageMixin]: "updateExploreData" method is not implemented'); } - - handlePaginationClick(button: WALLET_CONSTS.PaginationButton): void { - let current = 1; - - switch (button) { - case WALLET_CONSTS.PaginationButton.Prev: - current = this.currentPage - 1; - break; - case WALLET_CONSTS.PaginationButton.Next: - current = this.currentPage + 1; - break; - case WALLET_CONSTS.PaginationButton.First: - current = 1; - break; - case WALLET_CONSTS.PaginationButton.Last: - current = this.lastPage; - } - - this.currentPage = current; - } - - private initScrollbar(): void { - if (!this.tableComponent) return; - - const Scrollbar = Vue.extend(SScrollbar); - const scrollbar = new Scrollbar(); - scrollbar.$mount(); - - const elTable = this.tableComponent.$refs.table; - const elTableBodyWrapper = elTable.$refs.bodyWrapper; - const elTableHeaderWrapper = elTable.$refs.headerWrapper; - const elTableNativeTable = elTableBodyWrapper.getElementsByTagName('table')[0]; - const scrollbarWrap = scrollbar.$el.getElementsByClassName('el-scrollbar__wrap')[0]; - const scrollbarView = scrollbar.$el.getElementsByClassName('el-scrollbar__view')[0]; - - elTableBodyWrapper.appendChild(scrollbar.$el); - scrollbarView.appendChild(elTableNativeTable); - - this.$watch( - () => (scrollbar.$children[0] as any).moveX, - () => { - const scrollLeft = scrollbarWrap.scrollLeft; - // to scroll table content - elTableBodyWrapper.scrollLeft = scrollLeft; - elTableHeaderWrapper.scrollLeft = scrollLeft; - // to render box shadow on fixed table - elTable.scrollPosition = scrollLeft === 0 ? 'left' : 'right'; - } - ); - } } diff --git a/src/components/mixins/ScrollableTableMixin.ts b/src/components/mixins/ScrollableTableMixin.ts new file mode 100644 index 000000000..8ca6e6aee --- /dev/null +++ b/src/components/mixins/ScrollableTableMixin.ts @@ -0,0 +1,89 @@ +import { mixins, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; +import SScrollbar from '@soramitsu-ui/ui-vue2/lib/components/Scrollbar'; +import Vue from 'vue'; +import { Component, Mixins, Ref } from 'vue-property-decorator'; + +@Component +export default class ScrollableTableMixin extends Mixins( + mixins.LoadingMixin, + mixins.PaginationSearchMixin, + mixins.FormattedAmountMixin +) { + readonly FontSizeRate = WALLET_CONSTS.FontSizeRate; + readonly FontWeightRate = WALLET_CONSTS.FontWeightRate; + + @Ref('table') readonly tableComponent!: any; + + get loadingState(): boolean { + return this.parentLoading || this.loading; + } + + // should be already filtered & sorted + get preparedItems(): any[] { + console.warn('[ScrollableTableMixin]: "preparedItems" computed property is not implemented'); + return []; + } + + get total(): number { + return this.preparedItems.length; + } + + get tableItems() { + return this.getPageItems(this.preparedItems); + } + + async mounted(): Promise { + await this.$nextTick(); + this.initScrollbar(); + } + + public handlePaginationClick(button: WALLET_CONSTS.PaginationButton): void { + let current = 1; // First by default (instead of case WALLET_CONSTS.PaginationButton.First) + + switch (button) { + case WALLET_CONSTS.PaginationButton.Prev: + current = this.currentPage - 1; + break; + case WALLET_CONSTS.PaginationButton.Next: + current = this.currentPage + 1; + break; + case WALLET_CONSTS.PaginationButton.Last: + current = this.lastPage; + break; + } + + this.currentPage = current; + } + + public initScrollbar(): void { + if (!this.tableComponent) return; + + const Scrollbar = Vue.extend(SScrollbar); + const scrollbar = new Scrollbar(); + scrollbar.$mount(); + + const elTable = this.tableComponent.$refs.table; + const elTableBodyWrapper = elTable.$refs.bodyWrapper; + const elTableHeaderWrapper = elTable.$refs.headerWrapper; + const elTableNativeTable = elTableBodyWrapper.getElementsByTagName('table')[0]; + const scrollbarContainer = scrollbar.$el; + const scrollbarWrap = scrollbar.$el.getElementsByClassName('el-scrollbar__wrap')[0]; + const scrollbarView = scrollbar.$el.getElementsByClassName('el-scrollbar__view')[0]; + + scrollbarContainer.classList.add('scrollable-table'); + elTableBodyWrapper.appendChild(scrollbar.$el); + scrollbarView.appendChild(elTableNativeTable); + + this.$watch( + () => (scrollbar.$children[0] as any).moveX, + () => { + const scrollLeft = scrollbarWrap.scrollLeft; + // to scroll table content + elTableBodyWrapper.scrollLeft = scrollLeft; + elTableHeaderWrapper.scrollLeft = scrollLeft; + // to render box shadow on fixed table + elTable.scrollPosition = scrollLeft === 0 ? 'left' : 'right'; + } + ); + } +} diff --git a/src/components/mixins/SelectedTokensRouteMixin.ts b/src/components/mixins/SelectedTokensRouteMixin.ts index 93c9b9bca..fd6fee74e 100644 --- a/src/components/mixins/SelectedTokensRouteMixin.ts +++ b/src/components/mixins/SelectedTokensRouteMixin.ts @@ -36,6 +36,10 @@ export default class SelectedTokenRouteMixin extends Vue { const bothArePresented = !!(firstAddress && secondAddress); switch (this.$route.name) { + case PageNames.OrderBook: + // Second asset address should be used as quote for Orderbook /trade/base/quote + // only XOR for now, like /trade/xst/xor + return bothArePresented && secondAddress === XOR.address; case PageNames.RemoveLiquidity: return bothArePresented && api.dex.baseAssetsIds.includes(firstAddress); case PageNames.AddLiquidity: { @@ -75,6 +79,7 @@ export default class SelectedTokenRouteMixin extends Vue { * (b) `false` - when assets are equal; * (c) SWAP: `true` when both parameters are parsed as asset ids; * (d) ADD/REMOVE LIQUIDITY: `true` when both parameters are parsed as asset ids and first is from baseAssetIds; + * (e) ORDERBOOK: `true` is second === correct quote asset */ get isValidRoute(): boolean { const { first, second } = this.$route.params; @@ -84,7 +89,7 @@ export default class SelectedTokenRouteMixin extends Vue { } /** - * Sould be used in Add liquidity & Swap during mount + * Should be used in Add liquidity & Swap during mount * * Returns is valid state for routing life cycle */ diff --git a/src/components/mixins/ThemePaletteMixin.ts b/src/components/mixins/ThemePaletteMixin.ts index 312b69b2d..58ca87f39 100644 --- a/src/components/mixins/ThemePaletteMixin.ts +++ b/src/components/mixins/ThemePaletteMixin.ts @@ -3,7 +3,7 @@ import { Component, Vue } from 'vue-property-decorator'; import { getter } from '@/store/decorators'; import { getCssVariableValue as css } from '@/utils'; -import type Theme from '@soramitsu/soramitsu-js-ui/lib/types/Theme'; +import type Theme from '@soramitsu-ui/ui-vue2/lib/types/Theme'; @Component export default class ThemePaletteMixin extends Vue { diff --git a/src/components/pages/Moonpay/Confirmation.vue b/src/components/pages/Moonpay/Confirmation.vue index 917948bdb..d3d90e9d5 100644 --- a/src/components/pages/Moonpay/Confirmation.vue +++ b/src/components/pages/Moonpay/Confirmation.vue @@ -33,7 +33,7 @@ import { lazyComponent } from '@/router'; import { getter, state } from '@/store/decorators'; import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; -import type Theme from '@soramitsu/soramitsu-js-ui/lib/types/Theme'; +import type Theme from '@soramitsu-ui/ui-vue2/lib/types/Theme'; @Component({ components: { diff --git a/src/components/pages/Moonpay/Moonpay.vue b/src/components/pages/Moonpay/Moonpay.vue index b8d3baca0..cf30b63e5 100644 --- a/src/components/pages/Moonpay/Moonpay.vue +++ b/src/components/pages/Moonpay/Moonpay.vue @@ -21,8 +21,8 @@ import type { MoonpayTransaction } from '@/utils/moonpay'; import MoonpayBridgeInitMixin from './BridgeInitMixin'; -import type Theme from '@soramitsu/soramitsu-js-ui/lib/types/Theme'; import type { WALLET_TYPES } from '@soramitsu/soraneo-wallet-web'; +import type Theme from '@soramitsu-ui/ui-vue2/lib/types/Theme'; @Component({ components: { diff --git a/src/components/pages/Moonpay/MoonpayHistory.vue b/src/components/pages/Moonpay/MoonpayHistory.vue index 884f87094..e4b52ebeb 100644 --- a/src/components/pages/Moonpay/MoonpayHistory.vue +++ b/src/components/pages/Moonpay/MoonpayHistory.vue @@ -85,7 +85,7 @@ import { MoonpayTransactionStatus } from '../../../utils/moonpay'; import type { MoonpayTransaction, MoonpayCurrency, MoonpayCurrenciesById } from '../../../utils/moonpay'; import type { EthHistory } from '@sora-substrate/util/build/bridgeProxy/eth/types'; -import type Theme from '@soramitsu/soramitsu-js-ui/lib/types/Theme'; +import type Theme from '@soramitsu-ui/ui-vue2/lib/types/Theme'; const HistoryView = 'history'; const DetailsView = 'details'; @@ -242,6 +242,7 @@ export default class MoonpayHistory extends Mixins(mixins.PaginationSearchMixin, case WALLET_CONSTS.PaginationButton.Last: current = this.lastPage; this.isLtrDirection = false; + break; } this.currentPage = current; diff --git a/src/components/pages/Moonpay/Notification.vue b/src/components/pages/Moonpay/Notification.vue index 9626861e7..a6251a7d6 100644 --- a/src/components/pages/Moonpay/Notification.vue +++ b/src/components/pages/Moonpay/Notification.vue @@ -20,7 +20,7 @@ import { mutation, state, getter } from '@/store/decorators'; import { MoonpayNotifications } from './consts'; -import type Theme from '@soramitsu/soramitsu-js-ui/lib/types/Theme'; +import type Theme from '@soramitsu-ui/ui-vue2/lib/types/Theme'; @Component({ components: { diff --git a/src/components/pages/OrderBook/BookChartsWidget.vue b/src/components/pages/OrderBook/BookChartsWidget.vue new file mode 100644 index 000000000..bc801f017 --- /dev/null +++ b/src/components/pages/OrderBook/BookChartsWidget.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/src/components/pages/OrderBook/BookWidget.vue b/src/components/pages/OrderBook/BookWidget.vue new file mode 100644 index 000000000..9a193bdec --- /dev/null +++ b/src/components/pages/OrderBook/BookWidget.vue @@ -0,0 +1,598 @@ + + + + + {{ t('orderBook.orderBook') }} + + + + + + {{ selectedStep }} + + {{ + value + }} + + + + + {{ t('orderBook.price') }} + {{ t('orderBook.amount') }} + {{ t('orderBook.total') }} + + + + + {{ order.total }} + {{ order.amount }} + {{ order.price }} + + + + {{ t('orderBook.book.noAsks') }} + + + {{ lastPriceFormatted }} + + {{ fiatValue }} + + + + + {{ order.total }} + {{ order.amount }} + {{ order.price }} + + + + {{ t('orderBook.book.noBids') }} + + + + + + diff --git a/src/components/pages/OrderBook/BuySell.vue b/src/components/pages/OrderBook/BuySell.vue new file mode 100644 index 000000000..a38617190 --- /dev/null +++ b/src/components/pages/OrderBook/BuySell.vue @@ -0,0 +1,1081 @@ + + + + + + + {{ t('orderBook.tokenPair') }} + + + + {{ `${baseSymbol}-${quoteSymbol}` }} + + + + + + + {{ t('orderBook.price') }} + + + + + + {{ t('orderBook.change') }} + + + + + + {{ t('orderBook.dayVolume') }} + + + + + + + + + + + + + {{ t('orderBook.limit') }} + + + + + + + + {{ t('orderBook.market') }} + + + + + + + + + + + + + {{ t('orderBook.total') }} + + {{ amountAtPrice }} + + + + + + + + {{ reason }} + + + {{ reading }} + + + + + + + + {{ buttonText }} + + + + + + + + + + + + + + + diff --git a/src/components/pages/OrderBook/Dialogs/CancelOrders.vue b/src/components/pages/OrderBook/Dialogs/CancelOrders.vue new file mode 100644 index 000000000..3e1f3f4cf --- /dev/null +++ b/src/components/pages/OrderBook/Dialogs/CancelOrders.vue @@ -0,0 +1,61 @@ + + + + + {{ t('orderBook.dialog.askCancel') }} + + {{ t('orderBook.dialog.cancelAll') }} + + + + + + + + diff --git a/src/components/pages/OrderBook/Dialogs/CustomisePage.vue b/src/components/pages/OrderBook/Dialogs/CustomisePage.vue new file mode 100644 index 000000000..fe5c23e29 --- /dev/null +++ b/src/components/pages/OrderBook/Dialogs/CustomisePage.vue @@ -0,0 +1,18 @@ + + CustomisePage + + + diff --git a/src/components/pages/OrderBook/Dialogs/PlaceOrder.vue b/src/components/pages/OrderBook/Dialogs/PlaceOrder.vue new file mode 100644 index 000000000..31881280f --- /dev/null +++ b/src/components/pages/OrderBook/Dialogs/PlaceOrder.vue @@ -0,0 +1,183 @@ + + + + + {{ upperText }} + + + + {{ lowerText }} + + + + + + + + {{ t('exchange.confirm') }} + + + + + + + + + + diff --git a/src/components/pages/OrderBook/HistoryOrderWidget.vue b/src/components/pages/OrderBook/HistoryOrderWidget.vue new file mode 100644 index 000000000..93e12641e --- /dev/null +++ b/src/components/pages/OrderBook/HistoryOrderWidget.vue @@ -0,0 +1,309 @@ + + + + + + {{ openOrdersText }} + + + {{ t('orderBook.history.orderHistory') }} + + + {{ t('orderBook.history.tradeHistory') }} + + + + + {{ cancelText }} + + + {{ cancelAllText }} + + + + + + + + + + + {{ t('orderBook.history.connect') }} + + {{ t('connectWalletText') }} + + + + + + + + + + + + diff --git a/src/components/pages/OrderBook/MarketTradesWidget.vue b/src/components/pages/OrderBook/MarketTradesWidget.vue new file mode 100644 index 000000000..6fa6688ba --- /dev/null +++ b/src/components/pages/OrderBook/MarketTradesWidget.vue @@ -0,0 +1,142 @@ + + + + {{ t('orderBook.marketTrades') }} + + + + + + + + {{ t('orderBook.price') }} + + + {{ row.price }} + + + + + {{ t('orderBook.time') }} + + + {{ row.time }} + + + + + {{ t('orderBook.amount') }} + + + {{ row.amount }} + + + + + + + + + diff --git a/src/components/pages/OrderBook/Popovers/PairListPopover.vue b/src/components/pages/OrderBook/Popovers/PairListPopover.vue new file mode 100644 index 000000000..3da34aa19 --- /dev/null +++ b/src/components/pages/OrderBook/Popovers/PairListPopover.vue @@ -0,0 +1,301 @@ + + + + {{ t('orderBook.tradingPair.choosePair') }} + + + + + + + + {{ t('orderBook.tokenPair') }} + + + + + {{ row.pair }} + + + + + + {{ t('orderBook.price') }} + + + + + + + + {{ t('orderBook.tradingPair.volume') }} + + + + + + + + {{ t('orderBook.tradingPair.dailyChange') }} + + + + + + + + {{ t('orderBook.tradingPair.status') }} + + + {{ mapBookStatus(row.status) }} + + + + + + + + + + + + diff --git a/src/components/pages/OrderBook/SetLimitOrderWidget.vue b/src/components/pages/OrderBook/SetLimitOrderWidget.vue new file mode 100644 index 000000000..77908995f --- /dev/null +++ b/src/components/pages/OrderBook/SetLimitOrderWidget.vue @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + diff --git a/src/components/pages/OrderBook/Tables/AllOrders.vue b/src/components/pages/OrderBook/Tables/AllOrders.vue new file mode 100644 index 000000000..a53ea6f22 --- /dev/null +++ b/src/components/pages/OrderBook/Tables/AllOrders.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/pages/OrderBook/Tables/OpenOrders.vue b/src/components/pages/OrderBook/Tables/OpenOrders.vue new file mode 100644 index 000000000..f6690fadb --- /dev/null +++ b/src/components/pages/OrderBook/Tables/OpenOrders.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/src/components/pages/OrderBook/Tables/OrderTable.vue b/src/components/pages/OrderBook/Tables/OrderTable.vue new file mode 100644 index 000000000..813f6b685 --- /dev/null +++ b/src/components/pages/OrderBook/Tables/OrderTable.vue @@ -0,0 +1,359 @@ + + + {{ t('orderBook.orderTable.noOrders') }} + + + + + {{ t('orderBook.orderTable.time') }} + + + + {{ row.created.date }} + {{ row.created.time }} + + + + + + {{ t('orderBook.orderTable.pair') }} + + + {{ row.pair }} + + + + + {{ t('orderBook.orderTable.side') }} + + + {{ row.side }} + + + + + {{ t('orderBook.price') }} + + + + {{ getString(row.price) }} + {{ row.quoteAssetSymbol }} + + + + + + {{ t('orderBook.amount') }} + + + + {{ getString(row.amount) }}/{{ getString(row.originalAmount) }} + {{ row.baseAssetSymbol }} + + + + + + % {{ t('orderBook.orderTable.filled') }} + + {{ row.filled }} + + + + {{ t('orderBook.orderTable.lifetime') }} + + + + {{ row.expires }} + + + + + + {{ t('orderBook.tradingPair.status') }} + + + {{ row.status }} + + + + + {{ t('orderBook.total') }} + + + ${{ row.total }} + + + + + + + + + + + + diff --git a/src/components/pages/OrderBook/TransactionDetails.vue b/src/components/pages/OrderBook/TransactionDetails.vue new file mode 100644 index 000000000..0419fc28e --- /dev/null +++ b/src/components/pages/OrderBook/TransactionDetails.vue @@ -0,0 +1,144 @@ + + + + + + + + + + + + diff --git a/src/components/pages/Rewards/AmountTable.vue b/src/components/pages/Rewards/AmountTable.vue index 57bce3c9a..30c7288b4 100644 --- a/src/components/pages/Rewards/AmountTable.vue +++ b/src/components/pages/Rewards/AmountTable.vue @@ -78,8 +78,8 @@ @@ -285,6 +333,39 @@ $el-input-class: '.el-input'; } } } + +.input-line--footer-with-slider { + @include input-slider; + width: 100%; + + .el-slider__button { + background-color: #fff; + border-radius: 4px; + transform: rotate(-45deg); + } + + .el-slider__stop { + height: 10px; + width: 10px; + border-radius: 2px; + top: -1.8px; + border: 1.3px solid var(--s-color-base-content-tertiary); + transform: translateX(-50%) rotate(-45deg); + } + + .asset-info { + display: flex; + width: 100%; + justify-content: space-between; + } + + .delimiter { + background-color: var(--s-color-base-border-secondary); + width: 100%; + height: 1px; + margin: 14px 0 4px 0; + } +} -@/modules/staking/demeter/types diff --git a/src/modules/staking/sora/components/ClaimRewardsDialog.vue b/src/modules/staking/sora/components/ClaimRewardsDialog.vue index 32754aaae..82dbea23c 100644 --- a/src/modules/staking/sora/components/ClaimRewardsDialog.vue +++ b/src/modules/staking/sora/components/ClaimRewardsDialog.vue @@ -38,7 +38,7 @@ @@ -82,7 +82,7 @@ import type { CodecString } from '@sora-substrate/util'; FormattedAmountWithFiatValue: components.FormattedAmountWithFiatValue, }, }) -export default class ClaimRewardsDialog extends Mixins(StakingMixin, mixins.DialogMixin, mixins.LoadingMixin) { +export default class ClaimRewardsDialog extends Mixins(StakingMixin, mixins.DialogMixin, mixins.TransactionMixin) { @Prop({ default: () => true, type: Boolean }) readonly isAdding!: boolean; rewardsDestination = ''; @@ -167,16 +167,18 @@ export default class ClaimRewardsDialog extends Mixins(StakingMixin, mixins.Dial } async handleConfirm(): Promise { - await this.payout({ - payouts: this.pendingRewards - ? this.pendingRewards.map((r) => ({ era: r.era, validators: r.validators.map((v) => v.address) })) - : [], - payee: this.rewardsDestination !== this.payeeAddress ? this.rewardsDestination : undefined, - }); + await this.withNotifications(async () => { + await this.payout({ + payouts: this.pendingRewards + ? this.pendingRewards.map((r) => ({ era: r.era, validators: r.validators.map((v) => v.address) })) + : [], + payee: this.rewardsDestination !== this.payeeAddress ? this.rewardsDestination : undefined, + }); - await this.getPendingRewards(); + await this.getPendingRewards(); - this.closeDialog(); + this.closeDialog(); + }); } checkPendingRewards(): void { diff --git a/src/modules/staking/sora/components/PendingRewardsDialog.vue b/src/modules/staking/sora/components/PendingRewardsDialog.vue index c7c575f1a..a1f968923 100644 --- a/src/modules/staking/sora/components/PendingRewardsDialog.vue +++ b/src/modules/staking/sora/components/PendingRewardsDialog.vue @@ -69,7 +69,7 @@ @@ -129,8 +129,8 @@ type Reward = { export default class PendingRewardsDialog extends Mixins( StakingMixin, ValidatorsMixin, - mixins.DialogMixin, - mixins.LoadingMixin + mixins.TransactionMixin, + mixins.DialogMixin ) { payoutNetworkFee: string | null = null; selectedRewards: Reward[] = []; @@ -235,13 +235,15 @@ export default class PendingRewardsDialog extends Mixins( } async handleConfirm(): Promise { - await this.payout({ - payouts: this.payouts, - }); + await this.withNotifications(async () => { + await this.payout({ + payouts: this.payouts, + }); - await this.getPendingRewards(); + await this.getPendingRewards(); - this.closeDialog(); + this.closeDialog(); + }); } } diff --git a/src/modules/staking/sora/components/StakeDialog.vue b/src/modules/staking/sora/components/StakeDialog.vue index ff567168f..941aafcea 100644 --- a/src/modules/staking/sora/components/StakeDialog.vue +++ b/src/modules/staking/sora/components/StakeDialog.vue @@ -43,7 +43,7 @@ @@ -68,7 +68,6 @@ import { Component, Mixins, Watch, Prop } from 'vue-property-decorator'; import { Components } from '@/consts'; import { lazyComponent } from '@/router'; -import { state } from '@/store/decorators'; import { StakeDialogMode } from '../consts'; import StakingMixin from '../mixins/StakingMixin'; @@ -83,11 +82,9 @@ import type { CodecString } from '@sora-substrate/util'; AccountCard: components.AccountCard, }, }) -export default class StakeDialog extends Mixins(StakingMixin, mixins.DialogMixin, mixins.LoadingMixin) { +export default class StakeDialog extends Mixins(StakingMixin, mixins.TransactionMixin, mixins.DialogMixin) { @Prop({ required: true, type: String }) readonly mode!: StakeDialogMode; - @state.wallet.settings.shouldBalanceBeHidden private shouldBalanceBeHidden!: boolean; - @Watch('visible') private resetValue() { if (this.visible) { @@ -202,13 +199,14 @@ export default class StakeDialog extends Mixins(StakingMixin, mixins.DialogMixin async handleConfirm(): Promise { this.setStakeAmount(this.value); + let extrinsic = this.unbond; if (this.mode === StakeDialogMode.NEW) { - await this.bondAndNominate(); + extrinsic = this.bondAndNominate; } else if (this.mode === StakeDialogMode.ADD) { - await this.bondExtra(); - } else { - await this.unbond(); + extrinsic = this.bondExtra; } + + await this.withNotifications(async () => await extrinsic()); this.$emit('confirm'); } } diff --git a/src/modules/staking/sora/components/ValidatorsDialog.vue b/src/modules/staking/sora/components/ValidatorsDialog.vue index 961de5f84..63474bb07 100644 --- a/src/modules/staking/sora/components/ValidatorsDialog.vue +++ b/src/modules/staking/sora/components/ValidatorsDialog.vue @@ -9,7 +9,13 @@ - + {{ confirmText }} @@ -49,7 +55,7 @@ import type { MyStakingInfo } from '@sora-substrate/util/build/staking/types'; InfoLine: components.InfoLine, }, }) -export default class ValidatorsDialog extends Mixins(StakingMixin, mixins.DialogMixin, mixins.LoadingMixin) { +export default class ValidatorsDialog extends Mixins(StakingMixin, mixins.DialogMixin, mixins.TransactionMixin) { @mutation.staking.setStakingInfo setStakingInfo!: (stakingInfo: MyStakingInfo) => void; @action.staking.getStakingInfo getStakingInfo!: AsyncFnWithoutArgs; @@ -164,16 +170,18 @@ export default class ValidatorsDialog extends Mixins(StakingMixin, mixins.Dialog if (this.mode === ValidatorsListMode.USER) { this.isSelectingEditingMode = true; } else { - await this.nominate(); + await this.withNotifications(async () => { + await this.nominate(); - if (!this.stakingInfo) throw new Error('There is no staking info'); + if (!this.stakingInfo) throw new Error('There is no staking info'); - this.setStakingInfo({ - ...this.stakingInfo, - myValidators: this.selectedValidators.map((v) => v.address), - }); + this.setStakingInfo({ + ...this.stakingInfo, + myValidators: this.selectedValidators.map((v) => v.address), + }); - this.mode = ValidatorsListMode.USER; + this.mode = ValidatorsListMode.USER; + }); } } diff --git a/src/modules/staking/sora/mixins/StakingMixin.ts b/src/modules/staking/sora/mixins/StakingMixin.ts index 97814bd87..89475c38b 100644 --- a/src/modules/staking/sora/mixins/StakingMixin.ts +++ b/src/modules/staking/sora/mixins/StakingMixin.ts @@ -53,7 +53,6 @@ export default class StakingMixin extends Mixins(mixins.FormattedAmountMixin, Tr @mutation.staking.setShowValidatorsFilterDialog setShowValidatorsFilterDialog!: (value: boolean) => void; @mutation.staking.selectValidators selectValidators!: (validators: ValidatorInfoFull[]) => void; - @action.staking.bond bond!: AsyncFnWithoutArgs; @action.staking.nominate nominate!: AsyncFnWithoutArgs; @action.staking.bondAndNominate bondAndNominate!: AsyncFnWithoutArgs; @action.staking.getBondAndNominateNetworkFee getBondAndNominateNetworkFee!: () => Promise; diff --git a/src/modules/staking/sora/views/Overview.vue b/src/modules/staking/sora/views/Overview.vue index f9c80c5be..021637186 100644 --- a/src/modules/staking/sora/views/Overview.vue +++ b/src/modules/staking/sora/views/Overview.vue @@ -97,7 +97,7 @@ v-if="stakingInitialized" :label="t('soraStaking.info.redeemable')" :value="redeemableFundsFormatted" - :asset-symbol="rewardAsset?.symbol" + :asset-symbol="stakingAsset?.symbol" :fiat-value="redeemableFundsFiat" /> = [ name: PageNames.ExploreTokens, component: lazyView(PageNames.ExploreTokens), }, + { + path: 'books', + name: PageNames.ExploreBooks, + component: lazyView(PageNames.ExploreBooks), + }, ], }, { @@ -265,6 +270,11 @@ const routes: Array = [ name: PageNames.Stats, component: lazyView(PageNames.Stats), }, + { + path: '/trade/:first?/:second?', + name: PageNames.OrderBook, + component: lazyView(PageNames.OrderBook), + }, { path: '*', redirect: '/swap', diff --git a/src/store/bridge/actions.ts b/src/store/bridge/actions.ts index 341527005..02fcb9605 100644 --- a/src/store/bridge/actions.ts +++ b/src/store/bridge/actions.ts @@ -139,7 +139,7 @@ async function getEvmNetworkFee(context: ActionContext): Promise const bridgeRegisteredAsset = rootState.assets.registeredAssets[asset.address]; const decimals = state.isSoraToEvm ? asset.decimals : asset.externalDecimals; // using max balance to not overflow contract calculation - const value = FPNumber.fromCodecValue(state.assetSenderBalance!, decimals).toString(); + const value = FPNumber.fromCodecValue(state.assetSenderBalance ?? 0, decimals).toString(); fee = await getEthNetworkFee( asset, diff --git a/src/store/consts.ts b/src/store/consts.ts index c638b9a07..7bca9a655 100644 --- a/src/store/consts.ts +++ b/src/store/consts.ts @@ -19,6 +19,7 @@ export enum Module { Staking = 'staking', DemeterFarming = 'demeterFarming', SoraCard = 'soraCard', + OrderBook = 'orderBook', } export const Modules = [...Object.values(Module), ...WalletModules]; diff --git a/src/store/index.ts b/src/store/index.ts index f0f90bee6..d9ef15ee9 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -8,6 +8,7 @@ import assets from './assets'; import bridge from './bridge'; import demeterFarming from './demeterFarming'; import moonpay from './moonpay'; +import orderBook from './orderBook'; import pool from './pool'; import referrals from './referrals'; import removeLiquidity from './removeLiquidity'; @@ -40,6 +41,7 @@ const modules = { staking, demeterFarming, soraCard, + orderBook, }; const { store, rootGetterContext, rootActionContext } = createDirectStore({ diff --git a/src/store/orderBook/actions.ts b/src/store/orderBook/actions.ts new file mode 100644 index 000000000..5fa5fdf14 --- /dev/null +++ b/src/store/orderBook/actions.ts @@ -0,0 +1,195 @@ +import { api } from '@soramitsu/soraneo-wallet-web'; +import { defineActions } from 'direct-vuex'; +import { combineLatest } from 'rxjs'; + +import { subscribeOnOrderBookUpdates, fetchOrderBooks } from '@/indexer/queries/orderBook'; + +import { orderBookActionContext } from '.'; + +import type { OrderBook } from '@sora-substrate/liquidity-proxy'; +import type { LimitOrder } from '@sora-substrate/util/build/orderBook/types'; +import type { Subscription } from 'rxjs'; + +const actions = defineActions({ + async getOrderBooksInfo(context): Promise { + const { commit, rootGetters } = orderBookActionContext(context); + const { whitelist } = rootGetters.wallet.account; + const orderBooks = await api.orderBook.getOrderBooks(); + + const orderBooksWhitelist = Object.entries(orderBooks).reduce>((buffer, [key, book]) => { + const { base, quote } = book.orderBookId; + if ([base, quote].every((address) => address in whitelist)) { + buffer[key] = book; + } + return buffer; + }, {}); + + commit.setOrderBooks(orderBooksWhitelist); + }, + + async updateOrderBooksStats(context): Promise { + const { commit } = orderBookActionContext(context); + + const orderBooksWithStats = await fetchOrderBooks(); + const orderBooksStats = (orderBooksWithStats ?? []).reduce((buffer, item) => { + const { + id: { base, quote }, + stats, + } = item; + + const key = api.orderBook.serializedKey(base, quote); + buffer[key] = stats; + return buffer; + }, {}); + + commit.setStats(orderBooksStats); + }, + + async subscribeToBidsAndAsks(context): Promise { + const { commit, dispatch, getters } = orderBookActionContext(context); + const { baseAsset, quoteAsset } = getters; + + dispatch.unsubscribeFromBidsAndAsks(); + + if (!(baseAsset && quoteAsset)) return; + + let asksSubscription!: Subscription; + let bidsSubscription!: Subscription; + + await Promise.all([ + new Promise((resolve) => { + asksSubscription = api.orderBook + .subscribeOnAggregatedAsks(baseAsset.address, quoteAsset.address) + .subscribe((asks) => { + commit.setAsks(asks.toReversed()); + resolve(); + }); + }), + new Promise((resolve) => { + bidsSubscription = api.orderBook + .subscribeOnAggregatedBids(baseAsset.address, quoteAsset.address) + .subscribe((bids) => { + commit.setBids(bids.toReversed()); + resolve(); + }); + }), + ]); + + commit.setOrderBookUpdates([asksSubscription, bidsSubscription]); + }, + + unsubscribeFromBidsAndAsks(context): void { + const { commit } = orderBookActionContext(context); + + commit.setAsks(); + commit.setBids(); + commit.resetOrderBookUpdates(); + }, + + async subscribeToOrderBookStats(context): Promise { + const { commit, dispatch, getters, state } = orderBookActionContext(context); + const { dexId } = state; + const { baseAsset, quoteAsset } = getters; + + dispatch.unsubscribeFromOrderBookStats(); + + if (!(baseAsset && quoteAsset)) return; + + const subscription = await subscribeOnOrderBookUpdates( + dexId, + baseAsset.address, + quoteAsset.address, + (data) => { + const { + id: { base, quote }, + stats, + deals, + } = data; + const key = api.orderBook.serializedKey(base, quote); + commit.setDeals(deals); + commit.setStats({ [key]: stats }); + }, + console.error + ); + + if (!subscription) return; + + commit.setOrderBookStatsUpdates(subscription); + }, + + unsubscribeFromOrderBookStats(context): void { + const { commit } = orderBookActionContext(context); + + commit.setDeals(); + commit.resetOrderBookStatsUpdates(); + }, + + async subscribeToUserLimitOrders(context): Promise { + const { commit, dispatch, getters } = orderBookActionContext(context); + const { baseAsset, quoteAsset, accountAddress } = getters; + + dispatch.unsubscribeFromUserLimitOrders(); + + if (!(accountAddress && baseAsset && quoteAsset)) return; + + let subscription!: Subscription; + + await new Promise((resolve) => { + subscription = api.orderBook + .subscribeOnUserLimitOrdersIds(baseAsset.address, quoteAsset.address, accountAddress) + .subscribe(async (ids) => { + const userLimitOrders = (await Promise.all( + ids.map((id) => api.orderBook.getLimitOrder(baseAsset.address, quoteAsset.address, id)) + )) as LimitOrder[]; + + const orders = userLimitOrders.map((el) => { + const amountStr = el.amount.toString(); + const originalAmountStr = el.originalAmount.toString(); + return { ...el, amountStr, originalAmountStr }; + }); + + commit.setUserLimitOrders(orders); + + resolve(); + }); + }); + + commit.setUserLimitOrderUpdates(subscription); + }, + + async subscribeOnLimitOrders(context, ids: number[]): Promise { + const { commit, getters, state } = orderBookActionContext(context); + const { baseAsset, quoteAsset, accountAddress } = getters; + + if (!(accountAddress && baseAsset && quoteAsset)) return; + + let subscription!: Subscription; + const observables = ids.map((id) => api.orderBook.subscribeOnLimitOrder(baseAsset.address, quoteAsset.address, id)); + + await new Promise((resolve) => { + subscription = combineLatest(observables).subscribe((updated) => { + const updatedOrders = updated.filter((item) => !!item) as LimitOrder[]; + if (updatedOrders.length) { + const userLimitOrders = state.userLimitOrders.map((order) => { + const found = updatedOrders.find((item) => item.id === order.id); + return found ?? order; + }); + commit.setUserLimitOrders(userLimitOrders); + resolve(); + } else { + resolve(); + } + }); + }); + + commit.setPagedUserLimitOrdersSubscription(subscription); + }, + + unsubscribeFromUserLimitOrders(context): void { + const { commit } = orderBookActionContext(context); + + commit.resetUserLimitOrderUpdates(); + }, +}); + +export default actions; diff --git a/src/store/orderBook/getters.ts b/src/store/orderBook/getters.ts new file mode 100644 index 000000000..0344e7b45 --- /dev/null +++ b/src/store/orderBook/getters.ts @@ -0,0 +1,63 @@ +import { OrderBook } from '@sora-substrate/liquidity-proxy'; +import { api } from '@soramitsu/soraneo-wallet-web'; +import { defineGetters } from 'direct-vuex'; + +import type { OrderBookStats, OrderBookDealData } from '@/types/orderBook'; +import { getBookDecimals } from '@/utils/orderBook'; + +import { OrderBookState } from './types'; + +import { orderBookGetterContext } from '.'; + +import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; + +const getters = defineGetters()({ + baseAsset(...args): Nullable { + const { state, rootGetters } = orderBookGetterContext(args); + if (!state.baseAssetAddress) return null; + return rootGetters.assets.assetDataByAddress(state.baseAssetAddress); + }, + quoteAsset(...args): Nullable { + const { state, rootGetters } = orderBookGetterContext(args); + if (!state.quoteAssetAddress) return null; + return rootGetters.assets.assetDataByAddress(state.quoteAssetAddress); + }, + orderBookId(...args): string { + const { getters } = orderBookGetterContext(args); + const { baseAsset, quoteAsset } = getters; + + if (!(baseAsset && quoteAsset)) return ''; + + return api.orderBook.serializedKey(baseAsset.address, quoteAsset.address); + }, + currentOrderBook(...args): Nullable { + const { getters, state } = orderBookGetterContext(args); + + if (!getters.orderBookId) return null; + + return state.orderBooks[getters.orderBookId]; + }, + orderBookStats(...args): Nullable { + const { getters, state } = orderBookGetterContext(args); + + if (!getters.orderBookId) return null; + + return state.orderBooksStats[getters.orderBookId]; + }, + orderBookDecimals(...args): number { + const { getters } = orderBookGetterContext(args); + + return getBookDecimals(getters.currentOrderBook); + }, + orderBookLastDeal(...args): Nullable { + const { state } = orderBookGetterContext(args); + + return state.deals[0] ?? null; + }, + accountAddress(...args): string { + const { rootState } = orderBookGetterContext(args); + return rootState.wallet.account.address; + }, +}); + +export default getters; diff --git a/src/store/orderBook/index.ts b/src/store/orderBook/index.ts new file mode 100644 index 000000000..745ae1971 --- /dev/null +++ b/src/store/orderBook/index.ts @@ -0,0 +1,23 @@ +import { defineModule } from 'direct-vuex'; + +import { localActionContext, localGetterContext } from '@/store'; +import { Module } from '@/store/consts'; + +import actions from './actions'; +import getters from './getters'; +import mutations from './mutations'; +import state from './state'; + +const orderBook = defineModule({ + namespaced: true, + state, + getters, + mutations, + actions, +}); + +const orderBookGetterContext = (args: [any, any, any, any]) => localGetterContext(args, Module.OrderBook, orderBook); +const orderBookActionContext = (context: any) => localActionContext(context, Module.OrderBook, orderBook); + +export { orderBookActionContext, orderBookGetterContext }; +export default orderBook; diff --git a/src/store/orderBook/mutations.ts b/src/store/orderBook/mutations.ts new file mode 100644 index 000000000..b36673708 --- /dev/null +++ b/src/store/orderBook/mutations.ts @@ -0,0 +1,84 @@ +import { PriceVariant } from '@sora-substrate/liquidity-proxy'; +import { defineMutations } from 'direct-vuex'; + +import { LimitOrderType } from '@/consts'; +import type { OrderBookDealData, OrderBookStats } from '@/types/orderBook'; + +import type { OrderBookState } from './types'; +import type { OrderBookId, OrderBookPriceVolume, OrderBook } from '@sora-substrate/liquidity-proxy'; +import type { LimitOrder } from '@sora-substrate/util/build/orderBook/types'; +import type { Subscription } from 'rxjs'; + +const mutations = defineMutations()({ + setOrderBooks(state, orderBooks: Record): void { + state.orderBooks = orderBooks; + }, + setCurrentOrderBook(state, { dexId, base, quote }: OrderBookId): void { + state.dexId = dexId; + state.baseAssetAddress = base; + state.quoteAssetAddress = quote; + }, + setBaseValue(state, value: string): void { + state.baseValue = value; + }, + setQuoteValue(state, value: string): void { + state.quoteValue = value; + }, + setSide(state, side: PriceVariant): void { + state.side = side; + }, + setLimitOrderType(state, type: LimitOrderType): void { + state.limitOrderType = type; + }, + setAsks(state, asks: readonly OrderBookPriceVolume[] = []): void { + state.asks = Object.freeze([...asks]); + }, + setBids(state, bids: readonly OrderBookPriceVolume[] = []): void { + state.bids = Object.freeze([...bids]); + }, + setDeals(state, deals: readonly OrderBookDealData[] = []): void { + state.deals = Object.freeze([...deals]); + }, + setStats(state, stats: Record): void { + state.orderBooksStats = Object.freeze({ ...state.orderBooksStats, ...stats }); + }, + setUserLimitOrders(state, limitOrders: LimitOrder[] = []): void { + state.userLimitOrders = Object.freeze([...limitOrders]); + }, + setOrderBookUpdates(state, subscriptions: Array): void { + state.orderBookUpdates = subscriptions; + }, + resetOrderBookUpdates(state): void { + state.orderBookUpdates?.forEach((subscription) => subscription?.unsubscribe()); + state.orderBookUpdates = []; + }, + setOrderBookStatsUpdates(state, subscription: VoidFunction): void { + state.orderBookStatsUpdates = subscription; + }, + resetOrderBookStatsUpdates(state): void { + state.orderBookStatsUpdates?.(); + state.orderBookStatsUpdates = null; + }, + setUserLimitOrderUpdates(state, subscription: Subscription): void { + state.userLimitOrderUpdates = subscription; + }, + resetUserLimitOrderUpdates(state): void { + state.userLimitOrderUpdates?.unsubscribe(); + state.userLimitOrderUpdates = null; + }, + setPagedUserLimitOrdersSubscription(state, subscription: Subscription): void { + state.pagedUserLimitOrdersSubscription = subscription; + }, + resetPagedUserLimitOrdersSubscription(state): void { + state.pagedUserLimitOrdersSubscription?.unsubscribe(); + state.pagedUserLimitOrdersSubscription = null; + }, + setOrdersToBeCancelled(state, orders): void { + state.ordersToBeCancelled = orders; + }, + setAmountSliderValue(state, percent: number) { + state.amountSliderValue = percent; + }, +}); + +export default mutations; diff --git a/src/store/orderBook/state.ts b/src/store/orderBook/state.ts new file mode 100644 index 000000000..8cfea5703 --- /dev/null +++ b/src/store/orderBook/state.ts @@ -0,0 +1,34 @@ +import { PriceVariant } from '@sora-substrate/liquidity-proxy'; +import { DexId } from '@sora-substrate/util/build/dex/consts'; + +import { LimitOrderType } from '@/consts'; + +import type { OrderBookState } from './types'; + +function initialState(): OrderBookState { + return { + orderBooks: {}, + dexId: DexId.XOR, + baseAssetAddress: null, + quoteAssetAddress: null, + limitOrderType: LimitOrderType.limit, + baseValue: '', + quoteValue: '', + orderBooksStats: {}, + deals: [], + asks: [], + bids: [], + userLimitOrders: [], + side: PriceVariant.Buy, + orderBookUpdates: [], + orderBookStatsUpdates: null, + userLimitOrderUpdates: null, + pagedUserLimitOrdersSubscription: null, + ordersToBeCancelled: [], + amountSliderValue: 0, + }; +} + +const state = initialState(); + +export default state; diff --git a/src/store/orderBook/types.ts b/src/store/orderBook/types.ts new file mode 100644 index 000000000..765bfcb6d --- /dev/null +++ b/src/store/orderBook/types.ts @@ -0,0 +1,29 @@ +import type { LimitOrderType } from '@/consts'; +import type { OrderBookStats, OrderBookDealData } from '@/types/orderBook'; + +import type { PriceVariant, OrderBookPriceVolume, OrderBook } from '@sora-substrate/liquidity-proxy'; +import type { DexId } from '@sora-substrate/util/build/dex/consts'; +import type { LimitOrder } from '@sora-substrate/util/build/orderBook/types'; +import type { Subscription } from 'rxjs'; + +export type OrderBookState = { + orderBooks: Record; + dexId: DexId; + baseAssetAddress: Nullable; + quoteAssetAddress: Nullable; + limitOrderType: LimitOrderType; + orderBooksStats: Record; + deals: readonly OrderBookDealData[]; + asks: readonly OrderBookPriceVolume[]; + bids: readonly OrderBookPriceVolume[]; + userLimitOrders: readonly LimitOrder[]; + baseValue: string; + quoteValue: string; + side: PriceVariant; + orderBookUpdates: Array; + orderBookStatsUpdates: Nullable; + userLimitOrderUpdates: Nullable; + pagedUserLimitOrdersSubscription: Nullable; + ordersToBeCancelled: Array; + amountSliderValue: number; +}; diff --git a/src/store/settings/actions.ts b/src/store/settings/actions.ts index a5c9ecbdd..c7f41dfaf 100644 --- a/src/store/settings/actions.ts +++ b/src/store/settings/actions.ts @@ -204,7 +204,7 @@ const actions = defineActions({ updateDocumentTitle(); updateFpNumberLocale(locale); commit.setLanguage(locale); - commit.updateDisplayRegions(); // based on locale + commit.updateIntlUtils(); // based on locale }, async setBlockNumber(context): Promise { const { commit } = settingsActionContext(context); diff --git a/src/store/settings/getters.ts b/src/store/settings/getters.ts index 39ef38022..c8a4d8cf4 100644 --- a/src/store/settings/getters.ts +++ b/src/store/settings/getters.ts @@ -64,6 +64,10 @@ const getters = defineGetters()({ const { state } = settingsGetterContext(args); return state.featureFlags.soraCard; }, + orderBookEnabled(...args): Nullable { + const { state } = settingsGetterContext(args); + return state.featureFlags.orderBook; + }, notificationActivated(...args): boolean { const { state } = settingsGetterContext(args); return state.browserNotifsPermission === 'granted'; diff --git a/src/store/settings/mutations.ts b/src/store/settings/mutations.ts index 34e11dd0b..0968f470f 100644 --- a/src/store/settings/mutations.ts +++ b/src/store/settings/mutations.ts @@ -95,12 +95,14 @@ const mutations = defineMutations()({ state.userDisclaimerApprove = true; settingsStorage.set('disclaimerApprove', true); }, - updateDisplayRegions(state): void { + updateIntlUtils(state): void { try { state.displayRegions = new Intl.DisplayNames([state.language], { type: 'region' }); + state.percentFormat = new Intl.NumberFormat([state.language], { style: 'percent', maximumFractionDigits: 2 }); } catch (error) { - console.warn('Intl.DisplayNames issue', error); + console.warn('Intl is not supported!', error); state.displayRegions = null; + state.percentFormat = null; } }, setFeatureFlags(state, featureFlags: FeatureFlags = {}): void { @@ -109,6 +111,9 @@ const mutations = defineMutations()({ setBlockNumber(state, value: number): void { state.blockNumber = value || 0; }, + setMenuCollapsed(state, collapsed: boolean): void { + state.menuCollapsed = collapsed; + }, setBlockNumberUpdates(state, subscription: Subscription): void { state.blockNumberUpdates = subscription; }, diff --git a/src/store/settings/state.ts b/src/store/settings/state.ts index cc40494ac..73332c259 100644 --- a/src/store/settings/state.ts +++ b/src/store/settings/state.ts @@ -22,12 +22,14 @@ function initialState(): SettingsState { node: node ? JSON.parse(node) : {}, language: getLocale(), displayRegions: undefined, + percentFormat: undefined, defaultNodes: [], customNodes: customNodes ? JSON.parse(customNodes) : [], nodeAddressConnecting: '', nodeConnectionAllowance: true, chainGenesisHash: '', faucetUrl: '', + menuCollapsed: false, selectNodeDialogVisibility: false, selectIndexerDialogVisibility: false, selectLanguageDialogVisibility: false, diff --git a/src/store/settings/types.ts b/src/store/settings/types.ts index 79232fa14..0eace1fa6 100644 --- a/src/store/settings/types.ts +++ b/src/store/settings/types.ts @@ -15,6 +15,7 @@ export type FeatureFlags = { x1ex?: boolean; charts?: boolean; soraCard?: boolean; + orderBook?: boolean; }; export type SettingsState = { @@ -27,12 +28,14 @@ export type SettingsState = { node: Partial; language: string; displayRegions: Nullable; + percentFormat: Nullable; defaultNodes: Array; customNodes: Array; nodeAddressConnecting: string; nodeConnectionAllowance: boolean; chainGenesisHash: string; faucetUrl: string; + menuCollapsed: boolean; selectNodeDialogVisibility: boolean; selectIndexerDialogVisibility: boolean; selectLanguageDialogVisibility: boolean; diff --git a/src/store/staking/actions.ts b/src/store/staking/actions.ts index 87ad66e25..75213ab6d 100644 --- a/src/store/staking/actions.ts +++ b/src/store/staking/actions.ts @@ -7,16 +7,6 @@ import type { Payouts } from '@sora-substrate/util/build/staking/types'; import type { Subscription } from 'rxjs'; const actions = defineActions({ - async bond(context): Promise { - const { state, getters } = stakingActionContext(context); - - const controller = state.controller || getters.stash; - - if (!state.payee) throw new Error('Payee is not set'); - - await api.staking.bond({ controller, value: state.stakeAmount, payee: state.payee }); - }, - async nominate(context): Promise { const { state, dispatch } = stakingActionContext(context); @@ -106,10 +96,6 @@ const actions = defineActions({ }); }, - async chill(context): Promise { - await api.staking.chill(); - }, - async getStakingInfo(context): Promise { const { getters, commit } = stakingActionContext(context); @@ -153,7 +139,7 @@ const actions = defineActions({ async getUnbondPeriod(context): Promise { const { commit } = stakingActionContext(context); - const unbondPeriod = await api.staking.getUnbondPeriod(); + const unbondPeriod = api.staking.getUnbondPeriod(); commit.setUnbondPeriod(unbondPeriod); }, @@ -161,7 +147,7 @@ const actions = defineActions({ async getMaxNominations(context): Promise { const { commit } = stakingActionContext(context); - const maxNominations = await api.staking.getMaxNominations(); + const maxNominations = api.staking.getMaxNominations(); commit.setMaxNominations(maxNominations); }, @@ -169,7 +155,7 @@ const actions = defineActions({ async getHistoryDepth(context): Promise { const { commit } = stakingActionContext(context); - const historyDepth = await api.staking.getHistoryDepth(); + const historyDepth = api.staking.getHistoryDepth(); commit.setHistoryDepth(historyDepth); }, @@ -179,7 +165,7 @@ const actions = defineActions({ commit.resetActiveEraUpdates(); - const observable = await api.staking.getActiveEraObservable(); + const observable = api.staking.getActiveEraObservable(); if (!observable) return; @@ -200,7 +186,7 @@ const actions = defineActions({ commit.resetCurrentEraUpdates(); - const observable = await api.staking.getCurrentEraObservable(); + const observable = api.staking.getCurrentEraObservable(); if (!observable) return; @@ -223,7 +209,7 @@ const actions = defineActions({ if (!state.currentEra) throw new Error('Current era is not set'); - const observable = await api.staking.getEraTotalStakeObservable(state.currentEra); + const observable = api.staking.getEraTotalStakeObservable(state.currentEra); if (!observable) return; @@ -244,7 +230,7 @@ const actions = defineActions({ commit.resetControllerUpdates(); - const observable = await api.staking.getControllerObservable(getters.stash); + const observable = api.staking.getControllerObservable(getters.stash); if (!observable) return; @@ -265,7 +251,7 @@ const actions = defineActions({ commit.resetPayeeUpdates(); - const observable = await api.staking.getPayeeObservable(getters.stash); + const observable = api.staking.getPayeeObservable(getters.stash); if (!observable) return; @@ -286,7 +272,7 @@ const actions = defineActions({ commit.resetNominationsUpdates(); - const observable = await api.staking.getNominationsObservable(getters.stash); + const observable = api.staking.getNominationsObservable(getters.stash); if (!observable) return; @@ -307,7 +293,7 @@ const actions = defineActions({ commit.resetAccountLedgerUpdates(); - const observable = await api.staking.getAccountLedgerObservable(getters.stash); + const observable = api.staking.getAccountLedgerObservable(getters.stash); if (!observable) return; diff --git a/src/store/swap/mutations.ts b/src/store/swap/mutations.ts index bc8b8f679..d230de1fd 100644 --- a/src/store/swap/mutations.ts +++ b/src/store/swap/mutations.ts @@ -67,6 +67,9 @@ const mutations = defineMutations()({ state.isAvailable = isAvailable; state.liquiditySources = liquiditySources; }, + setLiquiditySource(state, liquiditySource): void { + state.liquiditySources = [liquiditySource]; + }, selectDexId(state, dexId: number) { state.selectedDexId = dexId; }, diff --git a/src/store/types.ts b/src/store/types.ts index 4af26079f..c0f16b432 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -3,7 +3,7 @@ import type store from '@/store'; import type { VUEX_TYPES } from '@soramitsu/soraneo-wallet-web'; import type { VueDecorator } from 'vue-class-component'; -type BaseModuleDecorator = { +type BaseModuleDecorator = { router: VUEX_TYPES.BaseDecorator; web3: VUEX_TYPES.BaseDecorator; assets: VUEX_TYPES.BaseDecorator; @@ -19,6 +19,7 @@ type BaseModuleDecorator; demeterFarming: VUEX_TYPES.BaseDecorator; soraCard: VUEX_TYPES.BaseDecorator; + orderBook: VUEX_TYPES.BaseDecorator; }; export type StateDecorators = BaseModuleDecorator< @@ -36,7 +37,8 @@ export type StateDecorators = BaseModuleDecorator< typeof store.state.rewards, typeof store.state.staking, typeof store.state.demeterFarming, - typeof store.state.soraCard + typeof store.state.soraCard, + typeof store.state.orderBook > & VUEX_TYPES.WalletStateDecorators; @@ -55,7 +57,8 @@ export type GettersDecorators = BaseModuleDecorator< typeof store.getters.rewards, typeof store.getters.staking, typeof store.getters.demeterFarming, - typeof store.getters.soraCard + typeof store.getters.soraCard, + typeof store.getters.orderBook > & VUEX_TYPES.WalletGettersDecorators & { libraryDesignSystem: VueDecorator; libraryTheme: VueDecorator }; @@ -74,7 +77,8 @@ export type CommitDecorators = BaseModuleDecorator< typeof store.commit.rewards, typeof store.commit.staking, typeof store.commit.demeterFarming, - typeof store.commit.soraCard + typeof store.commit.soraCard, + typeof store.commit.orderBook > & VUEX_TYPES.WalletCommitDecorators; @@ -93,6 +97,7 @@ export type DispatchDecorators = BaseModuleDecorator< typeof store.dispatch.rewards, typeof store.dispatch.staking, typeof store.dispatch.demeterFarming, - typeof store.dispatch.soraCard + typeof store.dispatch.soraCard, + typeof store.dispatch.orderBook > & VUEX_TYPES.WalletDispatchDecorators; diff --git a/src/styles/_layout.scss b/src/styles/_layout.scss index b7bd75250..5c861279b 100644 --- a/src/styles/_layout.scss +++ b/src/styles/_layout.scss @@ -7,6 +7,7 @@ $basic-spacing-medium: $basic-spacing-mini * 4; $inner-window-width: 464px; $inner-window-height: 217px; +$sidebar-max-width: 190px; $inner-spacing-mini: 8px; $inner-spacing-tiny: math.div($inner-spacing-mini, 2); diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index 0d9028d3a..2aab15861 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -265,6 +265,7 @@ $button-custom-shadow: -1px -1px 5px rgba(0, 0, 0, 0.05), 1px 1px 5px rgba(0, 0, text-align: center; &.is-active { background: var(--s-color-base-background); + box-shadow: var(--s-shadow-element); } @if $withBottomMargin == true { &:last-child { @@ -286,10 +287,6 @@ $button-custom-shadow: -1px -1px 5px rgba(0, 0, 0, 0.05), 1px 1px 5px rgba(0, 0, position: relative; } - &.is-active { - box-shadow: var(--s-shadow-element); - } - .el-collapse-item__header { height: auto; min-height: #{$collapse-icon-height + $inner-spacing-medium * 2}; @@ -311,16 +308,22 @@ $button-custom-shadow: -1px -1px 5px rgba(0, 0, 0, 0.05), 1px 1px 5px rgba(0, 0, font-weight: 600; } -@mixin scrollbar($marginOffset: 0, $verticalRight: 2px, $isLeft: false) { +@mixin scrollbar( + $marginOffset: 0, + $verticalRight: 2px, + $isLeft: false, + $withHorizontalScroll: false, + $hideVerticalScroll: false +) { &.el-scrollbar { margin-left: $marginOffset; margin-right: $marginOffset; & > .el-scrollbar__wrap { - overflow-x: hidden; - margin-bottom: 0px !important; // to disable element-ui negative margin - } - & > .el-scrollbar__wrap { + @if $withHorizontalScroll == false { + overflow-x: hidden; + margin-bottom: 0px !important; // to disable element-ui negative margin + } &, & > .el-scrollbar__view { display: flex; @@ -328,13 +331,15 @@ $button-custom-shadow: -1px -1px 5px rgba(0, 0, 0, 0.05), 1px 1px 5px rgba(0, 0, flex-flow: column nowrap; } } - & > .el-scrollbar__bar { - &.is-vertical { - right: $verticalRight; + & > .el-scrollbar__bar.is-vertical { + right: $verticalRight; - @if $isLeft == true { - left: 0; - } + @if $isLeft == true { + left: 0; + } + + @if $hideVerticalScroll == true { + width: 0; } } } @@ -615,12 +620,22 @@ $button-custom-shadow: -1px -1px 5px rgba(0, 0, 0, 0.05), 1px 1px 5px rgba(0, 0, &-address { display: flex; font-size: var(--s-font-size-extra-mini); + .tokens-item-address__value { + &.token-address { + font-size: var(--s-font-size-extra-mini); + font-weight: 400; + color: var(--s-color-base-content-primary); + } + } } &-tokens { display: flex; flex-flow: column nowrap; align-items: flex-end; } + &-token { + font-size: var(--s-font-size-small); + } &-price { font-size: var(--s-font-size-medium); white-space: nowrap; @@ -628,15 +643,6 @@ $button-custom-shadow: -1px -1px 5px rgba(0, 0, 0, 0.05), 1px 1px 5px rgba(0, 0, &-amount.formatted-amount--fiat-value { color: var(--s-color-base-content-primary) !important; } - &-address { - .tokens-item-address__value { - &.token-address { - font-size: var(--s-font-size-extra-mini); - font-weight: 400; - color: var(--s-color-base-content-primary); - } - } - } } } @@ -679,12 +685,8 @@ $button-custom-shadow: -1px -1px 5px rgba(0, 0, 0, 0.05), 1px 1px 5px rgba(0, 0, {$tabs-class} { #{$tabs-class}__header { width: 100%; - } - #{$tabs-class} { - &__header { - #{$tabs-class}__item { - font-weight: 600; - } + #{$tabs-class}__item { + font-weight: 600; } } } diff --git a/src/styles/soramitsu-variables.scss b/src/styles/soramitsu-variables.scss index b9702008d..b3d33c7e3 100644 --- a/src/styles/soramitsu-variables.scss +++ b/src/styles/soramitsu-variables.scss @@ -1,40 +1,40 @@ -$s-color-brand-day: #A19A9D; // 0d0248 +$s-color-brand-day: #a19a9d; // 0d0248 // Primary theme colors -$s-color-theme-accent: #F8087B; // NEU bright pink fluro -$s-color-theme-accent-hover: #F754A3; // NEU pale quartz pink +$s-color-theme-accent: #f8087b; // NEU bright pink fluro +$s-color-theme-accent-hover: #f754a3; // NEU pale quartz pink $s-color-theme-accent-pressed: #bf065f; $s-color-theme-accent-focused: #ab0555; // Secondary theme colors -$s-color-theme-secondary: #44E5B2; -$s-color-theme-secondary-hover: #24DAA0; -$s-color-theme-secondary-pressed: #24DAA0; -$s-color-theme-secondary-focused: #24DAA0; +$s-color-theme-secondary: #44e5b2; +$s-color-theme-secondary-hover: #24daa0; +$s-color-theme-secondary-pressed: #24daa0; +$s-color-theme-secondary-focused: #24daa0; // Base content colors -$s-color-base-content-primary: #2A171F; -$s-color-base-content-secondary: #A19A9D; -$s-color-base-content-tertiary: #D5CDD0; +$s-color-base-content-primary: #2a171f; +$s-color-base-content-secondary: #a19a9d; +$s-color-base-content-tertiary: #d5cdd0; $s-color-base-content-quaternary: #75787b; // Base misc colors -$s-color-base-background: #FAF4F8; -$s-color-base-border-primary: #F7F3F4; -$s-color-base-border-secondary: #EDE4E7; -$s-color-base-background-hover: #F7F3F4; -$s-color-base-disabled: #FDF7FB; -$s-color-base-on-disabled: #A19A9D; -$s-color-base-on-accent: #FFFFFF; +$s-color-base-background: #faf4f8; +$s-color-base-border-primary: #f7f3f4; +$s-color-base-border-secondary: #ede4e7; +$s-color-base-background-hover: #f7f3f4; +$s-color-base-disabled: #fdf7fb; +$s-color-base-on-disabled: #a19a9d; +$s-color-base-on-accent: #ffffff; // Utility colors -$s-color-utility-body: #F7F3F4; -$s-color-utility-surface: #FDF7FB; +$s-color-utility-body: #f7f3f4; +$s-color-utility-surface: #fdf7fb; $s-color-utility-overlay: rgba(42, 23, 31, 0.1); // Status colors -$s-color-status-success: #34AD87; -$s-color-status-warning: #EBA332; -$s-color-status-error: #F754A3; -$s-color-status-info: #479AEF; -$s-color-status-success-background: #B9EBDB; -$s-color-status-warning-background: #FCEEBD; -$s-color-status-error-background: #FFD8EB; -$s-color-status-info-background: #C6E2FF; +$s-color-status-success: #34ad87; +$s-color-status-warning: #eba332; +$s-color-status-error: #f754a3; +$s-color-status-info: #479aef; +$s-color-status-success-background: #b9ebdb; +$s-color-status-warning-background: #fceebd; +$s-color-status-error-background: #ffd8eb; +$s-color-status-info-background: #c6e2ff; // Fiat colors $s-color-fiat-value: rgba(71, 154, 239, 1) !default; $s-color-rewards: #c0e2ff !default; @@ -46,7 +46,7 @@ $s-size-big: 58px; // Shadows $s-shadow-surface: 1px 1px 5px var(--s-shadow-color-dark), inset 1px 1px 1px var(--s-shadow-color-dark); $s-shadow-tooltip: 0px 1px 4px rgba(13, 2, 72, 0.35); -$s-shadow-tab: 1px 1px 2px #FFFFFF, inset 1px 1px 2px rgba(0, 0, 0, 0.1); // 0px 1px 1px rgba(0, 0, 0, 0.1); +$s-shadow-tab: 1px 1px 2px #ffffff, inset 1px 1px 2px rgba(0, 0, 0, 0.1); // 0px 1px 1px rgba(0, 0, 0, 0.1); $s-shadow-dropdown: 0px 4px 8px rgba(19, 19, 19, 0.15); $s-shadow-mobile-tap-bar: 0px -4px 8px rgba(19, 19, 19, 0.15); $s-shadow-mobile-side-menu: -4px 4px 8px rgba(19, 19, 19, 0.15); @@ -57,8 +57,8 @@ $s-border-radius-small: $s-border-radius-base * 1.5; $s-border-radius-medium: $s-border-radius-base * 2; $s-border-radius-big: $s-border-radius-base * 3; // Fonts paths -$s-font-family-default-path: '~@soramitsu/soramitsu-js-ui/lib/assets/fonts/Sora-VariableFont_wght.ttf' !default; -$s-font-family-mono-path: '~@soramitsu/soramitsu-js-ui/lib/assets/fonts/JetBrainsMono-Regular.woff' !default; +$s-font-family-default-path: '~@soramitsu-ui/ui-vue2/lib/assets/fonts/Sora-VariableFont_wght.ttf' !default; +$s-font-family-mono-path: '~@soramitsu-ui/ui-vue2/lib/assets/fonts/JetBrainsMono-Regular.woff' !default; $s-font-family-icons-path: '~@/assets/fonts/polkaswap_icons.ttf' !default; // Font Feature Settings $s-font-feature-settings-common: normal; @@ -75,9 +75,9 @@ $s-transition-default: all 0.125s ease-in-out; $s-asset-item-height: 71px; $s-asset-item-height--fiat: 86px; // override dark theme on disabled -$s-color-base-on-disabled--dark: #C29AB7; +$s-color-base-on-disabled--dark: #c29ab7; -@import "../../node_modules/@soramitsu/soramitsu-js-ui/lib/styles/index"; +@import '../../node_modules/@soramitsu-ui/ui-vue2/lib/styles/index'; :root { --s-color-brand-day: #{$s-color-brand-day}; diff --git a/src/types/chart.ts b/src/types/chart.ts new file mode 100644 index 000000000..b0db3887a --- /dev/null +++ b/src/types/chart.ts @@ -0,0 +1,8 @@ +/** "open", "close", "low", "high" data */ +export type OCLH = [number, number, number, number]; + +export type SnapshotItem = { + timestamp: number; + price: OCLH; + volume: number; +}; diff --git a/src/types/orderBook.ts b/src/types/orderBook.ts new file mode 100644 index 000000000..d0d4c073a --- /dev/null +++ b/src/types/orderBook.ts @@ -0,0 +1,47 @@ +import { INDEXER_TYPES } from '@soramitsu/soraneo-wallet-web'; + +import type { OrderBookId, PriceVariant } from '@sora-substrate/liquidity-proxy'; +import type { FPNumber, CodecString } from '@sora-substrate/util'; +import type { LimitOrder } from '@sora-substrate/util/build/orderBook/types'; + +export enum Filter { + open = 'open', + all = 'all', + executed = 'executed', +} + +export enum Cancel { + multiple = 'multiple', + all = 'all', +} + +export const OrderStatus = INDEXER_TYPES.OrderStatus; + +export type OrderBookDealData = { + timestamp: number; + side: PriceVariant; + price: FPNumber; + amount: FPNumber; +}; + +export type OrderBookStats = { + baseAssetReserves?: CodecString; + quoteAssetReserves?: CodecString; + price: FPNumber; + priceChange: FPNumber; + volume: FPNumber; + status: string; +}; + +export type OrderBookWithStats = { + id: OrderBookId; + stats: OrderBookStats; +}; + +export type OrderBookUpdateData = OrderBookWithStats & { + deals: OrderBookDealData[]; +}; + +export type OrderData = LimitOrder & { + status: string; +}; diff --git a/src/types/tabs.ts b/src/types/tabs.ts index ce9b64459..fb92f0756 100644 --- a/src/types/tabs.ts +++ b/src/types/tabs.ts @@ -1,3 +1,8 @@ +export enum OrderBookTabs { + Limit = 'limit', + Market = 'market', +} + export enum AlertTypeTabs { Drop = 'drop', Raise = 'raise', diff --git a/src/utils/bridge/eth/utils.ts b/src/utils/bridge/eth/utils.ts index 3ba446d2c..1feed0caf 100644 --- a/src/utils/bridge/eth/utils.ts +++ b/src/utils/bridge/eth/utils.ts @@ -122,7 +122,7 @@ export async function getIncomingEvmTransactionData({ asset, value, recipient, g const amount = new FPNumber(value, asset.externalDecimals).toCodecString(); - const contractAddress = getContractAddress(KnownEthBridgeAsset.Other)!; + const contractAddress = getContractAddress(KnownEthBridgeAsset.Other) as string; const contractAbi = SmartContracts[SmartContractType.EthBridge][KnownEthBridgeAsset.Other].abi; const contract = new ethers.Contract(contractAddress, contractAbi, signer); @@ -159,7 +159,7 @@ export async function getOutgoingEvmTransactionData({ const symbol = asset.symbol as KnownEthBridgeAsset; const isValOrXor = [KnownEthBridgeAsset.XOR, KnownEthBridgeAsset.VAL].includes(symbol); const bridgeAsset: KnownEthBridgeAsset = isValOrXor ? symbol : KnownEthBridgeAsset.Other; - const contractAddress = getContractAddress(bridgeAsset)!; + const contractAddress = getContractAddress(bridgeAsset) as string; const contractAbi = SmartContracts[SmartContractType.EthBridge][bridgeAsset].abi; const contract = new ethers.Contract(contractAddress, contractAbi, signer); @@ -267,10 +267,10 @@ export async function getEthNetworkFee( try { const { contract, method, args } = await getIncomingEvmTransactionData(txParams); - const signer = contract.runner!; + const signer = contract.runner; const tx = await contract[method].populateTransaction(...args); - txGasLimit = await signer.estimateGas!(tx); + txGasLimit = (await signer?.estimateGas?.(tx)) ?? BigInt(0); } catch { txGasLimit = getEthBridgeIncomingGasLimit(asset.externalAddress); } diff --git a/src/utils/bridge/sub/classes/adapter.ts b/src/utils/bridge/sub/classes/adapter.ts index 01c6fe43a..3a9fb4393 100644 --- a/src/utils/bridge/sub/classes/adapter.ts +++ b/src/utils/bridge/sub/classes/adapter.ts @@ -269,6 +269,13 @@ export class SubNetworksConnector { protected getConnection( network: SubNetwork, connectorAdapter?: Adapter + ): SubNetworkConnection; + + protected getConnection(network: undefined, connectorAdapter?: Adapter): undefined; + + protected getConnection( + network?: SubNetwork, + connectorAdapter?: Adapter ): SubNetworkConnection | undefined { if (!network) return undefined; @@ -312,7 +319,7 @@ export class SubNetworksConnector { public async init(destination: SubNetwork, connector?: SubNetworksConnector): Promise { const [soraParachain, relaychain, parachain] = this.getChains(destination); // Create adapters - this.soraParachain = this.getConnection(soraParachain, connector?.soraParachain?.adapter)!; + this.soraParachain = this.getConnection(soraParachain, connector?.soraParachain?.adapter); this.relaychain = this.getConnection(relaychain, connector?.relaychain?.adapter); this.parachain = this.getConnection(parachain, connector?.parachain?.adapter); // link destination network diff --git a/src/utils/bridge/sub/classes/history.ts b/src/utils/bridge/sub/classes/history.ts index 0f88e4dcd..7799c01c2 100644 --- a/src/utils/bridge/sub/classes/history.ts +++ b/src/utils/bridge/sub/classes/history.ts @@ -25,7 +25,7 @@ import type { ActionContext } from 'vuex'; const hasFinishedState = (item: Nullable) => { if (!item) return false; - return [BridgeTxStatus.Done, BridgeTxStatus.Failed].includes(item.transactionState!); + return [BridgeTxStatus.Done, BridgeTxStatus.Failed].includes(item.transactionState as BridgeTxStatus); }; const getType = (isOutgoing: boolean) => { @@ -252,7 +252,7 @@ class SubBridgeHistory extends SubNetworksConnector { history.transactionState = BridgeTxStatus.Failed; } - history.externalNetwork = subBridgeApi.getSoraParachain(history.externalNetwork!); + history.externalNetwork = subBridgeApi.getSoraParachain(history.externalNetwork as SubNetwork); history.externalBlockId = history.parachainBlockId; history.to = formatSubAddress(history.to as string, this.parachainApi.registry.chainSS58 as number); history.parachainBlockId = undefined; // parachain is external network @@ -374,7 +374,7 @@ class SubBridgeHistory extends SubNetworksConnector { ); const signer = feeEvent.event.data[0].toString(); // signer is spent balance for fee - history.externalNetwork = subBridgeApi.getSoraParachain(history.externalNetwork!); + history.externalNetwork = subBridgeApi.getSoraParachain(history.externalNetwork as SubNetwork); history.externalNetworkFee = feeEvent.event.data[1].toString(); history.externalBlockId = parachainBlockId; history.to = formatSubAddress(signer, this.parachainApi.registry.chainSS58 as number); diff --git a/src/utils/index.ts b/src/utils/index.ts index b8ab25656..3ce3cb43b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,6 +2,7 @@ import { FPNumber, CodecString } from '@sora-substrate/util'; import { isNativeAsset } from '@sora-substrate/util/build/assets'; import { XOR } from '@sora-substrate/util/build/assets/consts'; import { api, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; +import scrollbarWidth from 'element-ui/src/utils/scrollbar-width'; import debounce from 'lodash/debounce'; import { app, ZeroStringValue } from '@/consts'; @@ -20,6 +21,12 @@ type AssetWithBalance = AccountAsset | RegisteredAccountAsset; type PoolAssets = { baseAsset: T; poolAsset: T }; +export async function waitUntil(condition: () => boolean): Promise { + if (condition()) return; + await delay(250); + await waitUntil(condition); +} + export async function waitForSoraNetworkFromEnv(): Promise { return new Promise((resolve) => { store.original.watch( @@ -374,3 +381,5 @@ export const sortPools = (a: PoolAssets, b: PoolAssets) = return byBaseAsset === 0 ? sortAssets(a.poolAsset, b.poolAsset) : byBaseAsset; }; + +export const calcElScrollGutter: () => number = scrollbarWidth; diff --git a/src/utils/orderBook.ts b/src/utils/orderBook.ts new file mode 100644 index 000000000..f64490168 --- /dev/null +++ b/src/utils/orderBook.ts @@ -0,0 +1,9 @@ +import type { OrderBook } from '@sora-substrate/liquidity-proxy'; + +export const MAX_ORDERS_PER_SIDE = 1024; +export const MAX_ORDERS_PER_USER = 1024; +export const MAX_ORDERS_PER_SINGLE_PRICE = 1024; + +export function getBookDecimals(orderBook: Nullable): number { + return orderBook?.stepLotSize?.toString().split('.')[1]?.length ?? 2; +} diff --git a/src/views/About.vue b/src/views/About.vue index c62dfec4b..142098875 100644 --- a/src/views/About.vue +++ b/src/views/About.vue @@ -158,7 +158,7 @@ import TranslationMixin from '@/components/mixins/TranslationMixin'; import Web3Logo from '@/components/shared/Logo/Web3.vue'; import { getter } from '@/store/decorators'; -import type Theme from '@soramitsu/soramitsu-js-ui/lib/types/Theme'; +import type Theme from '@soramitsu-ui/ui-vue2/lib/types/Theme'; @Component({ components: { diff --git a/src/views/Bridge.vue b/src/views/Bridge.vue index dbfc82c67..9f69c557a 100644 --- a/src/views/Bridge.vue +++ b/src/views/Bridge.vue @@ -78,7 +78,9 @@ v-if="changeSenderWalletEvm" class="connect-wallet-btn disconnect" @click="resetEvmProviderConnection" - >{{ t('disconnectWalletText') }} + > + {{ t('disconnectWalletText') }} + {{ t('disconnectWalletText') }} + > + {{ t('disconnectWalletText') }} + + + + + + + + + # + + + + + + + {{ t('nameText') }} + + + + {{ $index + startIndex + 1 }} + + + {{ row.baseAsset.symbol }}-{{ row.targetAsset.symbol }} + + + + + + + + Price + + + + + + + + + + + + 1D % + + + + + + + + + + + 1D Vol. + + + + + {{ row.volumeDayFormatted.suffix }} + + + + + + + + {{ TranslationConsts.TVL }} + + + + + + + + {{ row.tvlFormatted.suffix }} + + + + + + + + + + + + diff --git a/src/views/Explore/Container.vue b/src/views/Explore/Container.vue index 8503aebda..7c8747dec 100644 --- a/src/views/Explore/Container.vue +++ b/src/views/Explore/Container.vue @@ -69,12 +69,16 @@ export default class ExploreContainer extends Mixins(mixins.LoadingMixin, Transl } get tabs(): Array<{ name: string; label: string }> { - return [PageNames.ExploreFarming, PageNames.ExplorePools, PageNames.ExploreStaking, PageNames.ExploreTokens].map( - (name) => ({ - name, - label: this.t(`pageTitle.${name}`), - }) - ); + return [ + PageNames.ExploreFarming, + PageNames.ExplorePools, + PageNames.ExploreStaking, + PageNames.ExploreTokens, + PageNames.ExploreBooks, + ].map((name) => ({ + name, + label: this.t(`pageTitle.${name}`), + })); } get pageName(): string { @@ -87,7 +91,11 @@ export default class ExploreContainer extends Mixins(mixins.LoadingMixin, Transl /** Shown only for logged in users and for any tab on page except Tokens */ get switcherAvailable(): boolean { - return this.pageName !== PageNames.ExploreTokens && this.isLoggedIn; + if (!this.isLoggedIn) return false; + + return [PageNames.ExploreFarming, PageNames.ExplorePools, PageNames.ExploreStaking].includes( + this.pageName as PageNames + ); } handleTabChange(name: string): void { diff --git a/src/views/Explore/Demeter.vue b/src/views/Explore/Demeter.vue index f3cf31ad0..31b928ccc 100644 --- a/src/views/Explore/Demeter.vue +++ b/src/views/Explore/Demeter.vue @@ -109,7 +109,7 @@ value-can-be-hidden :font-size-rate="FontSizeRate.SMALL" :value="balance" - class="explore-table-item-price explore-table-item-amount" + class="explore-table-item-token" > @@ -158,12 +158,11 @@ + + diff --git a/src/views/Rewards.vue b/src/views/Rewards.vue index 6367f8822..14f697860 100644 --- a/src/views/Rewards.vue +++ b/src/views/Rewards.vue @@ -129,7 +129,7 @@ import ethersUtil from '@/utils/ethers-util'; import type { AccountAsset, Asset } from '@sora-substrate/util/build/assets/types'; import type { RewardInfo, RewardsInfo } from '@sora-substrate/util/build/rewards/types'; -import type Theme from '@soramitsu/soramitsu-js-ui/lib/types/Theme'; +import type Theme from '@soramitsu-ui/ui-vue2/lib/types/Theme'; @Component({ components: { diff --git a/src/views/SoraCard.vue b/src/views/SoraCard.vue index 032825910..dc13ab468 100644 --- a/src/views/SoraCard.vue +++ b/src/views/SoraCard.vue @@ -21,7 +21,7 @@ import SubscriptionsMixin from '@/components/mixins/SubscriptionsMixin'; import { Components } from '@/consts'; import { lazyComponent } from '@/router'; import { action, state, getter, mutation } from '@/store/decorators'; -import { AttemptCounter, UserInfo, VerificationStatus } from '@/types/card'; +import { AttemptCounter, VerificationStatus } from '@/types/card'; import { waitForSoraNetworkFromEnv } from '@/utils'; import type { NavigationGuardNext, Route } from 'vue-router'; diff --git a/src/views/Swap.vue b/src/views/Swap.vue index bf0fbfce2..2bdafff78 100644 --- a/src/views/Swap.vue +++ b/src/views/Swap.vue @@ -147,8 +147,8 @@ @@ -217,11 +217,12 @@ export default class Swap extends Mixins( @state.swap.isAvailable isAvailable!: boolean; @state.swap.swapQuote private swapQuote!: Nullable; @state.swap.allowLossPopup private allowLossPopup!: boolean; + @state.router.prev private prevRoute!: Nullable; @getter.assets.xor private xor!: AccountAsset; - @getter.swap.swapLiquiditySource private liquiditySource!: Nullable; + @getter.swap.swapLiquiditySource liquiditySource!: Nullable; @getter.settings.chartsFlagEnabled chartsFlagEnabled!: boolean; - @getter.settings.nodeIsConnected private nodeIsConnected!: boolean; + @getter.settings.nodeIsConnected nodeIsConnected!: boolean; @getter.settings.chartsEnabled chartsEnabled!: boolean; @getter.wallet.account.isLoggedIn isLoggedIn!: boolean; @getter.swap.tokenFrom tokenFrom!: Nullable; @@ -382,10 +383,12 @@ export default class Swap extends Mixins( ); } - created() { + created(): void { this.withApi(async () => { this.parseCurrentRoute(); - if (this.tokenFrom && this.tokenTo) { + // Need to wait the previous page beforeDestroy somehow to set the route params + // TODO: [STEFAN]: add the core logic for each component using common Mixin + vuex router module + if (this.tokenFrom && this.tokenTo && this.prevRoute !== PageNames.OrderBook) { this.updateRouteAfterSelectTokens(this.tokenFrom, this.tokenTo); } else if (this.isValidRoute && this.firstRouteAddress && this.secondRouteAddress) { await this.setTokenFromAddress(this.firstRouteAddress); diff --git a/tests/mocks/tokens.ts b/tests/mocks/tokens.ts index 046e5c9f3..d49a5a987 100644 --- a/tests/mocks/tokens.ts +++ b/tests/mocks/tokens.ts @@ -44,6 +44,7 @@ export const MOCK_ACCOUNT_ASSETS: Array = [ symbol: KnownSymbols.XOR, name: 'SORA', decimals: FPNumber.DEFAULT_PRECISION, + isMintable: true, balance: { transferable: '123400000000000000000', total: '123400000000000000000', @@ -59,6 +60,7 @@ export const MOCK_ACCOUNT_ASSETS: Array = [ symbol: KnownSymbols.VAL, name: 'SORA Validator Token', decimals: FPNumber.DEFAULT_PRECISION, + isMintable: true, balance: { transferable: '0', total: '0', @@ -74,6 +76,7 @@ export const MOCK_ACCOUNT_ASSETS: Array = [ symbol: KnownSymbols.PSWAP, name: 'Polkaswap', decimals: FPNumber.DEFAULT_PRECISION, + isMintable: true, balance: { transferable: '1000000000000', total: '1000000000000', diff --git a/tests/unit/components/App/Header/AccountButton.spec.ts b/tests/unit/components/App/Header/AccountButton.spec.ts index 060f0e1f5..4535d2010 100644 --- a/tests/unit/components/App/Header/AccountButton.spec.ts +++ b/tests/unit/components/App/Header/AccountButton.spec.ts @@ -3,7 +3,7 @@ // // import AccountButton from '@/components/App/Header/AccountButton.vue'; // import { Account } from '@soramitsu/soraneo-wallet-web/lib/types/common'; -// import Theme from '@soramitsu/soramitsu-js-ui/lib/types/Theme'; +// import Theme from '@soramitsu-ui/ui-vue2/lib/types/Theme'; // import { useDescribe, localVue } from '../../../../utils'; // import { MOCK_ACCOUNT } from '../../../../mocks/account'; // import { formatAddress } from '../../../../../src/utils'; diff --git a/tests/utils/index.ts b/tests/utils/index.ts index 201a914ac..ea81be9bf 100644 --- a/tests/utils/index.ts +++ b/tests/utils/index.ts @@ -1,5 +1,5 @@ -import SoramitsuElements, { Message, MessageBox, Notification } from '@soramitsu/soramitsu-js-ui'; import Wallet from '@soramitsu/soraneo-wallet-web'; +import SoramitsuElements, { Message, MessageBox, Notification } from '@soramitsu-ui/ui-vue2'; import { createLocalVue } from '@vue/test-utils'; import Vue, { VueConstructor } from 'vue'; import Vuex from 'vuex'; diff --git a/yarn.lock b/yarn.lock index a5dee1806..725fa4c6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2349,68 +2349,68 @@ resolved "https://registry.yarnpkg.com/@soda/get-current-script/-/get-current-script-1.0.2.tgz#a53515db25d8038374381b73af20bb4f2e508d87" integrity sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w== -"@sora-substrate/api@1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@sora-substrate/api/-/api-1.28.0.tgz#9534b4621db69d457e758de8d2d8477f5743d04d" - integrity sha512-2OlJE4AVKw22QLJWMQOLRmAnPOY2IdRpo15Vb410dQZ//whAWeCdb+z2gso/xzBbBAKkJMTDN18TdCSHLhwEnQ== +"@sora-substrate/api@1.29.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@sora-substrate/api/-/api-1.29.1.tgz#bdb48accee2c439cf13e9c34f31497157538150d" + integrity sha512-NK/h6OnImqnEY3gO/74JgCBUPfaor6p3YcjmRt8BnpefVXdHkMSNfVt1jLn+6g4pO0vC0T/WT+9Awudixhcl+Q== dependencies: "@open-web3/orml-api-derive" "1.1.4" "@polkadot/api" "9.14.2" - "@sora-substrate/types" "1.28.0" + "@sora-substrate/types" "1.29.1" -"@sora-substrate/connection@1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@sora-substrate/connection/-/connection-1.28.0.tgz#9ca8790b561ee417e67f3fcd91001593f522807e" - integrity sha512-11HEICfy3JwtdRGJwJMB4/Z4SINUf4kcn5PcsCKwZMC5DZDQADylp7pcy6UwGcr/Eq4hwvl6iI6R5XH9lXLuqQ== +"@sora-substrate/connection@1.29.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@sora-substrate/connection/-/connection-1.29.1.tgz#80dfe398811f3421cf841a578dae9ce6b326e4a4" + integrity sha512-0r04vqij21TsgK9AGxM/Ef07/6jeqLnFDeSVIJeT47/wNoVrOQj+ZiZwOwB4AcFp1af9Y85E/yTJ4cY2CbWTUA== dependencies: - "@sora-substrate/api" "1.28.0" + "@sora-substrate/api" "1.29.1" -"@sora-substrate/liquidity-proxy@1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@sora-substrate/liquidity-proxy/-/liquidity-proxy-1.28.0.tgz#c036dc55219116aaffb3ba78efb849241d492f32" - integrity sha512-IwF4rJtq/6GzkeyRgP9/gIPZ6McxxKjLonQXMqGFKdZVy/1rnZXWXhZQLJ+V3btRdSNszsgM6Mzoi26Civ7vsw== +"@sora-substrate/liquidity-proxy@1.29.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@sora-substrate/liquidity-proxy/-/liquidity-proxy-1.29.1.tgz#321b80a9bdef131c5f0f2c5d29bef0b7b18a7dae" + integrity sha512-iFqL3Xz5ipGS2QhMoeslHLJVhmVUFldI4TomELV3llJLsRuJo0gE4Ng6sejAmjCPbPWKEuajbyqGm9DZtHvHQQ== dependencies: - "@sora-substrate/math" "1.28.0" + "@sora-substrate/math" "1.29.1" -"@sora-substrate/math@1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@sora-substrate/math/-/math-1.28.0.tgz#289681ad1b9d23b52a5158c5eadfa8a94391504e" - integrity sha512-ouTCvQPAyiIAT2hqkGIXqPVVbK3Q7tHQC6PUhTPVIjljL+64ybI9+z1LZz08oK2KHu1kHU3x3Txz9yHMMr+U/w== +"@sora-substrate/math@1.29.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@sora-substrate/math/-/math-1.29.1.tgz#3afddc41c87ca1544d288b8966004581ca929a45" + integrity sha512-Zg64yVFKej4h2jgonYH8Wgg8ctwDZNCmkaJZEriOU5wAiqoDZ1w4v7+tbPtID98m8lfNsixd2t7JZ+smEcyobw== dependencies: "@polkadot/types" "9.14.2" bignumber.js "^9.0.1" lodash "^4.17.15" -"@sora-substrate/type-definitions@1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@sora-substrate/type-definitions/-/type-definitions-1.28.0.tgz#64b99491622ad4923c70c652db28e39ef17c5403" - integrity sha512-BYfv5ZXl8fGnXoUgQkB55UaF5jM2k1aCjVz0l6o19dlXJeXDRNAeLzr2GRcvuEyb57AifqceTPEchcghDqe1Pw== +"@sora-substrate/type-definitions@1.29.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@sora-substrate/type-definitions/-/type-definitions-1.29.1.tgz#3da72f9948f9a8b51a3b71a6a401b8ec706e9f4c" + integrity sha512-ekWqL9r+o2YfBwgrvlYRMSmVfw/zZkvPVptKlUClCN60sOU/8RghjprGxV1WCGEiQw8xftHz51L808uuGgCTgA== dependencies: "@open-web3/orml-type-definitions" "1.1.4" -"@sora-substrate/types@1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@sora-substrate/types/-/types-1.28.0.tgz#332e0af02496436ccf9f4ea26b90fb3686c20872" - integrity sha512-ce2OhFr5BWKUCY4ot6OGOllt1EwHvC7WBZ/ORWmSMVtYr5qVbl13gwBnU94MVUD9T//gw9/Y28fb0CVQ4674fA== +"@sora-substrate/types@1.29.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@sora-substrate/types/-/types-1.29.1.tgz#ebe9c5d16b3e30ad519edf0d0f08811d71c8cb55" + integrity sha512-FwFnAkuq46+spFHFY3JfG0+61qUp+UqpRkx5+ttDKKhHBi7EqckGWlz98xjiC5cYEmWgtGIlmgPvBfznb8MgrA== dependencies: "@open-web3/api-mobx" "0.9.4-26" "@open-web3/orml-types" "1.1.4" "@polkadot/api" "9.14.2" "@polkadot/typegen" "9.14.2" "@polkadot/types" "9.14.2" - "@sora-substrate/type-definitions" "1.28.0" + "@sora-substrate/type-definitions" "1.29.1" -"@sora-substrate/util@1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@sora-substrate/util/-/util-1.28.0.tgz#2fa20c0252f03da8d9ffd170b478b2d97168f941" - integrity sha512-FoN6dyEIgqUbYjBPKKBJwKCK4MIRpHcs0rLRqqqB0GCcp1hfCwihYmru/mhZOs5qFmq9WqMFzK0k0FgPYlCRdw== +"@sora-substrate/util@1.29.1": + version "1.29.1" + resolved "https://registry.yarnpkg.com/@sora-substrate/util/-/util-1.29.1.tgz#08062519915e4340dba63301c7044a0e06643bb0" + integrity sha512-HYwC7fJFJwCppjS6xJhLDorsxW4tkMDiaR2QrirunFkVVNhvXCUjDLxWcGTlQZigjGURrsWsEcE86RYzFVOOEg== dependencies: "@polkadot/ui-keyring" "2.12.1" - "@sora-substrate/api" "1.28.0" - "@sora-substrate/connection" "1.28.0" - "@sora-substrate/liquidity-proxy" "1.28.0" - "@sora-substrate/math" "1.28.0" - "@sora-substrate/types" "1.28.0" + "@sora-substrate/api" "1.29.1" + "@sora-substrate/connection" "1.29.1" + "@sora-substrate/liquidity-proxy" "1.29.1" + "@sora-substrate/math" "1.29.1" + "@sora-substrate/types" "1.29.1" axios "^0.21.1" crypto-js "^4.0.0" lodash "^4.17.15" @@ -2426,10 +2426,10 @@ "@polkadot/types" "9.14.2" "@polkadot/util" "10.4.2" -"@soramitsu/soramitsu-js-ui@^1.0.46": - version "1.1.0" - resolved "https://nexus.iroha.tech/repository/npm-group/@soramitsu/soramitsu-js-ui/-/soramitsu-js-ui-1.1.0.tgz#6572a47b69f69cf59ba7566abd73502a8f47b851" - integrity sha512-U0kn9lA/ENNo/DDiZ79PgPsODys1tpl8nsYwY4YyxC+Vc+T7h7TMy40yiIQe4cIGL9p+G9s2wJFyvOSt+v46kQ== +"@soramitsu-ui/ui-vue2@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@soramitsu-ui/ui-vue2/-/ui-vue2-1.1.1.tgz#c410869e09617bd3772d2663e6a40e193eecc5ec" + integrity sha512-BDSdDFPGKue3EKaCgFf5iycDlCqWA1zL2uMCC36hZR1uNXvBQADjbHPP6gcxbeCwB/ZRVDIksNviBclXenDBFg== dependencies: core-js "^3.26.1" element-resize-detector "^1.2.4" @@ -2443,15 +2443,15 @@ vue-property-decorator "^9.1.2" vuex "^3.6.2" -"@soramitsu/soraneo-wallet-web@1.28.0": - version "1.28.0" - resolved "https://nexus.iroha.tech/repository/npm-group/@soramitsu/soraneo-wallet-web/-/soraneo-wallet-web-1.28.0.tgz#a0bbc34aecd369aba3259e322eb9e0b23734d726" - integrity sha512-+n8Y3FpeG511FaszYpqeIziSTtFKOfYg7WjAvZ1TxtKCk14zuRijcYnR8awd4aJA2v5+SOBvg2h/Tw2s6VF9TQ== +"@soramitsu/soraneo-wallet-web@1.29.4": + version "1.29.4" + resolved "https://nexus.iroha.tech/repository/npm-group/@soramitsu/soraneo-wallet-web/-/soraneo-wallet-web-1.29.4.tgz#dd39840bd680449630530ea537ba3d503acb07a0" + integrity sha512-3ALaMj7rPuHiNQGymiRYIZXKeDQfRBI+DoKXxq3NMu+lK52bb4lsnMoUazwck/cFJ5492mAH0P3GWfupwqX6rA== dependencies: "@polkadot/vue-identicon" "2.12.1" - "@sora-substrate/util" "1.28.0" + "@sora-substrate/util" "1.29.1" "@sora-test/wallet-connect" "^0.0.9" - "@soramitsu/soramitsu-js-ui" "^1.0.46" + "@soramitsu-ui/ui-vue2" "^1.1.1" "@urql/core" "^4.1.2" "@zxing/browser" "^0.1.4" "@zxing/library" "^0.20.0" @@ -3529,7 +3529,7 @@ dependencies: vue-eslint-parser "^7.0.0" -"@vue/test-utils@^1.2.2": +"@vue/test-utils@^1.3.6": version "1.3.6" resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.3.6.tgz#6656bd8fa44dd088b4ad80ff1ee28abe7e5ddf87" integrity sha512-udMmmF1ts3zwxUJEIAj5ziioR900reDrt6C9H3XpWPsLBx2lpHKoA4BTdd9HNIYbkGltWw+JjWJ+5O6QBwiyEw== @@ -3549,7 +3549,7 @@ vue-hot-reload-api "^2.3.0" vue-style-loader "^4.1.0" -"@vue/vue2-jest@^27.0.0-alpha.2": +"@vue/vue2-jest@^27.0.0": version "27.0.0" resolved "https://registry.yarnpkg.com/@vue/vue2-jest/-/vue2-jest-27.0.0.tgz#456076e27f7fa0179a37b04baea3e0b3cf786c6d" integrity sha512-r8YGOuqEWpAf2wGfgxfOL6Jce3WYOMcYji2qd8kuDe466ZsybHFeMryMJi6JrELOOI+MCA/8eFsSOx1KoJa7Dg== @@ -4102,12 +4102,7 @@ acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.0.5, acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -acorn@^8.10.0: +acorn@^8.0.4, acorn@^8.0.5, acorn@^8.10.0, acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2: version "8.11.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== @@ -6428,7 +6423,7 @@ easy-stack@1.0.1: resolved "https://registry.yarnpkg.com/easy-stack/-/easy-stack-1.0.1.tgz#8afe4264626988cabb11f3c704ccd0c835411066" integrity sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w== -echarts@^5.4.1: +echarts@^5.4.3: version "5.4.3" resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.4.3.tgz#f5522ef24419164903eedcfd2b506c6fc91fb20c" integrity sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA== @@ -6483,7 +6478,7 @@ electron-builder@^22.2.0: update-notifier "^5.1.0" yargs "^17.0.1" -electron-devtools-installer@^3.1.0: +electron-devtools-installer@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/electron-devtools-installer/-/electron-devtools-installer-3.2.0.tgz#acc48d24eb7033fe5af284a19667e73b78d406d0" integrity sha512-t3UczsYugm4OAbqvdImMCImIMVdFzJAHgbwHpkl5jmfu1izVgUcP/mnrPqJIpEeCK1uZGpt+yHgWEN+9EwoYhQ== @@ -6936,7 +6931,7 @@ eslint-plugin-node@^11.1.0: resolve "^1.10.1" semver "^6.1.0" -eslint-plugin-prettier@^4.0.0: +eslint-plugin-prettier@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== @@ -7004,7 +6999,7 @@ eslint-webpack-plugin@^3.1.0: normalize-path "^3.0.0" schema-utils "^4.0.0" -eslint@^7.29.0: +eslint@^7.32.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== @@ -9689,7 +9684,7 @@ jest-worker@^28.0.2: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^27.1.0, jest@^27.2.2: +jest@^27.1.0, jest@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== @@ -9754,7 +9749,7 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsdom@^16.6.0: +jsdom@^16.6.0, jsdom@^16.7.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== @@ -11856,7 +11851,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -"prettier@^1.18.2 || ^2.0.0", prettier@^2.2.1: +"prettier@^1.18.2 || ^2.0.0", prettier@^2.8.8: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -13589,7 +13584,7 @@ ts-custom-error@^3.2.1: resolved "https://registry.yarnpkg.com/ts-custom-error/-/ts-custom-error-3.3.1.tgz#8bd3c8fc6b8dc8e1cb329267c45200f1e17a65d1" integrity sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A== -ts-jest@^27.0.5: +ts-jest@^27.1.5: version "27.1.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== @@ -14116,7 +14111,7 @@ vue-class-component@^7.2.6: resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-7.2.6.tgz#8471e037b8e4762f5a464686e19e5afc708502e4" integrity sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w== -vue-cli-plugin-electron-builder@^3.0.0-alpha.3: +vue-cli-plugin-electron-builder@^3.0.0-alpha.4: version "3.0.0-alpha.4" resolved "https://registry.yarnpkg.com/vue-cli-plugin-electron-builder/-/vue-cli-plugin-electron-builder-3.0.0-alpha.4.tgz#3a3aae23c593bf8a8ebaf681e3f4e614b83e6aeb" integrity sha512-YHt0w3onxE6F5OJJTkArm2gIpJi0QW8QEp7uMV+vg+Ze8C3To/Qt8K5eIPkVs72uxqjiBhmE2Xapl3rHz9kEJg== @@ -14172,7 +14167,7 @@ vue-hot-reload-api@^2.3.0: resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2" integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog== -vue-i18n@^8.11.2, vue-i18n@^8.28.2: +vue-i18n@^8.28.2: version "8.28.2" resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.28.2.tgz#913558066e274395c0a9f40b2f3393d5c2636840" integrity sha512-C5GZjs1tYlAqjwymaaCPDjCyGo10ajUphiwA922jKt9n7KPpqR7oM1PCwYzhB/E7+nT3wfdG3oRre5raIT1rKA== @@ -14256,7 +14251,7 @@ vuedraggable@^2.24.3: dependencies: sortablejs "1.10.2" -vuex@^3.1.3, vuex@^3.6.2: +vuex@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71" integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
+ {{ reading }} +