From 1f930600b19df7ee3a95e120d1bdc2c96f5cbeba Mon Sep 17 00:00:00 2001 From: Venrable18 Date: Thu, 16 Oct 2025 11:57:55 +0100 Subject: [PATCH] made changes to the contract, contract-test-file, lib, use-stacks-hooks- and component folder --- .vscode/settings.json | 3 +- contracts/amm.clar | 10 + frontend/components/pool-exists-checker.tsx | 202 ++++++++++++++++++++ frontend/hooks/use-stacks.ts | 125 ++++++++++++ frontend/lib/amm.ts | 52 +++++ tests/amm.test.ts | 53 +++++ 6 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 frontend/components/pool-exists-checker.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 3062519..97c791a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { - "files.eol": "\n" + "files.eol": "\n", + "wake.compiler.solc.remappings": [] } diff --git a/contracts/amm.clar b/contracts/amm.clar index cd74ce7..01ee020 100644 --- a/contracts/amm.clar +++ b/contracts/amm.clar @@ -321,6 +321,16 @@ ) ) + + +;; pool-exists +;; Given a pool ID, returns whether the pool exists +(define-read-only (pool-exists (pool-id (buff 20))) + (ok (is-some (map-get? pools pool-id))) +) + + + ;; get-pool-data ;; Given a pool ID, returns the current state of the pool from the mapping (define-read-only (get-pool-data (pool-id (buff 20))) diff --git a/frontend/components/pool-exists-checker.tsx b/frontend/components/pool-exists-checker.tsx new file mode 100644 index 0000000..cc81b49 --- /dev/null +++ b/frontend/components/pool-exists-checker.tsx @@ -0,0 +1,202 @@ +"use client"; + +import { usePoolExists, usePoolExistsByTokens } from "@/hooks/use-stacks"; +import { useState } from "react"; + +interface PoolExistsCheckerProps { + className?: string; +} + +export function PoolExistsChecker({ className = "" }: PoolExistsCheckerProps) { + const [poolId, setPoolId] = useState(""); + const [token0, setToken0] = useState(""); + const [token1, setToken1] = useState(""); + const [fee, setFee] = useState(500); + const [checkMethod, setCheckMethod] = useState<"poolId" | "tokens">("tokens"); + + const poolExistsById = usePoolExists(checkMethod === "poolId" ? poolId : null); + const poolExistsByTokens = usePoolExistsByTokens( + checkMethod === "tokens" ? token0 : null, + checkMethod === "tokens" ? token1 : null, + checkMethod === "tokens" ? fee : null + ); + + const currentResult = checkMethod === "poolId" ? poolExistsById : poolExistsByTokens; + + return ( +
+

Pool Existence Checker

+ + {/* Method Selection */} +
+ +
+ + +
+
+ + {checkMethod === "tokens" ? ( +
+
+ + setToken0(e.target.value)} + placeholder="ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.mock-token" + className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+
+ + setToken1(e.target.value)} + placeholder="ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.mock-token-2" + className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+
+ + setFee(Number(e.target.value))} + placeholder="500" + className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ {poolExistsByTokens.poolId && ( +
+ +
+ {poolExistsByTokens.poolId} +
+
+ )} +
+ ) : ( +
+ + setPoolId(e.target.value)} + placeholder="Enter pool ID in hex format" + className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ )} + + {/* Results */} +
+

Result

+ + {currentResult.loading && ( +
+
+ Checking pool existence... +
+ )} + + {currentResult.error && ( +
+ Error: {currentResult.error} +
+ )} + + {currentResult.exists !== null && !currentResult.loading && !currentResult.error && ( +
+
+ + Pool {currentResult.exists ? 'EXISTS' : 'DOES NOT EXIST'} + +
+ )} + + {currentResult.exists !== null && ( + + )} +
+
+ ); +} + +interface PoolStatusIndicatorProps { + poolId: string | null; + className?: string; +} + +export function PoolStatusIndicator({ poolId, className = "" }: PoolStatusIndicatorProps) { + const { exists, loading, error } = usePoolExists(poolId); + + if (!poolId) { + return null; + } + + if (loading) { + return ( +
+
+ Checking... +
+ ); + } + + if (error) { + return ( +
+
+ Error +
+ ); + } + + if (exists === null) { + return null; + } + + return ( +
+
+ {exists ? 'Active' : 'Not Found'} +
+ ); +} \ No newline at end of file diff --git a/frontend/hooks/use-stacks.ts b/frontend/hooks/use-stacks.ts index 9e3da4a..231fa21 100644 --- a/frontend/hooks/use-stacks.ts +++ b/frontend/hooks/use-stacks.ts @@ -1,6 +1,8 @@ import { addLiquidity, + checkPoolExists, createPool, + getPoolIdForTokens, Pool, removeLiquidity, swap, @@ -20,6 +22,18 @@ const appDetails = { icon: "https://cryptologos.cc/logos/stacks-stx-logo.png", }; +// Pool existence interfaces for external use +export interface UsePoolExistsResult { + exists: boolean | null; + loading: boolean; + error: string | null; + refetch: () => void; +} + +export interface UsePoolExistsByTokensResult extends UsePoolExistsResult { + poolId: string | null; +} + export function useStacks() { const [userData, setUserData] = useState(null); @@ -129,6 +143,25 @@ export function useStacks() { } } + // Pool existence functions + async function checkPoolExistence(poolId: string): Promise { + try { + return await checkPoolExists(poolId); + } catch (err) { + console.error("Error checking pool existence:", err); + return false; + } + } + + async function getPoolIdForTokenPair(token0: string, token1: string, fee: number): Promise { + try { + return await getPoolIdForTokens(token0, token1, fee); + } catch (err) { + console.error("Error getting pool ID:", err); + return null; + } + } + useEffect(() => { if (userSession.isSignInPending()) { userSession.handlePendingSignIn().then((userData) => { @@ -147,5 +180,97 @@ export function useStacks() { handleRemoveLiquidity, connectWallet, disconnectWallet, + checkPoolExistence, + getPoolIdForTokenPair, + }; +} + +// Dedicated pool existence hooks +export function usePoolExists(poolId: string | null): UsePoolExistsResult { + const [exists, setExists] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const checkExists = async () => { + if (!poolId) { + setExists(null); + setLoading(false); + setError(null); + return; + } + + setLoading(true); + setError(null); + + try { + const result = await checkPoolExists(poolId); + setExists(result); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to check pool existence"; + setError(errorMessage); + setExists(null); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + checkExists(); + }, [poolId]); + + return { + exists, + loading, + error, + refetch: checkExists, + }; +} + +export function usePoolExistsByTokens( + token0: string | null, + token1: string | null, + fee: number | null +): UsePoolExistsByTokensResult { + const [poolId, setPoolId] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const poolExistsResult = usePoolExists(poolId); + + const checkPoolId = async () => { + if (!token0 || !token1 || fee === null) { + setPoolId(null); + setError(null); + return; + } + + setLoading(true); + setError(null); + + try { + const id = await getPoolIdForTokens(token0, token1, fee); + setPoolId(id); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to get pool ID"; + setError(errorMessage); + setPoolId(null); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + checkPoolId(); + }, [token0, token1, fee]); + + return { + poolId, + exists: poolExistsResult.exists, + loading: loading || poolExistsResult.loading, + error: error || poolExistsResult.error, + refetch: () => { + checkPoolId(); + poolExistsResult.refetch(); + }, }; } diff --git a/frontend/lib/amm.ts b/frontend/lib/amm.ts index a0c73b2..cf463a8 100644 --- a/frontend/lib/amm.ts +++ b/frontend/lib/amm.ts @@ -247,3 +247,55 @@ export async function getUserLiquidity(pool: Pool, user: string) { if (userLiquidityResult.value.type !== "uint") return 0; return parseInt(userLiquidityResult.value.value.toString()); } + +export async function checkPoolExists(poolId: string): Promise { + try { + const poolExistsResult = await fetchCallReadOnlyFunction({ + contractAddress: AMM_CONTRACT_ADDRESS, + contractName: AMM_CONTRACT_NAME, + functionName: "pool-exists", + functionArgs: [bufferCV(Buffer.from(poolId, "hex"))], + senderAddress: AMM_CONTRACT_ADDRESS, + network: STACKS_TESTNET, + }); + + if (poolExistsResult.type !== "ok") return false; + if (poolExistsResult.value.type !== "bool") return false; + return poolExistsResult.value.value; + } catch (error) { + console.error("Error checking pool exists:", error); + return false; + } +} + +export async function getPoolIdForTokens(token0: string, token1: string, fee: number): Promise { + try { + // Ensure correct token ordering + const token0Hex = cvToHex(principalCV(token0)); + const token1Hex = cvToHex(principalCV(token1)); + if (token0Hex > token1Hex) { + [token0, token1] = [token1, token0]; + } + + const poolIdResult = await fetchCallReadOnlyFunction({ + contractAddress: AMM_CONTRACT_ADDRESS, + contractName: AMM_CONTRACT_NAME, + functionName: "get-pool-id", + functionArgs: [ + Cl.tuple({ + "token-0": principalCV(token0), + "token-1": principalCV(token1), + fee: uintCV(fee), + }), + ], + senderAddress: AMM_CONTRACT_ADDRESS, + network: STACKS_TESTNET, + }); + + if (poolIdResult.type !== "buffer") return null; + return poolIdResult.value; + } catch (error) { + console.error("Error getting pool ID:", error); + return null; + } +} diff --git a/tests/amm.test.ts b/tests/amm.test.ts index c4f730a..ac1d94f 100644 --- a/tests/amm.test.ts +++ b/tests/amm.test.ts @@ -132,8 +132,61 @@ describe("AMM Tests", () => { ); expect(tokenTwoAmountWithdrawn).toBeLessThan(withdrawableTokenTwoPreSwap); }); + + + + + it("should check if pool exists", () => { + const { result: poolId } = getPoolId(); + const poolExistsBeforeCreation = simnet.callReadOnlyFn( + "amm", + "pool-exists", + [poolId], + alice + ); + expect(poolExistsBeforeCreation.result).toBeOk(Cl.bool(false)); + + createPool(); + + + const poolExistsAfterCreation = simnet.callReadOnlyFn( + "amm", + "pool-exists", + [poolId], + alice + ); + expect(poolExistsAfterCreation.result).toBeOk(Cl.bool(true)); + + + + const differentPoolId = simnet.callReadOnlyFn( + "amm", + "get-pool-id", + [ + Cl.tuple({ + "token-0": mockTokenOne, + "token-1": mockTokenTwo, + fee: Cl.uint(1000), + }), + ], + alice + ); + + const differentPoolExists = simnet.callReadOnlyFn( + "amm", + "pool-exists", + [differentPoolId.result], + alice + ); + expect(differentPoolExists.result).toBeOk(Cl.bool(false)); + }); }); + + + + + function createPool() { return simnet.callPublicFn( "amm",