diff --git a/package-lock.json b/package-lock.json index 3a04f3114..1d2e646f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,15 +36,18 @@ "axios": "^1.6.8", "buffer": "^6.0.3", "classnames": "^2.5.1", + "mermaid": "^11.4.1", "notistack": "^3.0.1", "rc-scrollbars": "^1.1.6", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-flow": "^1.0.3", "react-hook-form": "^7.53.0", "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", "react-slick": "^0.30.2", "react-window": "^1.8.10", + "reactflow": "^11.11.4", "recharts": "^2.13.3", "redux": "^5.0.1", "redux-persist": "^6.0.0", @@ -126,6 +129,26 @@ "node": ">=6.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.0.0.tgz", + "integrity": "sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==", + "dependencies": { + "package-manager-detector": "^0.2.8", + "tinyexec": "^0.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", + "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@aptos-labs/aptos-cli": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@aptos-labs/aptos-cli/-/aptos-cli-1.0.2.tgz", @@ -499,6 +522,45 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==" + }, "node_modules/@chromatic-com/storybook": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-1.9.0.tgz", @@ -1989,6 +2051,73 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" + }, + "node_modules/@iconify/utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", + "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", + "dependencies": { + "@antfu/install-pkg": "^1.0.0", + "@antfu/utils": "^8.1.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.0", + "globals": "^15.14.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.0.0", + "mlly": "^1.7.4" + } + }, + "node_modules/@iconify/utils/node_modules/confbox": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.1.tgz", + "integrity": "sha512-hkT3yDPFbs95mNCy1+7qNKC6Pro+/ibzYxtM2iqEigpf0sVw+bg4Zh9/snjsBcf990vfIsg5+1U7VyiyBb3etg==" + }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@iconify/utils/node_modules/local-pkg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz", + "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.0.1", + "quansync": "^0.2.8" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@iconify/utils/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" + }, + "node_modules/@iconify/utils/node_modules/pkg-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", + "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "dependencies": { + "confbox": "^0.2.1", + "exsolve": "^1.0.1", + "pathe": "^2.0.3" + } + }, "node_modules/@invariant-labs/locker-eclipse-sdk": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@invariant-labs/locker-eclipse-sdk/-/locker-eclipse-sdk-0.0.20.tgz", @@ -2423,6 +2552,14 @@ "react": ">=16" } }, + "node_modules/@mermaid-js/parser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz", + "integrity": "sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==", + "dependencies": { + "langium": "3.0.0" + } + }, "node_modules/@metaplex-foundation/beet": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@metaplex-foundation/beet/-/beet-0.7.1.tgz", @@ -4096,6 +4233,282 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@react-spring/zdog": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/zdog/-/zdog-9.7.5.tgz", + "integrity": "sha512-VV7vmb52wGHgDA1ry6hv+QgxTs78fqjKEQnj+M8hiBg+dwOsTtqqM24ADtc4cMAhPW+eZhVps8ZNKtjt8ouHFA==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.7.5", + "@react-spring/core": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-zdog": ">=1.0", + "zdog": ">=1.0" + } + }, + "node_modules/@reactflow/background": { + "version": "11.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz", + "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/background/node_modules/zustand": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", + "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/controls": { + "version": "11.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", + "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/controls/node_modules/zustand": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", + "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/core": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", + "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core/node_modules/zustand": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", + "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/minimap": { + "version": "11.7.14", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", + "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==", + "dependencies": { + "@reactflow/core": "11.11.4", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/minimap/node_modules/zustand": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", + "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/node-resizer": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", + "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.4", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-resizer/node_modules/zustand": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", + "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/node-toolbar": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", + "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-toolbar/node_modules/zustand": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", + "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@redux-saga/core": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.3.0.tgz", @@ -6055,36 +6468,147 @@ "@types/node": "*" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, "node_modules/@types/d3-array": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", "license": "MIT" }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" + }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "license": "MIT" }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, "node_modules/@types/d3-delaunay": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", "license": "MIT" }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" + }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "license": "MIT" }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==" + }, "node_modules/@types/d3-format": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.5.tgz", "integrity": "sha512-mLxrC1MSWupOSncXN/HOlWUAAIffAEBaI4+PKy2uMPsKe4FNZlk7qrbTjmzJXITQQqBHivaks4Td18azgqnotA==", "license": "MIT" }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==" + }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", @@ -6100,6 +6624,21 @@ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "license": "MIT" }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" + }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", @@ -6115,6 +6654,11 @@ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", "license": "MIT" }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==" + }, "node_modules/@types/d3-shape": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", @@ -6142,10 +6686,27 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, - "node_modules/@types/doctrine": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", - "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/doctrine": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", + "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", "dev": true, "license": "MIT" }, @@ -6156,6 +6717,55 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -8034,6 +8644,30 @@ "node": "*" } }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -8107,6 +8741,11 @@ "node": ">= 0.10" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -8279,7 +8918,6 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, "license": "MIT" }, "node_modules/console-browserify": { @@ -8308,6 +8946,14 @@ "dev": true, "license": "MIT" }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -8516,6 +9162,89 @@ "integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==", "license": "MIT" }, + "node_modules/cytoscape": { + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.31.1.tgz", + "integrity": "sha512-Hx5Mtb1+hnmAKaZZ/7zL1Y5HTFYOjdDswZy/jD+1WINRU8KVi1B7+vlHdsTwY+VCFucTreoyu1RDzQJ9u0d2Hw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -8528,6 +9257,40 @@ "node": ">=12" } }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -8537,6 +9300,17 @@ "node": ">=12" } }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-delaunay": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", @@ -8549,6 +9323,69 @@ "node": ">=12" } }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -8558,12 +9395,55 @@ "node": ">=12" } }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==", "license": "BSD-3-Clause" }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-interpolate": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", @@ -8585,6 +9465,65 @@ "node": ">=12" } }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -8614,6 +9553,14 @@ "node": ">=12" } }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-shape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", @@ -8680,6 +9627,72 @@ "node": ">=12" } }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz", + "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -8945,6 +9958,14 @@ "url": "https://bevry.me/fund" } }, + "node_modules/dompurify": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", + "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dot-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", @@ -9793,8 +10814,12 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", - "license": "Apache-2.0", - "optional": true + "license": "Apache-2.0" + }, + "node_modules/exsolve": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.4.tgz", + "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==" }, "node_modules/external-editor": { "version": "3.1.0", @@ -10308,6 +11333,11 @@ "dev": true, "license": "MIT" }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -11235,6 +12265,21 @@ "node": ">=18" } }, + "node_modules/katex": { + "version": "0.16.21", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", + "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/keccak": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", @@ -11260,10 +12305,81 @@ "json-buffer": "3.0.1" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==" + }, + "node_modules/konva": { + "version": "9.3.20", + "resolved": "https://registry.npmjs.org/konva/-/konva-9.3.20.tgz", + "integrity": "sha512-7XPD/YtgfzC8b1c7z0hhY5TF1IO/pBYNa29zMTA2PeBaqI0n5YplUeo4JRuRcljeAF8lWtW65jePZZF7064c8w==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/lavrton" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/konva" + }, + { + "type": "github", + "url": "https://github.com/sponsors/lavrton" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/langium": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.0.0.tgz", + "integrity": "sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==" + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11359,6 +12475,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -11499,6 +12620,24 @@ "dev": true, "license": "MIT" }, + "node_modules/marked": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz", + "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -11567,6 +12706,508 @@ "node": ">= 7.6.0" } }, + "node_modules/mermaid": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.4.1.tgz", + "integrity": "sha512-Mb01JT/x6CKDWaxigwfZYuYmDZ6xtrNwNlidKZwkSrDaY9n90tdrJTV5Umk+wP1fZscGptmKFXHsXMDEVZ+Q6A==", + "dependencies": { + "@braintree/sanitize-url": "^7.0.1", + "@iconify/utils": "^2.1.32", + "@mermaid-js/parser": "^0.3.0", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.2", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.11", + "dayjs": "^1.11.10", + "dompurify": "^3.2.1", + "katex": "^0.16.9", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^13.0.2", + "roughjs": "^4.6.6", + "stylis": "^4.3.1", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.1" + } + }, + "node_modules/mermaid/node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==" + }, + "node_modules/metro": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.81.4.tgz", + "integrity": "sha512-78f0aBNPuwXW7GFnSc+Y0vZhbuQorXxdgqQfvSRqcSizqwg9cwF27I05h47tL8AzQcizS1JZncvq4xf5u/Qykw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.25.1", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.81.4", + "metro-cache": "0.81.4", + "metro-cache-key": "0.81.4", + "metro-config": "0.81.4", + "metro-core": "0.81.4", + "metro-file-map": "0.81.4", + "metro-resolver": "0.81.4", + "metro-runtime": "0.81.4", + "metro-source-map": "0.81.4", + "metro-symbolicate": "0.81.4", + "metro-transform-plugins": "0.81.4", + "metro-transform-worker": "0.81.4", + "mime-types": "^2.1.27", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-babel-transformer": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.81.4.tgz", + "integrity": "sha512-WW0yswWrW+eTVK9sYD+b1HwWOiUlZlUoomiw9TIOk0C+dh2V90Wttn/8g62kYi0Y4i+cJfISerB2LbV4nuRGTA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.25.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-cache": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.81.4.tgz", + "integrity": "sha512-sxCPH3gowDxazSaZZrwdNPEpnxR8UeXDnvPjBF9+5btDBNN2DpWvDAXPvrohkYkFImhc0LajS2V7eOXvu9PnvQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "metro-core": "0.81.4" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-cache-key": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.81.4.tgz", + "integrity": "sha512-3SaWQybvf1ivasjBegIxzVKLJzOpcz+KsnGwXFOYADQq0VN4cnM7tT+u2jkOhk6yJiiO1WIjl68hqyMOQJRRLg==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-config": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.81.4.tgz", + "integrity": "sha512-QnhMy3bRiuimCTy7oi5Ug60javrSa3lPh0gpMAspQZHY9h6y86jwHtZPLtlj8hdWQESIlrbeL8inMSF6qI/i9Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.81.4", + "metro-cache": "0.81.4", + "metro-core": "0.81.4", + "metro-runtime": "0.81.4" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/metro-config/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "license": "MIT", + "peer": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "license": "MIT", + "peer": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/metro-config/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "license": "MIT", + "peer": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-config/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-core": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.81.4.tgz", + "integrity": "sha512-GdL4IgmgJhrMA/rTy2lRqXKeXfC77Rg+uvhUEkbhyfj/oz7PrdSgvIFzziapjdHwk1XYq0KyFh/CcVm8ZawG6A==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.81.4" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-file-map": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.81.4.tgz", + "integrity": "sha512-qUIBzkiqOi3qEuscu4cJ83OYQ4hVzjON19FAySWqYys9GKCmxlKa7LkmwqdpBso6lQl+JXZ7nCacX90w5wQvPA==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-file-map/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro-file-map/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-minify-terser": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.81.4.tgz", + "integrity": "sha512-oVvq/AGvqmbhuijJDZZ9npeWzaVyeBwQKtdlnjcQ9fH7nR15RiBr5y2zTdgTEdynqOIb1Kc16l8CQIUSzOWVFA==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-resolver": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.81.4.tgz", + "integrity": "sha512-Ng7G2mXjSExMeRzj6GC19G6IJ0mfIbOLgjArsMWJgtt9ViZiluCwgWsMW9juBC5NSwjJxUMK2x6pC5NIMFLiHA==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-runtime": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.81.4.tgz", + "integrity": "sha512-fBoRgqkF69CwyPtBNxlDi5ha26Zc8f85n2THXYoh13Jn/Bkg8KIDCdKPp/A1BbSeNnkH/++H2EIIfnmaff4uRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-source-map": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.81.4.tgz", + "integrity": "sha512-IOwVQ7mLqoqvsL70RZtl1EyE3f9jp43kVsAsb/B/zoWmu0/k4mwEhGLTxmjdXRkLJqPqPrh7WmFChAEf9trW4Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.25.3", + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.81.4", + "nullthrows": "^1.1.1", + "ob1": "0.81.4", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-source-map/node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-symbolicate": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.81.4.tgz", + "integrity": "sha512-rWxTmYVN6/BOSaMDUHT8HgCuRf6acd0AjHkenYlHpmgxg7dqdnAG1hLq999q2XpW5rX+cMamZD5W5Ez2LqGaag==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.81.4", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-symbolicate/node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "license": "MIT", + "peer": true + }, + "node_modules/metro-transform-plugins": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.81.4.tgz", + "integrity": "sha512-nlP069nDXm4v28vbll4QLApAlvVtlB66rP6h+ml8Q/CCQCPBXu2JLaoxUmkIOJQjLhMRUcgTyQHq+TXWJhydOQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.81.4", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.81.4.tgz", + "integrity": "sha512-lKAeRZ8EUMtx2cA/Y4KvICr9bIr5SE03iK3lm+l9wyn2lkjLUuPjYVep159inLeDqC6AtSubsA8MZLziP7c03g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "metro": "0.81.4", + "metro-babel-transformer": "0.81.4", + "metro-cache": "0.81.4", + "metro-cache-key": "0.81.4", + "metro-minify-terser": "0.81.4", + "metro-source-map": "0.81.4", + "metro-transform-plugins": "0.81.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/metro/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/metro/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT", + "peer": true + }, + "node_modules/metro/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/micro-ftch": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", @@ -11730,7 +13371,6 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", - "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.14.0", @@ -11743,7 +13383,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/ms": { @@ -12342,6 +13981,14 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", + "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", + "dependencies": { + "quansync": "^0.2.7" + } + }, "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", @@ -12436,6 +14083,11 @@ "dev": true, "license": "MIT" }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -12588,7 +14240,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.1.8", @@ -12600,9 +14251,22 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/polished": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", @@ -12971,6 +14635,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.8.tgz", + "integrity": "sha512-4+saucphJMazjt7iOM27mbFCk+D9dd/zmgMDCzRZ8MEoBfYp7lAvoN38et/phRQF6wOPMy/OROBGgoWeSKyluA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ] + }, "node_modules/querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", @@ -13122,6 +14801,11 @@ "react": "^18.3.1" } }, + "node_modules/react-flow": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/react-flow/-/react-flow-1.0.3.tgz", + "integrity": "sha512-Rx4Oqqbe9y7NyrUGzCrmtNvuGuMJBVjfIeMFuwbtbMc9f22sUSmxMjIA0c8r6Z7iMtB1zOWoGkTgfpmmpH9ReQ==" + }, "node_modules/react-hook-form": { "version": "7.55.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.55.0.tgz", @@ -13280,6 +14964,35 @@ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-zdog": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/react-zdog/-/react-zdog-1.2.2.tgz", + "integrity": "sha512-Ix7ALha91aOEwiHuxumCeYbARS5XNpc/w0v145oGkM6poF/CvhKJwzLhM5sEZbtrghMA+psAhOJkCTzJoseicA==", + "license": "MIT", + "peer": true, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "resize-observer-polyfill": "^1.5.1" + } + }, + "node_modules/reactflow": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", + "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==", + "dependencies": { + "@reactflow/background": "11.3.14", + "@reactflow/controls": "11.2.14", + "@reactflow/core": "11.11.4", + "@reactflow/minimap": "11.7.14", + "@reactflow/node-resizer": "2.2.14", + "@reactflow/node-toolbar": "1.3.14" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -13696,6 +15409,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/rpc-websockets": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.1.1.tgz", @@ -13781,6 +15505,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -14660,6 +16389,11 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==" + }, "node_modules/tinypool": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", @@ -14829,7 +16563,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.10" @@ -15058,10 +16791,9 @@ } }, "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "dev": true, + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", "license": "MIT" }, "node_modules/undici-types": { @@ -15999,6 +17731,59 @@ "dev": true, "license": "MIT" }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index 58723a865..12ac1910e 100644 --- a/package.json +++ b/package.json @@ -45,15 +45,18 @@ "axios": "^1.6.8", "buffer": "^6.0.3", "classnames": "^2.5.1", + "mermaid": "^11.4.1", "notistack": "^3.0.1", "rc-scrollbars": "^1.1.6", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-flow": "^1.0.3", "react-hook-form": "^7.53.0", "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", "react-slick": "^0.30.2", "react-window": "^1.8.10", + "reactflow": "^11.11.4", "recharts": "^2.13.3", "redux": "^5.0.1", "redux-persist": "^6.0.0", diff --git a/src/components/InvariantAgregatorHeader/InvariantAgregatorHeader.tsx b/src/components/InvariantAgregatorHeader/InvariantAgregatorHeader.tsx new file mode 100644 index 000000000..78f35ad73 --- /dev/null +++ b/src/components/InvariantAgregatorHeader/InvariantAgregatorHeader.tsx @@ -0,0 +1,39 @@ +import useStyles from './style' +import { Box, useMediaQuery, useTheme } from '@mui/material' +import HeaderLogo from '@static/png/InvariantAggregator/header-logo.png' +import SolarLogo from '@static/png/InvariantAggregator/solar.png' +import LifinityLogo from '@static/png/InvariantAggregator/lifinity.png' +import OrcaLogo from '@static/png/InvariantAggregator/Orca.png' +import UmbraLogo from '@static/png/InvariantAggregator/umbra.png' +import InvariantLogo from '@static/png/InvariantAggregator/Invariant.png' + +const InvariantAgregatorHeader = () => { + const { classes } = useStyles() + const theme = useTheme() + const isMobile = useMediaQuery(theme.breakpoints.down('sm')) + + const logos = [ + { src: SolarLogo, alt: 'Solar' }, + { src: LifinityLogo, alt: 'Lifinity' }, + { src: OrcaLogo, alt: 'Orca' }, + { src: UmbraLogo, alt: 'Umbra' }, + { src: InvariantLogo, alt: 'Invariant' } + ] + + return ( + + + Header Logo + + + {logos.map((logo, index) => ( + + {logo.alt} + + ))} + + + ) +} + +export default InvariantAgregatorHeader diff --git a/src/components/InvariantAgregatorHeader/style.ts b/src/components/InvariantAgregatorHeader/style.ts new file mode 100644 index 000000000..ff017e443 --- /dev/null +++ b/src/components/InvariantAgregatorHeader/style.ts @@ -0,0 +1,70 @@ +import { colors } from '@static/theme' +import { makeStyles } from 'tss-react/mui' + +const useStyles = makeStyles()(theme => { + return { + container: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + padding: '12px', + minHeight: '56px', + background: colors.invariant.dark, + borderRadius: '24px', + gap: '8px', + border: `2px solid ${colors.invariant.light}`, + [theme.breakpoints.down('sm')]: { + flexDirection: 'column', + gap: '10px', + padding: '16px 12px', + height: 'auto' + } + }, + header_logo_wrapper: { + display: 'flex', + height: '100%', + justifyContent: 'center', + alignItems: 'center', + [theme.breakpoints.down('sm')]: { + marginBottom: '8px' + } + }, + item_wrapper: { + width: '75%', + height: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '8px', + [theme.breakpoints.down('sm')]: { + width: '100%', + flexWrap: 'wrap', + gap: '12px', + justifyContent: 'space-around' + } + }, + item: { + height: '56px', + width: '56px', + borderRadius: '100%', + background: `${colors.invariant.component}`, + border: `2px solid ${colors.invariant.light}`, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + [theme.breakpoints.down('sm')]: { + height: '48px', + width: '48px' + }, + '& img': { + maxWidth: '70%', + maxHeight: '70%', + objectFit: 'contain' + } + } + } +}) + +export default useStyles diff --git a/src/components/Modals/TransactionRouteModal/TransactionRouteModal.tsx b/src/components/Modals/TransactionRouteModal/TransactionRouteModal.tsx new file mode 100644 index 000000000..6f8791ae3 --- /dev/null +++ b/src/components/Modals/TransactionRouteModal/TransactionRouteModal.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import { Popover, Box } from '@mui/material' +import useStyles from './styles' +import { FlowChartProps } from '@components/TransactionRoute/FlowChartGrid/types/types' +import TransactionRoute from '@components/TransactionRoute/TransactionRoute' + +export interface ISelectWalletModal extends FlowChartProps { + open: boolean + handleClose: () => void + + // setIsOpenSelectWallet: (isOpen: boolean) => void +} + +export const TransactionRouteModal: React.FC = ({ + open, + handleClose, + routeData, + isLoading +}) => { + const { classes } = useStyles() + + return ( +
+ + + + + +
+ ) +} + +export default TransactionRouteModal diff --git a/src/components/Modals/TransactionRouteModal/styles.ts b/src/components/Modals/TransactionRouteModal/styles.ts new file mode 100644 index 000000000..992b4a7d2 --- /dev/null +++ b/src/components/Modals/TransactionRouteModal/styles.ts @@ -0,0 +1,183 @@ +import { colors, theme, typography } from '@static/theme' +import { makeStyles } from 'tss-react/mui' + +const useStyles = makeStyles()(() => { + return { + modalContainer: { + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + display: 'flex', + alignItems: 'center', + + justifyContent: 'center', + pointerEvents: 'none', + zIndex: 1300 + }, + popoverRoot: { + position: 'fixed', + width: '100%', + height: '100%', + display: 'flex', + alignItems: 'center', + + justifyContent: 'center', + overflow: 'auto' + }, + paper: { + position: 'relative', + width: '520px', + margin: '16px', + background: 'transparent', + boxShadow: 'none', + overflow: 'visible' + }, + root: { + height: 'max-content', + display: 'flex', + justifyContent: 'center', + borderRadius: 24, + padding: '20px 24px', + [theme.breakpoints.down('sm')]: { + padding: '16px 20px' + } + }, + buttonWrapper: { + background: colors.invariant.componentBcg, + borderRadius: '8px', + pointerEvents: 'auto', + padding: '8px' + }, + title: { + ...typography.heading1, + margin: '15px 10px', + textAlign: 'center', + width: '100%' + }, + footerTitle: { + ...typography.heading3, + textAlign: 'center', + color: colors.invariant.green + }, + subTitle: { + ...typography.body3, + marginTop: 12, + color: colors.invariant.textGrey, + textAlign: 'center', + width: '100%' + }, + buttonList: { + marginTop: 12 + }, + modalFooter: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'column', + marginTop: 24 + }, + buttonPrimary: { + height: 40, + width: 200, + pointerEvents: 'auto', + + marginTop: '5px', + color: colors.invariant.componentBcg, + ...typography.body1, + textTransform: 'none', + borderRadius: 14, + background: colors.invariant.pinkLinearGradientOpacity, + + '&:hover': { + background: colors.invariant.pinkLinearGradient, + boxShadow: '0px 0px 16px rgba(239, 132, 245, 0.35)', + '@media (hover: none)': { + background: colors.invariant.pinkLinearGradientOpacity, + boxShadow: 'none' + } + } + }, + footerSubtitle: { + fontWeight: 400, + color: colors.invariant.text + }, + divider: { + width: '100%', + height: 0, + background: colors.invariant.light, + margin: '24px 0' + }, + topCloseButton: { + pointerEvents: 'auto', + display: 'flex', + justifyContent: 'flex-end', + '&:hover': { + cursor: 'pointer', + filter: 'brightness(1.5)' + } + }, + button: { + color: colors.invariant.lightGrey, + borderRadius: 11, + padding: '20px', + width: '100%', + height: '65px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + background: colors.invariant.component, + + '&:hover': { + color: colors.white.main, + background: colors.invariant.light, + '@media (hover: none)': { + color: colors.invariant.lightGrey + } + }, + '&:first-of-type': { + marginBottom: '4px' + }, + '&:not(:first-of-type)': { + margin: '4px 0' + } + }, + + bottomCloseButton: { + pointerEvents: 'auto', + textDecoration: 'none', + '&:hover': { + cursor: 'pointer', + textDecoration: 'underline' + }, + marginTop: '10px', + borderBottomColor: colors.invariant.light, + color: colors.invariant.textGrey + }, + buttonName: { + textTransform: 'capitalize', + fontSize: '24px', + textAlign: 'center', + fontWeight: 400, + display: 'flex', + alignItems: 'center', + + justifyContent: 'center', + img: { + marginRight: '12px' + }, + [theme.breakpoints.down('sm')]: { + ...typography.body3 + } + }, + buttonContainer: { + width: 150, + display: 'flex', + justifyContent: 'flex-start' + } + } +}) + +export default useStyles diff --git a/src/components/Swap/Swap.stories.tsx b/src/components/Swap/Swap.stories.tsx index 14e27f4e9..45bd29256 100644 --- a/src/components/Swap/Swap.stories.tsx +++ b/src/components/Swap/Swap.stories.tsx @@ -62,6 +62,12 @@ type Story = StoryObj export const Primary: Story = { args: { + onRouteRefresh: fn(), + swapRouteChartData: { + swapRouteLoading: false, + swapRouteError: undefined, + swapRouteResponse: undefined + }, swapMultiplier: '01', promotedSwapPairs: [], commonTokens: [ diff --git a/src/components/Swap/Swap.tsx b/src/components/Swap/Swap.tsx index 58f5ccba7..7d7b8906f 100644 --- a/src/components/Swap/Swap.tsx +++ b/src/components/Swap/Swap.tsx @@ -4,13 +4,12 @@ import ExchangeAmountInput from '@components/Inputs/ExchangeAmountInput/Exchange import Slippage from '@components/Modals/Slippage/Slippage' import Refresher from '@common/Refresher/Refresher' import { BN } from '@coral-xyz/anchor' -import { Box, Button, Grid, Typography } from '@mui/material' +import { Box, Button, Grid, Typography, useMediaQuery } from '@mui/material' import { DEFAULT_TOKEN_DECIMAL, NetworkType, REFRESHER_INTERVAL, - SwapType, WETH_MIN_DEPOSIT_SWAP_FROM_AMOUNT_MAIN, WETH_MIN_DEPOSIT_SWAP_FROM_AMOUNT_TEST, WRAPPED_ETH_ADDRESS @@ -20,14 +19,12 @@ import { calculatePoints, convertBalanceToBN, findPairs, - handleSimulate, - handleSimulateWithHop, initialXtoY, printBN, ROUTES, trimLeadingZeros } from '@utils/utils' -import { Swap as SwapData } from '@store/reducers/swap' +import { Swap as SwapData, AgregatorSwapRoutes } from '@store/reducers/swap' import { Status } from '@store/reducers/solanaWallet' import { SwapToken } from '@store/selectors/solanaWallet' import { blurContent, createButtonActions, unblurContent } from '@utils/uiUtils' @@ -50,7 +47,10 @@ import AnimatedWaves from './AnimatedWaves/AnimatedWaves' import { EstimatedPointsLabel } from './EstimatedPointsLabel/EstimatedPointsLabel' import { useNavigate } from 'react-router-dom' import { FetcherRecords, Pair, SimulationTwoHopResult } from '@invariant-labs/sdk-eclipse' - +import TransactionRoute from '@components/TransactionRoute/TransactionRoute' +import InvariantAgregatorHeader from '@components/InvariantAgregatorHeader/InvariantAgregatorHeader' +import { theme } from '@static/theme' +import TransactionRouteModal from '@components/Modals/TransactionRouteModal/TransactionRouteModal' export interface Pools { tokenX: PublicKey tokenY: PublicKey @@ -78,7 +78,9 @@ export interface ISwap { swapData: SwapData tokens: SwapToken[] pools: PoolWithAddress[] + swapRouteChartData: AgregatorSwapRoutes tickmap: { [x: string]: Tickmap } + onRouteRefresh: (amountIn: BN, slippage: number, tokenFrom: PublicKey, tokenTo: PublicKey) => void onSwap: ( slippage: BN, knownPrice: BN, @@ -155,6 +157,7 @@ export const Swap: React.FC = ({ pools, tickmap, onSwap, + onRouteRefresh, onSetPair, progress, poolTicks, @@ -162,6 +165,7 @@ export const Swap: React.FC = ({ onConnectWallet, onDisconnectWallet, initialTokenFromIndex, + swapRouteChartData, initialTokenToIndex, handleAddToken, commonTokens, @@ -186,9 +190,6 @@ export const Swap: React.FC = ({ feeds, promotedSwapPairs, swapMultiplier, - market, - tokensDict, - swapAccounts, swapIsLoading }) => { const { classes } = useStyles() @@ -213,6 +214,8 @@ export const Swap: React.FC = ({ const [inputRef, setInputRef] = React.useState(inputTarget.DEFAULT) const [isFirstPairGivingPoints, setIsFirstPairGivingPoints] = React.useState(false) const [isSecondPairGivingPoints, setIsSecondPairGivingPoints] = React.useState(false) + const [isSimulating, setIsSimulating] = useState(false) + const [rateReversed, setRateReversed] = React.useState( tokenFromIndex && tokenToIndex ? !initialXtoY( @@ -227,7 +230,7 @@ export const Swap: React.FC = ({ initialHideUnknownTokensValue ) const [pointsForSwap, setPointsForSwap] = React.useState(new BN(0)) - const [simulateResult, setSimulateResult] = React.useState<{ + const [simulateResult, _setSimulateResult] = React.useState<{ amountOut: BN poolIndex: number AmountOutWithFee: BN @@ -244,12 +247,12 @@ export const Swap: React.FC = ({ priceImpact: new BN(0), error: [] }) - const [simulateWithHopResult, setSimulateWithHopResult] = useState<{ + const [simulateWithHopResult, _setSimulateWithHopResult] = useState<{ simulation: SimulationTwoHopResult | null route: [Pair, Pair] | null error: boolean }>({ simulation: null, route: null, error: false }) - const [simulationPath, setSimulationPath] = useState({ + const [simulationPath, _setSimulationPath] = useState({ tokenFrom: null, tokenBetween: null, tokenTo: null, @@ -261,12 +264,12 @@ export const Swap: React.FC = ({ secondPriceImpact: null }) const [bestAmount, setBestAmount] = useState(new BN(0)) - const [swapType, setSwapType] = useState(SwapType.Normal) const [addBlur, setAddBlur] = useState(false) const [wasIsFetchingNewPoolRun, setWasIsFetchingNewPoolRun] = useState(false) const [wasSwapIsLoadingRun, setWasSwapIsLoadingRun] = useState(false) const [isReversingTokens, setIsReversingTokens] = useState(false) - + const [isTransactionRouteModalOpen, setTransactionRouteModalOpen] = useState(false) + const isMd = useMediaQuery(theme.breakpoints.down('md')) const WETH_MIN_DEPOSIT_SWAP_FROM_AMOUNT = useMemo(() => { if (network === NetworkType.Testnet) { return WETH_MIN_DEPOSIT_SWAP_FROM_AMOUNT_TEST @@ -275,6 +278,13 @@ export const Swap: React.FC = ({ } }, [network]) + const prevParamsRef = useRef({ + tokenFrom: '', + tokenTo: '', + amount: '', + slippage: 0 + }) + const priceImpact = Math.max( +printBN(+simulationPath.firstPriceImpact, DECIMAL - 2), +printBN(+simulationPath.secondPriceImpact, DECIMAL - 2) @@ -303,50 +313,15 @@ export const Swap: React.FC = ({ if (tokenFromIndex === null || tokenToIndex === null) return if (!tokens[tokenFromIndex] || !tokens[tokenToIndex]) return - if (swapType === SwapType.WithHop) { - const isFirstPoints = promotedSwapPairs.some( - item => - (new PublicKey(item.tokenX).equals( - simulationPath.tokenFrom?.assetAddress ?? new PublicKey('') - ) && - new PublicKey(item.tokenY).equals( - simulationPath.tokenBetween?.assetAddress ?? new PublicKey('') - )) || - (new PublicKey(item.tokenX).equals( - simulationPath.tokenBetween?.assetAddress ?? new PublicKey('') - ) && - new PublicKey(item.tokenY).equals( - simulationPath.tokenFrom?.assetAddress ?? new PublicKey('') - )) - ) - const isSecondPoints = promotedSwapPairs.some( - item => - (new PublicKey(item.tokenX).equals( - simulationPath.tokenBetween?.assetAddress ?? new PublicKey('') - ) && - new PublicKey(item.tokenY).equals( - simulationPath.tokenTo?.assetAddress ?? new PublicKey('') - )) || - (new PublicKey(item.tokenX).equals( - simulationPath.tokenTo?.assetAddress ?? new PublicKey('') - ) && - new PublicKey(item.tokenY).equals( - simulationPath.tokenBetween?.assetAddress ?? new PublicKey('') - )) - ) - setIsFirstPairGivingPoints(isFirstPoints) - setIsSecondPairGivingPoints(isSecondPoints) - } else { - const isPoints = promotedSwapPairs.some( - item => - (new PublicKey(item.tokenX).equals(tokens[tokenToIndex].assetAddress) && - new PublicKey(item.tokenY).equals(tokens[tokenFromIndex].assetAddress)) || - (new PublicKey(item.tokenX).equals(tokens[tokenFromIndex].assetAddress) && - new PublicKey(item.tokenY).equals(tokens[tokenToIndex].assetAddress)) - ) - setIsFirstPairGivingPoints(isPoints) - setIsSecondPairGivingPoints(false) - } + const isPoints = promotedSwapPairs.some( + item => + (new PublicKey(item.tokenX).equals(tokens[tokenToIndex].assetAddress) && + new PublicKey(item.tokenY).equals(tokens[tokenFromIndex].assetAddress)) || + (new PublicKey(item.tokenX).equals(tokens[tokenFromIndex].assetAddress) && + new PublicKey(item.tokenY).equals(tokens[tokenToIndex].assetAddress)) + ) + setIsFirstPairGivingPoints(isPoints) + setIsSecondPairGivingPoints(false) setPointsForSwap(new BN(0)) @@ -362,71 +337,42 @@ export const Swap: React.FC = ({ }, 500) return () => clearTimeout(urlUpdateTimeoutRef.current) - }, [ - tokenFromIndex, - tokenToIndex, - tokens.length, - network, - promotedSwapPairs, - simulationPath.tokenFrom, - simulationPath.tokenBetween, - simulationPath.tokenTo - ]) + }, [tokenFromIndex, tokenToIndex, tokens.length, network, promotedSwapPairs]) useEffect(() => { - if (simulateResult && (isFirstPairGivingPoints || isSecondPairGivingPoints)) { + if ( + swapRouteChartData.swapSimulateDetails && + (isFirstPairGivingPoints || isSecondPairGivingPoints) + ) { const pointsPerUSD = new BN(pointsPerUsdFee, 'hex') - if (swapType === SwapType.WithHop) { - const firstFeed = feeds[simulationPath.tokenFrom?.assetAddress.toString() ?? ''] - const secondFeed = feeds[simulationPath.tokenBetween?.assetAddress.toString() ?? ''] - - const firstPoints = calculatePoints( - simulationPath.firstAmount ?? new BN(0), - simulationPath.tokenFrom?.decimals ?? 0, - simulationPath.firstPair.feeTier.fee ?? new BN(0), - firstFeed?.price ?? '0', - firstFeed?.priceDecimals ?? 0, - pointsPerUSD - ) - const secondPoints = calculatePoints( - simulationPath.secondAmount ?? new BN(0), - simulationPath.tokenBetween?.decimals ?? 0, - simulationPath.secondPair.feeTier.fee ?? new BN(0), - secondFeed?.price ?? '0', - secondFeed?.priceDecimals ?? 0, - pointsPerUSD - ) - setPointsForSwap(firstPoints.add(secondPoints)) + const feePercentage = pools[simulateResult.poolIndex ?? 0]?.fee ?? new BN(0) + let desiredAmount: string + let desiredIndex: number | null + if (inputRef === inputTarget.FROM) { + desiredIndex = tokenFromIndex + desiredAmount = amountFrom } else { - const feePercentage = pools[simulateResult.poolIndex ?? 0]?.fee ?? new BN(0) - let desiredAmount: string - let desiredIndex: number | null - if (inputRef === inputTarget.FROM) { - desiredIndex = tokenFromIndex - desiredAmount = amountFrom - } else { - desiredIndex = tokenToIndex - desiredAmount = amountTo - } - const feed = feeds[tokens[desiredIndex!].assetAddress.toString()] - const amount = convertBalanceToBN(desiredAmount, tokens[desiredIndex!].decimals) - - if (!feed || !feed.price || simulateResult.amountOut.eqn(0)) { - setPointsForSwap(new BN(0)) - return - } + desiredIndex = tokenToIndex + desiredAmount = amountTo + } + const feed = feeds[tokens[desiredIndex!].assetAddress.toString()] + const amount = convertBalanceToBN(desiredAmount, tokens[desiredIndex!].decimals) - const points = calculatePoints( - amount, - tokens[desiredIndex!].decimals, - feePercentage, - feed.price, - feed.priceDecimals, - pointsPerUSD - ) - setPointsForSwap(points) + if (!feed || !feed.price || simulateResult.amountOut.eqn(0)) { + setPointsForSwap(new BN(0)) + return } + + const points = calculatePoints( + amount, + tokens[desiredIndex!].decimals, + feePercentage, + feed.price, + feed.priceDecimals, + pointsPerUSD + ) + setPointsForSwap(points) } else { setPointsForSwap(new BN(0)) } @@ -448,37 +394,25 @@ export const Swap: React.FC = ({ useEffect(() => { if ( - inputRef === inputTarget.FROM && + inputRef === inputTarget.TO && !isReversingTokens && - !(amountFrom === '' && amountTo === '') + !(amountFrom === '' && amountTo === '') && + !isSimulating ) { simulateWithTimeout() } - }, [ - amountFrom, - // tokenToIndex, - // tokenFromIndex, - slippTolerance, - Object.keys(poolTicks).length, - Object.keys(tickmap).length - ]) + }, [amountTo, slippTolerance, Object.keys(poolTicks).length, Object.keys(tickmap).length]) useEffect(() => { if ( - inputRef === inputTarget.TO && + inputRef === inputTarget.FROM && !isReversingTokens && - !(amountFrom === '' && amountTo === '') + !(amountFrom === '' && amountTo === '') && + !isSimulating ) { simulateWithTimeout() } - }, [ - amountTo, - // tokenToIndex, - // tokenFromIndex, - slippTolerance, - Object.keys(poolTicks).length, - Object.keys(tickmap).length - ]) + }, [amountFrom, slippTolerance, Object.keys(poolTicks).length, Object.keys(tickmap).length]) useEffect(() => { if (progress === 'none' && !(amountFrom === '' && amountTo === '')) { @@ -487,17 +421,20 @@ export const Swap: React.FC = ({ }, [progress]) const simulateWithTimeout = () => { + if (isSimulating) return + setThrottle(true) + setIsSimulating(true) clearTimeout(timeoutRef.current) const timeout = setTimeout(() => { setSimulateAmount().finally(() => { setThrottle(false) + setIsSimulating(false) }) }, 500) timeoutRef.current = timeout as unknown as number } - useEffect(() => { if (tokenFromIndex !== null && tokenToIndex !== null) { if (inputRef === inputTarget.FROM) { @@ -514,7 +451,7 @@ export const Swap: React.FC = ({ } setAddBlur(false) - }, [bestAmount, simulateResult, simulateWithHopResult]) + }, [bestAmount, swapRouteChartData]) useEffect(() => { updateEstimatedAmount() @@ -561,163 +498,40 @@ export const Swap: React.FC = ({ const setSimulateAmount = async () => { setAddBlur(true) - if (tokenFromIndex !== null && tokenToIndex !== null && !swapIsLoading) { - if (inputRef === inputTarget.FROM) { - const [simulateValue, simulateWithHopValue] = await Promise.all([ - handleSimulate( - pools, - poolTicks, - tickmap, - fromFee(new BN(Number(+slippTolerance * 1000))), - tokens[tokenFromIndex].assetAddress, - tokens[tokenToIndex].assetAddress, - convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals), - true - ), - handleSimulateWithHop( - market, - tokens[tokenFromIndex].assetAddress, - tokens[tokenToIndex].assetAddress, - convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals), - true, - swapAccounts - ) - ]) - - updateSimulation(simulateValue, simulateWithHopValue) - setSimulateResult(simulateValue) - setSimulateWithHopResult(simulateWithHopValue) - } else if (inputRef === inputTarget.TO) { - const [simulateValue, simulateWithHopValue] = await Promise.all([ - handleSimulate( - pools, - poolTicks, - tickmap, - fromFee(new BN(Number(+slippTolerance * 1000))), - tokens[tokenFromIndex].assetAddress, - tokens[tokenToIndex].assetAddress, - convertBalanceToBN(amountTo, tokens[tokenToIndex].decimals), - false - ), - handleSimulateWithHop( - market, - tokens[tokenFromIndex].assetAddress, - tokens[tokenToIndex].assetAddress, - convertBalanceToBN(amountTo, tokens[tokenToIndex].decimals), - false, - swapAccounts - ) - ]) - - updateSimulation(simulateValue, simulateWithHopValue) - setSimulateResult(simulateValue) - setSimulateWithHopResult(simulateWithHopValue) - } + if ( + tokenFromIndex !== null && + tokenToIndex !== null && + !swapIsLoading && + !swapRouteChartData.swapRouteLoading + ) { + // if (inputRef === inputTarget.FROM) { + updateSimulation() + // } else if (inputRef === inputTarget.TO) { + // } } else { setAddBlur(false) } } + const updateSimulation = () => { + const amountFromAPI = swapRouteChartData.swapRouteResponse?.destinationToken?.rawAmount + console.log(amountFromAPI, 'amountFromAPI') - const updateSimulation = ( - simulateResult: { - amountOut: BN - poolIndex: number - AmountOutWithFee: BN - estimatedPriceAfterSwap: BN - minimumReceived: BN - priceImpact: BN - error: string[] - }, - simulateWithHopResult: { - simulation: SimulationTwoHopResult | null - route: [Pair, Pair] | null - error: boolean - } - ) => { - let useTwoHop = false - - const isSimulateError = - simulateResult.error.length > 0 || simulateResult.amountOut.eq(new BN(0)) - const isSimulateWithHopError = simulateWithHopResult.error - - if (isSimulateError && !isSimulateWithHopError) { - useTwoHop = true - } + if (amountFromAPI) { + const bestAmountValue = new BN(amountFromAPI) + setBestAmount(bestAmountValue) - if ( - (isSimulateError && isSimulateWithHopError) || - (!isSimulateError && !isSimulateWithHopError) - ) { - if (inputRef === inputTarget.FROM) { - if ( - simulateWithHopResult?.simulation?.totalAmountOut.gte(simulateResult.amountOut) && - !simulateWithHopResult.error - ) { - useTwoHop = true - } - } else { - if ( - simulateWithHopResult?.simulation?.totalAmountIn - .add(simulateWithHopResult?.simulation?.swapHopOne.accumulatedFee) - .lte(simulateResult.amountOut) && - !simulateWithHopResult.error - ) { - useTwoHop = true + if (tokenFromIndex !== null && tokenToIndex !== null) { + if (inputRef === inputTarget.FROM) { + const amount = printBN(bestAmountValue, tokens[tokenToIndex].decimals) + console.log('UPDATE AMOUNT TO', amount) + setAmountTo(+amount === 0 ? '' : trimLeadingZeros(amount)) + } else if (inputRef === inputTarget.TO) { + const amount = printBN(bestAmountValue, tokens[tokenFromIndex].decimals) + console.log('UPDATE AMOUNT FROM', amount) + setAmountFrom(+amount === 0 ? '' : trimLeadingZeros(amount)) } } } - - if (useTwoHop && simulateWithHopResult.simulation && simulateWithHopResult.route) { - setSimulationPath({ - tokenFrom: tokens[tokenFromIndex ?? 0], - tokenBetween: - tokensDict[ - simulateWithHopResult.simulation.xToYHopOne - ? simulateWithHopResult.route[0].tokenY.toString() - : simulateWithHopResult.route[0].tokenX.toString() - ], - tokenTo: tokens[tokenToIndex ?? 0], - firstPair: simulateWithHopResult.route[0], - secondPair: simulateWithHopResult.route[1], - firstAmount: simulateWithHopResult.simulation.swapHopOne.accumulatedAmountIn.add( - simulateWithHopResult.simulation.swapHopOne.accumulatedFee - ), - secondAmount: simulateWithHopResult.simulation.swapHopTwo.accumulatedAmountIn.add( - simulateWithHopResult.simulation.swapHopTwo.accumulatedFee - ), - firstPriceImpact: simulateWithHopResult.simulation.swapHopOne.priceImpact, - secondPriceImpact: simulateWithHopResult.simulation.swapHopTwo.priceImpact - }) - setBestAmount( - inputRef === inputTarget.FROM - ? simulateWithHopResult.simulation?.swapHopTwo.accumulatedAmountOut.toString() - : simulateWithHopResult.simulation?.swapHopOne.accumulatedAmountIn - .add(simulateWithHopResult.simulation.swapHopOne.accumulatedFee) - .toString() - ) - setSwapType(SwapType.WithHop) - } else { - setSimulationPath({ - tokenFrom: tokens[tokenFromIndex ?? 0], - tokenBetween: null, - tokenTo: tokens[tokenToIndex ?? 0], - firstPair: new Pair( - pools[simulateResult.poolIndex].tokenX, - pools[simulateResult.poolIndex].tokenY, - { - fee: pools[simulateResult.poolIndex].fee, - tickSpacing: pools[simulateResult.poolIndex].tickSpacing - } - ) ?? { fee: new BN(0) }, - secondPair: null, - firstAmount: convertBalanceToBN(amountFrom, tokens[tokenFromIndex ?? 0].decimals), - secondAmount: null, - firstPriceImpact: simulateResult.priceImpact, - secondPriceImpact: null - }) - setBestAmount(simulateResult.amountOut) - setSwapType(SwapType.Normal) - } } const getIsXToY = (fromToken: PublicKey, toToken: PublicKey) => { @@ -737,7 +551,7 @@ export const Swap: React.FC = ({ } const isError = (error: string) => { - return swapType === SwapType.Normal ? simulateResult.error.some(err => err === error) : false + return simulateResult.error.some(err => err === error) } const isEveryPoolEmpty = useMemo(() => { @@ -840,8 +654,7 @@ export const Swap: React.FC = ({ return 'RPC connection error' } - // Fallback error message - if (swapType === SwapType.Normal && simulateResult.error.length !== 0) { + if (simulateResult.error.length !== 0) { console.warn('Errors not handled explictly', simulateResult.error) return 'Not enough liquidity' } @@ -912,8 +725,19 @@ export const Swap: React.FC = ({ amountTo !== '' const handleRefresh = async () => { + if (tokenFromIndex === null || tokenToIndex === null) { + return + } onRefresh(tokenFromIndex, tokenToIndex) setRefresherTime(REFRESHER_INTERVAL) + const currentAmount = convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals) + + onRouteRefresh( + currentAmount, + +slippTolerance, + tokens[tokenFromIndex].assetAddress, + tokens[tokenToIndex].assetAddress + ) } useEffect(() => { @@ -931,7 +755,13 @@ export const Swap: React.FC = ({ }, [swapIsLoading]) useEffect(() => { - if (wasIsFetchingNewPoolRun && wasSwapIsLoadingRun && !isFetchingNewPool && !swapIsLoading) { + if ( + wasIsFetchingNewPoolRun && + wasSwapIsLoadingRun && + !isFetchingNewPool && + !swapIsLoading && + !swapRouteChartData.swapRouteLoading + ) { void setSimulateAmount() setWasIsFetchingNewPoolRun(false) setWasSwapIsLoadingRun(false) @@ -939,7 +769,13 @@ export const Swap: React.FC = ({ setIsReversingTokens(false) } } - }, [wasIsFetchingNewPoolRun, wasSwapIsLoadingRun, isFetchingNewPool, swapIsLoading]) + }, [ + wasIsFetchingNewPoolRun, + wasSwapIsLoadingRun, + isFetchingNewPool, + swapIsLoading, + swapRouteChartData.swapSimulateDetails + ]) useEffect(() => { setRefresherTime(REFRESHER_INTERVAL) @@ -985,6 +821,35 @@ export const Swap: React.FC = ({ (getStateMessage() === 'Loading' && (inputRef === inputTarget.FROM || inputRef === inputTarget.DEFAULT)) + useEffect(() => { + if (tokenFromIndex === null || tokenToIndex === null) { + return + } + + const currentAmount = convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals) + + const hasParamsChanged = + tokens[tokenFromIndex].assetAddress.toString() !== prevParamsRef.current.tokenFrom || + tokens[tokenToIndex].assetAddress.toString() !== prevParamsRef.current.tokenTo || + !currentAmount.eq(new BN(prevParamsRef.current.amount)) || + +slippTolerance !== prevParamsRef.current.slippage + + if (hasParamsChanged) { + prevParamsRef.current = { + tokenFrom: tokens[tokenFromIndex].assetAddress.toString(), + tokenTo: tokens[tokenToIndex].assetAddress.toString(), + amount: currentAmount.toString(), + slippage: +slippTolerance + } + onRouteRefresh( + currentAmount, + +slippTolerance, + tokens[tokenFromIndex].assetAddress, + tokens[tokenToIndex].assetAddress + ) + } + }, [tokenFromIndex, tokenToIndex, amountFrom, amountTo, slippTolerance, isAnyBlurShowed]) + return ( {wrappedETHAccountExist && ( @@ -996,476 +861,536 @@ export const Swap: React.FC = ({ )} - - - Swap tokens - {network === NetworkType.Mainnet ? ( - -
- -
-
- ) : null} + {isMd && ( + + + )} + {isMd && ( + { + setTransactionRouteModalOpen(false) + unblurContent() + }} + /> + )} + + + + + + {!isMd && ( + + + + )} + + + Swap tokens + {network === NetworkType.Mainnet ? ( + +
+ +
+
+ ) : null} +
- - - - - - - - - - - - - - - - - -
- - - - - - Pay - - - { - if (value.match(/^\d*\.?\d*$/)) { - setAmountFrom(value) - setInputRef(inputTarget.FROM) - } - }} - placeholder={`0.${'0'.repeat(6)}`} - actionButtons={[ - { - label: 'Max', - variant: 'max', - onClick: () => { - actions.max(tokenFromIndex) - } - }, - { - label: '50%', - variant: 'half', - onClick: () => { - actions.half(tokenFromIndex) - } - } - ]} - tokens={tokens} - current={tokenFromIndex !== null ? tokens[tokenFromIndex] : null} - onSelect={setTokenFromIndex} - disabled={tokenFromIndex === tokenToIndex || tokenFromIndex === null} - hideBalances={walletStatus !== Status.Initialized} - handleAddToken={handleAddToken} - commonTokens={commonTokens} - limit={1e14} - initialHideUnknownTokensValue={initialHideUnknownTokensValue} - onHideUnknownTokensChange={e => { - onHideUnknownTokensChange(e) - setHideUnknownTokens(e) - }} - tokenPrice={tokenFromPriceData?.price} - priceLoading={priceFromLoading} - isBalanceLoading={isBalanceLoading} - showMaxButton={true} - showBlur={ - (inputRef === inputTarget.TO && addBlur) || - lockAnimation || - (getStateMessage() === 'Loading' && - (inputRef === inputTarget.TO || inputRef === inputTarget.DEFAULT)) - } - hiddenUnknownTokens={hideUnknownTokens} - network={network} - isPairGivingPoints={isFirstPairGivingPoints || isSecondPairGivingPoints} - /> - + + + + + + + + + + + - + + + +
{ - if (lockAnimation) return - setIsReversingTokens(true) - setRateLoading(true) - setLockAnimation(!lockAnimation) - setRotates(rotates + 1) - swap !== null ? setSwap(!swap) : setSwap(true) - setTimeout(() => { - const tmpAmount = amountTo - - const tmp = tokenFromIndex - setTokenFromIndex(tokenToIndex) - setTokenToIndex(tmp) - - setInputRef(inputTarget.FROM) - setAmountFrom(tmpAmount) - }, 50) - }}> - - Invert tokens + + - - - - - Receive - - - { - if (value.match(/^\d*\.?\d*$/)) { - setAmountTo(value) - setInputRef(inputTarget.TO) - } - }} - placeholder={`0.${'0'.repeat(6)}`} - actionButtons={[ - { - label: 'Max', - variant: 'max', - onClick: () => { - actions.max(tokenFromIndex) - } - }, - { - label: '50%', - variant: 'half', - onClick: () => { - actions.half(tokenFromIndex) - } - } - ]} - tokens={tokens} - current={tokenToIndex !== null ? tokens[tokenToIndex] : null} - onSelect={setTokenToIndex} - disabled={tokenFromIndex === tokenToIndex || tokenToIndex === null} - hideBalances={walletStatus !== Status.Initialized} - handleAddToken={handleAddToken} - commonTokens={commonTokens} - limit={1e14} - initialHideUnknownTokensValue={initialHideUnknownTokensValue} - onHideUnknownTokensChange={e => { - onHideUnknownTokensChange(e) - setHideUnknownTokens(e) - }} - tokenPrice={tokenToPriceData?.price} - priceLoading={priceToLoading} - isBalanceLoading={isBalanceLoading} - showMaxButton={false} - showBlur={ - (inputRef === inputTarget.FROM && addBlur) || - lockAnimation || - (getStateMessage() === 'Loading' && - (inputRef === inputTarget.FROM || inputRef === inputTarget.DEFAULT)) - } - hiddenUnknownTokens={hideUnknownTokens} - network={network} - isPairGivingPoints={isFirstPairGivingPoints || isSecondPairGivingPoints} - /> - - - {priceImpact > 5 && ( - - - High price impact: {priceImpact < 0.01 ? '<0.01%' : `${priceImpact.toFixed(2)}%`}! - This swap will cause a significant price movement. + + Pay + + + { + if (value.match(/^\d*\.?\d*$/)) { + setAmountFrom(value) + setInputRef(inputTarget.FROM) + } + }} + placeholder={`0.${'0'.repeat(6)}`} + actionButtons={[ + { + label: 'Max', + variant: 'max', + onClick: () => { + actions.max(tokenFromIndex) + } + }, + { + label: '50%', + variant: 'half', + onClick: () => { + actions.half(tokenFromIndex) + } + } + ]} + tokens={tokens} + current={tokenFromIndex !== null ? tokens[tokenFromIndex] : null} + onSelect={setTokenFromIndex} + disabled={tokenFromIndex === tokenToIndex || tokenFromIndex === null} + hideBalances={walletStatus !== Status.Initialized} + handleAddToken={handleAddToken} + commonTokens={commonTokens} + limit={1e14} + initialHideUnknownTokensValue={initialHideUnknownTokensValue} + onHideUnknownTokensChange={e => { + onHideUnknownTokensChange(e) + setHideUnknownTokens(e) + }} + tokenPrice={tokenFromPriceData?.price} + priceLoading={priceFromLoading} + isBalanceLoading={isBalanceLoading} + showMaxButton={true} + showBlur={ + (inputRef === inputTarget.TO && addBlur) || + lockAnimation || + (getStateMessage() === 'Loading' && + (inputRef === inputTarget.TO || inputRef === inputTarget.DEFAULT)) + } + hiddenUnknownTokens={hideUnknownTokens} + network={network} + isPairGivingPoints={isFirstPairGivingPoints || isSecondPairGivingPoints} + /> - - )} - {tokens[tokenFromIndex ?? '']?.isUnknown && ( - - - {tokens[tokenFromIndex ?? ''].symbol} is not verified + + + { + if (lockAnimation) return + setIsReversingTokens(true) + setRateLoading(true) + setLockAnimation(!lockAnimation) + setRotates(rotates + 1) + swap !== null ? setSwap(!swap) : setSwap(true) + setTimeout(() => { + const tmpAmount = amountTo + + const tmp = tokenFromIndex + setTokenFromIndex(tokenToIndex) + setTokenToIndex(tmp) + + setInputRef(inputTarget.FROM) + setAmountFrom(tmpAmount) + }, 50) + }}> + + Invert tokens + + - - )} - {tokens[tokenToIndex ?? '']?.isUnknown && ( - - - {tokens[tokenToIndex ?? ''].symbol} is not verified + + Receive + + + { + if (value.match(/^\d*\.?\d*$/)) { + setAmountTo(value) + setInputRef(inputTarget.TO) + } + }} + placeholder={`0.${'0'.repeat(6)}`} + actionButtons={[ + { + label: 'Max', + variant: 'max', + onClick: () => { + actions.max(tokenFromIndex) + } + }, + { + label: '50%', + variant: 'half', + onClick: () => { + actions.half(tokenFromIndex) + } + } + ]} + tokens={tokens} + current={tokenToIndex !== null ? tokens[tokenToIndex] : null} + onSelect={setTokenToIndex} + disabled={tokenFromIndex === tokenToIndex || tokenToIndex === null} + hideBalances={walletStatus !== Status.Initialized} + handleAddToken={handleAddToken} + commonTokens={commonTokens} + limit={1e14} + initialHideUnknownTokensValue={initialHideUnknownTokensValue} + onHideUnknownTokensChange={e => { + onHideUnknownTokensChange(e) + setHideUnknownTokens(e) + }} + tokenPrice={tokenToPriceData?.price} + priceLoading={priceToLoading} + isBalanceLoading={isBalanceLoading} + showMaxButton={false} + showBlur={ + (inputRef === inputTarget.FROM && addBlur) || + lockAnimation || + (getStateMessage() === 'Loading' && + (inputRef === inputTarget.FROM || inputRef === inputTarget.DEFAULT)) + } + hiddenUnknownTokens={hideUnknownTokens} + network={network} + isPairGivingPoints={isFirstPairGivingPoints || isSecondPairGivingPoints} + /> - - )} - - - - - {tokenFromIndex !== null && - tokenToIndex !== null && - tokenFromIndex !== tokenToIndex && ( - - - + {priceImpact > 5 && ( + + + High price impact:{' '} + {priceImpact < 0.01 ? '<0.01%' : `${priceImpact.toFixed(2)}%`}! This swap + will cause a significant price movement. + + + )} + {tokens[tokenFromIndex ?? '']?.isUnknown && ( + + + {tokens[tokenFromIndex ?? ''].symbol} is not verified + + + )} + {tokens[tokenToIndex ?? '']?.isUnknown && ( + + + {tokens[tokenToIndex ?? ''].symbol} is not verified + + + )} + + + + + {tokenFromIndex !== null && + tokenToIndex !== null && + tokenFromIndex !== tokenToIndex && ( + + + + + + )} + + {canShowDetails ? ( + + setRateReversed(!rateReversed)} + tokenFromSymbol={ + tokens[rateReversed ? tokenToIndex : tokenFromIndex].symbol + } + tokenToSymbol={tokens[rateReversed ? tokenFromIndex : tokenToIndex].symbol} + amount={rateReversed ? 1 / swapRate : swapRate} + tokenToDecimals={ + tokens[rateReversed ? tokenFromIndex : tokenToIndex].decimals + } + loading={getStateMessage() === 'Loading' || rateLoading || addBlur} /> -
+ + ) : null} + + + + {walletStatus !== Status.Initialized && getStateMessage() !== 'Loading' ? ( + + ) : getStateMessage() === 'Insufficient Wrapped ETH' ? ( + + { + if (tokenFromIndex === null || tokenToIndex === null) return + + onSwap( + fromFee(new BN(Number(+slippTolerance * 1000))), + simulateResult.estimatedPriceAfterSwap, + simulationPath.tokenFrom?.assetAddress ?? PublicKey.default, + simulationPath.tokenBetween?.assetAddress ?? null, + simulationPath.tokenTo?.assetAddress ?? PublicKey.default, + simulationPath.firstPair, + simulationPath.secondPair, + convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals), + convertBalanceToBN(amountTo, tokens[tokenToIndex].decimals), + inputRef === inputTarget.FROM + ) + }} + progress={progress} + /> + ) : ( + { + if (tokenFromIndex === null || tokenToIndex === null) return + + onSwap( + // fromFee(new BN(Number(+slippTolerance * 1000))), + // simulateResult.estimatedPriceAfterSwap, + // tokens[tokenFromIndex].assetAddress, + // tokens[tokenToIndex].assetAddress, + // simulateResult.poolIndex, + // convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals), + // convertBalanceToBN(amountTo, tokens[tokenToIndex].decimals), + // inputRef === inputTarget.FROM + fromFee(new BN(Number(+slippTolerance * 1000))), + simulateResult.estimatedPriceAfterSwap, + simulationPath.tokenFrom?.assetAddress ?? PublicKey.default, + simulationPath.tokenBetween?.assetAddress ?? null, + simulationPath.tokenTo?.assetAddress ?? PublicKey.default, + simulationPath.firstPair, + simulationPath.secondPair, + convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals), + convertBalanceToBN(amountTo, tokens[tokenToIndex].decimals), + inputRef === inputTarget.FROM + ) + }} + progress={progress} + /> )} + + + - {canShowDetails ? ( + + {!isMd && + (swapRouteChartData.swapRouteResponse || + swapRouteChartData.swapRouteError || + swapRouteChartData.swapRouteLoading) && ( - setRateReversed(!rateReversed)} - tokenFromSymbol={tokens[rateReversed ? tokenToIndex : tokenFromIndex].symbol} - tokenToSymbol={tokens[rateReversed ? tokenFromIndex : tokenToIndex].symbol} - amount={rateReversed ? 1 / swapRate : swapRate} - tokenToDecimals={tokens[rateReversed ? tokenFromIndex : tokenToIndex].decimals} - loading={getStateMessage() === 'Loading' || rateLoading || addBlur} + sx={{ + height: '100%', + width: '350px', + display: 'flex', + justifyContent: 'center', + alignItems: 'flex-end' + }}> + - ) : null} - - - - {walletStatus !== Status.Initialized && getStateMessage() !== 'Loading' ? ( - - ) : getStateMessage() === 'Insufficient Wrapped ETH' ? ( - - { - if (tokenFromIndex === null || tokenToIndex === null) return - - onSwap( - fromFee(new BN(Number(+slippTolerance * 1000))), - simulateResult.estimatedPriceAfterSwap, - simulationPath.tokenFrom?.assetAddress ?? PublicKey.default, - simulationPath.tokenBetween?.assetAddress ?? null, - simulationPath.tokenTo?.assetAddress ?? PublicKey.default, - simulationPath.firstPair, - simulationPath.secondPair, - convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals), - convertBalanceToBN(amountTo, tokens[tokenToIndex].decimals), - inputRef === inputTarget.FROM - ) - }} - progress={progress} - /> - - ) : ( - { - if (tokenFromIndex === null || tokenToIndex === null) return - - onSwap( - // fromFee(new BN(Number(+slippTolerance * 1000))), - // simulateResult.estimatedPriceAfterSwap, - // tokens[tokenFromIndex].assetAddress, - // tokens[tokenToIndex].assetAddress, - // simulateResult.poolIndex, - // convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals), - // convertBalanceToBN(amountTo, tokens[tokenToIndex].decimals), - // inputRef === inputTarget.FROM - fromFee(new BN(Number(+slippTolerance * 1000))), - simulateResult.estimatedPriceAfterSwap, - simulationPath.tokenFrom?.assetAddress ?? PublicKey.default, - simulationPath.tokenBetween?.assetAddress ?? null, - simulationPath.tokenTo?.assetAddress ?? PublicKey.default, - simulationPath.firstPair, - simulationPath.secondPair, - convertBalanceToBN(amountFrom, tokens[tokenFromIndex].decimals), - convertBalanceToBN(amountTo, tokens[tokenToIndex].decimals), - inputRef === inputTarget.FROM - ) - }} - progress={progress} - /> - )} - - + )} + Audit diff --git a/src/components/Swap/TransactionDetailsBox/TransactionDetailsBox.stories.tsx b/src/components/Swap/TransactionDetailsBox/TransactionDetailsBox.stories.tsx index 8147d071a..7aeb7abe6 100644 --- a/src/components/Swap/TransactionDetailsBox/TransactionDetailsBox.stories.tsx +++ b/src/components/Swap/TransactionDetailsBox/TransactionDetailsBox.stories.tsx @@ -29,20 +29,10 @@ export const Primary: Story = { args: { exchangeRate: { val: 123, symbol: 'ABC', decimal: 12 }, slippage: 0.5, + priceImpact: new BN(5000000000), open: true, - isLoadingRate: false, - simulationPath: { - tokenFrom: null, - tokenBetween: null, - tokenTo: null, - firstPair: null, - secondPair: null, - firstAmount: null, - secondAmount: null, - firstPriceImpact: null, - secondPriceImpact: null - } + isLoadingRate: false }, render: args => { return diff --git a/src/components/Swap/TransactionDetailsBox/TransactionDetailsBox.tsx b/src/components/Swap/TransactionDetailsBox/TransactionDetailsBox.tsx index ec1d625c5..1bf68221a 100644 --- a/src/components/Swap/TransactionDetailsBox/TransactionDetailsBox.tsx +++ b/src/components/Swap/TransactionDetailsBox/TransactionDetailsBox.tsx @@ -1,47 +1,30 @@ import React from 'react' import { Grid, Skeleton, Typography } from '@mui/material' -import { formatNumberWithoutSuffix, printBN } from '@utils/utils' +import { formatNumberWithoutSuffix } from '@utils/utils' import { useStyles } from './styles' import { BN } from '@coral-xyz/anchor' -import { DECIMAL } from '@invariant-labs/sdk-eclipse/lib/utils' - -import RouteBox from './RouteBox/RouteBox' -import { SimulationPath } from '../Swap' -import { DENOMINATOR } from '@invariant-labs/sdk-eclipse/src' interface IProps { open: boolean exchangeRate: { val: number; symbol: string; decimal: number } slippage: number priceImpact: BN + feePercent?: number isLoadingRate?: boolean - simulationPath: SimulationPath } const TransactionDetailsBox: React.FC = ({ open, exchangeRate, slippage, + feePercent, priceImpact, - isLoadingRate = false, - simulationPath + isLoadingRate = false }) => { const { classes } = useStyles({ open }) - const feePercent = Number( - printBN( - simulationPath.firstPair?.feeTier.fee.add( - DENOMINATOR.sub(simulationPath.firstPair?.feeTier.fee) - .mul(simulationPath.secondPair?.feeTier.fee ?? new BN(0)) - .div(DENOMINATOR) ?? new BN(0) - ) ?? new BN(0), - DECIMAL - 2 - ) - ) - return ( - Exchange rate: @@ -61,7 +44,10 @@ const TransactionDetailsBox: React.FC = ({ {isLoadingRate ? ( ) : ( - {`${feePercent.toFixed(2)}%`} + {`${feePercent ? feePercent.toFixed(2) : '-'}%`} )} @@ -71,7 +57,11 @@ const TransactionDetailsBox: React.FC = ({ ) : ( - {priceImpact < 0.01 ? '<0.01%' : `${priceImpact.toFixed(2)}%`} + {priceImpact && typeof priceImpact === 'number' && priceImpact < 0.01 + ? '<0.01%' + : typeof priceImpact === 'number' + ? `${priceImpact.toFixed(2)}%` + : '-'} )} diff --git a/src/components/Swap/style.ts b/src/components/Swap/style.ts index cb66680d7..93ec559e1 100644 --- a/src/components/Swap/style.ts +++ b/src/components/Swap/style.ts @@ -390,6 +390,15 @@ export const useStyles = makeStyles()((theme: Theme) => ({ paddingInline: 12, marginBottom: 16 }, + agregatorContainer: { + borderRadius: 8, + padding: 4, + + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + marginBottom: 16 + }, unwrapNowButton: { cursor: 'pointer' }, @@ -470,6 +479,45 @@ export const useStyles = makeStyles()((theme: Theme) => ({ borderBottomRightRadius: 24 } }, + + routeButton: { + border: 'none', + background: colors.invariant.pinkLinearGradientOpacity, + color: colors.invariant.newDark, + paddingInline: 12, + borderRadius: 14, + textTransform: 'none', + ...typography.body1, + height: 34, + minWidth: 130, + + [theme.breakpoints.down('sm')]: { + minWidth: 100, + width: 100 + }, + + '&:hover': { + boxShadow: `0 0 15px ${colors.invariant.light}`, + backgroundColor: colors.invariant.light, + '@media (hover: none)': { + background: colors.invariant.pinkLinearGradientOpacity, + boxShadow: 'none' + } + }, + + '&.Mui-disabled': { + background: colors.invariant.componentBcg, + backgroundImage: 'none !important', + opacity: 0.5, + pointerEvents: 'auto', + color: `${colors.invariant.textGrey} !important`, + '&:hover': { + background: colors.invariant.componentBcg, + boxShadow: 'none', + cursor: 'not-allowed' + } + } + }, gradientBorderForContainer: { background: 'transparent', boxShadow: 'none', diff --git a/src/components/TransactionRoute/FlowChartGrid/FlowChartGrid.tsx b/src/components/TransactionRoute/FlowChartGrid/FlowChartGrid.tsx new file mode 100644 index 000000000..b0882d00a --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/FlowChartGrid.tsx @@ -0,0 +1,86 @@ +import { Box } from '@mui/material' +import { FlowNode } from './components/FlowNode/FlowNode' +import { CellDefinition, FlowChartGridProps } from './types/types' + +const GridCell = ({ children, colSpan = 1, rowSpan = 1 }) => { + return ( + + {children} + + ) +} + +export const FlowChartGrid = ({ gridDefinition, cellSize = 80 }: FlowChartGridProps) => { + const processedGrid = gridDefinition.map(row => { + return row.map(cell => { + if (!cell || cell.type !== 'node') return cell + + const newCell = { ...cell } + + if (!newCell.connectors) { + newCell.connectors = [] + } + + return newCell + }) + }) + + const renderCellContent = (cell: CellDefinition | null) => { + if (!cell || !cell.type) return null + + if (cell.type === 'node') { + return ( + + ) + } + + return null + } + + const rows = processedGrid.length + const cols = processedGrid.reduce((max, row) => Math.max(max, row.length), 0) + + return ( + + {processedGrid.map((row, rowIndex) => + row.map((cell, colIndex) => ( + + {renderCellContent(cell)} + + )) + )} + + ) +} diff --git a/src/components/TransactionRoute/FlowChartGrid/components/FlowNode/FlowNode.tsx b/src/components/TransactionRoute/FlowChartGrid/components/FlowNode/FlowNode.tsx new file mode 100644 index 000000000..777924bac --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/components/FlowNode/FlowNode.tsx @@ -0,0 +1,91 @@ +import React from 'react' +import { Box, Typography } from '@mui/material' +import { NodeConnector } from '../NodeConnector/NodeConnector' +import { typography } from '@static/theme' +import { newTabIcon, unknownTokenIcon } from '@static/icons' +import { formatNumberWithoutSuffix } from '@utils/utils' +import { FlowNodeProps } from '../../types/types' +import { renderFlexLines } from '../../utils/renderFlexLines' +import { useStyles } from './style' + +export const FlowNode: React.FC = ({ + shape, + textA, + dexInfo, + textB, + connectors, + logoImg, + cornerPosition, + bigNode = false, + labelPos = 'bottom' +}) => { + const { classes } = useStyles() + + const CIRCLE_SIZE = bigNode ? 36 : 27 + const RECT_WIDTH = 90 + const RECT_HEIGHT = 50 + + return ( + + + {shape !== 'circle' && renderFlexLines(cornerPosition)} + + {dexInfo && ( + + {dexInfo.name} + + + {dexInfo.fee}% + + + + {dexInfo.name} + {'Exchange'} + + + + + )} + + {connectors.map((connector, index) => ( + + ))} + + + + {textA ?? ''} + + {(shape === 'circle' && textB && formatNumberWithoutSuffix(textB)) ?? ''} {textA ?? ''} + + + + ) +} diff --git a/src/components/TransactionRoute/FlowChartGrid/components/FlowNode/style.ts b/src/components/TransactionRoute/FlowChartGrid/components/FlowNode/style.ts new file mode 100644 index 000000000..6af4f490b --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/components/FlowNode/style.ts @@ -0,0 +1,80 @@ +import { makeStyles } from 'tss-react/mui' +import { colors, typography } from '@static/theme' + +export const useStyles = makeStyles()(_theme => { + return { + container: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + height: '100%', + justifyContent: 'center', + position: 'relative' + }, + nodeBase: { + display: 'flex', + backgroundColor: colors.invariant.component, + justifyContent: 'center', + alignItems: 'center', + position: 'absolute', + zIndex: 10, + gap: '6px', + backgroundSize: 'cover', + backgroundPosition: 'center' + }, + dexInfoContainer: { + fontFamily: 'Mukta', + fontStyle: 'normal', + fontWeight: 400, + fontSize: '12px', + lineHeight: '16px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + letterSpacing: '-0.03em', + color: 'white' + }, + dexLogo: { + marginRight: '8px', + width: '100%', + height: 'auto', + maxWidth: '18px', + maxHeight: '21px' + }, + dexFee: { + ...typography.caption3 + }, + dexLink: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + dexLinkText: { + ...typography.caption4, + color: colors.invariant.textGrey, + textDecoration: 'underline' + }, + dexLinkIcon: { + marginLeft: '4px', + width: 8, + height: 8 + }, + labelContainer: { + display: 'flex', + flexDirection: 'column', + lineHeight: '5px', + position: 'absolute' + }, + + textA: { + color: colors.invariant.textGrey, + ...typography.heading4, + textWrap: 'nowrap' + }, + textB: { + color: colors.invariant.text, + ...typography.body2, + textWrap: 'nowrap' + } + } +}) diff --git a/src/components/TransactionRoute/FlowChartGrid/components/NodeConnector/NodeConnector.tsx b/src/components/TransactionRoute/FlowChartGrid/components/NodeConnector/NodeConnector.tsx new file mode 100644 index 000000000..e826ce188 --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/components/NodeConnector/NodeConnector.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { Box } from '@mui/material' +import { NodeConnectorProps } from '../../types/types' +import { getConnectorStyles } from '../../utils/getConnectorStyles' + +export const NodeConnector: React.FC = ({ + direction = 'right', + withArrow = false, + shape, + longerConnector = false +}) => { + const styles = getConnectorStyles({ + direction, + longerConnector, + shape, + withArrow + }) + + if (!styles.container) return null + + return ( + + {styles.line && } + {styles.line1 && } + {styles.line2 && } + {styles.arrow && } + + ) +} diff --git a/src/components/TransactionRoute/FlowChartGrid/components/NodeConnector/style.ts b/src/components/TransactionRoute/FlowChartGrid/components/NodeConnector/style.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteError/TransactionRouteError.tsx b/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteError/TransactionRouteError.tsx new file mode 100644 index 000000000..15cae8dd0 --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteError/TransactionRouteError.tsx @@ -0,0 +1,20 @@ +import { Box, Typography } from '@mui/material' +import useStyles from './style' +import React from 'react' + +interface ITransactionRouteError { + error?: string +} + +const TransactionRouteError: React.FC = ({ error }) => { + const { classes } = useStyles() + + return ( + + Error! + {error} + + ) +} + +export default TransactionRouteError diff --git a/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteError/style.ts b/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteError/style.ts new file mode 100644 index 000000000..7c15c050f --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteError/style.ts @@ -0,0 +1,34 @@ +import { typography, colors } from '@static/theme' +import { makeStyles } from 'tss-react/mui' + +const useStyles = makeStyles()(_theme => { + return { + loaderContainer: { + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + color: 'white', + + textAlign: 'center' + }, + animationImage: { + width: '120px', + height: '120px', + marginBottom: '4px' + }, + pleaseWaitText: { + ...typography.heading4, + color: colors.invariant.green, + marginBottom: '4px' + }, + lookingForRouteText: { + ...typography.caption2, + color: colors.invariant.text + } + } +}) + +export default useStyles diff --git a/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteLoader/TransactionRouteLoader.tsx b/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteLoader/TransactionRouteLoader.tsx new file mode 100644 index 000000000..a967e1f2e --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteLoader/TransactionRouteLoader.tsx @@ -0,0 +1,19 @@ +import { Box, Typography } from '@mui/material' +import loadingAnimation from '@static/gif/loading.gif' +import useStyles from './style' + +const TransactionRouteLoader = () => { + const { classes } = useStyles() + + return ( + + Loading animation + Please Wait! + + We are looking for the best route! + + + ) +} + +export default TransactionRouteLoader diff --git a/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteLoader/style.ts b/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteLoader/style.ts new file mode 100644 index 000000000..b5adcfbdf --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/components/TransactionRouteLoader/style.ts @@ -0,0 +1,33 @@ +import { typography, colors } from '@static/theme' +import { makeStyles } from 'tss-react/mui' + +const useStyles = makeStyles()(_theme => { + return { + loaderContainer: { + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + color: 'white', + textAlign: 'center' + }, + animationImage: { + width: '120px', + height: '120px', + marginBottom: '4px' + }, + pleaseWaitText: { + ...typography.heading4, + color: colors.invariant.green, + marginBottom: '4px' + }, + lookingForRouteText: { + ...typography.body2, + color: colors.invariant.text + } + } +}) + +export default useStyles diff --git a/src/components/TransactionRoute/FlowChartGrid/types/types.ts b/src/components/TransactionRoute/FlowChartGrid/types/types.ts new file mode 100644 index 000000000..facd6fa3e --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/types/types.ts @@ -0,0 +1,122 @@ +import { BN } from '@coral-xyz/anchor' + +export type Direction = 'right' | 'left' | 'down' | 'up' +export type Shape = 'circle' | 'rect' +export type LabelPos = 'bottom' | 'right' + +export enum CornerPosition { + TopLeft = 'top-left', + TopRight = 'top-right', + BottomLeft = 'bottom-left', + BottomRight = 'bottom-right' +} + +interface BaseNodeProps { + shape: Shape + cornerPosition?: CornerPosition + bigNode?: boolean + labelPos?: LabelPos + textA?: string + textB?: string +} + +interface BaseConnectorProps { + direction: Direction + longerConnector?: boolean + withArrow?: boolean +} + +export interface NodeConnectorProps extends BaseConnectorProps { + shape: Shape +} + +export interface Connector extends BaseConnectorProps {} + +export interface DexInfo { + name: string + fee: number + logo?: string + link: string +} + +export interface FlowNodeProps extends BaseNodeProps { + showTriangleArrow?: boolean + arrowDirection?: Direction + dexInfo?: DexInfo + connectors: Connector[] + logoImg?: string +} + +export interface TokenInfo { + symbol: string + logoUrl: string + amount: number +} + +export interface ExchangeInfo { + name: string + logoUrl: string + fee: number + toToken?: TokenInfo +} + +export interface GridCellProps { + children?: React.ReactNode + colSpan?: number + rowSpan?: number +} + +export interface CellDefinition extends BaseNodeProps, GridCellProps { + type?: 'node' + arrowDirection?: Direction + showTriangleArrow?: boolean + logoImg?: string + dexInfo?: DexInfo + connectors?: Connector[] +} + +export interface FlowChartGridProps { + gridDefinition: (CellDefinition | null)[][] + cellSize?: number +} + +export interface RouteData { + sourceToken: TokenInfo + destinationToken: TokenInfo + exchanges: ExchangeInfo[] +} + +export interface TransactionRouteProps { + hopCount: number + routeData: RouteData +} + +export interface FlowChartTokenNode { + symbol: string + logoUrl: string + rawAmount: BN + amount: BN +} + +export interface RouteTemplateProps { + sourceToken: FlowChartTokenNode + + exchanges: Array<{ + name: string + logoUrl: string + fee: number + toToken?: FlowChartTokenNode + }> + destinationToken: FlowChartTokenNode +} + +export interface FlowChartProps { + routeData?: RouteTemplateProps + isLoading?: boolean + errorMessage?: string + showCloseButton?: boolean + handleClose?: () => void +} + +export type GridCell = CellDefinition | null +export type GridDefinition = GridCell[][] diff --git a/src/components/TransactionRoute/FlowChartGrid/utils/createArrowConnectorStyle.ts b/src/components/TransactionRoute/FlowChartGrid/utils/createArrowConnectorStyle.ts new file mode 100644 index 000000000..8bb57f866 --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/utils/createArrowConnectorStyle.ts @@ -0,0 +1,51 @@ +import { Theme } from '@emotion/react' +import { SxProps } from '@mui/material' + +const ARROW_SIZE = 5 + +export const createArrowStyle = ( + arrowDirection: 'left' | 'right' | 'up' | 'down', + lineColor: string +): SxProps => { + const baseStyle = { + width: 0, + height: 0 + } as SxProps + + switch (arrowDirection) { + case 'left': + return { + ...baseStyle, + borderTop: `${ARROW_SIZE}px solid transparent`, + borderBottom: `${ARROW_SIZE}px solid transparent`, + borderRight: `${ARROW_SIZE}px solid ${lineColor}`, + marginRight: '-1px' + } + case 'right': + return { + ...baseStyle, + borderTop: `${ARROW_SIZE}px solid transparent`, + borderBottom: `${ARROW_SIZE}px solid transparent`, + borderLeft: `${ARROW_SIZE}px solid ${lineColor}`, + marginLeft: '-1px' + } + case 'up': + return { + ...baseStyle, + borderLeft: `${ARROW_SIZE}px solid transparent`, + borderRight: `${ARROW_SIZE}px solid transparent`, + borderBottom: `${ARROW_SIZE}px solid ${lineColor}`, + marginBottom: '-1px' + } + case 'down': + return { + ...baseStyle, + borderLeft: `${ARROW_SIZE}px solid transparent`, + borderRight: `${ARROW_SIZE}px solid transparent`, + borderTop: `${ARROW_SIZE}px solid ${lineColor}`, + marginTop: '-1px' + } + default: + return baseStyle + } +} diff --git a/src/components/TransactionRoute/FlowChartGrid/utils/generateTemplates.ts b/src/components/TransactionRoute/FlowChartGrid/utils/generateTemplates.ts new file mode 100644 index 000000000..34a342596 --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/utils/generateTemplates.ts @@ -0,0 +1,335 @@ +import { CornerPosition, GridDefinition, RouteTemplateProps } from '../types/types' + +export const generateOneHopTemplate = (data: RouteTemplateProps): GridDefinition => { + const { sourceToken, destinationToken, exchanges } = data + + return [ + [ + { + type: 'node', + shape: 'circle', + bigNode: true, + textA: sourceToken.symbol, + textB: sourceToken.amount, + connectors: [{ direction: 'right' }], + logoImg: sourceToken.logoUrl + }, + { + type: 'node', + shape: 'rect', + showTriangleArrow: true, + cornerPosition: CornerPosition.BottomLeft, + connectors: [{ direction: 'down' }] + } + ], + [null, null], + [ + null, + { + type: 'node', + shape: 'rect', + dexInfo: { + logo: exchanges[0].logoUrl, + fee: exchanges[0].fee, + link: 'http://foo.bar', + name: exchanges[0].name + }, + connectors: [ + { direction: 'down', longerConnector: true }, + { direction: 'up', longerConnector: true } + ] + } + ], + [null, null], + [ + { + type: 'node', + shape: 'circle', + textA: destinationToken.symbol, + bigNode: true, + textB: destinationToken.amount, + logoImg: destinationToken.logoUrl + }, + { + type: 'node', + showTriangleArrow: true, + arrowDirection: 'left', + cornerPosition: CornerPosition.TopLeft, + shape: 'rect', + connectors: [{ direction: 'left', withArrow: true }] + } + ] + ] +} + +export const generateTwoHopTemplate = (data: RouteTemplateProps): GridDefinition => { + const { sourceToken, destinationToken, exchanges } = data + + return [ + [ + { + type: 'node', + shape: 'circle', + bigNode: true, + textA: sourceToken.symbol, + textB: sourceToken.amount, + connectors: [{ direction: 'right' }], + logoImg: sourceToken.logoUrl + }, + { + type: 'node', + shape: 'rect', + dexInfo: { + logo: exchanges[0].logoUrl, + fee: exchanges[0].fee, + link: 'http://foo.bar', + name: exchanges[0].name + }, + connectors: [{ direction: 'down', withArrow: true, longerConnector: true }] + } + ], + [null, null], + [ + null, + { + type: 'node', + shape: 'circle', + labelPos: 'right', + textA: exchanges[0].toToken?.symbol, + textB: exchanges[0].toToken?.amount, + connectors: [], + logoImg: exchanges[0].toToken?.logoUrl + } + ], + [null, null], + [ + { + type: 'node', + shape: 'circle', + bigNode: true, + textA: destinationToken.symbol, + textB: destinationToken.amount, + logoImg: destinationToken.logoUrl + }, + { + type: 'node', + shape: 'rect', + dexInfo: { + logo: exchanges[1].logoUrl, + fee: exchanges[1].fee, + link: 'http://foo.bar', + name: exchanges[1].name + }, + + connectors: [ + { direction: 'left', withArrow: true }, + { direction: 'up', longerConnector: true } + ] + } + ] + ] +} + +export const generateThreeHopTemplate = (data: RouteTemplateProps): GridDefinition => { + const { sourceToken, destinationToken, exchanges } = data + + return [ + [ + { + type: 'node', + shape: 'circle', + bigNode: true, + textA: sourceToken.symbol, + textB: sourceToken.amount, + logoImg: sourceToken.logoUrl + }, + + { + type: 'node', + shape: 'rect', + dexInfo: { + logo: exchanges[0].logoUrl, + fee: exchanges[0].fee, + link: 'http://foo.bar', + name: exchanges[0].name + }, + + connectors: [{ direction: 'down', withArrow: true }, { direction: 'left' }] + } + ], + [ + null, + { + type: 'node', + shape: 'circle', + labelPos: 'right', + textA: exchanges[0].toToken?.symbol, + textB: exchanges[0].toToken?.amount, + logoImg: exchanges[0].toToken?.logoUrl + } + ], + [ + null, + { + type: 'node', + shape: 'rect', + dexInfo: { + logo: exchanges[1].logoUrl, + fee: exchanges[1].fee, + link: 'http://foo.bar', + name: exchanges[1].name + }, + + connectors: [{ direction: 'down', withArrow: true }, { direction: 'up' }] + } + ], + [ + null, + { + type: 'node', + shape: 'circle', + labelPos: 'right', + textA: exchanges[1].toToken?.symbol, + textB: exchanges[1].toToken?.amount, + logoImg: exchanges[1].toToken?.logoUrl + } + ], + [ + { + type: 'node', + shape: 'circle', + bigNode: true, + textA: destinationToken.symbol, + textB: destinationToken.amount, + logoImg: destinationToken.logoUrl + }, + { + type: 'node', + shape: 'rect', + dexInfo: { + logo: exchanges[2].logoUrl, + fee: exchanges[2].fee, + link: 'http://foo.bar', + name: exchanges[2].name + }, + + connectors: [{ direction: 'up' }, { direction: 'left', withArrow: true }] + } + ] + ] +} + +export const generateFourHopTemplate = (data: RouteTemplateProps): GridDefinition => { + const { sourceToken, destinationToken, exchanges } = data + + return [ + [ + { + type: 'node', + shape: 'circle', + bigNode: true, + textA: sourceToken.symbol, + textB: sourceToken.amount, + logoImg: sourceToken.logoUrl + }, + { + type: 'node', + shape: 'rect', + dexInfo: { + logo: exchanges[0].logoUrl, + fee: exchanges[0].fee, + link: 'http://foo.bar', + name: exchanges[0].name + }, + + connectors: [{ direction: 'right' }, { direction: 'left' }] + }, + { + type: 'node', + shape: 'circle', + labelPos: 'right', + textA: exchanges[0].toToken?.symbol, + textB: exchanges[0].toToken?.amount, + connectors: [], + logoImg: exchanges[0].toToken?.logoUrl + } + ], + [ + null, + null, + { + type: 'node', + shape: 'rect', + dexInfo: { + logo: exchanges[1].logoUrl, + fee: exchanges[1].fee, + link: 'http://foo.bar', + name: exchanges[1].name + }, + + connectors: [{ direction: 'up' }, { direction: 'down', withArrow: true }] + } + ], + [ + null, + null, + { + type: 'node', + shape: 'circle', + labelPos: 'right', + textA: exchanges[1].toToken?.symbol, + textB: exchanges[1].toToken?.amount, + connectors: [], + logoImg: exchanges[1].toToken?.logoUrl + } + ], + [ + null, + null, + { + type: 'node', + shape: 'rect', + + dexInfo: { + logo: exchanges[2].logoUrl, + fee: exchanges[2].fee, + link: 'http://foo.bar', + name: exchanges[2].name + }, + + connectors: [{ direction: 'up' }, { direction: 'down', withArrow: true }] + } + ], + [ + { + type: 'node', + shape: 'circle', + bigNode: true, + textA: destinationToken.symbol, + textB: destinationToken.amount, + logoImg: destinationToken.logoUrl + }, + + { + type: 'node', + shape: 'rect', + + dexInfo: { + logo: exchanges[3].logoUrl, + fee: exchanges[3].fee, + link: 'http://foo.bar', + name: exchanges[3].name + }, + connectors: [{ direction: 'right' }, { direction: 'left', withArrow: true }] + }, + { + type: 'node', + shape: 'circle', + textA: exchanges[2].toToken?.symbol, + textB: exchanges[2].toToken?.amount, + connectors: [], + logoImg: exchanges[2].toToken?.logoUrl + } + ] + ] +} diff --git a/src/components/TransactionRoute/FlowChartGrid/utils/getConnectorStyles.ts b/src/components/TransactionRoute/FlowChartGrid/utils/getConnectorStyles.ts new file mode 100644 index 000000000..2e9d71521 --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/utils/getConnectorStyles.ts @@ -0,0 +1,122 @@ +import { Theme } from '@emotion/react' +import { SxProps } from '@mui/material' +import { Direction, Shape } from '../types/types' +import { createArrowStyle } from './createArrowConnectorStyle' + +const LINE_LENGTH = 24 +const LINE_COLOR = 'rgba(255, 255, 255, 0.2)' + +type ConnectorStyles = { + container: SxProps + line?: SxProps + line1?: SxProps + line2?: SxProps + arrow?: SxProps +} + +type ConnectorStyleProps = { + longerConnector: boolean + shape: Shape + direction: Direction + withArrow: boolean +} + +const getPositionAdjustment = (shape: Shape) => { + return shape === 'circle' ? { x: 0, y: -0.3 } : { x: -0.6, y: 0.3 } +} + +export const getConnectorStyles = ({ + direction, + longerConnector, + shape, + withArrow +}: ConnectorStyleProps): ConnectorStyles => { + const adjustment = getPositionAdjustment(shape) + + const downLength = longerConnector ? LINE_LENGTH + 85 : LINE_LENGTH + 6 + const upLength = longerConnector ? LINE_LENGTH + 52 : LINE_LENGTH + + switch (direction) { + case 'right': + return { + container: { + position: 'absolute', + right: -LINE_LENGTH, + top: '50%', + transform: `translateY(calc(-50% + ${adjustment.y}px))`, + height: '1px', + display: 'flex', + alignItems: 'center' + }, + line: { + width: LINE_LENGTH, + height: '0.5px', + backgroundColor: LINE_COLOR + }, + arrow: withArrow ? createArrowStyle('right', LINE_COLOR) : undefined + } + + case 'left': + return { + container: { + position: 'absolute', + left: -LINE_LENGTH + 8, + top: '50%', + transform: `translateY(calc(-50% + ${adjustment.y}px))`, + height: '0.5px', + display: 'flex', + alignItems: 'center', + flexDirection: 'row-reverse' + }, + line: { + width: LINE_LENGTH - 12, + height: '1px', + backgroundColor: LINE_COLOR + }, + arrow: withArrow ? createArrowStyle('left', LINE_COLOR) : undefined + } + + case 'down': + return { + container: { + position: 'absolute', + bottom: longerConnector ? -LINE_LENGTH - 88 : -LINE_LENGTH - 10, + left: '50%', + transform: `translateX(calc(-50% + ${adjustment.x}px))`, + width: '0.5px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center' + }, + line: { + height: downLength, + width: '0.5px', + backgroundColor: LINE_COLOR + }, + arrow: withArrow ? createArrowStyle('down', LINE_COLOR) : undefined + } + + case 'up': + return { + container: { + position: 'absolute', + top: longerConnector ? -LINE_LENGTH - 52 : -LINE_LENGTH, + left: '50%', + transform: `translateX(calc(-50% + ${adjustment.x}px))`, + width: '0.5px', + display: 'flex', + flexDirection: 'column-reverse', + alignItems: 'center' + }, + line: { + height: upLength, + width: '0.5px', + backgroundColor: LINE_COLOR + }, + arrow: withArrow ? createArrowStyle('up', LINE_COLOR) : undefined + } + + default: + return { container: {} } + } +} diff --git a/src/components/TransactionRoute/FlowChartGrid/utils/renderFlexLines.tsx b/src/components/TransactionRoute/FlowChartGrid/utils/renderFlexLines.tsx new file mode 100644 index 000000000..d7cf53923 --- /dev/null +++ b/src/components/TransactionRoute/FlowChartGrid/utils/renderFlexLines.tsx @@ -0,0 +1,105 @@ +import { Box } from '@mui/material' +import { CornerPosition } from '../types/types' + +export const renderFlexLines = (cornerPosition?: CornerPosition) => { + const baseLineStyle = { + position: 'absolute', + backgroundColor: 'rgba(255, 255, 255, 0.2)', + zIndex: 11 + } + + switch (cornerPosition) { + case CornerPosition.BottomLeft: + return ( + <> + + + + ) + case CornerPosition.TopLeft: + return ( + <> + + + + ) + case CornerPosition.TopRight: + return ( + <> + + + + ) + case CornerPosition.BottomRight: + return ( + <> + + + + ) + } +} diff --git a/src/components/TransactionRoute/TransactionRoute.stories.tsx b/src/components/TransactionRoute/TransactionRoute.stories.tsx new file mode 100644 index 000000000..e8fd38b7d --- /dev/null +++ b/src/components/TransactionRoute/TransactionRoute.stories.tsx @@ -0,0 +1,303 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { fn } from '@storybook/test' +import { MemoryRouter } from 'react-router-dom' +import TransactionRoute from './TransactionRoute' + +import SolarLogo from '../assets/solar-logo.png' +import InvariantLogo from '../assets/invariant-logo.png' +import LifinityLogo from '../assets/lifinity-logo.png' +import UmbraLogo from '../assets/umbra-logo.png' + +const meta = { + title: 'PageComponent/TransactionRoute', + component: TransactionRoute, + parameters: { + layout: 'centered' + }, + decorators: [ + Story => ( + + + + ) + ], + argTypes: { + isLoading: { + control: 'boolean', + description: 'Loading state of the component' + }, + showCloseButton: { + control: 'boolean', + description: 'Whether to show the close button' + } + } +} satisfies Meta + +export default meta +type Story = StoryObj + +export const OneHop: Story = { + args: { + routeData: { + sourceToken: { + rawAmount: 1, + symbol: 'ETH', + logoUrl: + 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk/logo.png', + amount: 1 + }, + destinationToken: { + rawAmount: 1, + symbol: 'MOON', + logoUrl: 'https://raw.githubusercontent.com/moon-meme/assets/main/Moon.png', + amount: 1 + }, + exchanges: [ + { + name: 'Solar', + logoUrl: SolarLogo, + fee: 0.01 + } + ] + }, + handleClose: fn(), + showCloseButton: true, + isLoading: false + } +} +export const TwoHops: Story = { + args: { + routeData: { + sourceToken: { + symbol: 'ETH', + rawAmount: 1, + logoUrl: + 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk/logo.png', + amount: 1 + }, + destinationToken: { + rawAmount: 1, + symbol: 'MOON', + logoUrl: 'https://raw.githubusercontent.com/moon-meme/assets/main/Moon.png', + amount: 1 + }, + exchanges: [ + { + name: 'Solar', + logoUrl: SolarLogo, + fee: 0.01, + toToken: { + rawAmount: 1, + symbol: 'LAIKA', + logoUrl: + 'https://statics.solscan.io/cdn/imgs/s60?ref=68747470733a2f2f736864772d64726976652e67656e65737973676f2e6e65742f387035714352796b774e767a463433484d6b31356243664c3678413934474b65365a526570696e6d576a44692f6c61696b612e706e67', + amount: 1 + } + }, + { + name: 'Umbra', + logoUrl: UmbraLogo, + fee: 0.05 + } + ] + }, + handleClose: fn(), + showCloseButton: true, + isLoading: false + } +} + +export const ThreeHops: Story = { + args: { + routeData: { + sourceToken: { + rawAmount: 1, + symbol: 'ETH', + logoUrl: + 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk/logo.png', + amount: 1 + }, + destinationToken: { + symbol: 'MOON', + logoUrl: 'https://raw.githubusercontent.com/moon-meme/assets/main/Moon.png', + amount: 1, + rawAmount: 1 + }, + exchanges: [ + { + name: 'Solar', + logoUrl: SolarLogo, + toToken: { + rawAmount: 1, + symbol: 'MOON', + logoUrl: 'https://raw.githubusercontent.com/moon-meme/assets/main/Moon.png', + amount: 1 + }, + fee: 0.01 + }, + { + name: 'Invariant', + logoUrl: InvariantLogo, + fee: 0.01, + toToken: { + rawAmount: 1, + symbol: 'MOON', + logoUrl: 'https://raw.githubusercontent.com/moon-meme/assets/main/Moon.png', + amount: 1 + } + }, + { + name: 'Lifinity', + logoUrl: LifinityLogo, + fee: 0.01 + } + ] + }, + handleClose: fn(), + showCloseButton: true, + isLoading: false + } +} + +export const FourHops: Story = { + args: { + routeData: { + sourceToken: { + rawAmount: 1, + symbol: 'ETH', + logoUrl: + 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk/logo.png', + amount: 1 + }, + destinationToken: { + rawAmount: 1, + symbol: 'MOON', + logoUrl: 'https://raw.githubusercontent.com/moon-meme/assets/main/Moon.png', + amount: 1 + }, + exchanges: [ + { + name: 'Solar', + logoUrl: SolarLogo, + toToken: { + rawAmount: 1, + symbol: 'LAIKA', + logoUrl: + 'https://statics.solscan.io/cdn/imgs/s60?ref=68747470733a2f2f736864772d64726976652e67656e65737973676f2e6e65742f387035714352796b774e767a463433484d6b31356243664c3678413934474b65365a526570696e6d576a44692f6c61696b612e706e67', + amount: 1 + }, + fee: 0.01 + }, + { + name: 'Invariant', + logoUrl: InvariantLogo, + toToken: { + rawAmount: 1, + symbol: 'LAIKA', + logoUrl: + 'https://statics.solscan.io/cdn/imgs/s60?ref=68747470733a2f2f736864772d64726976652e67656e65737973676f2e6e65742f387035714352796b774e767a463433484d6b31356243664c3678413934474b65365a526570696e6d576a44692f6c61696b612e706e67', + amount: 1 + }, + fee: 0.03 + }, + { + name: 'Umbra', + logoUrl: UmbraLogo, + toToken: { + rawAmount: 1, + symbol: 'SOL', + logoUrl: + 'https://statics.solscan.io/cdn/imgs/s60?ref=68747470733a2f2f736864772d64726976652e67656e65737973676f2e6e65742f387035714352796b774e767a463433484d6b31356243664c3678413934474b65365a526570696e6d576a44692f6c61696b612e706e67', + amount: 1 + }, + fee: 0.03 + }, + { + name: 'FooBar', + logoUrl: UmbraLogo, + toToken: { + rawAmount: 1, + symbol: 'LAIKA', + logoUrl: + 'https://statics.solscan.io/cdn/imgs/s60?ref=68747470733a2f2f736864772d64726976652e67656e65737973676f2e6e65742f387035714352796b774e767a463433484d6b31356243664c3678413934474b65365a526570696e6d576a44692f6c61696b612e706e67', + amount: 1 + }, + fee: 0.03 + } + ] + }, + handleClose: fn(), + showCloseButton: true, + isLoading: false + } +} + +export const Loading: Story = { + args: { + routeData: { + sourceToken: { + rawAmount: 1, + symbol: 'ETH', + logoUrl: + 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk/logo.png', + amount: 1 + }, + destinationToken: { + rawAmount: 1, + symbol: 'MOON', + logoUrl: 'https://raw.githubusercontent.com/moon-meme/assets/main/Moon.png', + amount: 1 + }, + exchanges: [ + { + name: 'Solar', + logoUrl: SolarLogo, + fee: 0.01 + }, + { + name: 'Invariant', + logoUrl: InvariantLogo, + fee: 0.01 + } + ] + }, + handleClose: fn(), + showCloseButton: true, + isLoading: true + } +} + +export const NoCloseButton: Story = { + args: { + routeData: { + sourceToken: { + rawAmount: 1, + symbol: 'ETH', + logoUrl: + 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk/logo.png', + amount: 1 + }, + destinationToken: { + rawAmount: 1, + symbol: 'MOON', + logoUrl: 'https://raw.githubusercontent.com/moon-meme/assets/main/Moon.png', + amount: 1 + }, + exchanges: [ + { + name: 'Solar', + logoUrl: SolarLogo, + fee: 0.01 + }, + { + name: 'Invariant', + logoUrl: InvariantLogo, + fee: 0.01 + } + ] + }, + handleClose: fn(), + showCloseButton: false, + isLoading: false + } +} diff --git a/src/components/TransactionRoute/TransactionRoute.tsx b/src/components/TransactionRoute/TransactionRoute.tsx new file mode 100644 index 000000000..16d404a5d --- /dev/null +++ b/src/components/TransactionRoute/TransactionRoute.tsx @@ -0,0 +1,80 @@ +import React from 'react' +import { Box, Typography } from '@mui/material' +import { FlowChartGrid } from './FlowChartGrid/FlowChartGrid' + +import { + generateFourHopTemplate, + generateOneHopTemplate, + generateThreeHopTemplate, + generateTwoHopTemplate +} from './FlowChartGrid/utils/generateTemplates' +import TransactionRouteLoader from './FlowChartGrid/components/TransactionRouteLoader/TransactionRouteLoader' +import { FlowChartProps, GridDefinition, RouteTemplateProps } from './FlowChartGrid/types/types' +import useStyles from './style' +import TransactionRouteError from './FlowChartGrid/components/TransactionRouteError/TransactionRouteError' + +const getTemplateForHopCount = (hopCount: number, data: RouteTemplateProps): GridDefinition => { + switch (hopCount) { + case 1: + return generateOneHopTemplate(data) + case 2: + return generateTwoHopTemplate(data) + case 3: + return generateThreeHopTemplate(data) + case 4: + return generateFourHopTemplate(data) + default: + return generateOneHopTemplate(data) + } +} + +const TransactionRoute: React.FC = ({ + routeData, + handleClose, + errorMessage, + showCloseButton = true, + isLoading = false +}) => { + const { classes } = useStyles({ + isLoading, + width: '280px' + }) + + const renderContent = () => { + if (isLoading) { + return + } + + if (errorMessage && (!routeData || !routeData.exchanges || routeData.exchanges.length === 0)) { + return + } + + if (routeData && routeData.exchanges && routeData.exchanges.length > 0) { + return ( + + ) + } + + return + } + + return ( + + {showCloseButton ? ( + + ) : null} + + Transaction route + + {renderContent()} + + + ) +} + +export default TransactionRoute diff --git a/src/components/TransactionRoute/style.ts b/src/components/TransactionRoute/style.ts new file mode 100644 index 000000000..fa16b3834 --- /dev/null +++ b/src/components/TransactionRoute/style.ts @@ -0,0 +1,114 @@ +import { colors, typography } from '@static/theme' +import { makeStyles } from 'tss-react/mui' + +const useStyles = makeStyles<{ isLoading: boolean; width: string }>()(( + theme, + { isLoading, width } +) => { + return { + container: { + width, + position: 'relative', + height: '490px', + display: 'flex', + justifyContent: 'space-around', + alignItems: 'center', + flexDirection: 'column', + backgroundColor: colors.invariant.component, + borderRadius: '24px', + color: 'white', + marginLeft: '10px', + padding: '0 20px', + transition: 'width .1s ease-in-out' + }, + routeTitle: { + ...typography.body2, + textAlign: 'center', + width: '100%', + color: colors.invariant.textGrey + }, + graphContainer: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: isLoading ? 'center' : 'flex-start', + position: 'relative', + width: '100%' + }, + tokenNode: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + position: 'absolute', + zIndex: 2 + }, + tokenIcon: { + width: 50, + height: 50, + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: theme.spacing(1), + backgroundColor: '#2a3154' + }, + tokenSymbol: { + fontWeight: 'bold', + fontSize: '0.875rem' + }, + tokenAmount: { + fontSize: '0.75rem', + opacity: 0.7 + }, + line: { + backgroundColor: 'rgba(255, 255, 255, 0.2)', + position: 'absolute', + zIndex: 0 + }, + exchangeLabel: { + backgroundColor: 'rgba(255, 255, 255, 0.1)', + borderRadius: 12, + padding: theme.spacing(0.5, 1.5), + fontSize: '0.75rem', + display: 'flex', + alignItems: 'center', + position: 'absolute', + zIndex: 2 + }, + exchangeIcon: { + width: 16, + height: 16, + marginRight: theme.spacing(0.5), + borderRadius: '50%', + backgroundColor: 'rgba(255, 255, 255, 0.2)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + }, + exchangePercentage: { + marginRight: theme.spacing(0.5) + }, + closeButton: { + position: 'absolute', + top: theme.spacing(2), + right: theme.spacing(2), + backgroundColor: 'transparent', + color: 'rgba(255, 255, 255, 0.5)', + border: 'none', + cursor: 'pointer', + fontSize: '1.5rem', + zIndex: 10 + }, + arrowHead: { + position: 'absolute', + width: 0, + height: 0, + borderTop: '6px solid transparent', + borderBottom: '6px solid transparent', + borderLeft: '8px solid rgba(255, 255, 255, 0.2)', + zIndex: 1 + } + } +}) + +export default useStyles diff --git a/src/containers/WrappedSwap/WrappedSwap.tsx b/src/containers/WrappedSwap/WrappedSwap.tsx index cd6ede8a4..3cb91a368 100644 --- a/src/containers/WrappedSwap/WrappedSwap.tsx +++ b/src/containers/WrappedSwap/WrappedSwap.tsx @@ -28,7 +28,7 @@ import { balance, accounts as solanaAccounts } from '@store/selectors/solanaWallet' -import { swap as swapPool, accounts, isLoading } from '@store/selectors/swap' +import { swap as swapPool, accounts, isLoading, swapRoute } from '@store/selectors/swap' import { PublicKey } from '@solana/web3.js' import { useEffect, useMemo, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' @@ -63,6 +63,7 @@ export const WrappedSwap = ({ initialTokenFrom, initialTokenTo }: Props) => { const walletStatus = useSelector(status) const swap = useSelector(swapPool) + const swapRouteChartData = useSelector(swapRoute) const tickmap = useSelector(tickMaps) const poolTicksForSimulation = useSelector(nearestPoolTicksForPair) const allPools = useSelector(poolsArraySortedByFees) @@ -355,105 +356,118 @@ export const WrappedSwap = ({ initialTokenFrom, initialTokenTo }: Props) => { const swapAccounts = useSelector(accounts) const swapIsLoading = useSelector(isLoading) - return ( - { - setProgress('progress') - dispatch( - actions.swap({ - slippage, - estimatedPriceAfterSwap, - firstPair, - secondPair, - tokenFrom, - tokenBetween, - tokenTo, - amountIn, - amountOut, - byAmountIn - }) - ) - }} - onSetPair={(tokenFrom, tokenTo) => { - setTokenFrom(tokenFrom) - setTokenTo(tokenTo) - - if (tokenFrom !== null) { - localStorage.setItem(`INVARIANT_LAST_TOKEN_FROM_${networkType}`, tokenFrom.toString()) - } - - if (tokenTo !== null) { - localStorage.setItem(`INVARIANT_LAST_TOKEN_TO_${networkType}`, tokenTo.toString()) - } - if (tokenFrom !== null && tokenTo !== null && !tokenFrom.equals(tokenTo)) { + <> + { + dispatch( + swapActions.fetchSwapRoute({ + amountIn, + tokenFrom, + tokenTo, + tokens: tokensList, + slippage + }) + ) + }} + onSwap={( + slippage, + estimatedPriceAfterSwap, + tokenFrom, + tokenBetween, + tokenTo, + firstPair, + secondPair, + amountIn, + amountOut, + byAmountIn + ) => { + setProgress('progress') dispatch( - poolsActions.getAllPoolsForPairData({ - first: tokenFrom, - second: tokenTo + actions.swap({ + slippage, + estimatedPriceAfterSwap, + firstPair, + secondPair, + tokenFrom, + tokenBetween, + tokenTo, + amountIn, + amountOut, + byAmountIn }) ) - } - }} - onConnectWallet={() => { - dispatch(walletActions.connect(false)) - }} - onDisconnectWallet={() => { - dispatch(walletActions.disconnect()) - }} - walletStatus={walletStatus} - tokens={tokensList} - pools={allPools} - swapData={swap} - progress={progress} - poolTicks={poolTicksForSimulation} - isWaitingForNewPool={isFetchingNewPool} - tickmap={tickmap} - initialTokenFromIndex={initialTokenFromIndex === -1 ? null : initialTokenFromIndex} - initialTokenToIndex={initialTokenToIndex === -1 ? null : initialTokenToIndex} - handleAddToken={addTokenHandler} - commonTokens={commonTokensForNetworks[networkType]} - initialHideUnknownTokensValue={initialHideUnknownTokensValue} - onHideUnknownTokensChange={setHideUnknownTokensValue} - tokenFromPriceData={tokenFromPriceData} - tokenToPriceData={tokenToPriceData} - priceFromLoading={priceFromLoading || isBalanceLoading} - priceToLoading={priceToLoading || isBalanceLoading} - onSlippageChange={onSlippageChange} - initialSlippage={initialSlippage} - isBalanceLoading={isBalanceLoading} - copyTokenAddressHandler={copyTokenAddressHandler} - ethBalance={ethBalance} - network={networkType} - unwrapWETH={unwrapWETH} - wrappedETHAccountExist={wrappedETHAccountExist} - isTimeoutError={isTimeoutError} - deleteTimeoutError={() => { - dispatch(connectionActions.setTimeoutError(false)) - }} - canNavigate={canNavigate} - pointsPerUsdFee={pointsPerUsdFee} - feeds={priceFeeds} - promotedSwapPairs={promotedSwapPairs} - swapMultiplier={multiplyer} - market={market} - tokensDict={tokensDict} - swapAccounts={swapAccounts} - swapIsLoading={swapIsLoading} - /> + }} + onSetPair={(tokenFrom, tokenTo) => { + setTokenFrom(tokenFrom) + setTokenTo(tokenTo) + + if (tokenFrom !== null) { + localStorage.setItem(`INVARIANT_LAST_TOKEN_FROM_${networkType}`, tokenFrom.toString()) + } + + if (tokenTo !== null) { + localStorage.setItem(`INVARIANT_LAST_TOKEN_TO_${networkType}`, tokenTo.toString()) + } + if (tokenFrom !== null && tokenTo !== null && !tokenFrom.equals(tokenTo)) { + dispatch( + poolsActions.getAllPoolsForPairData({ + first: tokenFrom, + second: tokenTo + }) + ) + } + }} + onConnectWallet={() => { + dispatch(walletActions.connect(false)) + }} + onDisconnectWallet={() => { + dispatch(walletActions.disconnect()) + }} + walletStatus={walletStatus} + tokens={tokensList} + pools={allPools} + swapData={swap} + progress={progress} + poolTicks={poolTicksForSimulation} + isWaitingForNewPool={isFetchingNewPool} + tickmap={tickmap} + initialTokenFromIndex={initialTokenFromIndex === -1 ? null : initialTokenFromIndex} + initialTokenToIndex={initialTokenToIndex === -1 ? null : initialTokenToIndex} + handleAddToken={addTokenHandler} + commonTokens={commonTokensForNetworks[networkType]} + initialHideUnknownTokensValue={initialHideUnknownTokensValue} + onHideUnknownTokensChange={setHideUnknownTokensValue} + tokenFromPriceData={tokenFromPriceData} + tokenToPriceData={tokenToPriceData} + priceFromLoading={priceFromLoading || isBalanceLoading} + priceToLoading={priceToLoading || isBalanceLoading} + onSlippageChange={onSlippageChange} + initialSlippage={initialSlippage} + isBalanceLoading={isBalanceLoading} + copyTokenAddressHandler={copyTokenAddressHandler} + ethBalance={ethBalance} + network={networkType} + unwrapWETH={unwrapWETH} + wrappedETHAccountExist={wrappedETHAccountExist} + isTimeoutError={isTimeoutError} + deleteTimeoutError={() => { + dispatch(connectionActions.setTimeoutError(false)) + }} + canNavigate={canNavigate} + pointsPerUsdFee={pointsPerUsdFee} + feeds={priceFeeds} + promotedSwapPairs={promotedSwapPairs} + swapMultiplier={multiplyer} + market={market} + tokensDict={tokensDict} + swapAccounts={swapAccounts} + swapIsLoading={swapIsLoading} + /> + ) } diff --git a/src/static/png/InvariantAggregator/Invariant.png b/src/static/png/InvariantAggregator/Invariant.png new file mode 100644 index 000000000..f516fe48b Binary files /dev/null and b/src/static/png/InvariantAggregator/Invariant.png differ diff --git a/src/static/png/InvariantAggregator/Orca.png b/src/static/png/InvariantAggregator/Orca.png new file mode 100644 index 000000000..41a6d8ea2 Binary files /dev/null and b/src/static/png/InvariantAggregator/Orca.png differ diff --git a/src/static/png/InvariantAggregator/header-logo.png b/src/static/png/InvariantAggregator/header-logo.png new file mode 100644 index 000000000..594c10531 Binary files /dev/null and b/src/static/png/InvariantAggregator/header-logo.png differ diff --git a/src/static/png/InvariantAggregator/lifinity.png b/src/static/png/InvariantAggregator/lifinity.png new file mode 100644 index 000000000..295fe8655 Binary files /dev/null and b/src/static/png/InvariantAggregator/lifinity.png differ diff --git a/src/static/png/InvariantAggregator/solar.png b/src/static/png/InvariantAggregator/solar.png new file mode 100644 index 000000000..13f1e58db Binary files /dev/null and b/src/static/png/InvariantAggregator/solar.png differ diff --git a/src/static/png/InvariantAggregator/umbra.png b/src/static/png/InvariantAggregator/umbra.png new file mode 100644 index 000000000..25f5345f7 Binary files /dev/null and b/src/static/png/InvariantAggregator/umbra.png differ diff --git a/src/store/consts/static.ts b/src/store/consts/static.ts index 9afaeb79b..494a9146f 100644 --- a/src/store/consts/static.ts +++ b/src/store/consts/static.ts @@ -638,8 +638,8 @@ export const getAddressTickerMap = (network: NetworkType): { [k: string]: string tETH: TETH_MAIN.address.toString(), MCT: MOCKED_TOKEN_MAIN.address.toString(), USDC: USDC_MAIN.address.toString(), - SOL: SOL_MAIN.address.toString(), USDT: USDT_MAIN.address.toString(), + SOL: SOL_MAIN.address.toString(), WIF: DOGWIFHAT_MAIN.address.toString(), LAIKA: LAIKA_MAIN.address.toString(), MOON: MOON_MAIN.address.toString(), diff --git a/src/store/reducers/swap.ts b/src/store/reducers/swap.ts index 3d0eb07c1..88b17ed35 100644 --- a/src/store/reducers/swap.ts +++ b/src/store/reducers/swap.ts @@ -6,6 +6,8 @@ import { PublicKey } from '@solana/web3.js' import { PayloadType } from '@store/consts/types' import { FetcherRecords } from '@invariant-labs/sdk-eclipse' import { Pair } from '@invariant-labs/sdk-eclipse/src' +import { RouteTemplateProps } from '@components/TransactionRoute/FlowChartGrid/types/types' +import { SwapToken } from '@store/selectors/solanaWallet' export interface Swap { slippage: BN @@ -23,6 +25,36 @@ export interface Swap { amountOut: BN } +export interface SwapRoute { + percent: number + swapInfo: { + ammKey: PublicKey + feeAmount: number + feeMint: PublicKey + inAmount: BN + inputMint: PublicKey + label: string + outAmount: BN + metadata: { + fee_rate?: number + fee?: number + tick_spacing: number + } + outputMint: PublicKey + } +} + +export interface SwapRoutesResponse { + inAmount: number + inputMint: PublicKey + outAmount: number + otherAmountThreshold: number + outputMint: PublicKey + platformFee: number | null + priceImpactPct: number + routePlan: SwapRoute[] +} + export interface Simulate { simulatePrice: BN fromToken: PublicKey @@ -33,10 +65,25 @@ export interface Simulate { inProgress?: boolean } +export interface AgregatorSimulateDetails { + priceImpactPct: number + otherAmountThreshold: number + amountOutWithFee: number + feePercent?: number +} + +export interface AgregatorSwapRoutes { + swapRouteLoading: boolean + swapRouteError?: string + swapSimulateDetails?: AgregatorSimulateDetails | undefined + swapRouteResponse?: RouteTemplateProps +} + export interface ISwapStore { swap: Swap accounts: FetcherRecords isLoading: boolean + swapRoute: AgregatorSwapRoutes } export const defaultState: ISwapStore = { @@ -52,6 +99,11 @@ export const defaultState: ISwapStore = { byAmountIn: false, amountOut: new BN(0) }, + swapRoute: { + swapRouteLoading: false, + swapSimulateDetails: undefined, + swapRouteResponse: undefined + }, accounts: { pools: {}, tickmaps: {}, @@ -86,6 +138,35 @@ const swapSlice = createSlice({ state.isLoading = true return state }, + setSwapRouteLoading(state, action: PayloadAction) { + state.swapRoute.swapRouteLoading = action.payload + return state + }, + setSwapRouteError(state, action: PayloadAction) { + state.swapRoute.swapRouteError = action.payload + return state + }, + setSwapRouteResponse(state, action: PayloadAction) { + state.swapRoute.swapRouteResponse = action.payload + state.swapRoute.swapRouteLoading = false + return state + }, + setSwapSimulateDetails(state, action: PayloadAction) { + state.swapRoute.swapSimulateDetails = action.payload + return state + }, + fetchSwapRoute( + state, + _action: PayloadAction<{ + amountIn: BN + slippage: number + tokens: SwapToken[] + tokenFrom: PublicKey + tokenTo: PublicKey + }> + ) { + return state + }, updateSwapPool(state, action: PayloadAction<{ address: PublicKey; pool: PoolStructure }>) { state.accounts.pools[action.payload.address.toString()] = action.payload.pool return state diff --git a/src/store/sagas/swap.ts b/src/store/sagas/swap.ts index da0ebe047..fa3d677d0 100644 --- a/src/store/sagas/swap.ts +++ b/src/store/sagas/swap.ts @@ -3,7 +3,7 @@ import { actions as snackbarsActions } from '@store/reducers/snackbars' import { actions as swapActions } from '@store/reducers/swap' import { swap } from '@store/selectors/swap' import { poolsArraySortedByFees, tickMaps, tokens } from '@store/selectors/pools' -import { accounts } from '@store/selectors/solanaWallet' +import { accounts, SwapToken } from '@store/selectors/solanaWallet' import { createAccount, getWallet } from './wallet' import { IWallet, Pair, routingEssentials } from '@invariant-labs/sdk-eclipse' import { getConnection, handleRpcError } from './connection' @@ -26,7 +26,12 @@ import { import { network, rpcAddress } from '@store/selectors/solanaConnection' import { actions as connectionActions } from '@store/reducers/solanaConnection' import { closeSnackbar } from 'notistack' -import { createLoaderKey, ensureError } from '@utils/utils' +import { + createLoaderKey, + ensureError, + getAgregatorSwapRoutesData, + transformRawSwapRoutesData +} from '@utils/utils' import { getMarketProgram } from '@utils/web3/programs/amm' import { createNativeAtaInstructions, @@ -44,6 +49,9 @@ import { import { PoolWithAddress } from '@store/reducers/pools' import nacl from 'tweetnacl' import { computeUnitsInstruction } from '@invariant-labs/sdk-eclipse/src' +import { BN } from '@coral-xyz/anchor' +import { AxiosError } from 'axios' +import { delay } from 'redux-saga/effects' export function* handleSwapWithETH(): Generator { const loaderSwappingTokens = createLoaderKey() @@ -1160,6 +1168,81 @@ export function* handleSwap(): Generator { yield* call(handleRpcError, error.message) } } +export function* handleFetchSwapRoute( + action: PayloadAction<{ + amountIn: BN + slippage: number + tokens: SwapToken[] + tokenFrom: PublicKey + tokenTo: PublicKey + }> +): Generator { + try { + const networkType = yield* select(network) + + const { amountIn, slippage, tokens, tokenFrom, tokenTo } = action.payload + + if ( + tokenFrom.equals(PublicKey.default) || + tokenTo.equals(PublicKey.default) || + amountIn.eq(new BN(0)) + ) { + return + } + + yield put(swapActions.setSwapRouteLoading(true)) + + yield delay(300) + + const slippageBps = slippage * 100 + + const routesData = yield* call(getAgregatorSwapRoutesData, { + inputMint: tokenFrom, + outputMint: tokenTo, + slippageBps, + amountIn + }) + + if (routesData) { + const { ...data } = routesData + const transformedData = transformRawSwapRoutesData(networkType, data, tokens) + + const totalFee = transformedData.exchanges.reduce((acc, route) => { + const fee = route.fee ?? 0 + return acc + fee + }, 0) + const amountOutWithFee = routesData.outAmount - totalFee + yield put( + swapActions.setSwapSimulateDetails({ + priceImpactPct: +routesData.priceImpactPct, + otherAmountThreshold: routesData.otherAmountThreshold, + amountOutWithFee, + feePercent: totalFee + }) + ) + + yield put( + swapActions.setSwapRouteResponse({ + ...transformedData + }) + ) + + yield put(swapActions.setSwapRouteError(undefined)) + } else { + yield put(swapActions.setSwapRouteResponse(undefined)) + yield put(swapActions.setSwapSimulateDetails(undefined)) + yield put(swapActions.setSwapRouteError('Failed to fetch swap routes')) + } + } catch (e: unknown) { + const error = ensureError(e) as AxiosError<{ message: string }> + yield put(swapActions.setSwapRouteResponse(undefined)) + yield put( + swapActions.setSwapRouteError(error.response?.data.message ?? 'Error fetching routes') + ) + } finally { + yield put(swapActions.setSwapRouteLoading(false)) + } +} export function* handleGetTwoHopSwapData( action: PayloadAction<{ tokenFrom: PublicKey; tokenTo: PublicKey }> @@ -1230,6 +1313,10 @@ export function* getTwoHopSwapDataHandler(): Generator { yield* takeLatest(swapActions.getTwoHopSwapData, handleGetTwoHopSwapData) } +export function* fetchSwapRouteHandler(): Generator { + yield* takeEvery(swapActions.fetchSwapRoute, handleFetchSwapRoute) +} + export function* swapSaga(): Generator { - yield* all([swapHandler, getTwoHopSwapDataHandler].map(spawn)) + yield* all([swapHandler, getTwoHopSwapDataHandler, fetchSwapRouteHandler].map(spawn)) } diff --git a/src/store/selectors/swap.ts b/src/store/selectors/swap.ts index e25e64455..851a5cda8 100644 --- a/src/store/selectors/swap.ts +++ b/src/store/selectors/swap.ts @@ -3,8 +3,13 @@ import { keySelectors, AnyProps } from './helpers' const store = (s: AnyProps) => s[swapSliceName] as ISwapStore -export const { swap, accounts, isLoading } = keySelectors(store, ['swap', 'accounts', 'isLoading']) - -export const swapSelectors = { swap, accounts, isLoading } +export const { swap, accounts, isLoading, swapRoute } = keySelectors(store, [ + 'swap', + 'swapRoute', + 'accounts', + 'isLoading' +]) + +export const swapSelectors = { swap, accounts, isLoading, swapRoute } export default swapSelectors diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b875c7d1d..bb71faab6 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -111,7 +111,15 @@ import { } from '@store/consts/types' import { sqrt } from '@invariant-labs/sdk-eclipse/lib/math' import { Metaplex } from '@metaplex-foundation/js' -import { apyToApr } from './uiUtils' +import { apyToApr, shortenAddress } from './uiUtils' +import { SwapRoutesResponse } from '@store/reducers/swap' + +import SolarLogo from '@static/png/InvariantAggregator/solar.png' +import LifinityLogo from '@static/png/InvariantAggregator/lifinity.png' +import UmbraLogo from '@static/png/InvariantAggregator/umbra.png' +import InvariantLogo from '@static/png/InvariantAggregator/Invariant.png' +import OrcaLogo from '@static/png/InvariantAggregator/Orca.png' +import { SwapToken } from '@store/selectors/solanaWallet' export const transformBN = (amount: BN): string => { return (amount.div(new BN(1e2)).toNumber() / 1e4).toString() @@ -1913,6 +1921,29 @@ export const getPoolsAPY = async (name: string): Promise> } } +export const getAgregatorSwapRoutesData = async ({ + inputMint, + outputMint, + slippageBps, + amountIn +}: { + inputMint: PublicKey + outputMint: PublicKey + slippageBps: number + amountIn: BN +}): Promise => { + try { + const ROUTER_URL = ' https://agg.invariant.app' + const quoteUrl = `${ROUTER_URL}/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amountIn}&slippageBps=${slippageBps}` + const { data } = await axios.get(quoteUrl) + + return data + } catch (e: unknown) { + const error = ensureError(e) + throw error + } +} + export const getIncentivesRewardData = async ( name: string ): Promise> => { @@ -2238,3 +2269,96 @@ export const calculatePercentageRatio = ( tokenYPercentage: 100 - tokenXPercentage } } + +const reformatTicker = (ticker: string) => { + return ticker.length > 5 ? shortenAddress(ticker, 3) : ticker +} +export const transformRawSwapRoutesData = ( + network: NetworkType, + data: SwapRoutesResponse, + tokens: SwapToken[] +) => { + const exchangeMapping: Record = { + Invariant: { name: 'Invariant', logoUrl: InvariantLogo }, + Solar: { name: 'Solar', logoUrl: SolarLogo }, + Umbra: { name: 'Umbra', logoUrl: UmbraLogo }, + Orca: { name: 'Orca', logoUrl: OrcaLogo }, + Lifinity: { name: 'Lifinity', logoUrl: LifinityLogo } + } + + const calculateFee = (dexName: string, feeData: any) => { + switch (dexName) { + case 'Orca': + return (parseFloat(feeData.fee_rate) / 1000000) * 100 + case 'Invariant': + return parseFloat(feeData.fee) * 100 + + default: + return 0 + } + } + + const inputToken = tokens.filter( + item => item.assetAddress.toString() === data.inputMint.toString() + )[0] + const outputToken = tokens.filter( + item => item.assetAddress.toString() === data.outputMint.toString() + )[0] + + const inToken = { + inputAmount: inputToken ? printBN(data.inAmount, inputToken.decimals ?? 0) : 0, + inputRawAmount: inputToken ? data.inAmount : new BN(0), + inputTicker: reformatTicker(addressToTicker(network, data.inputMint.toString())) + } + + const outToken = { + outputAmount: outputToken ? printBN(data.outAmount, outputToken.decimals) : 0, + outputRawAmount: outputToken ? data.outAmount : new BN(0), + outputTicker: reformatTicker(addressToTicker(network, data.outputMint.toString())) + } + + const exchanges = data.routePlan.map((route, index) => { + const { label, outAmount, outputMint, metadata } = route.swapInfo + + const exchange = exchangeMapping[label] || { + name: label, + logoUrl: null + } + const token = tokens.filter(item => item.assetAddress.toString() === outputMint.toString())[0] + + const isLastSwap = index === data.routePlan.length - 1 + + const fee = calculateFee(label, metadata) + + const result: any = { + name: exchange.name, + logoUrl: exchange.logoUrl, + fee: fee + } + if (!isLastSwap) { + result.toToken = { + symbol: reformatTicker(addressToTicker(network, outputMint.toString())), + logoUrl: token.logoURI, + amount: token && outAmount ? printBN(outAmount, token.decimals) : 0 + } + } + + return result + }) + + return { + sourceToken: { + symbol: inToken.inputTicker, + rawAmount: inToken.inputRawAmount, + logoUrl: inputToken.logoURI, + amount: inToken.inputAmount + }, + destinationToken: { + symbol: outToken.outputTicker, + rawAmount: outToken.outputRawAmount, + logoUrl: outputToken.logoURI, + amount: outToken.outputAmount + }, + exchanges + } +}