diff --git a/.env.example b/.env.example index 080fba48..df1e93f1 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -VITE_CONTRACT_ADDRESSES_MARKET=0x08ca18ed550d6229f001641d43aac58e00f9eb7e25c9bea6d33716af61e43b2a +VITE_CONTRACT_ADDRESS_ORDERBOOK=0x164eae2ba74d71f3efb2a9adea4be8803cd464b17be841d2355f9a60301e0ff1 VITE_CONTRACT_ADDRESSES_TOKEN_FACTORY=0x3141a3f11e3f784364d57860e3a4dcf9b73d42e23fd49038773cefb09c633348 VITE_CONTRACT_ADDRESSES_PYTH=0x3cd5005f23321c8ae0ccfa98fb07d9a5ff325c483f21d2d9540d6897007600c9 VITE_INDEXER_URL=indexer.bigdevenergy.link/67b693c diff --git a/.github/workflows/deploy-to-cf-pages.yaml b/.github/workflows/deploy-to-cf-pages.yaml index 26fef827..fe32869a 100644 --- a/.github/workflows/deploy-to-cf-pages.yaml +++ b/.github/workflows/deploy-to-cf-pages.yaml @@ -1,5 +1,5 @@ name: Cloudflare Pages Deploy - + on: push: branches: @@ -11,16 +11,18 @@ on: jobs: deploy: environment: - name: ${{ (github.ref == 'refs/heads/main' && 'production') || (github.ref == 'refs/heads/develop' && 'dev') || 'preview' }} + name: + ${{ (github.ref == 'refs/heads/main' && 'production') || (github.ref == 'refs/heads/develop' && 'dev') || + 'preview' }} permissions: actions: read # Only required for private GitHub Repo contents: read deployments: write pull-requests: write runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 5 steps: - - name: 'Checkout Github Action' + - name: "Checkout Github Action" uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 @@ -40,12 +42,13 @@ jobs: echo VITE_CONTRACT_ADDRESSES_MARKET=${{ vars.VITE_CONTRACT_ADDRESSES_MARKET }} >> .env echo VITE_CONTRACT_ADDRESSES_PYTH=${{ vars.VITE_CONTRACT_ADDRESSES_PYTH }} >> .env echo VITE_CONTRACT_ADDRESSES_TOKEN_FACTORY=${{ vars.VITE_CONTRACT_ADDRESSES_TOKEN_FACTORY }} >> .env + echo VITE_CONTRACT_ADDRESS_ORDERBOOK=${{ vars.VITE_CONTRACT_ADDRESS_ORDERBOOK }} >> .env echo VITE_INDEXER_URL=${{ vars.VITE_INDEXER_URL }} >> .env cat .env - name: Build Project run: | npm run build - - name: 'Deploy to Cloudflare Pages' + - name: "Deploy to Cloudflare Pages" uses: andykenward/github-actions-cloudflare-pages@v2.3.1 with: cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/package.json b/package.json index 1a38baa0..4169a5ad 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "deploy": "gh-pages -d build" }, "dependencies": { - "@compolabs/spark-orderbook-ts-sdk": "^1.4.8", + "@compolabs/spark-orderbook-ts-sdk": "^1.5.0", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@fuels/connectors": "^0.9.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7604c2b..5dde2afc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@compolabs/spark-orderbook-ts-sdk': - specifier: ^1.4.8 - version: 1.4.8(@types/react@18.3.3)(fuels@0.93.0)(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.5.0 + version: 1.5.0(@types/react@18.3.3)(fuels@0.93.0)(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@emotion/react': specifier: ^11.11.3 version: 11.11.4(@types/react@18.3.3)(react@18.3.1) @@ -1014,8 +1014,8 @@ packages: '@coinbase/wallet-sdk@4.0.4': resolution: {integrity: sha512-74c040CRnGhfRjr3ArnkAgud86erIqdkPHNt5HR1k9u97uTIZCJww9eGYT67Qf7gHPpGS/xW8Be1D4dvRm63FA==} - '@compolabs/spark-orderbook-ts-sdk@1.4.8': - resolution: {integrity: sha512-K6Ig1UEUxtDqQS2kBTnfPJ4bYDtPeyQGl2M72mxz5Ow25WNJ5ZbA9x0/T9JS5gDCAM6rzzKco1KbeEkJKct1Kw==} + '@compolabs/spark-orderbook-ts-sdk@1.5.0': + resolution: {integrity: sha512-83clDWrz8IlgJesy6zAhyXt2yeidgbf2qhhxyq82X+6sv0BZDOoBUt/pG1VJ87EYpXbqNm1fuEKpi6TbUqJkcg==} engines: {node: '>=18'} peerDependencies: fuels: '>=0.93.0' @@ -8219,7 +8219,7 @@ snapshots: preact: 10.22.0 sha.js: 2.4.11 - '@compolabs/spark-orderbook-ts-sdk@1.4.8(@types/react@18.3.3)(fuels@0.93.0)(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@compolabs/spark-orderbook-ts-sdk@1.5.0(@types/react@18.3.3)(fuels@0.93.0)(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@apollo/client': 3.11.4(@types/react@18.3.3)(graphql-ws@5.16.0(graphql@16.9.0))(graphql@16.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) bignumber.js: 9.1.2 diff --git a/src/blockchain/FuelNetwork.ts b/src/blockchain/FuelNetwork.ts index 381dd0c8..a81c9fc6 100644 --- a/src/blockchain/FuelNetwork.ts +++ b/src/blockchain/FuelNetwork.ts @@ -30,7 +30,7 @@ import { import { Balances, FetchTradesParams, - MarketCreateEvent, + Market, PerpMaxAbsPositionSize, PerpPendingFundingPayment, SpotMarketVolume, @@ -66,6 +66,10 @@ export class FuelNetwork { return FuelNetwork.instance; } + setActiveMarket = (marketAddress: string) => { + this.orderbookSdk.setActiveMarketAddress(marketAddress); + }; + getAddress = (): Nullable => { return this.walletManager.address; }; @@ -189,8 +193,22 @@ export class FuelNetwork { // await this.sdk.fulfillPerpOrder(gasAsset, orderId, amount, tokenPriceFeed); }; - fetchSpotMarkets = async (limit: number): Promise => { - return this.orderbookSdk.fetchMarkets(limit); + fetchSpotMarkets = async (): Promise => { + const quoteToken = this.getTokenBySymbol("USDC"); + const assetIdList: [string, string][] = this.getTokenList().map((token) => [token.assetId, quoteToken.assetId]); + const markets = await this.orderbookSdk.fetchMarkets(assetIdList); + const marketIdList = assetIdList.map((pair) => markets[pair[0]]).filter(Boolean) as string[]; + + const marketConfigPromises = marketIdList.map((marketId) => this.orderbookSdk.fetchMarketConfig(marketId)); + + const marketConfigs = await Promise.all(marketConfigPromises); + + return marketConfigs.map((info, index) => ({ + id: String(index), + assetId: info.baseAssetId, + decimal: info.priceDecimals, + contractId: marketIdList[index], + })); }; fetchSpotMarketPrice = async (baseTokenAddress: string): Promise => { diff --git a/src/blockchain/constants.ts b/src/blockchain/constants.ts index 5cda4d5a..153aba85 100644 --- a/src/blockchain/constants.ts +++ b/src/blockchain/constants.ts @@ -1,18 +1,22 @@ +import { OrderbookContracts } from "@compolabs/spark-orderbook-ts-sdk"; + import TOKEN_LOGOS from "@src/constants/tokenLogos"; import { Token } from "@src/entity/Token"; import TOKENS_JSON from "./tokens.json"; -const CONTRACT_ADDRESSES_MARKET = import.meta.env.VITE_CONTRACT_ADDRESSES_MARKET; +const CONTRACT_ADDRESS_ORDERBOOK = import.meta.env.VITE_CONTRACT_ADDRESS_ORDERBOOK; const CONTRACT_ADDRESSES_TOKEN_FACTORY = import.meta.env.VITE_CONTRACT_ADDRESSES_TOKEN_FACTORY; const CONTRACT_ADDRESSES_PYTH = import.meta.env.VITE_CONTRACT_ADDRESSES_PYTH; const INDEXER_URL = import.meta.env.VITE_INDEXER_URL; -export const CONTRACT_ADDRESSES = { - market: CONTRACT_ADDRESSES_MARKET, +export const CONTRACT_ADDRESSES: OrderbookContracts = { + market: "", // Markets will be retrieved from the order book + orderbook: CONTRACT_ADDRESS_ORDERBOOK, tokenFactory: CONTRACT_ADDRESSES_TOKEN_FACTORY, pyth: CONTRACT_ADDRESSES_PYTH, }; + export interface Network { name: string; url: string; diff --git a/src/blockchain/types/index.ts b/src/blockchain/types/index.ts index 114d7d77..967fb260 100644 --- a/src/blockchain/types/index.ts +++ b/src/blockchain/types/index.ts @@ -17,10 +17,11 @@ export type FetchTradesParams = { user?: string; }; -export type MarketCreateEvent = { +export type Market = { id: string; assetId: string; decimal: number; + contractId: string; }; export type SpotMarketVolume = { diff --git a/src/entity/PerpMarket.ts b/src/entity/PerpMarket.ts index 57dfba50..483d6783 100644 --- a/src/entity/PerpMarket.ts +++ b/src/entity/PerpMarket.ts @@ -16,11 +16,13 @@ interface PerpMarketParams { pausedIndexPrice?: BN; pausedTimestamp?: number; closedPrice?: BN; + contractAddress: string; } export class PerpMarket { readonly baseToken: Token; readonly quoteToken: Token; + readonly contractAddress: string; readonly imRatio: BN; readonly mmRatio: BN; @@ -44,6 +46,7 @@ export class PerpMarket { this.pausedIndexPrice = params.pausedIndexPrice; this.pausedTimestamp = params.pausedTimestamp; this.closedPrice = params.closedPrice; + this.contractAddress = params.contractAddress; makeAutoObservable(this); } diff --git a/src/entity/SpotMarket.ts b/src/entity/SpotMarket.ts index b089a7ec..27649e7a 100644 --- a/src/entity/SpotMarket.ts +++ b/src/entity/SpotMarket.ts @@ -9,15 +9,18 @@ import { Token } from "./Token"; export class SpotMarket { readonly baseToken: Token; readonly quoteToken: Token; + readonly contractAddress: string; price: BN = BN.ZERO; setPrice = (price: BN) => (this.price = price); - constructor(baseToken: string, quoteToken: string) { + constructor(baseToken: string, quoteToken: string, contractAddress: string) { const bcNetwork = FuelNetwork.getInstance(); this.baseToken = bcNetwork.getTokenByAssetId(baseToken); this.quoteToken = bcNetwork.getTokenByAssetId(quoteToken); + this.contractAddress = contractAddress; + makeAutoObservable(this); } diff --git a/src/screens/TradeScreen/RightBlock/CreateOrder/CreateOrderVM.tsx b/src/screens/TradeScreen/RightBlock/CreateOrder/CreateOrderVM.tsx index 7bc134e3..4a9bde02 100644 --- a/src/screens/TradeScreen/RightBlock/CreateOrder/CreateOrderVM.tsx +++ b/src/screens/TradeScreen/RightBlock/CreateOrder/CreateOrderVM.tsx @@ -1,6 +1,5 @@ import React, { PropsWithChildren, useMemo } from "react"; import { - AssetType, CreateOrderParams, FulfillOrderManyParams, GetOrdersParams, @@ -314,7 +313,6 @@ class CreateOrderVM { } else { const order: CreateOrderParams = { amount: this.inputAmount.toString(), - assetType: AssetType.Base, price: this.inputPrice.toString(), type, feeAssetId: bcNetwork.getTokenBySymbol("ETH").assetId, @@ -335,7 +333,6 @@ class CreateOrderVM { const order: FulfillOrderManyParams = { amount: this.inputAmount.toString(), - assetType: AssetType.Base, orderType: type, limitType: timeInForce, price: diff --git a/src/screens/TradeScreen/TradeScreen.tsx b/src/screens/TradeScreen/TradeScreen.tsx index 44f0f30f..11fa8347 100644 --- a/src/screens/TradeScreen/TradeScreen.tsx +++ b/src/screens/TradeScreen/TradeScreen.tsx @@ -3,7 +3,6 @@ import { useLocation, useParams } from "react-router-dom"; import { observer } from "mobx-react"; import Loader from "@src/components/Loader"; -import { PerpMarket, SpotMarket } from "@src/entity"; import { useMedia } from "@src/hooks/useMedia"; import { CreateOrderVMProvider } from "@src/screens/TradeScreen/RightBlock/CreateOrder/CreateOrderVM"; import { useStores } from "@stores"; @@ -28,27 +27,14 @@ const TradeScreen: React.FC = observer(() => { const { marketId } = useParams<{ marketId: string }>(); const isPerp = location.pathname.includes("PERP"); - - const isMarketExists = (markets: SpotMarket[] | PerpMarket[]) => markets.some((market) => market.symbol === marketId); - - const spotMarketExists = isMarketExists(tradeStore.spotMarkets); - const perpMarketExists = isMarketExists(tradeStore.perpMarkets); + useEffect(() => { + tradeStore.selectActiveMarket(isPerp, marketId); + }, [marketId, isPerp]); if (!tradeStore.initialized) { return ; } - const selectedMarket = !marketId - ? tradeStore.defaultMarketSymbol - : spotMarketExists - ? marketId - : perpMarketExists - ? marketId - : tradeStore.defaultMarketSymbol; - - tradeStore.setIsPerp(isPerp && tradeStore.isPerpAvailable); - tradeStore.setMarketSymbol(selectedMarket); - return ( // TradeScreenImpl оборачивается в CreateOrderSpotVMProvider чтобы при нажатии на ордер в OrderbookAndTradesInterface устанавливать значение в RightBlock diff --git a/src/stores/SwapStore.ts b/src/stores/SwapStore.ts index 0b21c62c..71c9104e 100644 --- a/src/stores/SwapStore.ts +++ b/src/stores/SwapStore.ts @@ -1,5 +1,4 @@ import { - AssetType, FulfillOrderManyParams, GetOrdersParams, LimitType, @@ -110,7 +109,6 @@ class SwapStore { const order: FulfillOrderManyParams = { amount: isBuy ? formattedVolume : formattedAmount, - assetType: AssetType.Base, orderType: this.buyToken.symbol === "BTC" ? OrderType.Buy : OrderType.Sell, limitType: LimitType.FOK, // TODO: Check is it correct price: sellOrders[sellOrders.length - 1].price.toString(), diff --git a/src/stores/TradeStore.ts b/src/stores/TradeStore.ts index 36cf6640..293dc5fb 100644 --- a/src/stores/TradeStore.ts +++ b/src/stores/TradeStore.ts @@ -25,8 +25,7 @@ class TradeStore { spotMarkets: SpotMarket[] = []; perpMarkets: PerpMarket[] = []; marketSelectionOpened = false; - marketSymbol: Nullable = null; - readonly defaultMarketSymbol = "BTC-USDC"; + marketSymbol: string = "BTC-USDC"; isPerp = false; setIsPerp = (value: boolean) => (this.isPerp = value); @@ -89,6 +88,28 @@ class TradeStore { setMarketSymbol = (v: string) => (this.marketSymbol = v); + selectActiveMarket = (isPerp: boolean, marketId?: string) => { + const bcNetwork = FuelNetwork.getInstance(); + + const getMarket = (markets: T[]) => + markets.find((market) => market.symbol === marketId); + + const spotMarket = getMarket(this.spotMarkets); + const perpMarket = getMarket(this.perpMarkets); + + if (spotMarket || perpMarket) { + this.setMarketSymbol(marketId!); + } + + if (spotMarket) { + bcNetwork.setActiveMarket(spotMarket.contractAddress); + } else if (perpMarket) { + bcNetwork.setActiveMarket(perpMarket.contractAddress); + } + + this.setIsPerp(isPerp && this.isPerpAvailable); + }; + addToFav = (marketId: string) => { if (!this.favMarkets.includes(marketId)) { this.setFavMarkets([...this.favMarkets, marketId]); @@ -161,10 +182,15 @@ class TradeStore { const bcNetwork = FuelNetwork.getInstance(); try { - const markets = await bcNetwork!.fetchSpotMarkets(100); + const markets = await bcNetwork!.fetchSpotMarkets(); + const spotMarkets = markets .filter((market) => bcNetwork!.getTokenByAssetId(market.assetId) !== undefined) - .map((market) => new SpotMarket(market.assetId, bcNetwork!.getTokenBySymbol("USDC").assetId)); + .map( + (market) => new SpotMarket(market.assetId, bcNetwork!.getTokenBySymbol("USDC").assetId, market.contractId), + ); + + bcNetwork.setActiveMarket(markets[0].contractId); this.setSpotMarkets(spotMarkets); await this.updateMarketPrices();