From 517b7fa0f55500024b534afe2c5c5a1bdbce4d81 Mon Sep 17 00:00:00 2001 From: deep-path <476044723@qq.com> Date: Fri, 15 Nov 2024 15:06:51 +0800 Subject: [PATCH 1/4] fix: hash translate --- .../HashResultModal/ModalWithCountdown.tsx | 40 +- components/HashResultModal/index.tsx | 50 ++ screens/Trading/index.tsx | 46 +- utils/near.ts | 32 ++ utils/txhashContract.ts | 75 +++ utils/useHooks.ts | 501 ++++++++++++++++++ 6 files changed, 732 insertions(+), 12 deletions(-) rename screens/Trading/components/positionResultTips.tsx => components/HashResultModal/ModalWithCountdown.tsx (74%) create mode 100644 components/HashResultModal/index.tsx create mode 100644 utils/near.ts create mode 100644 utils/txhashContract.ts create mode 100644 utils/useHooks.ts diff --git a/screens/Trading/components/positionResultTips.tsx b/components/HashResultModal/ModalWithCountdown.tsx similarity index 74% rename from screens/Trading/components/positionResultTips.tsx rename to components/HashResultModal/ModalWithCountdown.tsx index ae6fd285..e07008f8 100644 --- a/screens/Trading/components/positionResultTips.tsx +++ b/components/HashResultModal/ModalWithCountdown.tsx @@ -1,8 +1,32 @@ import React, { useState, useEffect } from "react"; -import { NearIcon, NearIconMini } from "../../MarginTrading/components/Icon"; -import { CloseIcon } from "../../../components/Icons/Icons"; +import { NearIcon, NearIconMini } from "../../screens/MarginTrading/components/Icon"; +import { CloseIcon } from "../Icons/Icons"; -const ModalWithCountdown = ({ show, onClose }) => { +interface PositionResultProps { + show: boolean; + onClose: () => void; + title?: string; + type?: "Long" | "Short"; + price?: string; + positionSize?: { + amount: string; + symbol: string; + usdValue: string; + }; +} + +const ModalWithCountdown = ({ + show, + onClose, + title = "Open Position", + type = "Long", + price = "$0.00", + positionSize = { + amount: "0", + symbol: "NEAR", + usdValue: "$0.00", + }, +}: PositionResultProps) => { const [isModalVisible, setIsModalVisible] = useState(false); const [countdown, setCountdown] = useState(10); const [progress, setProgress] = useState(100); @@ -62,25 +86,25 @@ const ModalWithCountdown = ({ show, onClose }) => {
- Open Position + {title}
- Long Near + {type} {positionSize.symbol}
Filled
Price - $3.34 + {price}
Position Size - 45.2435 NEAR - ($149.35) + {positionSize.amount} {positionSize.symbol} + ({positionSize.usdValue})
{renderProgressBar()}
diff --git a/components/HashResultModal/index.tsx b/components/HashResultModal/index.tsx new file mode 100644 index 00000000..662e2736 --- /dev/null +++ b/components/HashResultModal/index.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import ModalWithCountdown from "./ModalWithCountdown"; + +interface ShowPositionResultParams { + title?: string; + type?: "Long" | "Short"; + price?: string; + transactionHashes?: string; + positionSize?: { + amount: string; + symbol: string; + usdValue: string; + }; +} + +let container: HTMLDivElement | null = null; + +export const showPositionResult = (params: ShowPositionResultParams) => { + if (params.transactionHashes) { + const shownTxs = localStorage.getItem("shownTransactions") || "[]"; + const shownTxArray = JSON.parse(shownTxs); + + if (shownTxArray.includes(params.transactionHashes)) { + return; + } + + shownTxArray.push(params.transactionHashes); + if (shownTxArray.length > 100) { + shownTxArray.shift(); + } + localStorage.setItem("shownTransactions", JSON.stringify(shownTxArray)); + } + + if (!container) { + container = document.createElement("div"); + container.id = "position-result-container"; + document.body.appendChild(container); + } + + const handleClose = () => { + if (container) { + ReactDOM.unmountComponentAtNode(container); + container.remove(); + container = null; + } + }; + + ReactDOM.render(, container); +}; diff --git a/screens/Trading/index.tsx b/screens/Trading/index.tsx index fb6bc03f..2535774b 100644 --- a/screens/Trading/index.tsx +++ b/screens/Trading/index.tsx @@ -21,11 +21,13 @@ import { useMarginConfigToken } from "../../hooks/useMarginConfig"; import { setCategoryAssets1, setCategoryAssets2 } from "../../redux/marginTrading"; import { useMarginAccount } from "../../hooks/useMarginAccount"; import { useAccountId, usePortfolioAssets } from "../../hooks/hooks"; -import ModalWithCountdown from "./components/positionResultTips"; +import { useRouterQuery, getTransactionResult } from "../../utils/txhashContract"; +import { showPositionResult } from "../../components/HashResultModal"; init_env("dev"); const Trading = () => { + const { query } = useRouterQuery(); const accountId = useAccountId(); const { marginAccountList, parseTokenValue, getAssetDetails, getAssetById } = useMarginAccount(); const { categoryAssets1, categoryAssets2 } = useMarginConfigToken(); @@ -54,7 +56,21 @@ const Trading = () => { // useEffect(() => { if (router.query.transactionHashes) { - setShowModal(true); + // + showPositionResult({ + title: "Open Position", + type: "Long", // optional,"Long" 或 "Short" + price: "100", // optional, + transactionHashes: Array.isArray(router.query.transactionHashes) + ? router.query.transactionHashes[0] + : (router.query.transactionHashes as string), + positionSize: { + // optional, + amount: "10", + symbol: "NEAR", + usdValue: "1000", + }, + }); } }, [router]); @@ -139,6 +155,30 @@ const Trading = () => { }, 200); }; + useEffect(() => { + handleTransactions(); + }, [query?.transactionHashes]); + + const handleTransactions = async () => { + if (query?.transactionHashes) { + try { + const txhash = Array.isArray(query?.transactionHashes) + ? query?.transactionHashes + : (query?.transactionHashes as string).split(","); + const results = await Promise.all( + txhash.map(async (txHash: string) => { + const result: any = await getTransactionResult(txHash); + console.log(result); + return { txHash }; + }), + ); + console.log(results, ";rrrr"); + } catch (error) { + console.error("Error processing transactions:", error); + } + } + }; + // return ( @@ -270,8 +310,6 @@ const Trading = () => { {accountId && } - - ); }; diff --git a/utils/near.ts b/utils/near.ts new file mode 100644 index 00000000..abf3b8b5 --- /dev/null +++ b/utils/near.ts @@ -0,0 +1,32 @@ +import { keyStores, utils, connect, Contract } from "near-api-js"; +import { Transaction as WSTransaction } from "@near-wallet-selector/core"; +import BN from "bn.js"; +import getConfig from "./config"; + +const config = getConfig(); + +export const ONE_YOCTO_NEAR = "0.000000000000000000000001"; +export const LP_STORAGE_AMOUNT = "0.01"; + +export const getGas = (gas?: string) => (gas ? new BN(gas) : new BN("100000000000000")); + +export async function getNear() { + const keyStore = new keyStores.BrowserLocalStorageKeyStore(); + const nearConnection = await connect({ keyStore, ...config }); + return nearConnection; +} +export async function getKeypomNear() { + const keyStore = new keyStores.BrowserLocalStorageKeyStore(undefined, "keypom:"); + const nearConnection = await connect({ keyStore, ...config }); + return nearConnection; +} +export async function getAccount() { + const nearConnection = await getNear(); + const account = await nearConnection.account(window.accountId || ""); + return account; +} +export async function getKeypomAccount() { + const nearConnection = await getKeypomNear(); + const account = await nearConnection.account(window.accountId || ""); + return account; +} diff --git a/utils/txhashContract.ts b/utils/txhashContract.ts new file mode 100644 index 00000000..03e97fdd --- /dev/null +++ b/utils/txhashContract.ts @@ -0,0 +1,75 @@ +import { JsonRpcProvider } from "near-api-js/lib/providers"; +import { useRouter } from "next/router"; +import { useMemo, useCallback } from "react"; +import { base58 } from "ethers/lib/utils"; +import { getNear } from "./near"; +import { getAccount as getAccountWallet } from "./wallet-selector-compat"; + +export const handleTransactionResult = (outcome) => { + if (Array.isArray(outcome)) { + const errorMessage = outcome.find((o) => o.status?.Failure?.ActionError)?.status.Failure + .ActionError as string; + if (errorMessage) { + throw new Error(JSON.stringify(errorMessage)); + } + return outcome; + } else { + const errorMessage = outcome.status?.Failure?.ActionError as string; + if (errorMessage) { + throw new Error(JSON.stringify(errorMessage)); + } + if (typeof outcome === "object") return outcome; + } +}; + +export const getTransactionResult = async (txhash: string) => { + const near = await getNear(); + const txHashArray = base58.decode(txhash); + const result = await near.connection.provider.txStatusReceipts(txHashArray, "unnused"); + return handleTransactionResult(result); +}; + +// export const checkTransaction = async (txHash: string) => { +// const account = await getAccountWallet(); +// const { accountId } = account; +// const near = await getNear(); +// return (near.connection.provider as JsonRpcProvider).sendJsonRpc("EXPERIMENTAL_tx_status", [ +// txHash, +// accountId, +// ]); +// }; + +// export const checkTransactionStatus = async (txHash: string) => { +// const near = await getNear(); +// const account = await getAccountWallet(); +// const { accountId } = account; +// return near.connection.provider.txStatus(txHash, accountId); +// }; + +export function useRouterQuery() { + const router = useRouter(); + const query = useMemo(() => router.query, [router.query]); + + const replaceQuery = useCallback( + (q: Record, options?: { shallow?: boolean }) => { + const newQuery = { ...router.query }; + Object.entries(q).forEach(([key, value]) => { + if (value === undefined || value === null) { + delete newQuery[key]; + } else { + newQuery[key] = value; + } + }); + router.replace( + { + query: newQuery, + }, + undefined, + { shallow: options?.shallow ?? true }, + ); + }, + [router], + ); + + return { query, replaceQuery }; +} diff --git a/utils/useHooks.ts b/utils/useHooks.ts new file mode 100644 index 00000000..00dbe9e9 --- /dev/null +++ b/utils/useHooks.ts @@ -0,0 +1,501 @@ +"use client"; + +import { + type DependencyList, + type EffectCallback, + useCallback, + useEffect, + useMemo, + useState, + useRef, + useLayoutEffect, +} from "react"; +import { debounce, type DebounceSettings } from "lodash"; +import { useRouter } from "next/router"; + +export function useClient() { + const [isClient, setIsClient] = useState(false); + useEffect(() => { + setIsClient(true); + }, []); + + return { isClient }; +} + +type DebounceOptions = number | ({ wait: number } & Partial); +type RequestOptions = { + refreshDeps?: React.DependencyList; + before?: () => boolean | undefined; + manual?: boolean; + onSuccess?: (res: T) => void; + onError?: (err: Error) => void; + debounceOptions?: DebounceOptions; + retryCount?: number; + retryInterval?: number; + pollingInterval?: number; +}; + +export function useRequest(request: () => Promise, options?: RequestOptions) { + const [data, setData] = useState(undefined); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(undefined); + + const { + refreshDeps = [], + before, + manual, + onSuccess, + onError, + debounceOptions, + retryCount = 0, + retryInterval = 0, + pollingInterval, + } = useMemo(() => options || {}, [options]); + + const pollingTimer = useRef(null); + const clearPolling = useCallback(() => { + if (pollingTimer.current) { + clearTimeout(pollingTimer.current); + pollingTimer.current = null; + } + }, []); + + const run = useCallback(async () => { + clearPolling(); + let attempts = 0; + + const executeRequest = async () => { + try { + setLoading(true); + const res = await request(); + setData(res); + onSuccess?.(res); + return true; + } catch (err) { + console.error(err); + setError(err instanceof Error ? err : new Error(String(err))); + onError?.(err instanceof Error ? err : new Error(String(err))); + return false; + } finally { + setLoading(false); + } + }; + + const attemptRequest = async () => { + const success = await executeRequest(); + if (!success && attempts < retryCount) { + attempts += 1; + setTimeout(attemptRequest, retryInterval); + } + }; + + if (before && !before()) return; + attemptRequest(); + + if (pollingInterval) { + pollingTimer.current = setTimeout(run, pollingInterval); + } + }, [ + clearPolling, + before, + pollingInterval, + request, + onSuccess, + onError, + retryCount, + retryInterval, + ]); + + useDebouncedEffect( + () => { + if (manual) return; + if (before && !before()) return; + clearPolling(); + run(); + return () => clearPolling(); + }, + [...refreshDeps, clearPolling], + debounceOptions, + ); + + return { + run, + data, + setData, + loading, + setLoading, + error, + setError, + clearPolling, + }; +} + +export function useDebouncedEffect( + effect: EffectCallback, + deps: React.DependencyList, + debounceOptions?: DebounceOptions, +) { + useEffect(() => { + const options = + typeof debounceOptions === "number" ? { wait: debounceOptions } : debounceOptions; + const debouncedEffect = debounce( + () => { + const cleanupFn = effect(); + if (cleanupFn) { + debouncedEffect.flush = cleanupFn as any; + } + }, + options?.wait, + options, + ); + + debouncedEffect(); + + return () => { + debouncedEffect.cancel(); + if (debouncedEffect.flush) { + debouncedEffect.flush(); + } + }; + }, [...deps]); +} + +export function useDebouncedMemo( + factory: () => T, + deps: DependencyList, + debounceOptions?: DebounceOptions, +) { + const options = useMemo(() => { + return typeof debounceOptions === "number" ? { wait: debounceOptions } : debounceOptions; + }, [debounceOptions]); + + const [debouncedValue, setDebouncedValue] = useState(() => factory()); + + const debouncedUpdate = useMemo( + () => + debounce( + () => { + setDebouncedValue(factory()); + }, + options?.wait || 300, + options, + ), + [options, factory], + ); + + useEffect(() => { + debouncedUpdate(); + + return () => { + debouncedUpdate.cancel(); + }; + }, [...deps, debouncedUpdate]); + + return debouncedValue; +} + +export function useAsyncMemo( + factory: () => Promise | undefined | null, + deps: DependencyList, + initial?: T, +) { + const [val, setVal] = useState(initial); + useDebouncedEffect( + () => { + let cancel = false; + const promise = factory(); + if (promise === undefined || promise === null) return; + promise.then((v) => { + if (!cancel) { + setVal(v); + } + }); + return () => { + cancel = true; + }; + }, + deps, + 300, + ); + return val; +} + +export function useAutoResetState(defaultValue: T, wait?: number) { + const [state, set] = useState(defaultValue); + const setState = (value: T) => { + set(value); + setTimeout(() => { + set(defaultValue); + }, wait || 1000); + }; + return [state, setState] as const; +} + +export function useInterval(callback: () => void, delay: number) { + const savedCallback = useRef<() => void>(); + const intervalId = useRef(null); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + function tick() { + savedCallback.current?.(); + } + + if (delay !== undefined && delay !== null) { + intervalId.current = window.setInterval(tick, delay); + return () => { + if (intervalId.current) { + window.clearInterval(intervalId.current); + } + }; + } + }, [delay]); + + const cancel = () => { + if (intervalId.current !== null) { + window.clearInterval(intervalId.current); + intervalId.current = null; + } + }; + + return cancel; +} + +export interface UseInfiniteScrollProps { + /** + * Whether the infinite scroll is enabled. + * @default true + */ + isEnabled?: boolean; + /** + * Whether there are more items to load, the observer will disconnect when there are no more items to load. + */ + hasMore?: boolean; + /** + * The distance in pixels before the end of the items that will trigger a call to load more. + * @default 250 + */ + distance?: number; + /** + * Use loader element for the scroll detection. + */ + shouldUseLoader?: boolean; + /** + * Callback to load more items. + */ + onLoadMore?: () => void; +} + +export function useInfiniteScroll(props: UseInfiniteScrollProps = {}) { + const { hasMore, distance = 250, isEnabled = true, shouldUseLoader = true, onLoadMore } = props; + + const scrollContainerRef = useRef(null); + const loaderRef = useRef(null); + + const previousY = useRef(); + const previousRatio = useRef(0); + + useLayoutEffect(() => { + const scrollContainerNode = scrollContainerRef.current; + + if (!isEnabled || !scrollContainerNode || !hasMore) return; + + if (shouldUseLoader) { + const loaderNode = loaderRef.current; + + if (!loaderNode) return; + + const options = { + root: scrollContainerNode, + rootMargin: `0px 0px ${distance}px 0px`, + }; + + const listener = (entries: IntersectionObserverEntry[]) => { + entries.forEach(({ isIntersecting, intersectionRatio, boundingClientRect = {} }) => { + const y = boundingClientRect.y || 0; + + if ( + isIntersecting && + intersectionRatio >= previousRatio.current && + (!previousY.current || y < previousY.current) + ) { + onLoadMore?.(); + } + previousY.current = y; + previousRatio.current = intersectionRatio; + }); + }; + + const observer = new IntersectionObserver(listener, options); + + observer.observe(loaderNode); + + return () => observer.disconnect(); + } else { + const debouncedOnLoadMore = onLoadMore ? debounce(onLoadMore, 200) : undefined; + + const checkIfNearBottom = () => { + if ( + scrollContainerNode.scrollHeight - scrollContainerNode.scrollTop <= + scrollContainerNode.clientHeight + distance + ) { + debouncedOnLoadMore?.(); + } + }; + + scrollContainerNode.addEventListener("scroll", checkIfNearBottom); + + return () => { + scrollContainerNode.removeEventListener("scroll", checkIfNearBottom); + }; + } + }, [hasMore, distance, isEnabled, onLoadMore, shouldUseLoader]); + + return [loaderRef, scrollContainerRef]; +} + +export function useCopyClipboard() { + const [copied, setCopied] = useState(false); + + const copy = useCallback( + (text: string) => { + navigator.clipboard.writeText(text).then(() => { + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 2000); + }); + }, + [setCopied], + ); + + return { copied, copy }; +} + +export function useRouterQuery() { + const router = useRouter(); + const query = useMemo(() => router.query, [router.query]); + + const replaceQuery = useCallback( + (q: Record, options?: { shallow?: boolean }) => { + const newQuery = { ...router.query }; + Object.entries(q).forEach(([key, value]) => { + if (value === undefined || value === null) { + delete newQuery[key]; + } else { + newQuery[key] = value; + } + }); + router.replace( + { + query: newQuery, + }, + undefined, + { shallow: options?.shallow ?? true }, + ); + }, + [router], + ); + + return { query, replaceQuery }; +} + +export function safeJSONParse(str: string): T | undefined { + try { + return JSON.parse(str) as T; + } catch (e) { + console.error("safeJSONParse", e); + return undefined; + } +} + +export function safeJSONStringify(obj: any): string | undefined { + try { + return JSON.stringify(obj); + } catch (e) { + console.error("safeJSONStringify", e); + return undefined; + } +} + +export function storageStore(namespace?: string, options?: { storage?: Storage }) { + if (typeof window === "undefined") return; + const namespaceNew = namespace || "default"; + const storage = options?.storage || window?.localStorage; + const namespaceKey = (key: string) => { + return `${namespaceNew}:${key}`; + }; + return { + set(key: string, value: any) { + const val = safeJSONStringify(value); + val ? storage.setItem(namespaceKey(key), val) : storage.removeItem(namespaceKey(key)); + }, + get(key: string) { + const val = storage.getItem(namespaceKey(key)); + return val ? safeJSONParse(val) : undefined; + }, + remove(key: string) { + storage.removeItem(namespaceKey(key)); + }, + clearAll: function clearAll() { + for (const key in storage) { + if (key.startsWith(`${namespace}:`)) { + storage.removeItem(key); + } + } + }, + }; +} + +export function useStorageState( + key: string, + defaultValue: T, + options?: { storage?: Storage; namespace?: string }, +) { + const { storage, namespace = "DELTA_DEFAULT" } = options || {}; + const storageAPI = storageStore(namespace, { storage }); + const [state, _setState] = useState(() => { + const storedValue = storageAPI?.get(key) as T; + return storedValue !== undefined ? storedValue : defaultValue; + }); + + const setState: typeof _setState = (value) => { + _setState(value); + const val = typeof value === "function" ? (value as any)(state) : value; + if (val === undefined) { + storageAPI?.remove(key); + } else { + storageAPI?.set(key, val); + } + }; + + return [state, setState] as const; +} + +export function useScrollToBottom(callback?: () => void) { + const elementRef = useRef(null); + + useEffect(() => { + const handleScroll = () => { + if (!elementRef.current) return; + + const { scrollTop, scrollHeight, clientHeight } = elementRef.current; + + if (scrollTop + clientHeight >= scrollHeight) { + if (callback) callback(); + } + }; + + const element = elementRef.current; + element?.addEventListener("scroll", handleScroll); + + return () => { + element?.removeEventListener("scroll", handleScroll); + }; + }, [callback]); + + return elementRef; +} From 4266cfe9b9d675f59cebfda42b452f787f305665 Mon Sep 17 00:00:00 2001 From: deep-path <476044723@qq.com> Date: Mon, 18 Nov 2024 10:19:12 +0800 Subject: [PATCH 2/4] fix: init number --- screens/Trading/components/TradingOperate.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/screens/Trading/components/TradingOperate.tsx b/screens/Trading/components/TradingOperate.tsx index 36f884c1..465d1ce9 100644 --- a/screens/Trading/components/TradingOperate.tsx +++ b/screens/Trading/components/TradingOperate.tsx @@ -48,8 +48,8 @@ const TradingOperate = () => { const [isDisabled, setIsDisabled] = useState(false); // - const [longInput, setLongInput] = useState(0); - const [shortInput, setShortInput] = useState(0); + const [longInput, setLongInput] = useState(""); + const [shortInput, setShortInput] = useState(""); const [longOutput, setLongOutput] = useState(0); const [shortOutput, setShortOutput] = useState(0); @@ -79,12 +79,12 @@ const TradingOperate = () => { setLiqPrice(0); setRangeMount(1); if (tabString == "long") { - setShortInput(0); + setShortInput(""); setShortInputUsd(0); setShortOutput(0); setShortOutputUsd(0); } else { - setLongInput(0); + setLongInput(""); setLongInputUsd(0); setLongOutput(0); setLongOutputUsd(0); @@ -252,9 +252,9 @@ const TradingOperate = () => { let liqPriceX = 0; if (rangeMount > 1) { if (activeTab == "long") { - const k1 = longInput * rangeMount * (getAssetPrice(ReduxcategoryAssets2) as any); + const k1 = Number(longInput) * rangeMount * (getAssetPrice(ReduxcategoryAssets2) as any); const k2 = 1 - marginConfigTokens.min_safty_buffer / 10000; - liqPriceX = (k1 / k2 - longInput) / longOutput; + liqPriceX = (k1 / k2 - Number(longInput)) / longOutput; } else { liqPriceX = (((Number(shortInput) + From 1d76006ace82178ce2806bb4ac9db2afb460bbf3 Mon Sep 17 00:00:00 2001 From: deep-path <476044723@qq.com> Date: Mon, 18 Nov 2024 16:25:39 +0800 Subject: [PATCH 3/4] fix: margin trading bug fix --- .../HashResultModal/ModalWithCountdown.tsx | 14 +++- components/HashResultModal/index.tsx | 11 ++- interfaces/margin.ts | 1 + redux/marginConfigSlice.ts | 2 + redux/marginConfigState.ts | 2 + screens/Trading/components/TradingOperate.tsx | 16 ++++ screens/Trading/index.tsx | 77 +++++++++++++------ utils/txhashContract.ts | 6 ++ 8 files changed, 98 insertions(+), 31 deletions(-) diff --git a/components/HashResultModal/ModalWithCountdown.tsx b/components/HashResultModal/ModalWithCountdown.tsx index e07008f8..7d534338 100644 --- a/components/HashResultModal/ModalWithCountdown.tsx +++ b/components/HashResultModal/ModalWithCountdown.tsx @@ -71,7 +71,11 @@ const ModalWithCountdown = ({ const renderProgressBar = () => (
); @@ -88,7 +92,9 @@ const ModalWithCountdown = ({ {title}
{type} {positionSize.symbol} @@ -104,7 +110,9 @@ const ModalWithCountdown = ({ Position Size {positionSize.amount} {positionSize.symbol} - ({positionSize.usdValue}) + + ({(Number(positionSize.amount) * Number(price)).toFixed(6)}) +
{renderProgressBar()}
diff --git a/components/HashResultModal/index.tsx b/components/HashResultModal/index.tsx index 662e2736..a3b9f3f9 100644 --- a/components/HashResultModal/index.tsx +++ b/components/HashResultModal/index.tsx @@ -1,5 +1,5 @@ import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import ModalWithCountdown from "./ModalWithCountdown"; interface ShowPositionResultParams { @@ -15,6 +15,7 @@ interface ShowPositionResultParams { } let container: HTMLDivElement | null = null; +let root: ReturnType | null = null; export const showPositionResult = (params: ShowPositionResultParams) => { if (params.transactionHashes) { @@ -36,15 +37,19 @@ export const showPositionResult = (params: ShowPositionResultParams) => { container = document.createElement("div"); container.id = "position-result-container"; document.body.appendChild(container); + root = createRoot(container); } const handleClose = () => { + if (root) { + root.unmount(); + } if (container) { - ReactDOM.unmountComponentAtNode(container); container.remove(); container = null; + root = null; } }; - ReactDOM.render(, container); + root?.render(); }; diff --git a/interfaces/margin.ts b/interfaces/margin.ts index adaeef51..daf80479 100644 --- a/interfaces/margin.ts +++ b/interfaces/margin.ts @@ -9,6 +9,7 @@ export interface IMarginConfig { open_position_fee_rate: number; registered_dexes: { [dexId: string]: number }; registered_tokens: { [tokenId: string]: number }; + max_active_user_margin_position: number; } export interface IMarginTradingPositionView { diff --git a/redux/marginConfigSlice.ts b/redux/marginConfigSlice.ts index 609c9d65..a9325d6f 100644 --- a/redux/marginConfigSlice.ts +++ b/redux/marginConfigSlice.ts @@ -33,6 +33,7 @@ export const marginConfigSlice = createSlice({ open_position_fee_rate, registered_dexes, registered_tokens, + max_active_user_margin_position, } = action.payload; state.max_leverage_rate = max_leverage_rate; state.pending_debt_scale = pending_debt_scale; @@ -42,6 +43,7 @@ export const marginConfigSlice = createSlice({ state.open_position_fee_rate = open_position_fee_rate; state.registered_dexes = registered_dexes; state.registered_tokens = registered_tokens; + state.max_active_user_margin_position = max_active_user_margin_position; }); }, }); diff --git a/redux/marginConfigState.ts b/redux/marginConfigState.ts index 3775c3e0..e2300dcc 100644 --- a/redux/marginConfigState.ts +++ b/redux/marginConfigState.ts @@ -10,6 +10,7 @@ export interface IMarginConfigState { registered_tokens: Record; status: Status; fetchedAt: string | undefined; + max_active_user_margin_position: number; } export const initialState: IMarginConfigState = { @@ -23,4 +24,5 @@ export const initialState: IMarginConfigState = { registered_tokens: {}, status: undefined, fetchedAt: undefined, + max_active_user_margin_position: 0, }; diff --git a/screens/Trading/components/TradingOperate.tsx b/screens/Trading/components/TradingOperate.tsx index 465d1ce9..e8a7eb93 100644 --- a/screens/Trading/components/TradingOperate.tsx +++ b/screens/Trading/components/TradingOperate.tsx @@ -434,6 +434,14 @@ const TradingOperate = () => {
+
+
Minimum received
+
+ {Number(toInternationalCurrencySystem_number(longOutput)) * + (1 - slippageTolerance / 100)}{" "} + NEAR +
+
Position Size
@@ -569,6 +577,14 @@ const TradingOperate = () => {
+
+
Minimum received
+
+ {Number(toInternationalCurrencySystem_number(longOutput)) * + (1 - slippageTolerance / 100)}{" "} + NEAR +
+
Position Size
diff --git a/screens/Trading/index.tsx b/screens/Trading/index.tsx index 2535774b..018b9f02 100644 --- a/screens/Trading/index.tsx +++ b/screens/Trading/index.tsx @@ -21,12 +21,13 @@ import { useMarginConfigToken } from "../../hooks/useMarginConfig"; import { setCategoryAssets1, setCategoryAssets2 } from "../../redux/marginTrading"; import { useMarginAccount } from "../../hooks/useMarginAccount"; import { useAccountId, usePortfolioAssets } from "../../hooks/hooks"; -import { useRouterQuery, getTransactionResult } from "../../utils/txhashContract"; +import { useRouterQuery, getTransactionResult, parsedArgs } from "../../utils/txhashContract"; import { showPositionResult } from "../../components/HashResultModal"; init_env("dev"); const Trading = () => { + const marginConfig = useAppSelector(getMarginConfig); const { query } = useRouterQuery(); const accountId = useAccountId(); const { marginAccountList, parseTokenValue, getAssetDetails, getAssetById } = useMarginAccount(); @@ -53,27 +54,6 @@ const Trading = () => { }; let timer; - // - useEffect(() => { - if (router.query.transactionHashes) { - // - showPositionResult({ - title: "Open Position", - type: "Long", // optional,"Long" 或 "Short" - price: "100", // optional, - transactionHashes: Array.isArray(router.query.transactionHashes) - ? router.query.transactionHashes[0] - : (router.query.transactionHashes as string), - positionSize: { - // optional, - amount: "10", - symbol: "NEAR", - usdValue: "1000", - }, - }); - } - }, [router]); - // computed currentTokenCate1 dropdown useEffect(() => { if (id) { @@ -168,11 +148,58 @@ const Trading = () => { const results = await Promise.all( txhash.map(async (txHash: string) => { const result: any = await getTransactionResult(txHash); - console.log(result); - return { txHash }; + const hasStorageDeposit = result.transaction.actions.some( + (action: any) => action?.FunctionCall?.method_name === "margin_execute_with_pyth", + ); + return { txHash, result, hasStorageDeposit }; }), ); - console.log(results, ";rrrr"); + results.forEach(({ txHash, result, hasStorageDeposit }: any) => { + if (hasStorageDeposit) { + // handleClaimTokenMobile({ level, count }); + console.log(result); + + const args = parsedArgs(result?.transaction?.actions?.[0]?.FunctionCall?.args || ""); + const { actions } = JSON.parse(args || ""); + + // const ft_on_transfer_id = result?.receipts?.findIndex((r: any) => + // r?.receipt?.Action?.actions?.some( + // (a: any) => a?.FunctionCall?.method_name === "margin_execute_with_pyth", + // ), + // ); + + // const ft_on_transfer_logs = + // result?.receipts_outcome?.[ft_on_transfer_id]?.outcome?.logs || ""; + // const ft_on_transfer_log = ft_on_transfer_logs?.[ft_on_transfer_logs?.length - 1]; + // const idx = ft_on_transfer_log?.indexOf("{"); + + // const parsed_ft_on_transfer_log = JSON.parse(ft_on_transfer_log.slice(idx) || ""); + const isLong = actions[0]?.OpenPosition?.token_p_id == "wrap.testnet"; + console.log(actions); + console.log(marginConfig); + showPositionResult({ + title: "Open Position", + type: isLong ? "Long" : "Short", + price: (isLong + ? Number(shrinkToken(actions[0]?.OpenPosition?.token_d_amount, 18)) / + Number(shrinkToken(actions[0]?.OpenPosition?.min_token_p_amount, 24)) + : Number(shrinkToken(actions[0]?.OpenPosition?.min_token_p_amount, 18)) / + Number(shrinkToken(actions[0]?.OpenPosition?.token_d_amount, 24)) + ).toString(), + transactionHashes: Array.isArray(router.query.transactionHashes) + ? router.query.transactionHashes[0] + : (router.query.transactionHashes as string), + positionSize: { + // optional, + amount: isLong + ? shrinkToken(actions[0]?.OpenPosition?.min_token_p_amount, 24, 6) + : shrinkToken(actions[0]?.OpenPosition?.token_d_amount, 24, 6), + symbol: "NEAR", + usdValue: "1000", + }, + }); + } + }); } catch (error) { console.error("Error processing transactions:", error); } diff --git a/utils/txhashContract.ts b/utils/txhashContract.ts index 03e97fdd..5e394bb2 100644 --- a/utils/txhashContract.ts +++ b/utils/txhashContract.ts @@ -73,3 +73,9 @@ export function useRouterQuery() { return { query, replaceQuery }; } + +export const parsedArgs = (res: any) => { + const buff = Buffer.from(res, "base64"); + const parsedData = buff.toString("ascii"); + return parsedData; +}; From 6c77ee6c72d38cefd1c129875e787aad7069427f Mon Sep 17 00:00:00 2001 From: deep-path <476044723@qq.com> Date: Mon, 18 Nov 2024 17:27:27 +0800 Subject: [PATCH 4/4] fix:margin trading --- interfaces/margin.ts | 2 +- redux/marginConfigSlice.ts | 4 +- redux/marginConfigState.ts | 4 +- .../components/ChangeCollateralMobile.tsx | 6 +-- screens/Trading/components/Table.tsx | 6 +-- screens/Trading/components/TradingOperate.tsx | 44 ++++++++++++++++--- 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/interfaces/margin.ts b/interfaces/margin.ts index daf80479..9be68997 100644 --- a/interfaces/margin.ts +++ b/interfaces/margin.ts @@ -4,7 +4,7 @@ export interface IMarginConfig { max_leverage_rate: number; pending_debt_scale: number; max_slippage_rate: number; - min_safty_buffer: number; + min_safety_buffer: number; margin_debt_discount_rate: number; open_position_fee_rate: number; registered_dexes: { [dexId: string]: number }; diff --git a/redux/marginConfigSlice.ts b/redux/marginConfigSlice.ts index a9325d6f..e901deac 100644 --- a/redux/marginConfigSlice.ts +++ b/redux/marginConfigSlice.ts @@ -28,7 +28,7 @@ export const marginConfigSlice = createSlice({ max_leverage_rate, pending_debt_scale, max_slippage_rate, - min_safty_buffer, + min_safety_buffer, margin_debt_discount_rate, open_position_fee_rate, registered_dexes, @@ -38,7 +38,7 @@ export const marginConfigSlice = createSlice({ state.max_leverage_rate = max_leverage_rate; state.pending_debt_scale = pending_debt_scale; state.max_slippage_rate = max_slippage_rate; - state.min_safty_buffer = min_safty_buffer; + state.min_safety_buffer = min_safety_buffer; state.margin_debt_discount_rate = margin_debt_discount_rate; state.open_position_fee_rate = open_position_fee_rate; state.registered_dexes = registered_dexes; diff --git a/redux/marginConfigState.ts b/redux/marginConfigState.ts index e2300dcc..bb1f85bf 100644 --- a/redux/marginConfigState.ts +++ b/redux/marginConfigState.ts @@ -3,7 +3,7 @@ export interface IMarginConfigState { max_leverage_rate: number; pending_debt_scale: number; max_slippage_rate: number; - min_safty_buffer: number; + min_safety_buffer: number; margin_debt_discount_rate: number; open_position_fee_rate: number; registered_dexes: Record; @@ -17,7 +17,7 @@ export const initialState: IMarginConfigState = { max_leverage_rate: 0, pending_debt_scale: 0, max_slippage_rate: 0, - min_safty_buffer: 0, + min_safety_buffer: 0, margin_debt_discount_rate: 0, open_position_fee_rate: 0, registered_dexes: {}, diff --git a/screens/Trading/components/ChangeCollateralMobile.tsx b/screens/Trading/components/ChangeCollateralMobile.tsx index 7130c9f4..fe6c9288 100644 --- a/screens/Trading/components/ChangeCollateralMobile.tsx +++ b/screens/Trading/components/ChangeCollateralMobile.tsx @@ -119,13 +119,13 @@ const ChangeCollateralMobile = ({ open, onClose, rowData, collateralTotal }) => (rowData.data.debt_cap * ((debt_assets_d?.unit_acc_hp_interest ?? 0) - rowData.data.uahpi_at_open)) / 10 ** 18; - const decrease_cap = total_cap * (marginConfigTokens.min_safty_buffer / 10000); - const denominator = sizeValueLong * (1 - marginConfigTokens.min_safty_buffer / 10000); + const decrease_cap = total_cap * (marginConfigTokens.min_safety_buffer / 10000); + const denominator = sizeValueLong * (1 - marginConfigTokens.min_safety_buffer / 10000); const LiqPrice = denominator !== 0 ? (total_debt + total_hp_fee + - (priceC * leverageC * marginConfigTokens.min_safty_buffer) / 10000 - + (priceC * leverageC * marginConfigTokens.min_safety_buffer) / 10000 - priceC * leverageC) / denominator : 0; diff --git a/screens/Trading/components/Table.tsx b/screens/Trading/components/Table.tsx index b79b77b8..66808b21 100644 --- a/screens/Trading/components/Table.tsx +++ b/screens/Trading/components/Table.tsx @@ -234,14 +234,14 @@ const PositionRow = ({ const total_debt = leverageD * priceD; const total_hp_fee = (item.debt_cap * ((debt_assets_d?.unit_acc_hp_interest ?? 0) - item.uahpi_at_open)) / 10 ** 18; - // const decrease_cap = total_cap * (marginConfigTokens.min_safty_buffer / 10000); - const denominator = sizeValueLong * (1 - marginConfigTokens.min_safty_buffer / 10000); + // const decrease_cap = total_cap * (marginConfigTokens.min_safety_buffer / 10000); + const denominator = sizeValueLong * (1 - marginConfigTokens.min_safety_buffer / 10000); // total_cap - decrease_cap === total_debt + total_hp_fee; const LiqPrice = denominator !== 0 ? (total_debt + total_hp_fee + - (priceC * leverageC * marginConfigTokens.min_safty_buffer) / 10000 - + (priceC * leverageC * marginConfigTokens.min_safety_buffer) / 10000 - priceC * leverageC) / denominator : 0; diff --git a/screens/Trading/components/TradingOperate.tsx b/screens/Trading/components/TradingOperate.tsx index e8a7eb93..0df51f0e 100644 --- a/screens/Trading/components/TradingOperate.tsx +++ b/screens/Trading/components/TradingOperate.tsx @@ -253,7 +253,7 @@ const TradingOperate = () => { if (rangeMount > 1) { if (activeTab == "long") { const k1 = Number(longInput) * rangeMount * (getAssetPrice(ReduxcategoryAssets2) as any); - const k2 = 1 - marginConfigTokens.min_safty_buffer / 10000; + const k2 = 1 - marginConfigTokens.min_safety_buffer / 10000; liqPriceX = (k1 / k2 - Number(longInput)) / longOutput; } else { liqPriceX = @@ -261,15 +261,41 @@ const TradingOperate = () => { Number(shrinkToken(estimateData?.min_amount_out, decimalsC))) as any) * rangeMount * (getAssetPrice(ReduxcategoryAssets2) as any) * - (1 - marginConfigTokens.min_safty_buffer / 10000)) / + (1 - marginConfigTokens.min_safety_buffer / 10000)) / shortOutput; } } + // const total_debt = + // (shrinkToken(ReduxcategoryAssets2.margin_debt.balance, decimalsD) as any) * priceD; + // const total_hp_fee = + // (ReduxcategoryAssets2.margin_debt.debt_cap * (unit_acc_hp_interest - uahpi_at_open)) / + // 10 ** 18; + + // const numerator = + // total_debt + + // total_hp_fee - + // (shrinkToken(ReduxcategoryAssets1.margin_debt.balance, decimalsD) as any) * + // priceC * + // (1 - marginConfigTokens.min_safety_buffer / 10000); + + // const denominator = + // estimateData?.min_amount_out * (1 - marginConfigTokens.min_safety_buffer / 10000); + + console.log(marginConfigTokens, ReduxcategoryAssets2, ReduxcategoryAssets1); + setLiqPrice(liqPriceX); } }, [longOutput, shortOutput]); + const Fee = useMemo(() => { + console.log(ReduxcategoryAssets1, ReduxcategoryAssets2); + return { + fee: (Number(longInput || shortInput) * config.open_position_fee_rate) / 10000, + price: getAssetPrice(ReduxcategoryAssets1), + }; + }, [longInput, shortInput, ReduxcategoryAssets1]); + function getAssetPrice(categoryId) { return categoryId ? assets.data[categoryId["token_id"]].price?.usd : 0; } @@ -458,9 +484,11 @@ const TradingOperate = () => {
Fee
-

0.26

+

{Fee.fee}

NEAR - ($0.89) + + (${Fee.fee * (Fee.price || 0)}) +
@@ -580,7 +608,7 @@ const TradingOperate = () => {
Minimum received
- {Number(toInternationalCurrencySystem_number(longOutput)) * + {Number(toInternationalCurrencySystem_number(shortOutput)) * (1 - slippageTolerance / 100)}{" "} NEAR
@@ -601,9 +629,11 @@ const TradingOperate = () => {
Fee
-

0.26

+

{Fee.fee}

NEAR - ($0.89) + + (${Fee.fee * (Fee.price || 0)}) +