From ef68c7a6126317392402a27389f4f7a7800462e2 Mon Sep 17 00:00:00 2001 From: Truphile Date: Wed, 17 Jun 2026 11:00:34 -0400 Subject: [PATCH 1/2] sponsor withdrawal flow --- src/hooks/useTransaction.ts | 52 +++++++ src/pages/Sponsors.tsx | 229 ++++++++++++++++++++++++++++++- src/services/sponsors.service.ts | 23 ++++ 3 files changed, 299 insertions(+), 5 deletions(-) create mode 100644 src/hooks/useTransaction.ts create mode 100644 src/services/sponsors.service.ts diff --git a/src/hooks/useTransaction.ts b/src/hooks/useTransaction.ts new file mode 100644 index 0000000..e0d2b44 --- /dev/null +++ b/src/hooks/useTransaction.ts @@ -0,0 +1,52 @@ +import { useState } from 'react' +import { signTransaction } from '@stellar/freighter-api' +import { STELLAR_NETWORK } from '../constants/config' + +interface UseTransactionReturn { + isLoading: boolean + error: string | null + execute: ( + getXdr: () => Promise<{ xdr: string }>, + submit: (signedXdr: string) => Promise + ) => Promise +} + +export function useTransaction(): UseTransactionReturn { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const execute = async ( + getXdr: () => Promise<{ xdr: string }>, + submit: (signedXdr: string) => Promise + ): Promise => { + setIsLoading(true) + setError(null) + try { + const { xdr } = await getXdr() + + const networkPassphrase = (STELLAR_NETWORK as string) === 'PUBLIC' + ? 'Public Global Stellar Network ; October 2015' + : 'Test SDF Network ; September 2015' + + const signedResponse = await signTransaction(xdr, { + networkPassphrase, + }) + + if (!signedResponse || signedResponse.error) { + throw new Error(typeof signedResponse.error === 'string' ? signedResponse.error : 'User rejected the transaction') + } + + const result = await submit(signedResponse.signedTxXdr) + return result + } catch (err: unknown) { + console.error('Transaction failed:', err) + const message = err instanceof Error ? err.message : 'Transaction failed' + setError(message) + throw err + } finally { + setIsLoading(false) + } + } + + return { isLoading, error, execute } +} diff --git a/src/pages/Sponsors.tsx b/src/pages/Sponsors.tsx index 3219e69..f3beb56 100644 --- a/src/pages/Sponsors.tsx +++ b/src/pages/Sponsors.tsx @@ -1,11 +1,230 @@ +import { useState } from 'react' +import { useQuery } from '@tanstack/react-query' +import { sponsorsService } from '../services/sponsors.service' +import { useTransaction } from '../hooks/useTransaction' +import { useWallet } from '../hooks/useWallet' +import { Button } from '../components/ui/Button' +import { Card } from '../components/ui/Card' +import { Badge } from '../components/ui/Badge' +import { Spinner } from '../components/ui/Spinner' +import { + TrendingUp, + Wallet, + ArrowUpRight, + CheckCircle2, + AlertCircle, + ExternalLink +} from 'lucide-react' + export function Sponsors() { + const { isConnected } = useWallet() + const [shares, setShares] = useState('') + const [successData, setSuccessData] = useState<{ + hash: string + amount: number + profit: number + } | null>(null) + + const { data: poolInfo, isLoading: poolLoading } = useQuery({ + queryKey: ['poolInfo'], + queryFn: sponsorsService.getPoolInfo, + enabled: isConnected + }) + + const { execute, isLoading: txLoading, error: txError } = useTransaction() + + const handleWithdraw = async (e: React.FormEvent) => { + e.preventDefault() + if (!shares || isNaN(Number(shares))) return + + try { + const result = await execute( + () => sponsorsService.withdraw(Number(shares)), + (signedXdr) => sponsorsService.submitTransaction(signedXdr) + ) + setSuccessData(result) + setShares('') + } catch { + // Error handled by useTransaction and shown in UI + } + } + + if (!isConnected) { + return ( +
+

+ Connect your wallet to sponsor +

+

+ You need a Stellar wallet to manage your liquidity pool shares. +

+
+ ) + } + + const previewUsdc = Number(shares) * (poolInfo?.sharePrice || 0) + return (
-

Sponsor Dashboard

-

- Pool stats and deposit options coming soon. -

+
+

+ Sponsor Dashboard +

+

+ Manage your deposits and earn yield from learner loans. +

+
+ +
+
+
+ +
+
+ +
+ +
+

Total Deposits

+

+ {poolLoading ? : `${poolInfo?.totalDeposits.toLocaleString()} USDC`} +

+
+ + +
+
+ +
+
+

Available Liquidity

+

+ {poolLoading ? : `${poolInfo?.availableLiquidity.toLocaleString()} USDC`} +

+
+
+ + +

+ Liquidity Pool Details +

+
+
+

Total Shares

+

+ {poolLoading ? '...' : poolInfo?.totalShares.toLocaleString()} +

+
+
+

Share Price

+

+ {poolLoading ? '...' : `${poolInfo?.sharePrice.toFixed(4)} USDC`} +

+
+
+

Locked Capital

+

+ {poolLoading ? '...' : `${poolInfo?.lockedLiquidity.toLocaleString()} USDC`} +

+
+
+

Status

+

Active

+
+
+
+
+ +
+ +

+ Withdraw Funds +

+
+
+ +
+ setShares(e.target.value)} + placeholder="0.00" + className="w-full bg-bg border border-border rounded-xl px-4 py-3 + text-text-primary focus:outline-none focus:border-brand transition-colors" + /> +
+ SHARES +
+
+
+ + {Number(shares) > 0 && ( +
+
+ Preview Value: + {previewUsdc.toFixed(2)} USDC +
+

+ Estimated amount based on current share price. Final amount may vary slightly. +

+
+ )} + + {txError && ( +
+ +

{txError}

+
+ )} + + +
+
+ + {successData && ( + +
+
+ +
+

Withdrawal Successful

+
+ +
+
+ Amount Received: + {successData.amount} USDC +
+
+ Realized Profit: + +{successData.profit} USDC +
+
+ + + View on Stellar.expert + + +
+ )} +
+
) } diff --git a/src/services/sponsors.service.ts b/src/services/sponsors.service.ts new file mode 100644 index 0000000..08db6be --- /dev/null +++ b/src/services/sponsors.service.ts @@ -0,0 +1,23 @@ +import { api } from './api' +import type { PoolInfo } from '../types' + +export const sponsorsService = { + getPoolInfo: async (): Promise => { + const res = await api.get('/liquidity/pool-info') + return res.data + }, + + withdraw: async (shares: number): Promise<{ xdr: string }> => { + const res = await api.post('/liquidity/withdraw', { shares }) + return res.data + }, + + submitTransaction: async (signedXdr: string): Promise<{ + hash: string + amount: number + profit: number + }> => { + const res = await api.post('/liquidity/submit', { xdr: signedXdr }) + return res.data + }, +} From 78ab7b62b470ae0bacd54b1f394adb2f65f363bb Mon Sep 17 00:00:00 2001 From: Truphile Date: Wed, 17 Jun 2026 11:13:19 -0400 Subject: [PATCH 2/2] lint fix --- src/components/layout/Navbar.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index a6c5b30..958a1fc 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -24,10 +24,6 @@ export function Navbar() { return () => window.removeEventListener('scroll', handle) }, []) - useEffect(() => { - setMobileOpen(false) - }, [pathname]) - return (