diff --git a/.github/workflows/build-release-binary.yml b/.github/workflows/build-release-binary.yml index e388999..f6128eb 100644 --- a/.github/workflows/build-release-binary.yml +++ b/.github/workflows/build-release-binary.yml @@ -83,12 +83,22 @@ jobs: cache: yarn cache-dependency-path: 'frontend/yarn.lock' - - name: Build ${{ matrix.bin }} frontend + - name: Build maker frontend + if: matrix.bin == 'maker' shell: bash run: | - cd frontend; + cd maker-frontend; yarn - APP=${{ matrix.bin }} yarn build + APP=maker yarn build + + # Overwrite taker frontend with our new fancy UI + - name: Build taker frontend + if: matrix.bin == 'taker' + shell: bash + run: | + cd taker-frontend; + yarn + yarn build - name: Build ${{ matrix.target }} ${{ matrix.bin }} release binary run: cargo build --target=${{ matrix.target }} --release --bin ${{ matrix.bin }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5fb742..2fb125b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,38 +28,64 @@ jobs: - uses: Swatinem/rust-cache@v1.3.0 - run: cargo clippy --workspace --all-targets -- -D warnings - check_frontend: + check_maker_frontend: defaults: run: - working-directory: frontend + working-directory: maker-frontend runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.4.0 - uses: actions/setup-node@v2 with: cache: 'yarn' - cache-dependency-path: frontend/yarn.lock + cache-dependency-path: maker-frontend/yarn.lock - run: yarn install - run: yarn run eslint - run: yarn run tsc - build_and_test_frontend: - strategy: - matrix: - app: [ maker, taker ] + check_taker_frontend: + defaults: + run: + working-directory: taker-frontend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.4.0 + - uses: actions/setup-node@v2 + with: + cache: 'yarn' + cache-dependency-path: taker-frontend/yarn.lock + - run: yarn install + - run: yarn run eslint + - run: yarn run tsc + + build_and_test_maker_frontend: + defaults: + run: + working-directory: maker-frontend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.4.0 + - uses: actions/setup-node@v2 + with: + cache: 'yarn' + cache-dependency-path: maker-frontend/yarn.lock + - run: yarn install + - run: APP=maker yarn test + - run: APP=maker yarn build + + build_and_test_taker_frontend: defaults: run: - working-directory: frontend + working-directory: taker-frontend runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.4.0 - uses: actions/setup-node@v2 with: cache: 'yarn' - cache-dependency-path: frontend/yarn.lock + cache-dependency-path: taker-frontend/yarn.lock - run: yarn install - - run: APP=${{ matrix.app }} yarn test - - run: APP=${{ matrix.app }} yarn build + - run: yarn build test_daemons: strategy: diff --git a/bors.toml b/bors.toml index 5463ba6..9aefe0d 100644 --- a/bors.toml +++ b/bors.toml @@ -1,9 +1,9 @@ status = [ "formatting", "clippy", - "check_frontend", - "build_and_test_frontend (maker)", - "build_and_test_frontend (taker)", + "check_maker_frontend", + "build_and_test_taker_frontend", + "build_and_test_maker_frontend", "test_daemons (ubuntu-latest)", "test_daemons (macos-latest)", "test_daemons (windows-latest)", diff --git a/daemon/build.rs b/daemon/build.rs index 1df7345..fdc86e2 100644 --- a/daemon/build.rs +++ b/daemon/build.rs @@ -2,8 +2,8 @@ use anyhow::Result; use vergen::{vergen, Config, SemverKind}; fn main() -> Result<()> { - std::fs::create_dir_all("../frontend/dist/maker")?; - std::fs::create_dir_all("../frontend/dist/taker")?; + std::fs::create_dir_all("../maker-frontend/dist/maker")?; + std::fs::create_dir_all("../taker-frontend/dist/taker")?; let mut config = Config::default(); *config.git_mut().semver_kind_mut() = SemverKind::Lightweight; diff --git a/daemon/src/routes_maker.rs b/daemon/src/routes_maker.rs index 86140ef..eae0485 100644 --- a/daemon/src/routes_maker.rs +++ b/daemon/src/routes_maker.rs @@ -193,7 +193,7 @@ pub async fn post_cfd_action( pub fn get_health_check() {} #[derive(RustEmbed)] -#[folder = "../frontend/dist/maker"] +#[folder = "../maker-frontend/dist/maker"] struct Asset; #[rocket::get("/assets/")] diff --git a/daemon/src/routes_taker.rs b/daemon/src/routes_taker.rs index 3e3e66e..5130321 100644 --- a/daemon/src/routes_taker.rs +++ b/daemon/src/routes_taker.rs @@ -197,7 +197,7 @@ pub fn margin_calc( } #[derive(RustEmbed)] -#[folder = "../frontend/dist/taker"] +#[folder = "../taker-frontend/dist/taker"] struct Asset; #[rocket::get("/assets/")] diff --git a/dprint.json b/dprint.json index 3241e34..9756968 100644 --- a/dprint.json +++ b/dprint.json @@ -7,16 +7,12 @@ "wrap_comments": true, "comment_width": 120 }, - "includes": ["**/*.{md,rs,toml,ts,tsx,js}"], - "excludes": ["**/target", - "**/sqlx-data.json", - "frontend/dist", - "**/node_modules" - ], + "includes": ["**/*.{md,rs,toml,ts,tsx,js,json}"], + "excludes": ["**/target", "**/sqlx-data.json", "maker-frontend/dist", "taker-frontend/dist", "**/node_modules"], "plugins": [ "https://plugins.dprint.dev/markdown-0.9.2.wasm", "https://plugins.dprint.dev/rustfmt-0.4.0.exe-plugin@c6bb223ef6e5e87580177f6461a0ab0554ac9ea6b54f78ea7ae8bf63b14f5bc2", - "https://plugins.dprint.dev/toml-0.4.0.wasm" + "https://plugins.dprint.dev/toml-0.4.0.wasm", "https://plugins.dprint.dev/typescript-0.35.0.wasm", "https://plugins.dprint.dev/json-0.7.2.wasm" ] diff --git a/frontend/.gitignore b/maker-frontend/.gitignore similarity index 100% rename from frontend/.gitignore rename to maker-frontend/.gitignore diff --git a/frontend/dynamicApp.ts b/maker-frontend/dynamicApp.ts similarity index 100% rename from frontend/dynamicApp.ts rename to maker-frontend/dynamicApp.ts diff --git a/frontend/index.html b/maker-frontend/index.html similarity index 100% rename from frontend/index.html rename to maker-frontend/index.html diff --git a/frontend/jest.config.js b/maker-frontend/jest.config.js similarity index 100% rename from frontend/jest.config.js rename to maker-frontend/jest.config.js diff --git a/frontend/jest.setup.js b/maker-frontend/jest.setup.js similarity index 100% rename from frontend/jest.setup.js rename to maker-frontend/jest.setup.js diff --git a/frontend/jest/mocks/cssMock.js b/maker-frontend/jest/mocks/cssMock.js similarity index 100% rename from frontend/jest/mocks/cssMock.js rename to maker-frontend/jest/mocks/cssMock.js diff --git a/frontend/package.json b/maker-frontend/package.json similarity index 100% rename from frontend/package.json rename to maker-frontend/package.json diff --git a/frontend/src/MakerApp.test.tsx b/maker-frontend/src/MakerApp.test.tsx similarity index 100% rename from frontend/src/MakerApp.test.tsx rename to maker-frontend/src/MakerApp.test.tsx diff --git a/frontend/src/MakerApp.tsx b/maker-frontend/src/MakerApp.tsx similarity index 100% rename from frontend/src/MakerApp.tsx rename to maker-frontend/src/MakerApp.tsx diff --git a/frontend/src/MakerClient.tsx b/maker-frontend/src/MakerClient.tsx similarity index 100% rename from frontend/src/MakerClient.tsx rename to maker-frontend/src/MakerClient.tsx diff --git a/frontend/src/TakerApp.test.tsx b/maker-frontend/src/TakerApp.test.tsx similarity index 100% rename from frontend/src/TakerApp.test.tsx rename to maker-frontend/src/TakerApp.test.tsx diff --git a/frontend/src/TakerApp.tsx b/maker-frontend/src/TakerApp.tsx similarity index 100% rename from frontend/src/TakerApp.tsx rename to maker-frontend/src/TakerApp.tsx diff --git a/frontend/src/components/BackendMonitor.tsx b/maker-frontend/src/components/BackendMonitor.tsx similarity index 100% rename from frontend/src/components/BackendMonitor.tsx rename to maker-frontend/src/components/BackendMonitor.tsx diff --git a/frontend/src/components/CurrencyInputField.tsx b/maker-frontend/src/components/CurrencyInputField.tsx similarity index 100% rename from frontend/src/components/CurrencyInputField.tsx rename to maker-frontend/src/components/CurrencyInputField.tsx diff --git a/frontend/src/components/CurrentPrice.tsx b/maker-frontend/src/components/CurrentPrice.tsx similarity index 100% rename from frontend/src/components/CurrentPrice.tsx rename to maker-frontend/src/components/CurrentPrice.tsx diff --git a/frontend/src/components/ErrorToast.tsx b/maker-frontend/src/components/ErrorToast.tsx similarity index 100% rename from frontend/src/components/ErrorToast.tsx rename to maker-frontend/src/components/ErrorToast.tsx diff --git a/frontend/src/components/Hooks.tsx b/maker-frontend/src/components/Hooks.tsx similarity index 100% rename from frontend/src/components/Hooks.tsx rename to maker-frontend/src/components/Hooks.tsx diff --git a/frontend/src/components/HttpError.tsx b/maker-frontend/src/components/HttpError.tsx similarity index 100% rename from frontend/src/components/HttpError.tsx rename to maker-frontend/src/components/HttpError.tsx diff --git a/frontend/src/components/NavLink.tsx b/maker-frontend/src/components/NavLink.tsx similarity index 100% rename from frontend/src/components/NavLink.tsx rename to maker-frontend/src/components/NavLink.tsx diff --git a/frontend/src/components/OrderTile.tsx b/maker-frontend/src/components/OrderTile.tsx similarity index 100% rename from frontend/src/components/OrderTile.tsx rename to maker-frontend/src/components/OrderTile.tsx diff --git a/frontend/src/components/Timestamp.tsx b/maker-frontend/src/components/Timestamp.tsx similarity index 100% rename from frontend/src/components/Timestamp.tsx rename to maker-frontend/src/components/Timestamp.tsx diff --git a/frontend/src/components/Types.tsx b/maker-frontend/src/components/Types.tsx similarity index 100% rename from frontend/src/components/Types.tsx rename to maker-frontend/src/components/Types.tsx diff --git a/frontend/src/components/Wallet.tsx b/maker-frontend/src/components/Wallet.tsx similarity index 100% rename from frontend/src/components/Wallet.tsx rename to maker-frontend/src/components/Wallet.tsx diff --git a/frontend/src/components/cfdtables/CfdTable.tsx b/maker-frontend/src/components/cfdtables/CfdTable.tsx similarity index 100% rename from frontend/src/components/cfdtables/CfdTable.tsx rename to maker-frontend/src/components/cfdtables/CfdTable.tsx diff --git a/frontend/src/favicon.svg b/maker-frontend/src/favicon.svg similarity index 100% rename from frontend/src/favicon.svg rename to maker-frontend/src/favicon.svg diff --git a/frontend/src/index.css b/maker-frontend/src/index.css similarity index 100% rename from frontend/src/index.css rename to maker-frontend/src/index.css diff --git a/frontend/src/logo.svg b/maker-frontend/src/logo.svg similarity index 100% rename from frontend/src/logo.svg rename to maker-frontend/src/logo.svg diff --git a/frontend/src/maker.tsx b/maker-frontend/src/maker.tsx similarity index 100% rename from frontend/src/maker.tsx rename to maker-frontend/src/maker.tsx diff --git a/frontend/src/taker.tsx b/maker-frontend/src/taker.tsx similarity index 100% rename from frontend/src/taker.tsx rename to maker-frontend/src/taker.tsx diff --git a/frontend/src/theme.tsx b/maker-frontend/src/theme.tsx similarity index 100% rename from frontend/src/theme.tsx rename to maker-frontend/src/theme.tsx diff --git a/frontend/src/vite-env.d.ts b/maker-frontend/src/vite-env.d.ts similarity index 100% rename from frontend/src/vite-env.d.ts rename to maker-frontend/src/vite-env.d.ts diff --git a/frontend/tsconfig.json b/maker-frontend/tsconfig.json similarity index 100% rename from frontend/tsconfig.json rename to maker-frontend/tsconfig.json diff --git a/frontend/vite.config.ts b/maker-frontend/vite.config.ts similarity index 100% rename from frontend/vite.config.ts rename to maker-frontend/vite.config.ts diff --git a/frontend/yarn.lock b/maker-frontend/yarn.lock similarity index 100% rename from frontend/yarn.lock rename to maker-frontend/yarn.lock diff --git a/start_all.sh b/start_all.sh index dc17cf4..ec0b3b1 100755 --- a/start_all.sh +++ b/start_all.sh @@ -5,4 +5,4 @@ export RUST_BACKTRACE=1 # A simple command to spin up the complete package, ie. both daemons and frontends. # A single 'ctrl+c' stops all processes. # The maker-id is generated from the makers seed found in daemon/util/testnet_seeds/maker_seed -(trap 'kill 0' SIGINT; cargo run --bin maker -- testnet & cargo run --bin taker -- --maker-id 10d4ba2ac3f7a22da4009d813ff1bc3f404dfe2cc93a32bedf1512aa9951c95e testnet -- & APP=maker yarn --cwd=./frontend dev & APP=taker yarn --cwd=./frontend dev) \ No newline at end of file +(trap 'kill 0' SIGINT; cargo run --bin maker -- testnet & cargo run --bin taker -- --maker-id 10d4ba2ac3f7a22da4009d813ff1bc3f404dfe2cc93a32bedf1512aa9951c95e testnet -- & APP=maker yarn --cwd=./maker-frontend dev & yarn --cwd=./taker-frontend dev) \ No newline at end of file diff --git a/taker-frontend/.gitignore b/taker-frontend/.gitignore new file mode 100644 index 0000000..2887e72 --- /dev/null +++ b/taker-frontend/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist-ssr +*.local +dist/ diff --git a/taker-frontend/index.html b/taker-frontend/index.html new file mode 100644 index 0000000..9de0385 --- /dev/null +++ b/taker-frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Itchy Stats + + +
+ + + diff --git a/taker-frontend/package.json b/taker-frontend/package.json new file mode 100644 index 0000000..675117c --- /dev/null +++ b/taker-frontend/package.json @@ -0,0 +1,67 @@ +{ + "name": "itchysats", + "version": "0.1.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "eslint": "eslint src/**/*.{ts,tsx}", + "tsc": "tsc" + }, + "dependencies": { + "@chakra-ui/icons": "^1.0.16", + "@chakra-ui/react": "^1.6.10", + "@emotion/react": "^11", + "@emotion/styled": "^11", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "@testing-library/jest-dom": "^5.9.0", + "@testing-library/react": "^10.2.1", + "@testing-library/user-event": "^12.0.2", + "@types/jest": "^25.0.0", + "@types/node": "^12.0.0", + "@types/react": "^17.0.34", + "@types/react-dom": "^17.0.11", + "@types/react-router-dom": "5.3.1", + "framer-motion": "^4", + "react-async": "^10.0.1", + "react-icons": "^3.0.0", + "react-refresh": "^0.10.0", + "react-router-dom": "5.3.0", + "react-scripts": "4.0.3", + "react-sse-hooks": "^1.0.5", + "web-vitals": "^0.2.2", + "@vitejs/plugin-react": "^1.0.0", + "typescript": "^4.4.4", + "vite": "^2.6.13" + }, + "devDependencies": { + "@types/eslint": "^7", + "@types/react": "^17.0.34", + "@types/react-dom": "^17.0.11", + "typescript": "^4.4.4", + "vite": "^2.6.13" + }, + "eslintConfig": { + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "parserOptions": { + "project": "./tsconfig.json" + }, + "extends": [ + "react-app", + "react-app/jest" + ], + "rules": { + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/promise-function-async": "error", + "require-await": "off", + "@typescript-eslint/require-await": "error", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-unused-vars": "error", + "react-hooks/exhaustive-deps": "error" + } + } +} diff --git a/taker-frontend/src/App.tsx b/taker-frontend/src/App.tsx new file mode 100644 index 0000000..c3add14 --- /dev/null +++ b/taker-frontend/src/App.tsx @@ -0,0 +1,168 @@ +import { Box, StackDivider, useToast, VStack } from "@chakra-ui/react"; +import * as React from "react"; +import { useEffect, useState } from "react"; +import { useAsync } from "react-async"; +import { Route, Switch } from "react-router-dom"; +import { useEventSource } from "react-sse-hooks"; +import History from "./components/History"; +import Nav from "./components/NavBar"; +import Trade from "./components/Trade"; +import { + Cfd, + CfdOrderRequestPayload, + intoCfd, + intoOrder, + MarginRequestPayload, + MarginResponse, + Order, + StateGroupKey, + WalletInfo, +} from "./components/Types"; +import { Wallet } from "./components/Wallet"; +import useLatestEvent from "./Hooks"; + +async function getMargin(payload: MarginRequestPayload): Promise { + let res = await fetch(`/api/calculate/margin`, { method: "POST", body: JSON.stringify(payload) }); + + if (!res.status.toString().startsWith("2")) { + throw new Error("failed to create new CFD order request: " + res.status + ", " + res.statusText); + } + + return res.json(); +} + +async function postCfdOrderRequest(payload: CfdOrderRequestPayload) { + let res = await fetch(`/api/cfd/order`, { method: "POST", body: JSON.stringify(payload) }); + if (!res.status.toString().startsWith("2")) { + console.log(`Error${JSON.stringify(res)}`); + throw new Error("failed to create new CFD order request: " + res.status + ", " + res.statusText); + } +} + +export const App = () => { + const toast = useToast(); + + let source = useEventSource({ source: "/api/feed" }); + const walletInfo = useLatestEvent(source, "wallet"); + const order = useLatestEvent(source, "order", intoOrder); + const cfdsOrUndefined = useLatestEvent(source, "cfds", intoCfd); + let cfds = cfdsOrUndefined ? cfdsOrUndefined! : []; + cfds.sort((a, b) => a.order_id.localeCompare(b.order_id)); + + let [quantity, setQuantity] = useState("0"); + let [margin, setMargin] = useState("0"); + let [userHasEdited, setUserHasEdited] = useState(false); + + const { price, min_quantity, max_quantity, leverage, liquidation_price: liquidationPrice } = order || {}; + + let effectiveQuantity = userHasEdited ? quantity : (min_quantity?.toString() || "0"); + + let { run: calculateMargin } = useAsync({ + deferFn: async ([payload]: any[]) => { + try { + let res = await getMargin(payload as MarginRequestPayload); + setMargin(res.margin.toString()); + } catch (e) { + const description = typeof e === "string" ? e : JSON.stringify(e); + + toast({ + title: "Error", + description, + status: "error", + duration: 9000, + isClosable: true, + }); + } + }, + }); + + let { run: makeNewOrderRequest, isLoading: isCreatingNewOrderRequest } = useAsync({ + deferFn: async ([payload]: any[]) => { + try { + await postCfdOrderRequest(payload as CfdOrderRequestPayload); + } catch (e) { + console.error(`Error received: ${JSON.stringify(e)}`); + const description = typeof e === "string" ? e : JSON.stringify(e); + + toast({ + title: "Error", + description, + status: "error", + duration: 9000, + isClosable: true, + }); + } + }, + }); + + useEffect(() => { + if (!order) { + return; + } + let quantity = effectiveQuantity ? Number.parseFloat(effectiveQuantity) : 0; + let payload: MarginRequestPayload = { + leverage: order.leverage, + price: order.price, + quantity, + }; + calculateMargin(payload); + }, // Eslint demands us to include `calculateMargin` in the list of dependencies. + // We don't want that as we will end up in an endless loop. It is safe to ignore `calculateMargin` because + // nothing in `calculateMargin` depends on outside values, i.e. is guaranteed to be stable. + // eslint-disable-next-line react-hooks/exhaustive-deps + [margin, effectiveQuantity, order]); + + const format = (val: any) => `$` + val; + const parse = (val: any) => val.replace(/^\$/, ""); + + return ( + <> +