From 1b3c44d1dbd013d16c1a73ff741510cc1ebde4e1 Mon Sep 17 00:00:00 2001 From: fiqriBTI Date: Wed, 20 Aug 2025 15:23:53 +0700 Subject: [PATCH 01/12] feat: bba wrapping ui done --- .prettierrc | 2 +- public/BBA_logo_wrapping.svg | 106 +++++++++++++++++ public/WBBA_logo_wrapping.svg | 108 ++++++++++++++++++ src/app/(walletConnected)/wrapping/page.tsx | 105 +++++++++++++++++ src/app/globals.css | 2 +- src/components/common/WalletButton.tsx | 2 +- src/constants/service.ts | 7 +- .../wrapping/components/WrapBalanceItem.tsx | 36 ++++++ .../wrapping/components/WrapContentCard.tsx | 86 ++++++++++++++ .../wrapping/components/WrapInputItem.tsx | 99 ++++++++++++++++ src/features/wrapping/services.ts | 35 ++++++ 11 files changed, 584 insertions(+), 4 deletions(-) create mode 100644 public/BBA_logo_wrapping.svg create mode 100644 public/WBBA_logo_wrapping.svg create mode 100644 src/app/(walletConnected)/wrapping/page.tsx create mode 100644 src/features/wrapping/components/WrapBalanceItem.tsx create mode 100644 src/features/wrapping/components/WrapContentCard.tsx create mode 100644 src/features/wrapping/components/WrapInputItem.tsx create mode 100644 src/features/wrapping/services.ts diff --git a/.prettierrc b/.prettierrc index 882a16c..bb0d54b 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,7 +3,7 @@ "singleQuote": true, "useTabs": true, "tabWidth": 2, - "printWidth": 120, + "printWidth": 100, "trailingComma": "none", "endOfLine": "auto" } diff --git a/public/BBA_logo_wrapping.svg b/public/BBA_logo_wrapping.svg new file mode 100644 index 0000000..3e85525 --- /dev/null +++ b/public/BBA_logo_wrapping.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/WBBA_logo_wrapping.svg b/public/WBBA_logo_wrapping.svg new file mode 100644 index 0000000..725adbe --- /dev/null +++ b/public/WBBA_logo_wrapping.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/(walletConnected)/wrapping/page.tsx b/src/app/(walletConnected)/wrapping/page.tsx new file mode 100644 index 0000000..73ffca8 --- /dev/null +++ b/src/app/(walletConnected)/wrapping/page.tsx @@ -0,0 +1,105 @@ +'use client' + +import { useCallback, useState } from 'react' + +import { balanceFormater } from '@/components/common/WalletButton' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import WrapBalanceItem from '@/features/wrapping/components/WrapBalanceItem' +import WrapContent from '@/features/wrapping/components/WrapContentCard' +import { useGetWBBABalance } from '@/features/wrapping/services' +import { useGetBalance } from '@/services/wallet' +import StaticTokens from '@/staticData/tokens' + +export default function Wrapping() { + const getBBABalance = useGetBalance() + const getWBBABalance = useGetWBBABalance() + const BBABalance = balanceFormater(getBBABalance.data ?? 0) + const WBBABalance = (getWBBABalance.data?.balance ?? 0) / Math.pow(10, StaticTokens[0].decimals) + const [inputAmount, setInputAmount] = useState('') + const [currentBase, setCurrentBase] = useState<'WBBA' | 'BBA'>('BBA') + + const isAmountPositive = Number(inputAmount) >= 0 + + const generalInvalid = inputAmount === '' || Number(inputAmount) <= 0 || !isAmountPositive + const isWrapInvalid = generalInvalid || Number(inputAmount) > BBABalance + const isUnwrapInvalid = generalInvalid || Number(inputAmount) > WBBABalance + + return ( +
+
+

+ BBA Wrapping +

+
+ Easily convert between BBA and WBBA. Use WBBA for swaps and liquidity pools. +
+
+
+ + +
+
+ { + setInputAmount('') + setCurrentBase(val as 'WBBA' | 'BBA') + }} + > + + + Wrap + + + Unwrap + + + + console.log('action')} + /> + + + console.log('action')} + /> + + +
+
+ ) +} diff --git a/src/app/globals.css b/src/app/globals.css index 041ead6..ad63a69 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -79,7 +79,7 @@ body { --secondary-light-yellow: 35 54% 19%; --secondary-light-blue: 213 42% 18%; --secondary-hover-green: 110 75% 61%; - --secondary-dark-grey: 0 0% 100%; + --secondary-dark-grey: 0 1% 64%; --secondary-transparent-green: hsla(110, 50%, 9%, 0.36); --secondary-strokes: 0 0% 100%; --box: 0 0% 20%; diff --git a/src/components/common/WalletButton.tsx b/src/components/common/WalletButton.tsx index 4302f28..4a86fc9 100644 --- a/src/components/common/WalletButton.tsx +++ b/src/components/common/WalletButton.tsx @@ -11,7 +11,7 @@ import { useIsMobile } from '@/hooks/isMobile' import { useGetBalance } from '@/services/wallet' import { useWalletListDialog } from '@/stores/walletDialog' -const balanceFormater = (balance: number) => Math.round((balance / BBA_DALTON_UNIT) * 100000) / 100000 +export const balanceFormater = (balance: number) => Math.round((balance / BBA_DALTON_UNIT) * 100000) / 100000 function BalanceValue() { const getBalanceQuery = useGetBalance() diff --git a/src/constants/service.ts b/src/constants/service.ts index 403c34b..6469ec1 100644 --- a/src/constants/service.ts +++ b/src/constants/service.ts @@ -48,13 +48,18 @@ const POOL_SERVICE_KEY = { DEPOSIT_LIQUIDITY: 'deposit-liquidity' } as const +const WRAPPING_SERVICE_KEY = { + GET_WBBA_BALANCE: 'get-wbba-balance' +} as const + const SERVICES_KEY = { GLOBAL: GLOBAL_SERVICE_KEY, WALLET: WALLET_SERVICE_KEY, TOKEN: TOKEN_SERVICE_KEY, NFT: NFT_SERVICE_KEY, SWAP: SWAP_SERVICE_KEY, - POOL: POOL_SERVICE_KEY + POOL: POOL_SERVICE_KEY, + WRAPPING: WRAPPING_SERVICE_KEY } as const export default SERVICES_KEY diff --git a/src/features/wrapping/components/WrapBalanceItem.tsx b/src/features/wrapping/components/WrapBalanceItem.tsx new file mode 100644 index 0000000..14b7284 --- /dev/null +++ b/src/features/wrapping/components/WrapBalanceItem.tsx @@ -0,0 +1,36 @@ +import Image from 'next/image' + +import { Skeleton } from '@/components/ui/skeleton' +import { useIsMobile } from '@/hooks/isMobile' + +interface WrapBalanceItemProps { + type: 'BBA' | 'WBBA' + balance: number + isLoading?: boolean +} + +export default function WrapBalanceItem({ type, balance, isLoading }: WrapBalanceItemProps) { + const isWBBA = type === 'WBBA' + const isMobile = useIsMobile() + return ( +
+
+

{`${type} Balance`}

+ {isLoading ? ( + + ) : ( +

{`${balance.toLocaleString(undefined, { + minimumFractionDigits: 0, + maximumFractionDigits: 6 + })} ${type}`}

+ )} +
+ {isWBBA +
+ ) +} diff --git a/src/features/wrapping/components/WrapContentCard.tsx b/src/features/wrapping/components/WrapContentCard.tsx new file mode 100644 index 0000000..1b48a15 --- /dev/null +++ b/src/features/wrapping/components/WrapContentCard.tsx @@ -0,0 +1,86 @@ +import { Dispatch, SetStateAction } from 'react' +import toast from 'react-hot-toast' +import { FaArrowDownLong, FaArrowRightLong } from 'react-icons/fa6' + +import { Button } from '@/components/ui/button' +import { useIsMobile } from '@/hooks/isMobile' + +import WrapInputItem from './WrapInputItem' + +interface WrapContentProps { + base: 'BBA' | 'WBBA' + target: 'BBA' | 'WBBA' + baseBalance: number + targetBalance: number + inputAmount: string + setInputAmount: Dispatch> + isInvalid: boolean + onAction: () => void +} + +export default function WrapContent({ + base, + target, + baseBalance, + targetBalance, + inputAmount, + setInputAmount, + isInvalid, + onAction +}: WrapContentProps) { + const isMobile = useIsMobile() + return ( +
+ {/* Input Section */} +
+ +
+
+ {isMobile ? ( + + ) : ( + + )} +
+
+ +
+ + {/* Rate Section */} +
+

Rate

+
+

1 {base}

+ = +

1 {target}

+
+
+ + {/* Action Button */} + +
+ ) +} diff --git a/src/features/wrapping/components/WrapInputItem.tsx b/src/features/wrapping/components/WrapInputItem.tsx new file mode 100644 index 0000000..15c29c9 --- /dev/null +++ b/src/features/wrapping/components/WrapInputItem.tsx @@ -0,0 +1,99 @@ +import Image from 'next/image' +import { Dispatch, SetStateAction } from 'react' + +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { useIsMobile } from '@/hooks/isMobile' +import { cn } from '@/lib/utils' + +interface WrapInputItemProps { + isBase?: boolean + disable?: boolean + type: 'BBA' | 'WBBA' + balance: number + amount: string + setAmount: Dispatch> +} + +export default function WrapInputItem({ + isBase = false, + disable, + type, + balance, + amount, + setAmount +}: WrapInputItemProps) { + const isWBBA = type === 'WBBA' + const isMobile = useIsMobile() + const isBalanceNotEnough = isBase && Number(amount) > balance + const isAmountPositive = Number(amount) >= 0 + const isInValid = isBalanceNotEnough || !isAmountPositive + + const onMaxClick = () => + setAmount( + balance.toLocaleString(undefined, { + minimumFractionDigits: 0, + maximumFractionDigits: 6 + }) + ) + + return ( +
+
+

{isBase ? 'From' : 'To'}

+
+
+ setAmount(e.target.value)} + /> + {isBase && ( + + )} +
+ {isBalanceNotEnough && ( +

Balance is not enough

+ )} + {!isAmountPositive && ( +

+ Amount can not be negative +

+ )} +
+
+
+ {isWBBA +
{type}
+
+
+ ) +} diff --git a/src/features/wrapping/services.ts b/src/features/wrapping/services.ts new file mode 100644 index 0000000..bbf6ada --- /dev/null +++ b/src/features/wrapping/services.ts @@ -0,0 +1,35 @@ +import { getAssociatedTokenAddress, NATIVE_MINT } from '@bbachain/spl-token' +import { useConnection, useWallet } from '@bbachain/wallet-adapter-react' +import { PublicKey } from '@bbachain/web3.js' +import { useQuery } from '@tanstack/react-query' + +import SERVICES_KEY from '@/constants/service' + +import { TGetUserBalanceData } from '../swap/types' + +export function useGetWBBABalance() { + const { publicKey: ownerAddress } = useWallet() + const { connection } = useConnection() + return useQuery({ + queryKey: [SERVICES_KEY.WRAPPING.GET_WBBA_BALANCE], + queryFn: async () => { + if (!ownerAddress) throw new Error('No wallet connected') + + try { + const mint = new PublicKey(NATIVE_MINT.toBase58()) + const ata = await getAssociatedTokenAddress(mint, ownerAddress) + const balanceAmount = await connection.getTokenAccountBalance(ata) + return { balance: Number(balanceAmount.value.amount) } + } catch (e) { + console.error('Balance fetch error:', e) + return { balance: 0 } + } + } + }) +} + +export function useWrapBBA() { + +} + + From 6d4fc586a5d92774dd0f16deb4cffba1f0278aba Mon Sep 17 00:00:00 2001 From: fiqriBTI Date: Thu, 21 Aug 2025 01:32:10 +0700 Subject: [PATCH 02/12] feat: wrap bba done --- src/app/(walletConnected)/wrapping/page.tsx | 39 ++++-- src/constants/service.ts | 4 +- .../wrapping/components/WrapContentCard.tsx | 6 +- src/features/wrapping/services.ts | 128 +++++++++++++++++- src/features/wrapping/types.ts | 9 ++ 5 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 src/features/wrapping/types.ts diff --git a/src/app/(walletConnected)/wrapping/page.tsx b/src/app/(walletConnected)/wrapping/page.tsx index 73ffca8..f176ce8 100644 --- a/src/app/(walletConnected)/wrapping/page.tsx +++ b/src/app/(walletConnected)/wrapping/page.tsx @@ -1,22 +1,24 @@ 'use client' -import { useCallback, useState } from 'react' +import { useEffect, useState } from 'react' +import toast from 'react-hot-toast' import { balanceFormater } from '@/components/common/WalletButton' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import WrapBalanceItem from '@/features/wrapping/components/WrapBalanceItem' import WrapContent from '@/features/wrapping/components/WrapContentCard' -import { useGetWBBABalance } from '@/features/wrapping/services' +import { useGetWBBABalance, useUnwrapBBA, useWrapBBA } from '@/features/wrapping/services' import { useGetBalance } from '@/services/wallet' import StaticTokens from '@/staticData/tokens' export default function Wrapping() { + const wrapBBAMutation = useWrapBBA() + const unwrapWBBAMutation = useUnwrapBBA() const getBBABalance = useGetBalance() const getWBBABalance = useGetWBBABalance() const BBABalance = balanceFormater(getBBABalance.data ?? 0) const WBBABalance = (getWBBABalance.data?.balance ?? 0) / Math.pow(10, StaticTokens[0].decimals) const [inputAmount, setInputAmount] = useState('') - const [currentBase, setCurrentBase] = useState<'WBBA' | 'BBA'>('BBA') const isAmountPositive = Number(inputAmount) >= 0 @@ -24,6 +26,23 @@ export default function Wrapping() { const isWrapInvalid = generalInvalid || Number(inputAmount) > BBABalance const isUnwrapInvalid = generalInvalid || Number(inputAmount) > WBBABalance + const onWrapBBA = () => wrapBBAMutation.mutate({ amount: Number(inputAmount) }) + const onUnwrapWBBA = () => unwrapWBBAMutation.mutate({ amount: Number(inputAmount) }) + + useEffect(() => { + if (wrapBBAMutation.isSuccess && wrapBBAMutation.data) { + setInputAmount('') + toast.success(wrapBBAMutation.data.message) + } + }, [wrapBBAMutation.data, wrapBBAMutation.isSuccess]) + + useEffect(() => { + if (unwrapWBBAMutation.isSuccess && unwrapWBBAMutation.data) { + setInputAmount('') + toast.success(unwrapWBBAMutation.data.message) + } + }, [unwrapWBBAMutation.data, unwrapWBBAMutation.isSuccess]) + return (
@@ -43,13 +62,7 @@ export default function Wrapping() { />
- { - setInputAmount('') - setCurrentBase(val as 'WBBA' | 'BBA') - }} - > + setInputAmount('')}> console.log('action')} + isLoading={wrapBBAMutation.isPending} + onAction={onWrapBBA} /> @@ -95,7 +109,8 @@ export default function Wrapping() { inputAmount={inputAmount} setInputAmount={setInputAmount} isInvalid={isUnwrapInvalid} - onAction={() => console.log('action')} + isLoading={unwrapWBBAMutation.isPending} + onAction={onUnwrapWBBA} /> diff --git a/src/constants/service.ts b/src/constants/service.ts index 6469ec1..ea9ea9e 100644 --- a/src/constants/service.ts +++ b/src/constants/service.ts @@ -49,7 +49,9 @@ const POOL_SERVICE_KEY = { } as const const WRAPPING_SERVICE_KEY = { - GET_WBBA_BALANCE: 'get-wbba-balance' + GET_WBBA_BALANCE: 'get-wbba-balance', + WRAP_BBA: 'wrap-bba', + UNWRAP_WBBA: 'unwrap-wbba' } as const const SERVICES_KEY = { diff --git a/src/features/wrapping/components/WrapContentCard.tsx b/src/features/wrapping/components/WrapContentCard.tsx index 1b48a15..24eb47d 100644 --- a/src/features/wrapping/components/WrapContentCard.tsx +++ b/src/features/wrapping/components/WrapContentCard.tsx @@ -1,3 +1,4 @@ +import { Loader2 } from 'lucide-react' import { Dispatch, SetStateAction } from 'react' import toast from 'react-hot-toast' import { FaArrowDownLong, FaArrowRightLong } from 'react-icons/fa6' @@ -15,6 +16,7 @@ interface WrapContentProps { inputAmount: string setInputAmount: Dispatch> isInvalid: boolean + isLoading: boolean onAction: () => void } @@ -26,6 +28,7 @@ export default function WrapContent({ inputAmount, setInputAmount, isInvalid, + isLoading, onAction }: WrapContentProps) { const isMobile = useIsMobile() @@ -71,10 +74,11 @@ export default function WrapContent({
- +
diff --git a/src/features/wrapping/services.ts b/src/features/wrapping/services.ts index bce1039..0a5ffd0 100644 --- a/src/features/wrapping/services.ts +++ b/src/features/wrapping/services.ts @@ -15,7 +15,7 @@ import StaticTokens from '@/staticData/tokens' import { TGetUserBalanceData } from '../swap/types' -import { TWrapPayload, TWrapResponse } from './types' +import { TUnwrapResponse, TWrapPayload, TWrapResponse } from './types' export function useGetWBBABalance() { const { publicKey: ownerAddress } = useWallet() @@ -100,7 +100,7 @@ export function useUnwrapBBA() { const { connection } = useConnection() const queryClient = useQueryClient() - return useMutation({ + return useMutation({ mutationKey: [SERVICES_KEY.WRAPPING.UNWRAP_WBBA], mutationFn: async (payload) => { if (!ownerAddress) throw new Error('No wallet connected') @@ -119,38 +119,51 @@ export function useUnwrapBBA() { }) const isUnwrapAll = payload.amount === Number(formattedWBBABalance) + const signatures: string[] = [] - if (isUnwrapAll) { - // 🔹 CASE 1: Unwrap all (close ATA) + { console.log('🔓 Closing WBBA account (unwrap all)...') - const closeIx = createCloseAccountInstruction(wbbaAccount, ownerAddress, ownerAddress) - unwrapTxInstructions.push(closeIx) - } else { - // 🔹 CASE 2: Unwrap partial - const unwrapWBBADaltons = bbaTodaltons(payload.amount) - - console.log(`🔓 Unwrapping partial ${payload.amount} BBA...`) - - // transfer lamports from WBBA ATA back to wallet - const transferIx = SystemProgram.transfer({ - fromPubkey: wbbaAccount, - toPubkey: ownerAddress, - daltons: unwrapWBBADaltons - }) + const closeAccountIx = createCloseAccountInstruction( + wbbaAccount, + ownerAddress, + ownerAddress + ) + const closeAccountTx = new Transaction().add(closeAccountIx) + const closeAccountSig = await sendTransaction(closeAccountTx, connection) + signatures.push(closeAccountSig) + await connection.confirmTransaction(closeAccountSig, 'confirmed') + } - // sync ATA balance - const syncIx = createSyncNativeInstruction(wbbaAccount) + if (!isUnwrapAll) { + // 🔹 CASE Unwrap partial + const leftOverWBBAAmounts = Number(formattedWBBABalance) - payload.amount + const leftOverWBBADaltons = bbaTodaltons(leftOverWBBAAmounts) - unwrapTxInstructions.push(transferIx, syncIx) - } + const createWBBAIx = createAssociatedTokenAccountInstruction( + ownerAddress, + wbbaAccount, + ownerAddress, + NATIVE_MINT + ) + unwrapTxInstructions.push(createWBBAIx) - const tx = new Transaction().add(...unwrapTxInstructions) + // transfer left over WBBA daltons back from BBA to WBBA + const transferBBAIx = SystemProgram.transfer({ + fromPubkey: ownerAddress, + toPubkey: wbbaAccount, + daltons: leftOverWBBADaltons + }) + const syncBBAIx = createSyncNativeInstruction(wbbaAccount) + unwrapTxInstructions.push(transferBBAIx, syncBBAIx) - const signature = await sendTransaction(tx, connection) + const tx = new Transaction().add(...unwrapTxInstructions) + const unwrapSignature = await sendTransaction(tx, connection) + signatures.push(unwrapSignature) + } return { message: `Successfully unwrapped ${payload.amount} WBBA to ${payload.amount} BBA`, - signature + signatures } } catch (err: any) { console.error('❌ Unwrap BBA failed:', err) diff --git a/src/features/wrapping/types.ts b/src/features/wrapping/types.ts index 7782bab..21e0715 100644 --- a/src/features/wrapping/types.ts +++ b/src/features/wrapping/types.ts @@ -7,3 +7,7 @@ export type TWrapPayload = { export type TWrapResponse = TSuccessMessage & { signature: string } + +export type TUnwrapResponse = TSuccessMessage & { + signatures: string[] +} From 56b7ab85f043ce19b3ae598667a41301bce058af Mon Sep 17 00:00:00 2001 From: fiqriBTI Date: Thu, 21 Aug 2025 18:43:45 +0700 Subject: [PATCH 05/12] feat: change minor variables naming --- src/features/wrapping/services.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/wrapping/services.ts b/src/features/wrapping/services.ts index 0a5ffd0..4d1b48b 100644 --- a/src/features/wrapping/services.ts +++ b/src/features/wrapping/services.ts @@ -136,8 +136,8 @@ export function useUnwrapBBA() { if (!isUnwrapAll) { // 🔹 CASE Unwrap partial - const leftOverWBBAAmounts = Number(formattedWBBABalance) - payload.amount - const leftOverWBBADaltons = bbaTodaltons(leftOverWBBAAmounts) + const remainingWBBAAmounts = Number(formattedWBBABalance) - payload.amount + const remainingWBBADaltons = bbaTodaltons(remainingWBBAAmounts) const createWBBAIx = createAssociatedTokenAccountInstruction( ownerAddress, @@ -151,7 +151,7 @@ export function useUnwrapBBA() { const transferBBAIx = SystemProgram.transfer({ fromPubkey: ownerAddress, toPubkey: wbbaAccount, - daltons: leftOverWBBADaltons + daltons: remainingWBBADaltons }) const syncBBAIx = createSyncNativeInstruction(wbbaAccount) unwrapTxInstructions.push(transferBBAIx, syncBBAIx) From fe0f599280fff1f3e0f7a78dd3e09337cfd26683 Mon Sep 17 00:00:00 2001 From: fiqriBTI Date: Thu, 21 Aug 2025 18:55:39 +0700 Subject: [PATCH 06/12] feat: add wrapping navigation link and improve navbar style for xl screen device --- src/components/layout/Navbar.tsx | 2 +- src/staticData/navbar.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index ef8d0f0..48daff8 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -154,7 +154,7 @@ export default function Navbar() { useEffect(() => setMounted(true), []) return ( -
@@ -588,7 +639,9 @@ export default function CreatePool() { form.setValue('rangeType', value as 'full-range' | 'custom-range')} + onValueChange={(value) => + form.setValue('rangeType', value as 'full-range' | 'custom-range') + } >
- Full Range Selected + + Full Range Selected +

Your liquidity will be active across all price ranges (0 to ∞) @@ -622,7 +677,9 @@ export default function CreatePool() { name="minInitialPrice" render={({ field }) => ( - Min Price + + Min Price + ( - Max Price + + Max Price + -

Deposit Summary

+

+ Deposit Summary +

Total Value: @@ -746,7 +807,9 @@ export default function CreatePool() {

{selectedBaseToken?.symbol}/{selectedQuoteToken?.symbol}

-

Fee: {form.watch('feeTier')}%

+

+ Fee: {form.watch('feeTier')}% +

@@ -760,7 +823,9 @@ export default function CreatePool() { {/* Pool Stats Preview */}
-

Initial TVL

+

+ Initial TVL +

${totalDepositValue.toFixed(2)}

@@ -771,7 +836,9 @@ export default function CreatePool() {

Swap Fee

-

{form.watch('feeTier')}%

+

+ {form.watch('feeTier')}% +

@@ -786,13 +853,17 @@ export default function CreatePool() {
-
Initial Price:
+
+ Initial Price: +

{form.watch('initialPrice')} {exchangeRate}

-
Price Range:
+
+ Price Range: +

{form.watch('rangeType') === 'custom-range' ? `${form.watch('minInitialPrice')} - ${form.watch('maxInitialPrice')}` @@ -800,14 +871,20 @@ export default function CreatePool() {

-
Trading Fee:
+
+ Trading Fee: +

{form.watch('feeTier')}% per trade

-
Pool Type:
-

Constant Product (x*y=k)

+
+ Pool Type: +
+

+ Constant Product (x*y=k) +

@@ -835,12 +912,16 @@ export default function CreatePool() { alt={selectedBaseToken?.symbol} />
-

{selectedBaseToken?.symbol}

+

+ {selectedBaseToken?.symbol} +

{selectedBaseToken?.name}

-

{baseTokenAmount} tokens

+

+ {baseTokenAmount} tokens +

${baseTokenPrice.toFixed(2)}

@@ -854,12 +935,16 @@ export default function CreatePool() { alt={selectedQuoteToken?.symbol} />
-

{selectedQuoteToken?.symbol}

+

+ {selectedQuoteToken?.symbol} +

{selectedQuoteToken?.name}

-

{quoteTokenAmount} tokens

+

+ {quoteTokenAmount} tokens +

${quoteTokenPrice.toFixed(2)}

@@ -874,7 +959,9 @@ export default function CreatePool() {
-
Trading Fee Revenue:
+
+ Trading Fee Revenue: +

{form.watch('feeTier')}% per trade

@@ -888,17 +975,27 @@ export default function CreatePool() {

-
Est. Daily Fees:
+
+ Est. Daily Fees: +

- ${((totalDepositValue * 0.01 * parseFloat(form.watch('feeTier'))) / 100).toFixed(4)} + $ + {( + (totalDepositValue * 0.01 * parseFloat(form.watch('feeTier'))) / + 100 + ).toFixed(4)}

-
Est. Annual APR:
+
+ Est. Annual APR: +

{( - ((((totalDepositValue * 0.01 * parseFloat(form.watch('feeTier'))) / 100) * 365) / + ((((totalDepositValue * 0.01 * parseFloat(form.watch('feeTier'))) / + 100) * + 365) / totalDepositValue) * 100 ).toFixed(2)} @@ -919,8 +1016,9 @@ export default function CreatePool() { Important Notice

- Creating a liquidity pool involves risk. Token prices can fluctuate, and you may experience - impermanent loss. Please ensure you understand the risks before proceeding. + Creating a liquidity pool involves risk. Token prices can fluctuate, and + you may experience impermanent loss. Please ensure you understand the + risks before proceeding.

@@ -944,7 +1042,9 @@ export default function CreatePool() { - + diff --git a/src/features/liquidityPool/services.ts b/src/features/liquidityPool/services.ts index 7233c11..4aa1da9 100644 --- a/src/features/liquidityPool/services.ts +++ b/src/features/liquidityPool/services.ts @@ -26,7 +26,12 @@ import ENDPOINTS from '@/constants/endpoint' import SERVICES_KEY from '@/constants/service' import { addBBAToPoolAccount, bbaTodaltons, daltonsToBBA } from '@/lib/bbaWrapping' import { formatTokenToDaltons } from '@/lib/utils' -import { isBBAPool, getBBAPositionInPool, isNativeBBA, getWBBAMintAddress } from '@/staticData/tokens' +import { + isBBAPool, + getBBAPositionInPool, + isNativeBBA, + getWBBAMintAddress +} from '@/staticData/tokens' import { useGetCoinGeckoTokenPrice } from '../swap/services' import { getCoinGeckoId } from '../swap/utils' @@ -97,7 +102,10 @@ const confirmTransactionWithTimeout = async ( setTimeout(() => reject(new Error('Transaction confirmation timeout')), timeoutMs) }) - const confirmPromise = connection.confirmTransaction({ signature, ...latestBlockhash }, 'confirmed') + const confirmPromise = connection.confirmTransaction( + { signature, ...latestBlockhash }, + 'confirmed' + ) const confirmation: any = await Promise.race([confirmPromise, timeoutPromise]) @@ -172,7 +180,10 @@ export const useGetPools = () => { console.error('❌ Error fetching pools:', error) // Try to return cached data if available - const cachedData = queryClient.getQueryData([SERVICES_KEY.POOL.GET_POOLS, connection.rpcEndpoint]) + const cachedData = queryClient.getQueryData([ + SERVICES_KEY.POOL.GET_POOLS, + connection.rpcEndpoint + ]) if (cachedData) { console.log('📋 Returning cached data due to fetch error') return cachedData as { message: string; data: OnchainPoolData[] } @@ -200,7 +211,8 @@ export const useGetPoolStats = () => { const totalLiquidity = pools.reduce((sum, pool) => sum + pool.tvl, 0) const totalVolume = pools.reduce((sum, pool) => sum + pool.volume24h, 0) const totalFees = pools.reduce((sum, pool) => sum + pool.fees24h, 0) - const averageAPR = pools.length > 0 ? pools.reduce((sum, pool) => sum + pool.apr24h, 0) / pools.length : 0 + const averageAPR = + pools.length > 0 ? pools.reduce((sum, pool) => sum + pool.apr24h, 0) / pools.length : 0 const topPoolsByLiquidity = [...pools].sort((a, b) => b.tvl - a.tvl).slice(0, 10) @@ -337,7 +349,8 @@ export const useGetTransactionsByPoolId = ({ baseMint: MintInfo | undefined quoteMint: MintInfo | undefined }) => { - const isValidParams = !!poolId?.trim() && !!baseMint?.address?.trim() && !!quoteMint?.address?.trim() + const isValidParams = + !!poolId?.trim() && !!baseMint?.address?.trim() && !!quoteMint?.address?.trim() const baseUSDValue = useGetCoinGeckoTokenPrice({ coinGeckoId: baseMint ? getCoinGeckoId(baseMint.address) : '' @@ -350,10 +363,16 @@ export const useGetTransactionsByPoolId = ({ const baseInitialPrice = baseUSDValue.data ?? 0 const quoteInitialPrice = quoteUSDValue.data ?? 0 - const areTokenPricesReady = baseUSDValue.status === 'success' && quoteUSDValue.status === 'success' + const areTokenPricesReady = + baseUSDValue.status === 'success' && quoteUSDValue.status === 'success' return useQuery({ - queryKey: [SERVICES_KEY.POOL.GET_TRANSACTIONS_BY_POOL_ID, poolId, baseMint?.address, quoteMint?.address], + queryKey: [ + SERVICES_KEY.POOL.GET_TRANSACTIONS_BY_POOL_ID, + poolId, + baseMint?.address, + quoteMint?.address + ], enabled: isValidParams && areTokenPricesReady, ...CACHE_CONFIG, retry: 2, @@ -453,7 +472,10 @@ export const useCreatePool = () => { // === BBA Pool Detection === const isBBAPoolPair = isBBAPool(payload.baseToken.address, payload.quoteToken.address) - const bbaPosition = getBBAPositionInPool(payload.baseToken.address, payload.quoteToken.address) + const bbaPosition = getBBAPositionInPool( + payload.baseToken.address, + payload.quoteToken.address + ) const isBBABase = bbaPosition === 'base' const isBBAQuote = bbaPosition === 'quote' @@ -491,7 +513,12 @@ export const useCreatePool = () => { if (!baseTokenInfo) { console.log('📝 Creating swap token A account...') - const ix = createAssociatedTokenAccountInstruction(ownerAddress, swapTokenAAccount, authority, baseMint) + const ix = createAssociatedTokenAccountInstruction( + ownerAddress, + swapTokenAAccount, + authority, + baseMint + ) const tx = new Transaction().add(ix) const sig = await sendTransactionWithRetry(tx, connection, sendTransaction) await confirmTransactionWithTimeout(connection, sig, latestBlockhash) @@ -508,7 +535,12 @@ export const useCreatePool = () => { const quoteTokenInfo = await connection.getAccountInfo(swapTokenBAccount) if (!quoteTokenInfo) { console.log('📝 Creating swap token B account...') - const ix = createAssociatedTokenAccountInstruction(ownerAddress, swapTokenBAccount, authority, quoteMint) + const ix = createAssociatedTokenAccountInstruction( + ownerAddress, + swapTokenBAccount, + authority, + quoteMint + ) const tx = new Transaction().add(ix) const sig = await sendTransactionWithRetry(tx, connection, sendTransaction) await confirmTransactionWithTimeout(connection, sig, latestBlockhash) @@ -535,7 +567,12 @@ export const useCreatePool = () => { }) // Step 2: Initialize mint with TEMPORARY authority (owner), will transfer later - const initMintIx = createInitializeMintInstruction(poolMint.publicKey, 2, ownerAddress, null) + const initMintIx = createInitializeMintInstruction( + poolMint.publicKey, + 2, + ownerAddress, + null + ) // Step 3: Create user's LP token account const poolTokenAccount = await getAssociatedTokenAddress(poolMint.publicKey, ownerAddress) @@ -579,7 +616,11 @@ export const useCreatePool = () => { ) const transferAuthorityTx = new Transaction().add(setAuthorityIx) - const transferSig = await sendTransactionWithRetry(transferAuthorityTx, connection, sendTransaction) + const transferSig = await sendTransactionWithRetry( + transferAuthorityTx, + connection, + sendTransaction + ) await confirmTransactionWithTimeout(connection, transferSig, latestBlockhash) console.log('✅ Pool mint authority transferred to swap authority:', transferSig) @@ -654,7 +695,9 @@ export const useCreatePool = () => { }) if (!baseInfo || !quoteInfo || !poolInfo || !feeUpdatedInfo) { - throw new Error('Some required accounts do not exist. Cannot proceed with pool initialization.') + throw new Error( + 'Some required accounts do not exist. Cannot proceed with pool initialization.' + ) } // === CRITICAL: Deposit Initial Liquidity === @@ -706,7 +749,9 @@ export const useCreatePool = () => { const userQuoteInfo = await connection.getAccountInfo(userQuoteTokenAccount) if (!userQuoteInfo) { - throw new Error('Quote token account not found. Please ensure you have the required tokens.') + throw new Error( + 'Quote token account not found. Please ensure you have the required tokens.' + ) } const userQuoteBalance = new BN(userQuoteInfo.data.slice(64, 72), 'le') @@ -738,7 +783,11 @@ export const useCreatePool = () => { // Combine all transfers const liquidityTx = new Transaction().add(transferBBAIx, syncBBAIx, transferQuoteIx) - const liquiditySig = await sendTransactionWithRetry(liquidityTx, connection, sendTransaction) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) console.log('✅ BBA/Token liquidity transferred to pool accounts:', liquiditySig) } else if (isBBAQuote) { @@ -750,7 +799,9 @@ export const useCreatePool = () => { const userBaseInfo = await connection.getAccountInfo(userBaseTokenAccount) if (!userBaseInfo) { - throw new Error('Base token account not found. Please ensure you have the required tokens.') + throw new Error( + 'Base token account not found. Please ensure you have the required tokens.' + ) } const userBaseBalance = new BN(userBaseInfo.data.slice(64, 72), 'le') @@ -792,7 +843,11 @@ export const useCreatePool = () => { // Combine all transfers const liquidityTx = new Transaction().add(transferBaseIx, transferBBAIx, syncBBAIx) - const liquiditySig = await sendTransactionWithRetry(liquidityTx, connection, sendTransaction) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) console.log('✅ Token/BBA liquidity transferred to pool accounts:', liquiditySig) } @@ -811,7 +866,9 @@ export const useCreatePool = () => { ]) if (!userBaseInfo || !userQuoteInfo) { - throw new Error('User token accounts not found. Please ensure you have the required tokens.') + throw new Error( + 'User token accounts not found. Please ensure you have the required tokens.' + ) } // Parse user balances @@ -821,8 +878,10 @@ export const useCreatePool = () => { console.log('👤 User Token Balances:', { baseBalance: userBaseBalance.toString(), quoteBalance: userQuoteBalance.toString(), - baseBalanceFormatted: userBaseBalance.div(new BN(1000000)).toString() + ` ${payload.baseToken.symbol}`, - quoteBalanceFormatted: userQuoteBalance.div(new BN(1000000)).toString() + ` ${payload.quoteToken.symbol}`, + baseBalanceFormatted: + userBaseBalance.div(new BN(1000000)).toString() + ` ${payload.baseToken.symbol}`, + quoteBalanceFormatted: + userQuoteBalance.div(new BN(1000000)).toString() + ` ${payload.quoteToken.symbol}`, requiredBase: baseAmountDaltons, requiredQuote: quoteAmountDaltons }) @@ -859,7 +918,11 @@ export const useCreatePool = () => { // Send initial liquidity transfer const liquidityTx = new Transaction().add(transferBaseIx, transferQuoteIx) - const liquiditySig = await sendTransactionWithRetry(liquidityTx, connection, sendTransaction) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) console.log('✅ Standard token liquidity transferred to pool accounts:', liquiditySig) } @@ -945,7 +1008,8 @@ export const useCreatePool = () => { console.log('🔍 Library-Generated Account List:', { accountCount: swapIx.keys.length, accounts: swapIx.keys.map( - (key, index) => `${index}. ${key.pubkey.toBase58()} (signer: ${key.isSigner}, writable: ${key.isWritable})` + (key, index) => + `${index}. ${key.pubkey.toBase58()} (signer: ${key.isSigner}, writable: ${key.isWritable})` ) }) @@ -979,7 +1043,8 @@ export const useCreatePool = () => { const { TokenSwapLayout } = await import('./onchain') const tokenSwapAccountSize = TokenSwapLayout.span // Use exact layout span instead of hardcoded 324 console.log('📏 TokenSwap account size from layout:', tokenSwapAccountSize) - const swapAccountDaltons = await connection.getMinimumBalanceForRentExemption(tokenSwapAccountSize) + const swapAccountDaltons = + await connection.getMinimumBalanceForRentExemption(tokenSwapAccountSize) const createSwapAccountIx = SystemProgram.createAccount({ fromPubkey: ownerAddress, @@ -1023,7 +1088,11 @@ export const useCreatePool = () => { console.log('⏳ Final transaction sent:', finalSig) // Wait for confirmation - const confirmation: any = await confirmTransactionWithTimeout(connection, finalSig, latestBlockhash) + const confirmation: any = await confirmTransactionWithTimeout( + connection, + finalSig, + latestBlockhash + ) if (confirmation.value?.err) { throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`) @@ -1055,7 +1124,9 @@ export const useCreatePool = () => { // Invalidate pools cache to refresh the list queryClient.invalidateQueries({ queryKey: [SERVICES_KEY.POOL.GET_POOLS] }) queryClient.invalidateQueries({ queryKey: [SERVICES_KEY.POOL.GET_POOL_STATS] }) - queryClient.invalidateQueries({ queryKey: [SERVICES_KEY.WALLET.GET_BALANCE, ownerAddress?.toBase58()] }) + queryClient.invalidateQueries({ + queryKey: [SERVICES_KEY.WALLET.GET_BALANCE, ownerAddress?.toBase58()] + }) }, onError: (error) => { console.error('❌ Pool creation failed:', error) @@ -1079,7 +1150,12 @@ export const useDepositToPool = () => { } >({ mutationKey: [SERVICES_KEY.POOL.DEPOSIT_LIQUIDITY, ownerAddress?.toBase58()], - mutationFn: async ({ pool, amountA, amountB, slippage = 1 }): Promise<{ signature: string }> => { + mutationFn: async ({ + pool, + amountA, + amountB, + slippage = 1 + }): Promise<{ signature: string }> => { if (!ownerAddress) throw new Error('Wallet not connected') console.log('🚀 Starting liquidity deposit using @bbachain/spl-token-swap library...') @@ -1233,7 +1309,10 @@ export const useDepositToPool = () => { }) // Use swap state as authoritative source (it should match pool data) - if (!tokenSwapState.tokenA.equals(poolTokenA) || !tokenSwapState.tokenB.equals(poolTokenB)) { + if ( + !tokenSwapState.tokenA.equals(poolTokenA) || + !tokenSwapState.tokenB.equals(poolTokenB) + ) { console.warn('⚠️ WARNING: Token accounts mismatch between swap state and pool data!') } } @@ -1251,19 +1330,34 @@ export const useDepositToPool = () => { if (!userAccountAInfo) { console.log('📝 Creating user token A account...') preInstructions.push( - createAssociatedTokenAccountInstruction(ownerAddress, sourceA, ownerAddress, effectiveMintA) + createAssociatedTokenAccountInstruction( + ownerAddress, + sourceA, + ownerAddress, + effectiveMintA + ) ) } if (!userAccountBInfo) { console.log('📝 Creating user token B account...') preInstructions.push( - createAssociatedTokenAccountInstruction(ownerAddress, sourceB, ownerAddress, effectiveMintB) + createAssociatedTokenAccountInstruction( + ownerAddress, + sourceB, + ownerAddress, + effectiveMintB + ) ) } if (!userPoolAccountInfo) { console.log('📝 Creating user LP token account...') preInstructions.push( - createAssociatedTokenAccountInstruction(ownerAddress, userPoolAccount, ownerAddress, tokenSwapState.poolMint) + createAssociatedTokenAccountInstruction( + ownerAddress, + userPoolAccount, + ownerAddress, + tokenSwapState.poolMint + ) ) } @@ -1353,7 +1447,9 @@ export const useDepositToPool = () => { if (totalSupply === BigInt(0)) { // Initial deposit - use geometric mean - poolTokenAmount = BigInt(Math.floor(Math.sqrt(Number(amountADaltons) * Number(amountBDaltons)))) + poolTokenAmount = BigInt( + Math.floor(Math.sqrt(Number(amountADaltons) * Number(amountBDaltons))) + ) } else { // Subsequent deposits - maintain pool ratio const tokensFromA = poolBalanceA.gt(new BN(0)) diff --git a/src/features/swap/components/SwapItem.tsx b/src/features/swap/components/SwapItem.tsx index 2977eac..61e700c 100644 --- a/src/features/swap/components/SwapItem.tsx +++ b/src/features/swap/components/SwapItem.tsx @@ -19,12 +19,14 @@ interface SwapItemProps { setInputAmount: (inputAmount: string) => void setTokenProps?: () => void noTitle?: boolean + noCheckBalance?: boolean disable?: boolean } export default function SwapItem({ type, noTitle = false, + noCheckBalance = false, disable = false, tokenProps, inputAmount, @@ -33,7 +35,7 @@ export default function SwapItem({ setTokenProps, setInputAmount }: SwapItemProps) { - const isBalanceNotEnough = Number(inputAmount) > balance + const isBalanceNotEnough = !noCheckBalance && Number(inputAmount) > balance const isAmountPositive = Number(inputAmount) >= 0 const isInValid = isBalanceNotEnough || !isAmountPositive @@ -95,9 +97,13 @@ export default function SwapItem({ Max )} - {isBalanceNotEnough &&

Balance is not enough

} + {isBalanceNotEnough && ( +

Balance is not enough

+ )} {!isAmountPositive && ( -

Amount can not be negative

+

+ Amount can not be negative +

)}
diff --git a/src/features/swap/components/TokenListDialog.tsx b/src/features/swap/components/TokenListDialog.tsx index f689eb1..e4968d0 100644 --- a/src/features/swap/components/TokenListDialog.tsx +++ b/src/features/swap/components/TokenListDialog.tsx @@ -7,10 +7,17 @@ import { AiOutlineQuestionCircle } from 'react-icons/ai' import { IoSearchOutline } from 'react-icons/io5' import { Button } from '@/components/ui/button' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogClose } from '@/components/ui/dialog' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogClose +} from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { cn } from '@/lib/utils' +import { ExtendedMintInfo } from '@/staticData/tokens' import { useGetAvailableTokens } from '../services' import { TTokenProps } from '../types' @@ -54,7 +61,11 @@ export default function TokenListDialog({ }, [search]) // Use the enhanced API hook with search - const { data: tokensResponse, isLoading, error } = useGetAvailableTokens(debouncedSearch.trim() || undefined) + const { + data: tokensResponse, + isLoading, + error + } = useGetAvailableTokens(debouncedSearch.trim() || undefined) const tokens = tokensResponse?.data || [] @@ -85,7 +96,9 @@ export default function TokenListDialog({ } // Filter out already selected token from the opposite side - const filteredTokens = tokens.filter((token: TTokenProps) => { + const filteredTokens = tokens.filter((token: ExtendedMintInfo) => { + if (token.isNative) return false + if (type === 'from' && selectedTo) { return token.address !== selectedTo.address } @@ -158,7 +171,9 @@ export default function TokenListDialog({ {!isLoading && !error && filteredTokens.length === 0 && (

No tokens found

- {search &&

Try searching for a different term

} + {search && ( +

Try searching for a different term

+ )}
)} @@ -205,9 +220,13 @@ export default function TokenListDialog({ {/* Token Info */}
-
{token.symbol}
+
+ {token.symbol} +
{token.tags?.includes('stablecoin') && ( - Stable + + Stable + )} {token.tags?.includes('native') && ( diff --git a/src/features/swap/services.ts b/src/features/swap/services.ts index b888a7b..81ba9a5 100644 --- a/src/features/swap/services.ts +++ b/src/features/swap/services.ts @@ -9,18 +9,38 @@ import { createCloseAccountInstruction, ASSOCIATED_TOKEN_PROGRAM_ID } from '@bbachain/spl-token' -import { createSwapInstruction, PROGRAM_ID as TOKEN_SWAP_PROGRAM_ID } from '@bbachain/spl-token-swap' +import { + createSwapInstruction, + PROGRAM_ID as TOKEN_SWAP_PROGRAM_ID +} from '@bbachain/spl-token-swap' import { useConnection, useWallet } from '@bbachain/wallet-adapter-react' -import { PublicKey, Transaction, SystemProgram, Keypair } from '@bbachain/web3.js' +import { + PublicKey, + Transaction, + SystemProgram, + Keypair, + TransactionInstruction +} from '@bbachain/web3.js' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import axios from 'axios' import { TGetTokensResponse } from '@/app/api/tokens/route' import ENDPOINTS from '@/constants/endpoint' import SERVICES_KEY from '@/constants/service' -import { wrapBBAtoWBBA, unwrapWBBAtoBBA, createTokenAccountManual, bbaTodaltons, daltonsToBBA } from '@/lib/bbaWrapping' +import { + wrapBBAtoWBBA, + unwrapWBBAtoBBA, + createTokenAccountManual, + bbaTodaltons, + daltonsToBBA +} from '@/lib/bbaWrapping' import { getTokenAccounts2 } from '@/lib/tokenAccount' -import { isNativeBBA, getWBBAMintAddress, isBBAPool, getBBAPositionInPool } from '@/staticData/tokens' +import { + isNativeBBA, + getWBBAMintAddress, + isBBAPool, + getBBAPositionInPool +} from '@/staticData/tokens' import { getAllPoolsFromOnchain, OnchainPoolData } from '../liquidityPool/onchain' import { TGetTokenDataResponse, TGetTokenResponse } from '../tokens/types' @@ -116,7 +136,9 @@ export const useGetTokensFromAPI = (searchQuery?: string) => params.append('includeAddress', 'true') } - const url = searchQuery ? `${ENDPOINTS.API.GET_TOKENS}?${params.toString()}` : ENDPOINTS.API.GET_TOKENS + const url = searchQuery + ? `${ENDPOINTS.API.GET_TOKENS}?${params.toString()}` + : ENDPOINTS.API.GET_TOKENS const res = await axios.get(url) return res.data as TGetTokensResponse @@ -138,7 +160,9 @@ export const useGetSwappableTokens2 = () => { }) ) - const filteredTokenData = tokenData.filter((token): token is TGetTokenDataResponse => token !== null) + const filteredTokenData = tokenData.filter( + (token): token is TGetTokenDataResponse => token !== null + ) return { message: `Successfully get token`, @@ -165,7 +189,11 @@ export const useGetSwappableTokens3 = () => { throw new Error(`Invalid owner: ${JSON.stringify(accountInfo.owner)}`) } - const data = new Uint8Array(accountInfo.data.buffer, accountInfo.data.byteOffset, accountInfo.data.byteLength) + const data = new Uint8Array( + accountInfo.data.buffer, + accountInfo.data.byteOffset, + accountInfo.data.byteLength + ) return { data: TokenSwapLayout.decode(data) } } }) @@ -180,18 +208,6 @@ export const useGetUserBalanceByMint = ({ mintAddress }: { mintAddress: string } if (!ownerAddress) throw new Error('No wallet connected') try { - // Import the native BBA detection function - const { isNativeBBA } = await import('@/staticData/tokens') - - // Handle native BBA token differently - if (isNativeBBA(mintAddress)) { - console.log('🪙 Fetching native BBA balance...') - const balance = await connection.getBalance(ownerAddress) - console.log('💰 Native BBA balance:', balance, 'daltons') - return { balance } - } - - // Handle SPL tokens (existing logic) console.log('🪙 Fetching SPL token balance for:', mintAddress) const mint = new PublicKey(mintAddress) const ata = await getAssociatedTokenAddress(mint, ownerAddress) @@ -256,18 +272,29 @@ export const useGetSwapTransactionByMint = ({ const amountPayload = Number(amount) * 10 ** decimals const slippageBps = slippage * 100 return useQuery({ - queryKey: [SERVICES_KEY.SWAP.GET_SWAP_TRANSACTION, swapType, inputMint, outputMint, amount, decimals, slippage], + queryKey: [ + SERVICES_KEY.SWAP.GET_SWAP_TRANSACTION, + swapType, + inputMint, + outputMint, + amount, + decimals, + slippage + ], queryFn: async () => { const baseType = swapType === 'BaseIn' ? 'in' : 'out' - const res = await axios.get(ENDPOINTS.RAYDIUM.GET_SWAP_TRANSACTION_BY_MINT + `/swap-base-${baseType}`, { - params: { - inputMint, - outputMint, - amount: amountPayload, - slippageBps, - txVersion: 'V0' + const res = await axios.get( + ENDPOINTS.RAYDIUM.GET_SWAP_TRANSACTION_BY_MINT + `/swap-base-${baseType}`, + { + params: { + inputMint, + outputMint, + amount: amountPayload, + slippageBps, + txVersion: 'V0' + } } - }) + ) return res.data }, enabled: !!amount || amount === '0', @@ -346,7 +373,11 @@ export function calculatePriceImpact( * @param outputMint - Output token mint address * @returns Best pool for the swap or null if no pool found */ -export function findBestPool(pools: OnchainPoolData[], inputMint: string, outputMint: string): OnchainPoolData | null { +export function findBestPool( + pools: OnchainPoolData[], + inputMint: string, + outputMint: string +): OnchainPoolData | null { const availablePools = pools.filter((pool) => { const hasInputToken = pool.mintA.address === inputMint || pool.mintB.address === inputMint const hasOutputToken = pool.mintA.address === outputMint || pool.mintB.address === outputMint @@ -450,8 +481,12 @@ export const useGetSwapQuote = ({ inputReserve, outputReserve, feeRate: bestPool.feeRate, - inputReserveRaw: isInputTokenA ? bestPool.reserveA.toString() : bestPool.reserveB.toString(), - outputReserveRaw: isInputTokenA ? bestPool.reserveB.toString() : bestPool.reserveA.toString() + inputReserveRaw: isInputTokenA + ? bestPool.reserveA.toString() + : bestPool.reserveB.toString(), + outputReserveRaw: isInputTokenA + ? bestPool.reserveB.toString() + : bestPool.reserveA.toString() }) // Validate reserves @@ -474,8 +509,18 @@ export const useGetSwapQuote = ({ feeRateDisplay: `${bestPool.feeRate}% -> ${(feeRateDecimal * 100).toFixed(2)}%` }) - const outputAmount = calculateOutputAmount(inputAmountNumber, inputReserve, outputReserve, feeRateDecimal) - const priceImpact = calculatePriceImpact(inputAmountNumber, inputReserve, outputReserve, feeRateDecimal) + const outputAmount = calculateOutputAmount( + inputAmountNumber, + inputReserve, + outputReserve, + feeRateDecimal + ) + const priceImpact = calculatePriceImpact( + inputAmountNumber, + inputReserve, + outputReserve, + feeRateDecimal + ) console.log('📈 Calculation results:', { inputAmount: inputAmountNumber, @@ -516,7 +561,12 @@ export const useGetSwapQuote = ({ throw error } }, - enabled: !!inputMint && !!outputMint && !!inputAmount && Number(inputAmount) > 0 && inputMint !== outputMint, + enabled: + !!inputMint && + !!outputMint && + !!inputAmount && + Number(inputAmount) > 0 && + inputMint !== outputMint, staleTime: 10000, // 10 seconds refetchInterval: 15000, // Refresh every 15 seconds retry: (failureCount, error) => { @@ -541,7 +591,9 @@ export const useGetAvailableTokens = (searchQuery?: string) => { params.append('includeAddress', 'true') } - const url = searchQuery ? `${ENDPOINTS.API.GET_TOKENS}?${params.toString()}` : ENDPOINTS.API.GET_TOKENS + const url = searchQuery + ? `${ENDPOINTS.API.GET_TOKENS}?${params.toString()}` + : ENDPOINTS.API.GET_TOKENS const response = await axios.get(url) console.log('✅ Tokens fetched:', { @@ -569,7 +621,9 @@ export const useGetPoolsForToken = (tokenAddress: string) => { const allPools = await getAllPoolsFromOnchain(connection) - return allPools.filter((pool) => pool.mintA.address === tokenAddress || pool.mintB.address === tokenAddress) + return allPools.filter( + (pool) => pool.mintA.address === tokenAddress || pool.mintB.address === tokenAddress + ) }, enabled: !!tokenAddress, staleTime: 60000 // 1 minute @@ -651,8 +705,6 @@ export const useExecuteSwap = () => { // Calculate all required values for debugging const isInputTokenA = pool.mintA.address === effectiveInputMint - const inputMintPubkey = new PublicKey(effectiveInputMint) - const outputMintPubkey = new PublicKey(effectiveOutputMint) const inputAmountNumber = Number(inputAmount) const inputDecimals = isInputTokenA ? pool.mintA.decimals : pool.mintB.decimals @@ -668,7 +720,12 @@ export const useExecuteSwap = () => { : Number(pool.reserveA) / Math.pow(10, pool.mintA.decimals) const feeRateDecimal = pool.feeRate > 1 ? pool.feeRate / 100 : pool.feeRate - const expectedOutput = calculateOutputAmount(inputAmountNumber, inputReserve, outputReserve, feeRateDecimal) + const expectedOutput = calculateOutputAmount( + inputAmountNumber, + inputReserve, + outputReserve, + feeRateDecimal + ) const expectedOutputDaltons = Math.floor(expectedOutput * Math.pow(10, outputDecimals)) const slippageMultiplier = 1 - slippage / 100 @@ -695,101 +752,17 @@ export const useExecuteSwap = () => { requiresWrapping: isBBASwap }) - let userInputTokenAccount: PublicKey - let userOutputTokenAccount: PublicKey - let needsUnwrapping = false - // Track whether we created a temporary WBBA ATA for BBA → Token swaps - let createdWBBAInputAccount = false - let preTxInstructions: any[] = [] - let postTxInstructions: any[] = [] - - if (isBBASwap) { - console.log('🪙 BBA swap detected - using WBBA for native BBA') - - if (isInputBBA) { - // BBA → Token: Use WBBA account for input - console.log('💰 BBA → Token swap: Using WBBA account for input') - - // Check BBA balance - const userBBABalance = await connection.getBalance(publicKey) - const requiredBBA = bbaTodaltons(inputAmountNumber) - - if (userBBABalance < requiredBBA) { - throw new Error( - `Insufficient BBA balance. Required: ${inputAmountNumber} BBA, Available: ${daltonsToBBA(userBBABalance)} BBA` - ) - } - - // Use WBBA (NATIVE_MINT) associated token account - userInputTokenAccount = await getAssociatedTokenAddress(NATIVE_MINT, publicKey) - - // Create WBBA account if it doesn't exist and fund it - const wbbaAccountInfo = await connection.getAccountInfo(userInputTokenAccount) - if (!wbbaAccountInfo) { - console.log('📝 Creating WBBA account and funding with BBA...') - const createWBBAIx = createAssociatedTokenAccountInstruction( - publicKey, - userInputTokenAccount, - publicKey, - NATIVE_MINT - ) - preTxInstructions.push(createWBBAIx) - createdWBBAInputAccount = true - } - - // Add instructions to transfer BBA and sync - const transferBBAIx = SystemProgram.transfer({ - fromPubkey: publicKey, - toPubkey: userInputTokenAccount, - daltons: requiredBBA - }) - const syncBBAIx = createSyncNativeInstruction(userInputTokenAccount) - preTxInstructions.push(transferBBAIx, syncBBAIx) - - // For output, use standard token account - userOutputTokenAccount = await getAssociatedTokenAddress(new PublicKey(outputMint), publicKey) - - // If we created a temporary WBBA input ATA for this swap, close it after swap to reclaim rent - if (createdWBBAInputAccount) { - const closeWbbaInputIx = createCloseAccountInstruction(userInputTokenAccount, publicKey, publicKey) - postTxInstructions.push(closeWbbaInputIx) - } - } else if (isOutputBBA) { - // Token → BBA: Swap to WBBA then unwrap - console.log('💰 Token → BBA swap: Will receive WBBA and unwrap to BBA') - - // For input, use standard token account - userInputTokenAccount = await getAssociatedTokenAddress(new PublicKey(inputMint), publicKey) - - // For output, use WBBA (NATIVE_MINT) associated token account - userOutputTokenAccount = await getAssociatedTokenAddress(NATIVE_MINT, publicKey) - - // Create WBBA account if it doesn't exist - const wbbaAccountInfo = await connection.getAccountInfo(userOutputTokenAccount) - if (!wbbaAccountInfo) { - console.log('📝 Creating WBBA account for output...') - const createWBBAIx = createAssociatedTokenAccountInstruction( - publicKey, - userOutputTokenAccount, - publicKey, - NATIVE_MINT - ) - preTxInstructions.push(createWBBAIx) - } + const preTxInstructions: TransactionInstruction[] = [] + const postTxInstructions: TransactionInstruction[] = [] - // Add instruction to unwrap WBBA to BBA after swap - needsUnwrapping = true - const closeWBBAIx = createCloseAccountInstruction(userOutputTokenAccount, publicKey, publicKey) - postTxInstructions.push(closeWBBAIx) - } else { - throw new Error('Unexpected BBA swap state') - } - } else { - // Standard token/token swap - console.log('🔄 Standard token/token swap') - userInputTokenAccount = await getAssociatedTokenAddress(new PublicKey(inputMint), publicKey) - userOutputTokenAccount = await getAssociatedTokenAddress(new PublicKey(outputMint), publicKey) - } + const userInputTokenAccount = await getAssociatedTokenAddress( + new PublicKey(inputMint), + publicKey + ) + const userOutputTokenAccount = await getAssociatedTokenAddress( + new PublicKey(outputMint), + publicKey + ) // Get pool info from BBA Chain const poolInfo = pool.swapData @@ -807,8 +780,12 @@ export const useExecuteSwap = () => { authority: swapAuthority, userTransferAuthority: publicKey, source: userInputTokenAccount, - swapSource: isInputTokenA ? new PublicKey(poolInfo.tokenAccountA) : new PublicKey(poolInfo.tokenAccountB), - swapDestination: isInputTokenA ? new PublicKey(poolInfo.tokenAccountB) : new PublicKey(poolInfo.tokenAccountA), + swapSource: isInputTokenA + ? new PublicKey(poolInfo.tokenAccountA) + : new PublicKey(poolInfo.tokenAccountB), + swapDestination: isInputTokenA + ? new PublicKey(poolInfo.tokenAccountB) + : new PublicKey(poolInfo.tokenAccountA), destination: userOutputTokenAccount, poolMint: new PublicKey(poolInfo.tokenPool), feeAccount: new PublicKey(poolInfo.feeAccount), @@ -864,7 +841,9 @@ export const useExecuteSwap = () => { // Add post-swap instructions (BBA unwrapping if needed) if (postTxInstructions.length > 0) { - console.log(`📝 Adding ${postTxInstructions.length} post-swap instructions (BBA unwrapping)`) + console.log( + `📝 Adding ${postTxInstructions.length} post-swap instructions (BBA unwrapping)` + ) postTxInstructions.forEach((ix) => transaction.add(ix)) } @@ -888,7 +867,12 @@ export const useExecuteSwap = () => { inputAmount: inputAmountNumber, outputAmount: expectedOutput, actualOutputAmount: expectedOutput, // TODO: Get actual from transaction logs - priceImpact: calculatePriceImpact(inputAmountNumber, inputReserve, outputReserve, feeRateDecimal), + priceImpact: calculatePriceImpact( + inputAmountNumber, + inputReserve, + outputReserve, + feeRateDecimal + ), executionTime: Date.now() - startTime, poolDetail: pool } as SwapExecutionResult @@ -906,12 +890,24 @@ export const useExecuteSwap = () => { queryClient.invalidateQueries({ queryKey: [SERVICES_KEY.POOL.GET_POOLS] }) queryClient.invalidateQueries({ queryKey: [SERVICES_KEY.POOL.GET_POOL_BY_ID, poolId] }) queryClient.invalidateQueries({ - queryKey: [SERVICES_KEY.POOL.GET_TRANSACTIONS_BY_POOL_ID, poolId, baseMint?.address, quoteMint?.address] + queryKey: [ + SERVICES_KEY.POOL.GET_TRANSACTIONS_BY_POOL_ID, + poolId, + baseMint?.address, + quoteMint?.address + ] + }) + queryClient.invalidateQueries({ + queryKey: [ + SERVICES_KEY.POOL.GET_TRANSACTIONS_BY_POOL_ID, + poolId, + quoteMint?.address, + baseMint?.address + ] }) queryClient.invalidateQueries({ - queryKey: [SERVICES_KEY.POOL.GET_TRANSACTIONS_BY_POOL_ID, poolId, quoteMint?.address, baseMint?.address] + queryKey: [SERVICES_KEY.WALLET.GET_BALANCE, publicKey?.toBase58()] }) - queryClient.invalidateQueries({ queryKey: [SERVICES_KEY.WALLET.GET_BALANCE, publicKey?.toBase58()] }) }, onError: (error) => { console.error('❌ Swap failed:', error) From 55272a84922d05f8cc52bec7c0fda08ce5472d5a Mon Sep 17 00:00:00 2001 From: fiqriBTI Date: Sat, 23 Aug 2025 18:52:10 +0700 Subject: [PATCH 08/12] feat: error pool creation using WBBA --- src/features/liquidityPool/services.ts | 48 +++++++++++++++++--------- src/features/swap/services.ts | 29 +++------------- 2 files changed, 35 insertions(+), 42 deletions(-) diff --git a/src/features/liquidityPool/services.ts b/src/features/liquidityPool/services.ts index 4aa1da9..7e0a000 100644 --- a/src/features/liquidityPool/services.ts +++ b/src/features/liquidityPool/services.ts @@ -7,7 +7,8 @@ import { createInitializeMintInstruction, getMint, NATIVE_MINT, - createSyncNativeInstruction + createSyncNativeInstruction, + createApproveInstruction } from '@bbachain/spl-token' import { CurveType, @@ -735,12 +736,19 @@ export const useCreatePool = () => { console.log('💰 BBA/Token pool (BBA as base)') // Check BBA balance (native daltons) - const userBBABalance = await connection.getBalance(ownerAddress) - const requiredBBA = bbaTodaltons(liquidityBaseAmount) + const userWBBATokenAccount = await getAssociatedTokenAddress(NATIVE_MINT, ownerAddress) + console.log('ini lewat bang') + const userWBBAInfo = await connection.getAccountInfo(userWBBATokenAccount) + if (!userWBBAInfo) { + throw new Error( + 'WBBA token account not found. Please ensure you have the required tokens.' + ) + } - if (userBBABalance < requiredBBA) { + const userWBBABalance = new BN(userWBBAInfo.data.slice(64, 72), 'le') + if (userWBBABalance.lt(new BN(baseAmountDaltons))) { throw new Error( - `Insufficient BBA balance. Required: ${liquidityBaseAmount} BBA, Available: ${daltonsToBBA(userBBABalance)} BBA` + `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userWBBABalance.div(new BN(1000000)).toString()}` ) } @@ -761,19 +769,16 @@ export const useCreatePool = () => { ) } - // Transfer BBA to pool (using special BBA handling) - console.log('🔄 Transferring BBA to pool account...') - const transferBBAIx = SystemProgram.transfer({ - fromPubkey: ownerAddress, - toPubkey: swapTokenAAccount, - daltons: requiredBBA - }) - - const { createSyncNativeInstruction } = await import('@bbachain/spl-token') - const syncBBAIx = createSyncNativeInstruction(swapTokenAAccount) - // Transfer quote token (standard SPL transfer) const { createTransferInstruction } = await import('@bbachain/spl-token') + + const transferBBAIx = createTransferInstruction( + userWBBATokenAccount, + swapTokenAAccount, + ownerAddress, + baseAmountDaltons + ) + const transferQuoteIx = createTransferInstruction( userQuoteTokenAccount, swapTokenBAccount, @@ -781,8 +786,17 @@ export const useCreatePool = () => { quoteAmountDaltons ) + console.log('re-check', { + swapTokenA: swapTokenAAccount.toBase58(), + wbbaTokenAccount: userWBBATokenAccount.toBase58(), + wbbaDaltons: baseAmountDaltons, + wbbaAmount: baseAmount + }) + // Combine all transfers - const liquidityTx = new Transaction().add(transferBBAIx, syncBBAIx, transferQuoteIx) + const liquidityTx = new Transaction().add(transferBBAIx, transferQuoteIx) + + console.log('this causes error') const liquiditySig = await sendTransactionWithRetry( liquidityTx, connection, diff --git a/src/features/swap/services.ts b/src/features/swap/services.ts index 81ba9a5..9e5fbbe 100644 --- a/src/features/swap/services.ts +++ b/src/features/swap/services.ts @@ -1,46 +1,25 @@ import * as BufferLayout from '@bbachain/buffer-layout' import { getAssociatedTokenAddress, - createAssociatedTokenAccountInstruction, TOKEN_PROGRAM_ID, NATIVE_MINT, - createApproveInstruction, - createSyncNativeInstruction, - createCloseAccountInstruction, - ASSOCIATED_TOKEN_PROGRAM_ID + createApproveInstruction } from '@bbachain/spl-token' import { createSwapInstruction, PROGRAM_ID as TOKEN_SWAP_PROGRAM_ID } from '@bbachain/spl-token-swap' import { useConnection, useWallet } from '@bbachain/wallet-adapter-react' -import { - PublicKey, - Transaction, - SystemProgram, - Keypair, - TransactionInstruction -} from '@bbachain/web3.js' +import { PublicKey, Transaction, TransactionInstruction } from '@bbachain/web3.js' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import axios from 'axios' import { TGetTokensResponse } from '@/app/api/tokens/route' import ENDPOINTS from '@/constants/endpoint' import SERVICES_KEY from '@/constants/service' -import { - wrapBBAtoWBBA, - unwrapWBBAtoBBA, - createTokenAccountManual, - bbaTodaltons, - daltonsToBBA -} from '@/lib/bbaWrapping' +import { bbaTodaltons } from '@/lib/bbaWrapping' import { getTokenAccounts2 } from '@/lib/tokenAccount' -import { - isNativeBBA, - getWBBAMintAddress, - isBBAPool, - getBBAPositionInPool -} from '@/staticData/tokens' +import { isNativeBBA } from '@/staticData/tokens' import { getAllPoolsFromOnchain, OnchainPoolData } from '../liquidityPool/onchain' import { TGetTokenDataResponse, TGetTokenResponse } from '../tokens/types' From c941384d4f1e54cf3e8f8a635360730dbc4cea80 Mon Sep 17 00:00:00 2001 From: fiqriBTI Date: Sat, 23 Aug 2025 19:12:41 +0700 Subject: [PATCH 09/12] feat: implemented all spl create pool service and old-service for development --- src/features/liquidityPool/old-services.ts | 852 +++++++++++++++++++++ src/features/liquidityPool/services.ts | 275 ++----- 2 files changed, 923 insertions(+), 204 deletions(-) create mode 100644 src/features/liquidityPool/old-services.ts diff --git a/src/features/liquidityPool/old-services.ts b/src/features/liquidityPool/old-services.ts new file mode 100644 index 0000000..b20f959 --- /dev/null +++ b/src/features/liquidityPool/old-services.ts @@ -0,0 +1,852 @@ +// development temporary file + +import { + TOKEN_PROGRAM_ID, + getAssociatedTokenAddress, + createAssociatedTokenAccountInstruction, + getMinimumBalanceForRentExemptMint, + MINT_SIZE, + createInitializeMintInstruction, + getMint, + NATIVE_MINT, + createSyncNativeInstruction +} from '@bbachain/spl-token' +import { + CurveType, + createInitializeInstruction, + PROGRAM_ID as TOKEN_SWAP_PROGRAM_ID, + createDepositAllTokenTypesInstruction, + TokenSwap +} from '@bbachain/spl-token-swap' +import { useConnection, useWallet } from '@bbachain/wallet-adapter-react' +import { Connection, Keypair, PublicKey, SystemProgram, Transaction } from '@bbachain/web3.js' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import axios from 'axios' +import BN from 'bn.js' + +import ENDPOINTS from '@/constants/endpoint' +import SERVICES_KEY from '@/constants/service' +import { addBBAToPoolAccount, bbaTodaltons, daltonsToBBA } from '@/lib/bbaWrapping' +import { formatTokenToDaltons } from '@/lib/utils' +import { + isBBAPool, + getBBAPositionInPool, + isNativeBBA, + getWBBAMintAddress +} from '@/staticData/tokens' + +import { useGetCoinGeckoTokenPrice } from '../swap/services' +import { getCoinGeckoId } from '../swap/utils' + +import { TransactionListProps } from './components/TransactionColumns' +import { getAllPoolsFromOnchain, OnchainPoolData } from './onchain' +import { + MintInfo, + PoolData, + TCreatePoolPayload, + TCreatePoolResponse, + TGetPoolsResponse, + TGetPoolTransactionResponse, + TransactionData +} from './types' +import { processTransactionData } from './utils' + +// Enhanced retry configuration +const RETRY_CONFIG = { + attempts: 3, + delay: (attemptIndex: number) => Math.min(1000 * 2 ** attemptIndex, 30000), // Exponential backoff + retryCondition: (error: any) => { + // Retry on network errors, timeouts, and 5xx errors + return !error.response || error.response.status >= 500 || error.code === 'NETWORK_ERROR' + } +} + +// Transaction helper with retry and delay +const sendTransactionWithRetry = async ( + transaction: Transaction, + connection: Connection, + sendTransaction: any, + options?: { signers?: any[]; skipPreflight?: boolean; preflightCommitment?: string } +) => { + for (let attempt = 1; attempt <= RETRY_CONFIG.attempts; attempt++) { + try { + console.log(`📤 Sending transaction (attempt ${attempt}/${RETRY_CONFIG.attempts})...`) + + const signature = await sendTransaction(transaction, connection, options) + console.log(`✅ Transaction sent successfully:`, signature) + + return signature + } catch (error: any) { + console.error(`❌ Transaction attempt ${attempt} failed:`, error) + + if (attempt === RETRY_CONFIG.attempts || !RETRY_CONFIG.retryCondition(error)) { + throw error + } + + // Wait before retry + const delay = RETRY_CONFIG.delay(attempt - 1) + console.log(`⏳ Waiting ${delay}ms before retry...`) + await new Promise((resolve) => setTimeout(resolve, delay)) + } + } +} + +// Confirmation helper with timeout +const confirmTransactionWithTimeout = async ( + connection: Connection, + signature: string, + latestBlockhash: any, + timeoutMs = 30000 +) => { + console.log(`⏳ Confirming transaction: ${signature}`) + + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Transaction confirmation timeout')), timeoutMs) + }) + + const confirmPromise = connection.confirmTransaction( + { signature, ...latestBlockhash }, + 'confirmed' + ) + + const confirmation: any = await Promise.race([confirmPromise, timeoutPromise]) + + if (confirmation.value?.err) { + throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`) + } + + console.log(`✅ Transaction confirmed: ${signature}`) + return confirmation +} + +// Cache configuration +const CACHE_CONFIG = { + staleTime: 60000, // 1 minute - data is considered fresh + gcTime: 300000, // 5 minutes - data stays in cache (renamed from cacheTime) + refetchInterval: 300000, // Auto-refetch every 5 minutes + refetchOnWindowFocus: true, + refetchOnMount: true, + refetchOnReconnect: true +} + +// Create pool mutation remains the same but with better error handling +export const useCreatePool = () => { + const { connection } = useConnection() + const { publicKey: ownerAddress, sendTransaction, signTransaction } = useWallet() + const queryClient = useQueryClient() + + return useMutation({ + mutationKey: [SERVICES_KEY.POOL.CREATE_POOL, ownerAddress?.toBase58()], + mutationFn: async (payload) => { + if (!ownerAddress) { + throw new Error('Wallet not connected. Please connect your wallet to create a pool.') + } + + if (!signTransaction) { + throw new Error('Wallet does not support transaction signing.') + } + + console.log('🚀 Starting BBAChain Liquidity Pool creation process...') + console.log('📊 Pool Configuration:', { + baseToken: `${payload.baseToken.symbol} (${payload.baseToken.address})`, + quoteToken: `${payload.quoteToken.symbol} (${payload.quoteToken.address})`, + feeTier: `${payload.feeTier}%`, + initialPrice: `${payload.initialPrice} ${payload.baseToken.symbol} per ${payload.quoteToken.symbol}`, + baseAmount: `${payload.baseTokenAmount} ${payload.baseToken.symbol}`, + quoteAmount: `${payload.quoteTokenAmount} ${payload.quoteToken.symbol}`, + programId: TOKEN_SWAP_PROGRAM_ID.toBase58() + }) + + try { + // Validate pool creation requirements + console.log('🔍 Validating pool creation requirements...') + + // Check if tokens are different + if (payload.baseToken.address === payload.quoteToken.address) { + throw new Error('Base and quote tokens must be different') + } + + // Check if amounts are valid + const baseAmount = parseFloat(payload.baseTokenAmount) + const quoteAmount = parseFloat(payload.quoteTokenAmount) + + if (baseAmount <= 0 || quoteAmount <= 0) { + throw new Error('Token amounts must be greater than zero') + } + + // Check if initial price is valid + const initialPrice = parseFloat(payload.initialPrice) + if (initialPrice <= 0) { + throw new Error('Initial price must be greater than zero') + } + + console.log('✅ Pool validation passed') + + let latestBlockhash = await connection.getLatestBlockhash('confirmed') + const daltons = await getMinimumBalanceForRentExemptMint(connection) + const baseMint = new PublicKey(payload.baseToken.address) + const quoteMint = new PublicKey(payload.quoteToken.address) + + // === BBA Pool Detection === + const isBBAPoolPair = isBBAPool(payload.baseToken.address, payload.quoteToken.address) + const bbaPosition = getBBAPositionInPool( + payload.baseToken.address, + payload.quoteToken.address + ) + const isBBABase = bbaPosition === 'base' + const isBBAQuote = bbaPosition === 'quote' + + console.log('🔑 Token mint addresses:', { + baseMint: baseMint.toBase58(), + quoteMint: quoteMint.toBase58(), + isBBAPool: isBBAPoolPair, + bbaPosition: bbaPosition, + requiresSpecialHandling: isBBAPoolPair + }) + + if (isBBAPoolPair) { + console.log('🪙 BBA Pool detected - will use special handling for native token wrapping') + } + + const tokenSwap = Keypair.generate() + const [authority, bumpSeed] = PublicKey.findProgramAddressSync( + [tokenSwap.publicKey.toBuffer()], + TOKEN_SWAP_PROGRAM_ID + ) + + console.log('🔑 Authority Derivation:', { + tokenSwap: tokenSwap.publicKey.toBase58(), + authority: authority.toBase58(), + bumpSeed, + programId: TOKEN_SWAP_PROGRAM_ID.toBase58() + }) + + console.log('authority ', authority.toBase58()) + + // Create swap's token A account (owned by authority) + const swapTokenAAccount = await getAssociatedTokenAddress(baseMint, authority, true) + console.log('🏦 Swap Token A Account:', swapTokenAAccount.toBase58()) + const baseTokenInfo = await connection.getAccountInfo(swapTokenAAccount) + + if (!baseTokenInfo) { + console.log('📝 Creating swap token A account...') + const ix = createAssociatedTokenAccountInstruction( + ownerAddress, + swapTokenAAccount, + authority, + baseMint + ) + const tx = new Transaction().add(ix) + const sig = await sendTransactionWithRetry(tx, connection, sendTransaction) + await confirmTransactionWithTimeout(connection, sig, latestBlockhash) + console.log('✅ Swap Token A account created:', sig) + + // Add delay to prevent wallet extension race condition + console.log('⏳ Waiting 2 seconds before next transaction...') + await new Promise((resolve) => setTimeout(resolve, 2000)) + } + + // Create swap's token B account (owned by authority) + const swapTokenBAccount = await getAssociatedTokenAddress(quoteMint, authority, true) + console.log('🏦 Swap Token B Account:', swapTokenBAccount.toBase58()) + const quoteTokenInfo = await connection.getAccountInfo(swapTokenBAccount) + if (!quoteTokenInfo) { + console.log('📝 Creating swap token B account...') + const ix = createAssociatedTokenAccountInstruction( + ownerAddress, + swapTokenBAccount, + authority, + quoteMint + ) + const tx = new Transaction().add(ix) + const sig = await sendTransactionWithRetry(tx, connection, sendTransaction) + await confirmTransactionWithTimeout(connection, sig, latestBlockhash) + console.log('✅ Swap Token B account created:', sig) + + // Add delay to prevent wallet extension race condition + console.log('⏳ Waiting 2 seconds before next transaction...') + await new Promise((resolve) => setTimeout(resolve, 2000)) + } + + // === Create LP token mint === + console.log('🏭 Creating LP token mint...') + const poolMint = Keypair.generate() + + console.log('🔑 Generated LP token mint:', poolMint.publicKey.toBase58()) + + // Step 1: Create mint account (only requires poolMint signature) + const createMintIx = SystemProgram.createAccount({ + fromPubkey: ownerAddress, + newAccountPubkey: poolMint.publicKey, + space: MINT_SIZE, + daltons, + programId: TOKEN_PROGRAM_ID + }) + + // Step 2: Initialize mint with TEMPORARY authority (owner), will transfer later + const initMintIx = createInitializeMintInstruction( + poolMint.publicKey, + 2, + ownerAddress, + null + ) + + // Step 3: Create user's LP token account + const poolTokenAccount = await getAssociatedTokenAddress(poolMint.publicKey, ownerAddress) + const ataIx = createAssociatedTokenAccountInstruction( + ownerAddress, + poolTokenAccount, + ownerAddress, + poolMint.publicKey + ) + + // Create transaction using sendTransaction with signers (BBA wallet compatible approach) + console.log('📝 Creating LP mint transaction with temporary authority...') + const createPoolTx = new Transaction().add(createMintIx, initMintIx, ataIx) + + // Use sendTransaction which can handle additional signers + const poolSig = await sendTransactionWithRetry(createPoolTx, connection, sendTransaction, { + signers: [poolMint], // Pass poolMint as additional signer + skipPreflight: false, + preflightCommitment: 'confirmed' + }) + + console.log('⏳ LP mint transaction sent:', poolSig) + + latestBlockhash = await connection.getLatestBlockhash('confirmed') + await confirmTransactionWithTimeout(connection, poolSig, latestBlockhash) + console.log('✅ LP token mint created successfully') + + // Add delay to prevent wallet extension race condition + console.log('⏳ Waiting 2 seconds before transferring authority...') + await new Promise((resolve) => setTimeout(resolve, 2000)) + + // === CRITICAL: Transfer Pool Mint Authority to Swap Authority === + console.log('🔄 Transferring pool mint authority to swap authority...') + const { createSetAuthorityInstruction, AuthorityType } = await import('@bbachain/spl-token') + + const setAuthorityIx = createSetAuthorityInstruction( + poolMint.publicKey, + ownerAddress, // Current authority + AuthorityType.MintTokens, + authority // New authority (swap authority) + ) + + const transferAuthorityTx = new Transaction().add(setAuthorityIx) + const transferSig = await sendTransactionWithRetry( + transferAuthorityTx, + connection, + sendTransaction + ) + await confirmTransactionWithTimeout(connection, transferSig, latestBlockhash) + console.log('✅ Pool mint authority transferred to swap authority:', transferSig) + + // Add delay to prevent wallet extension race condition + console.log('⏳ Waiting 2 seconds before next transaction...') + await new Promise((resolve) => setTimeout(resolve, 2000)) + + // === Enhanced Fee Configuration === + const feeTierMap: Record = { + '0.01': { numerator: 1, denominator: 10000 }, // 0.01% + '0.05': { numerator: 5, denominator: 10000 }, // 0.05% + '0.1': { numerator: 1, denominator: 1000 }, // 0.1% + '0.25': { numerator: 25, denominator: 10000 }, // 0.25% + '0.3': { numerator: 3, denominator: 1000 }, // 0.3% + '1': { numerator: 1, denominator: 100 } // 1% + } + const feeConfig = feeTierMap[payload.feeTier] + if (!feeConfig) { + throw new Error( + `Invalid fee tier: ${payload.feeTier}%. Supported tiers: ${Object.keys(feeTierMap).join(', ')}%` + ) + } + + console.log('💰 Pool fee configuration:', { + tier: `${payload.feeTier}%`, + numerator: feeConfig.numerator, + denominator: feeConfig.denominator + }) + + console.log('token swap ', tokenSwap.publicKey.toBase58()) + console.log('authority ', authority.toBase58()) + console.log('token A ', swapTokenAAccount.toBase58()) + console.log('token B ', swapTokenBAccount.toBase58()) + console.log('Pool mint ', poolMint.publicKey.toBase58()) + console.log('Token program id ', TOKEN_PROGRAM_ID.toBase58()) + + latestBlockhash = await connection.getLatestBlockhash('confirmed') + const feeAccount = await getAssociatedTokenAddress(poolMint.publicKey, ownerAddress) + const feeInfo = await connection.getAccountInfo(feeAccount) + if (!feeInfo) { + const feeIx = createAssociatedTokenAccountInstruction( + ownerAddress, + feeAccount, + ownerAddress, + poolMint.publicKey + ) + const tx = new Transaction().add(feeIx) + const sig = await sendTransactionWithRetry(tx, connection, sendTransaction) + await confirmTransactionWithTimeout(connection, sig, latestBlockhash) + console.log('✅ Fee account created:', sig) + + // Add delay to prevent wallet extension race condition + console.log('⏳ Waiting 2 seconds before next transaction...') + await new Promise((resolve) => setTimeout(resolve, 2000)) + } + + // Verify all accounts exist before initialization + console.log('🔍 Verifying all required accounts exist...') + const [baseInfo, quoteInfo, poolInfo, feeUpdatedInfo] = await Promise.all([ + connection.getAccountInfo(swapTokenAAccount), + connection.getAccountInfo(swapTokenBAccount), + connection.getAccountInfo(poolMint.publicKey), + connection.getAccountInfo(feeAccount) + ]) + + console.log('📋 Account Verification:', { + swapTokenAAccount: !!baseInfo, + swapTokenBAccount: !!quoteInfo, + poolMint: !!poolInfo, + feeAccount: !!feeUpdatedInfo, + allExist: !!(baseInfo && quoteInfo && poolInfo && feeUpdatedInfo) + }) + + if (!baseInfo || !quoteInfo || !poolInfo || !feeUpdatedInfo) { + throw new Error( + 'Some required accounts do not exist. Cannot proceed with pool initialization.' + ) + } + + // === CRITICAL: Deposit Initial Liquidity === + console.log('💰 Adding initial liquidity to pool token accounts...') + + // Convert amounts to proper decimals (assuming 6 decimals for both tokens) + // Calculate quote amount from initial price: baseAmount / initialPrice + const liquidityBaseAmount = parseFloat(payload.baseTokenAmount) + const liquidityInitialPrice = parseFloat(payload.initialPrice) // SHIB per USDT + const liquidityQuoteAmount = liquidityBaseAmount / liquidityInitialPrice // USDT amount + + const baseAmountDaltons = formatTokenToDaltons(liquidityBaseAmount, 6) + const quoteAmountDaltons = formatTokenToDaltons(liquidityQuoteAmount, 6) + + console.log('💰 Initial Liquidity Amounts:', { + baseAmount: payload.baseTokenAmount, + calculatedQuoteAmount: liquidityQuoteAmount, + initialPrice: liquidityInitialPrice, + baseAmountDaltons, + quoteAmountDaltons, + swapTokenAAccount: swapTokenAAccount.toBase58(), + swapTokenBAccount: swapTokenBAccount.toBase58() + }) + + // === BBA-AWARE Liquidity Transfer === + console.log('💰 Preparing BBA-aware liquidity transfer...') + + latestBlockhash = await connection.getLatestBlockhash('confirmed') + if (isBBAPoolPair) { + console.log('🪙 BBA Pool - Using special native token handling') + + // === BBA Pool Logic === + if (isBBABase) { + // BBA is base token, other token is quote + console.log('💰 BBA/Token pool (BBA as base)') + + // Check BBA balance (native daltons) + const userBBABalance = await connection.getBalance(ownerAddress) + const requiredBBA = bbaTodaltons(liquidityBaseAmount) + + if (userBBABalance < requiredBBA) { + throw new Error( + `Insufficient BBA balance. Required: ${liquidityBaseAmount} BBA, Available: ${daltonsToBBA(userBBABalance)} BBA` + ) + } + + // Check quote token balance + const userQuoteTokenAccount = await getAssociatedTokenAddress(quoteMint, ownerAddress) + const userQuoteInfo = await connection.getAccountInfo(userQuoteTokenAccount) + + if (!userQuoteInfo) { + throw new Error( + 'Quote token account not found. Please ensure you have the required tokens.' + ) + } + + const userQuoteBalance = new BN(userQuoteInfo.data.slice(64, 72), 'le') + if (userQuoteBalance.lt(new BN(quoteAmountDaltons))) { + throw new Error( + `Insufficient ${payload.quoteToken.symbol} balance. Required: ${liquidityQuoteAmount}, Available: ${userQuoteBalance.div(new BN(1000000)).toString()}` + ) + } + + // Transfer BBA to pool (using special BBA handling) + console.log('🔄 Transferring BBA to pool account...') + const transferBBAIx = SystemProgram.transfer({ + fromPubkey: ownerAddress, + toPubkey: swapTokenAAccount, + daltons: requiredBBA + }) + + const { createSyncNativeInstruction } = await import('@bbachain/spl-token') + const syncBBAIx = createSyncNativeInstruction(swapTokenAAccount) + + // Transfer quote token (standard SPL transfer) + const { createTransferInstruction } = await import('@bbachain/spl-token') + const transferQuoteIx = createTransferInstruction( + userQuoteTokenAccount, + swapTokenBAccount, + ownerAddress, + quoteAmountDaltons + ) + + // Combine all transfers + const liquidityTx = new Transaction().add(transferBBAIx, syncBBAIx, transferQuoteIx) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) + await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) + console.log('✅ BBA/Token liquidity transferred to pool accounts:', liquiditySig) + } else if (isBBAQuote) { + // Token is base, BBA is quote + console.log('💰 Token/BBA pool (BBA as quote)') + + // Check base token balance + const userBaseTokenAccount = await getAssociatedTokenAddress(baseMint, ownerAddress) + const userBaseInfo = await connection.getAccountInfo(userBaseTokenAccount) + + if (!userBaseInfo) { + throw new Error( + 'Base token account not found. Please ensure you have the required tokens.' + ) + } + + const userBaseBalance = new BN(userBaseInfo.data.slice(64, 72), 'le') + if (userBaseBalance.lt(new BN(baseAmountDaltons))) { + throw new Error( + `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userBaseBalance.div(new BN(1000000)).toString()}` + ) + } + + // Check BBA balance (native daltons) + const userBBABalance = await connection.getBalance(ownerAddress) + const requiredBBA = bbaTodaltons(liquidityQuoteAmount) + + if (userBBABalance < requiredBBA) { + throw new Error( + `Insufficient BBA balance. Required: ${liquidityQuoteAmount} BBA, Available: ${daltonsToBBA(userBBABalance)} BBA` + ) + } + + // Transfer base token (standard SPL transfer) + const { createTransferInstruction } = await import('@bbachain/spl-token') + const transferBaseIx = createTransferInstruction( + userBaseTokenAccount, + swapTokenAAccount, + ownerAddress, + baseAmountDaltons + ) + + // Transfer BBA to pool (using special BBA handling) + console.log('🔄 Transferring BBA to pool account...') + const transferBBAIx = SystemProgram.transfer({ + fromPubkey: ownerAddress, + toPubkey: swapTokenBAccount, + daltons: requiredBBA + }) + + const { createSyncNativeInstruction } = await import('@bbachain/spl-token') + const syncBBAIx = createSyncNativeInstruction(swapTokenBAccount) + + // Combine all transfers + const liquidityTx = new Transaction().add(transferBaseIx, transferBBAIx, syncBBAIx) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) + await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) + console.log('✅ Token/BBA liquidity transferred to pool accounts:', liquiditySig) + } + } else { + // === Standard Token/Token Pool Logic === + console.log('🔄 Standard token/token pool - using SPL transfers') + + // First verify we have enough user balance + const userBaseTokenAccount = await getAssociatedTokenAddress(baseMint, ownerAddress) + const userQuoteTokenAccount = await getAssociatedTokenAddress(quoteMint, ownerAddress) + + // Check user balances + const [userBaseInfo, userQuoteInfo] = await Promise.all([ + connection.getAccountInfo(userBaseTokenAccount), + connection.getAccountInfo(userQuoteTokenAccount) + ]) + + if (!userBaseInfo || !userQuoteInfo) { + throw new Error( + 'User token accounts not found. Please ensure you have the required tokens.' + ) + } + + // Parse user balances + const userBaseBalance = new BN(userBaseInfo.data.slice(64, 72), 'le') + const userQuoteBalance = new BN(userQuoteInfo.data.slice(64, 72), 'le') + + console.log('👤 User Token Balances:', { + baseBalance: userBaseBalance.toString(), + quoteBalance: userQuoteBalance.toString(), + baseBalanceFormatted: + userBaseBalance.div(new BN(1000000)).toString() + ` ${payload.baseToken.symbol}`, + quoteBalanceFormatted: + userQuoteBalance.div(new BN(1000000)).toString() + ` ${payload.quoteToken.symbol}`, + requiredBase: baseAmountDaltons, + requiredQuote: quoteAmountDaltons + }) + + // Verify sufficient balance + if (userBaseBalance.lt(new BN(baseAmountDaltons))) { + throw new Error( + `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userBaseBalance.div(new BN(1000000)).toString()}` + ) + } + + if (userQuoteBalance.lt(new BN(quoteAmountDaltons))) { + throw new Error( + `Insufficient ${payload.quoteToken.symbol} balance. Required: ${liquidityQuoteAmount}, Available: ${userQuoteBalance.div(new BN(1000000)).toString()}` + ) + } + + // Transfer from user to pool accounts (standard SPL) + const { createTransferInstruction } = await import('@bbachain/spl-token') + + const transferBaseIx = createTransferInstruction( + userBaseTokenAccount, + swapTokenAAccount, + ownerAddress, + baseAmountDaltons + ) + + const transferQuoteIx = createTransferInstruction( + userQuoteTokenAccount, + swapTokenBAccount, + ownerAddress, + quoteAmountDaltons + ) + + // Send initial liquidity transfer + const liquidityTx = new Transaction().add(transferBaseIx, transferQuoteIx) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) + await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) + console.log('✅ Standard token liquidity transferred to pool accounts:', liquiditySig) + } + + // Add delay to prevent wallet extension race condition + console.log('⏳ Waiting 2 seconds before swap initialization...') + await new Promise((resolve) => setTimeout(resolve, 2000)) + + // === Swap Initialization === + + const swapCurve = { + curveType: CurveType.ConstantProduct, + calculator: new Array(32).fill(0) // 32 bytes for curve parameters + } + + const fees = { + tradeFeeNumerator: new BN(feeConfig.numerator), + tradeFeeDenominator: new BN(feeConfig.denominator), + ownerTradeFeeNumerator: new BN(0), + ownerTradeFeeDenominator: new BN(0), + ownerWithdrawFeeNumerator: new BN(0), + ownerWithdrawFeeDenominator: new BN(0), + hostFeeNumerator: new BN(0), + hostFeeDenominator: new BN(0) + } + + console.log('🔧 Creating token swap instruction using @bbachain/spl-token-swap...') + + // Create the token swap initialization instruction with all required accounts + console.log('🔧 Preparing swap initialization accounts...') + console.log('📋 Account Details:', { + tokenSwap: tokenSwap.publicKey.toBase58(), + authority: authority.toBase58(), + tokenA: swapTokenAAccount.toBase58(), + tokenB: swapTokenBAccount.toBase58(), + poolMint: poolMint.publicKey.toBase58(), + feeAccount: feeAccount.toBase58(), + destination: poolTokenAccount.toBase58(), + tokenProgram: TOKEN_PROGRAM_ID.toBase58(), + // Also log the mint addresses for debugging + mintA: baseMint.toBase58(), + mintB: quoteMint.toBase58() + }) + + // Use library function for exact BBAChain compatibility + console.log('🔧 Using @bbachain/spl-token-swap createInitializeInstruction...') + + const swapIx = createInitializeInstruction( + { + tokenSwap: tokenSwap.publicKey, + authority: authority, + tokenA: swapTokenAAccount, + tokenB: swapTokenBAccount, + poolMint: poolMint.publicKey, + feeAccount, + destination: poolTokenAccount, + tokenProgram: TOKEN_PROGRAM_ID + }, + { + fees, + swapCurve + } + ) + + console.log('📋 Library Instruction Analysis:', { + programId: swapIx.programId.toBase58(), + dataLength: swapIx.data.length, + accountCount: swapIx.keys.length, + firstDataBytes: Array.from(swapIx.data.slice(0, 10)) + .map((b) => `0x${b.toString(16).padStart(2, '0')}`) + .join(' '), + fees: { + tradeFeeNum: fees.tradeFeeNumerator.toString(), + tradeFeeDenom: fees.tradeFeeDenominator.toString() + }, + bumpSeed: bumpSeed, + // Complete instruction data dump for analysis + completeDataHex: Array.from(swapIx.data) + .map((b) => `0x${b.toString(16).padStart(2, '0')}`) + .join(' ') + }) + + console.log('🔍 Library-Generated Account List:', { + accountCount: swapIx.keys.length, + accounts: swapIx.keys.map( + (key, index) => + `${index}. ${key.pubkey.toBase58()} (signer: ${key.isSigner}, writable: ${key.isWritable})` + ) + }) + + console.log('✅ Token swap instruction created successfully') + console.log('📋 Swap Instruction Details:', { + tokenSwap: tokenSwap.publicKey.toBase58(), + authority: authority.toBase58(), + tokenA: swapTokenAAccount.toBase58(), + tokenB: swapTokenBAccount.toBase58(), + poolMint: poolMint.publicKey.toBase58(), + feeAccount: feeAccount.toBase58(), + destination: poolTokenAccount.toBase58(), + curveType: 'ConstantProduct', + tradeFee: `${feeConfig.numerator}/${feeConfig.denominator}` + }) + + // Log the instruction keys for debugging + console.log( + '🔍 Instruction accounts:', + swapIx.keys.map((key, index) => ({ + index, + pubkey: key.pubkey.toBase58(), + isSigner: key.isSigner, + isWritable: key.isWritable + })) + ) + + // === Create TokenSwap Account + Initialize in Single Transaction === + console.log('🏗️ Creating TokenSwap account and initializing...') + // Import TokenSwapLayout to get exact span + const { TokenSwapLayout } = await import('./onchain') + const tokenSwapAccountSize = TokenSwapLayout.span // Use exact layout span instead of hardcoded 324 + console.log('📏 TokenSwap account size from layout:', tokenSwapAccountSize) + const swapAccountDaltons = + await connection.getMinimumBalanceForRentExemption(tokenSwapAccountSize) + + const createSwapAccountIx = SystemProgram.createAccount({ + fromPubkey: ownerAddress, + newAccountPubkey: tokenSwap.publicKey, + space: tokenSwapAccountSize, + daltons: swapAccountDaltons, + programId: TOKEN_SWAP_PROGRAM_ID + }) + + console.log('📋 Transaction Instructions:', { + createAccount: 'Create TokenSwap account', + initialize: 'Initialize TokenSwap', + totalInstructions: 2 + }) + + // === Single Transaction Approach (Matching Working Test) === + console.log('🏗️ Creating single transaction with create + initialize...') + const finalTx = new Transaction().add(createSwapAccountIx, swapIx) + + latestBlockhash = await connection.getLatestBlockhash('confirmed') + + // Set recent blockhash and fee payer + finalTx.recentBlockhash = latestBlockhash.blockhash + finalTx.feePayer = ownerAddress + + console.log('📋 Final Transaction Analysis:', { + instructionCount: finalTx.instructions.length, + estimatedFee: 'Will be calculated by wallet', + signers: [ownerAddress.toBase58(), tokenSwap.publicKey.toBase58()], + recentBlockhash: latestBlockhash.blockhash.slice(0, 8) + '...' + }) + + // === IMPORTANT: Use sendTransaction with additional signers === + console.log('📝 Sending final transaction with BBA wallet...') + const finalSig = await sendTransactionWithRetry(finalTx, connection, sendTransaction, { + signers: [tokenSwap], // Additional signer required + skipPreflight: false, + preflightCommitment: 'confirmed' + }) + + console.log('⏳ Final transaction sent:', finalSig) + + // Wait for confirmation + const confirmation: any = await confirmTransactionWithTimeout( + connection, + finalSig, + latestBlockhash + ) + + if (confirmation.value?.err) { + throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`) + } + + console.log('✅ TokenSwap created and initialized successfully!') + console.log('🎉 Pool creation completed successfully!') + + // Return data in the expected format + return { + tokenSwap: tokenSwap.publicKey.toBase58(), + poolMint: poolMint.publicKey.toBase58(), + feeAccount: feeAccount.toBase58(), + lpTokenAccount: poolTokenAccount.toBase58(), + signature: finalSig, + baseToken: payload.baseToken, + quoteToken: payload.quoteToken, + baseTokenAmount: Number(payload.baseTokenAmount), + quoteTokenAmount: Number(payload.quoteTokenAmount), + message: 'Pool created successfully!' + } + } catch (error) { + console.error('❌ Pool creation failed:', error) + throw error + } + }, + onSuccess: (result) => { + console.log('✅ Pool creation successful') + // Invalidate pools cache to refresh the list + queryClient.invalidateQueries({ queryKey: [SERVICES_KEY.POOL.GET_POOLS] }) + queryClient.invalidateQueries({ queryKey: [SERVICES_KEY.POOL.GET_POOL_STATS] }) + queryClient.invalidateQueries({ + queryKey: [SERVICES_KEY.WALLET.GET_BALANCE, ownerAddress?.toBase58()] + }) + }, + onError: (error) => { + console.error('❌ Pool creation failed:', error) + } + }) +} diff --git a/src/features/liquidityPool/services.ts b/src/features/liquidityPool/services.ts index 7e0a000..f3192a7 100644 --- a/src/features/liquidityPool/services.ts +++ b/src/features/liquidityPool/services.ts @@ -710,8 +710,14 @@ export const useCreatePool = () => { const liquidityInitialPrice = parseFloat(payload.initialPrice) // SHIB per USDT const liquidityQuoteAmount = liquidityBaseAmount / liquidityInitialPrice // USDT amount - const baseAmountDaltons = formatTokenToDaltons(liquidityBaseAmount, 6) - const quoteAmountDaltons = formatTokenToDaltons(liquidityQuoteAmount, 6) + const baseAmountDaltons = formatTokenToDaltons( + liquidityBaseAmount, + payload.baseToken.decimals + ) + const quoteAmountDaltons = formatTokenToDaltons( + liquidityQuoteAmount, + payload.quoteToken.decimals + ) console.log('💰 Initial Liquidity Amounts:', { baseAmount: payload.baseTokenAmount, @@ -727,219 +733,80 @@ export const useCreatePool = () => { console.log('💰 Preparing BBA-aware liquidity transfer...') latestBlockhash = await connection.getLatestBlockhash('confirmed') - if (isBBAPoolPair) { - console.log('🪙 BBA Pool - Using special native token handling') - - // === BBA Pool Logic === - if (isBBABase) { - // BBA is base token, other token is quote - console.log('💰 BBA/Token pool (BBA as base)') - - // Check BBA balance (native daltons) - const userWBBATokenAccount = await getAssociatedTokenAddress(NATIVE_MINT, ownerAddress) - console.log('ini lewat bang') - const userWBBAInfo = await connection.getAccountInfo(userWBBATokenAccount) - if (!userWBBAInfo) { - throw new Error( - 'WBBA token account not found. Please ensure you have the required tokens.' - ) - } - - const userWBBABalance = new BN(userWBBAInfo.data.slice(64, 72), 'le') - if (userWBBABalance.lt(new BN(baseAmountDaltons))) { - throw new Error( - `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userWBBABalance.div(new BN(1000000)).toString()}` - ) - } - - // Check quote token balance - const userQuoteTokenAccount = await getAssociatedTokenAddress(quoteMint, ownerAddress) - const userQuoteInfo = await connection.getAccountInfo(userQuoteTokenAccount) - - if (!userQuoteInfo) { - throw new Error( - 'Quote token account not found. Please ensure you have the required tokens.' - ) - } - - const userQuoteBalance = new BN(userQuoteInfo.data.slice(64, 72), 'le') - if (userQuoteBalance.lt(new BN(quoteAmountDaltons))) { - throw new Error( - `Insufficient ${payload.quoteToken.symbol} balance. Required: ${liquidityQuoteAmount}, Available: ${userQuoteBalance.div(new BN(1000000)).toString()}` - ) - } - - // Transfer quote token (standard SPL transfer) - const { createTransferInstruction } = await import('@bbachain/spl-token') - - const transferBBAIx = createTransferInstruction( - userWBBATokenAccount, - swapTokenAAccount, - ownerAddress, - baseAmountDaltons - ) - - const transferQuoteIx = createTransferInstruction( - userQuoteTokenAccount, - swapTokenBAccount, - ownerAddress, - quoteAmountDaltons - ) - console.log('re-check', { - swapTokenA: swapTokenAAccount.toBase58(), - wbbaTokenAccount: userWBBATokenAccount.toBase58(), - wbbaDaltons: baseAmountDaltons, - wbbaAmount: baseAmount - }) - - // Combine all transfers - const liquidityTx = new Transaction().add(transferBBAIx, transferQuoteIx) - - console.log('this causes error') - const liquiditySig = await sendTransactionWithRetry( - liquidityTx, - connection, - sendTransaction - ) - await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) - console.log('✅ BBA/Token liquidity transferred to pool accounts:', liquiditySig) - } else if (isBBAQuote) { - // Token is base, BBA is quote - console.log('💰 Token/BBA pool (BBA as quote)') - - // Check base token balance - const userBaseTokenAccount = await getAssociatedTokenAddress(baseMint, ownerAddress) - const userBaseInfo = await connection.getAccountInfo(userBaseTokenAccount) - - if (!userBaseInfo) { - throw new Error( - 'Base token account not found. Please ensure you have the required tokens.' - ) - } - - const userBaseBalance = new BN(userBaseInfo.data.slice(64, 72), 'le') - if (userBaseBalance.lt(new BN(baseAmountDaltons))) { - throw new Error( - `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userBaseBalance.div(new BN(1000000)).toString()}` - ) - } - - // Check BBA balance (native daltons) - const userBBABalance = await connection.getBalance(ownerAddress) - const requiredBBA = bbaTodaltons(liquidityQuoteAmount) - - if (userBBABalance < requiredBBA) { - throw new Error( - `Insufficient BBA balance. Required: ${liquidityQuoteAmount} BBA, Available: ${daltonsToBBA(userBBABalance)} BBA` - ) - } - - // Transfer base token (standard SPL transfer) - const { createTransferInstruction } = await import('@bbachain/spl-token') - const transferBaseIx = createTransferInstruction( - userBaseTokenAccount, - swapTokenAAccount, - ownerAddress, - baseAmountDaltons - ) - - // Transfer BBA to pool (using special BBA handling) - console.log('🔄 Transferring BBA to pool account...') - const transferBBAIx = SystemProgram.transfer({ - fromPubkey: ownerAddress, - toPubkey: swapTokenBAccount, - daltons: requiredBBA - }) - - const { createSyncNativeInstruction } = await import('@bbachain/spl-token') - const syncBBAIx = createSyncNativeInstruction(swapTokenBAccount) - - // Combine all transfers - const liquidityTx = new Transaction().add(transferBaseIx, transferBBAIx, syncBBAIx) - const liquiditySig = await sendTransactionWithRetry( - liquidityTx, - connection, - sendTransaction - ) - await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) - console.log('✅ Token/BBA liquidity transferred to pool accounts:', liquiditySig) - } - } else { - // === Standard Token/Token Pool Logic === - console.log('🔄 Standard token/token pool - using SPL transfers') + // === Standard Token/Token Pool Logic and WBBA as WBBA is SPL === + console.log('🔄 Standard token/token pool - using SPL transfers') - // First verify we have enough user balance - const userBaseTokenAccount = await getAssociatedTokenAddress(baseMint, ownerAddress) - const userQuoteTokenAccount = await getAssociatedTokenAddress(quoteMint, ownerAddress) + // First verify we have enough user balance + const userBaseTokenAccount = await getAssociatedTokenAddress(baseMint, ownerAddress) + const userQuoteTokenAccount = await getAssociatedTokenAddress(quoteMint, ownerAddress) - // Check user balances - const [userBaseInfo, userQuoteInfo] = await Promise.all([ - connection.getAccountInfo(userBaseTokenAccount), - connection.getAccountInfo(userQuoteTokenAccount) - ]) + // Check user balances + const [userBaseInfo, userQuoteInfo] = await Promise.all([ + connection.getAccountInfo(userBaseTokenAccount), + connection.getAccountInfo(userQuoteTokenAccount) + ]) - if (!userBaseInfo || !userQuoteInfo) { - throw new Error( - 'User token accounts not found. Please ensure you have the required tokens.' - ) - } + if (!userBaseInfo || !userQuoteInfo) { + throw new Error( + 'User token accounts not found. Please ensure you have the required tokens.' + ) + } - // Parse user balances - const userBaseBalance = new BN(userBaseInfo.data.slice(64, 72), 'le') - const userQuoteBalance = new BN(userQuoteInfo.data.slice(64, 72), 'le') - - console.log('👤 User Token Balances:', { - baseBalance: userBaseBalance.toString(), - quoteBalance: userQuoteBalance.toString(), - baseBalanceFormatted: - userBaseBalance.div(new BN(1000000)).toString() + ` ${payload.baseToken.symbol}`, - quoteBalanceFormatted: - userQuoteBalance.div(new BN(1000000)).toString() + ` ${payload.quoteToken.symbol}`, - requiredBase: baseAmountDaltons, - requiredQuote: quoteAmountDaltons - }) + // Parse user balances + const userBaseBalance = new BN(userBaseInfo.data.slice(64, 72), 'le') + const userQuoteBalance = new BN(userQuoteInfo.data.slice(64, 72), 'le') + + console.log('👤 User Token Balances:', { + baseBalance: userBaseBalance.toString(), + quoteBalance: userQuoteBalance.toString(), + baseBalanceFormatted: + userBaseBalance.div(new BN(1000000)).toString() + ` ${payload.baseToken.symbol}`, + quoteBalanceFormatted: + userQuoteBalance.div(new BN(1000000)).toString() + ` ${payload.quoteToken.symbol}`, + requiredBase: baseAmountDaltons, + requiredQuote: quoteAmountDaltons + }) - // Verify sufficient balance - if (userBaseBalance.lt(new BN(baseAmountDaltons))) { - throw new Error( - `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userBaseBalance.div(new BN(1000000)).toString()}` - ) - } + // Verify sufficient balance + if (userBaseBalance.lt(new BN(baseAmountDaltons))) { + throw new Error( + `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userBaseBalance.div(new BN(1000000)).toString()}` + ) + } - if (userQuoteBalance.lt(new BN(quoteAmountDaltons))) { - throw new Error( - `Insufficient ${payload.quoteToken.symbol} balance. Required: ${liquidityQuoteAmount}, Available: ${userQuoteBalance.div(new BN(1000000)).toString()}` - ) - } + if (userQuoteBalance.lt(new BN(quoteAmountDaltons))) { + throw new Error( + `Insufficient ${payload.quoteToken.symbol} balance. Required: ${liquidityQuoteAmount}, Available: ${userQuoteBalance.div(new BN(1000000)).toString()}` + ) + } - // Transfer from user to pool accounts (standard SPL) - const { createTransferInstruction } = await import('@bbachain/spl-token') + // Transfer from user to pool accounts (standard SPL) + const { createTransferInstruction } = await import('@bbachain/spl-token') - const transferBaseIx = createTransferInstruction( - userBaseTokenAccount, - swapTokenAAccount, - ownerAddress, - baseAmountDaltons - ) + const transferBaseIx = createTransferInstruction( + userBaseTokenAccount, + swapTokenAAccount, + ownerAddress, + baseAmountDaltons + ) - const transferQuoteIx = createTransferInstruction( - userQuoteTokenAccount, - swapTokenBAccount, - ownerAddress, - quoteAmountDaltons - ) + const transferQuoteIx = createTransferInstruction( + userQuoteTokenAccount, + swapTokenBAccount, + ownerAddress, + quoteAmountDaltons + ) - // Send initial liquidity transfer - const liquidityTx = new Transaction().add(transferBaseIx, transferQuoteIx) - const liquiditySig = await sendTransactionWithRetry( - liquidityTx, - connection, - sendTransaction - ) - await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) - console.log('✅ Standard token liquidity transferred to pool accounts:', liquiditySig) - } + // Send initial liquidity transfer + const liquidityTx = new Transaction().add(transferBaseIx, transferQuoteIx) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) + await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) + console.log('✅ Standard token liquidity transferred to pool accounts:', liquiditySig) // Add delay to prevent wallet extension race condition console.log('⏳ Waiting 2 seconds before swap initialization...') From 33accd34a95265f9b534c5faea0faf7d996224ab Mon Sep 17 00:00:00 2001 From: fiqriBTI Date: Sun, 24 Aug 2025 23:57:39 +0700 Subject: [PATCH 10/12] feat: reverse swap and lp reverse to prev requirements --- src/features/liquidityPool/services.ts | 264 ++++++++++++++++++------- src/features/swap/services.ts | 135 +++++++++++-- 2 files changed, 315 insertions(+), 84 deletions(-) diff --git a/src/features/liquidityPool/services.ts b/src/features/liquidityPool/services.ts index f3192a7..928568b 100644 --- a/src/features/liquidityPool/services.ts +++ b/src/features/liquidityPool/services.ts @@ -709,15 +709,11 @@ export const useCreatePool = () => { const liquidityBaseAmount = parseFloat(payload.baseTokenAmount) const liquidityInitialPrice = parseFloat(payload.initialPrice) // SHIB per USDT const liquidityQuoteAmount = liquidityBaseAmount / liquidityInitialPrice // USDT amount + const baseTokenDecimal = payload.baseToken.decimals + const quoteTokenDecimal = payload.quoteToken.decimals - const baseAmountDaltons = formatTokenToDaltons( - liquidityBaseAmount, - payload.baseToken.decimals - ) - const quoteAmountDaltons = formatTokenToDaltons( - liquidityQuoteAmount, - payload.quoteToken.decimals - ) + const baseAmountDaltons = formatTokenToDaltons(liquidityBaseAmount, baseTokenDecimal) + const quoteAmountDaltons = formatTokenToDaltons(liquidityQuoteAmount, quoteTokenDecimal) console.log('💰 Initial Liquidity Amounts:', { baseAmount: payload.baseTokenAmount, @@ -733,80 +729,206 @@ export const useCreatePool = () => { console.log('💰 Preparing BBA-aware liquidity transfer...') latestBlockhash = await connection.getLatestBlockhash('confirmed') + if (isBBAPoolPair) { + console.log('🪙 BBA Pool - Using special native token handling') + + // === BBA Pool Logic === + if (isBBABase) { + // BBA is base token, other token is quote + console.log('💰 BBA/Token pool (BBA as base)') + + // Check BBA balance (native daltons) + const userBBABalance = await connection.getBalance(ownerAddress) + const requiredBBA = bbaTodaltons(liquidityBaseAmount) + + if (userBBABalance < requiredBBA) { + throw new Error( + `Insufficient BBA balance. Required: ${liquidityBaseAmount} BBA, Available: ${daltonsToBBA(userBBABalance)} BBA` + ) + } + + // Check quote token balance + const userQuoteTokenAccount = await getAssociatedTokenAddress(quoteMint, ownerAddress) + const userQuoteInfo = await connection.getAccountInfo(userQuoteTokenAccount) + + if (!userQuoteInfo) { + throw new Error( + 'Quote token account not found. Please ensure you have the required tokens.' + ) + } + + const userQuoteBalance = new BN(userQuoteInfo.data.slice(64, 72), 'le') + if (userQuoteBalance.lt(new BN(quoteAmountDaltons))) { + throw new Error( + `Insufficient ${payload.quoteToken.symbol} balance. Required: ${liquidityQuoteAmount}, Available: ${userQuoteBalance.div(new BN(1000000)).toString()}` + ) + } + + // Transfer BBA to pool (using special BBA handling) + console.log('🔄 Transferring BBA to pool account...') + const transferBBAIx = SystemProgram.transfer({ + fromPubkey: ownerAddress, + toPubkey: swapTokenAAccount, + daltons: requiredBBA + }) + + const { createSyncNativeInstruction } = await import('@bbachain/spl-token') + const syncBBAIx = createSyncNativeInstruction(swapTokenAAccount) + + // Transfer quote token (standard SPL transfer) + const { createTransferInstruction } = await import('@bbachain/spl-token') + const transferQuoteIx = createTransferInstruction( + userQuoteTokenAccount, + swapTokenBAccount, + ownerAddress, + quoteAmountDaltons + ) - // === Standard Token/Token Pool Logic and WBBA as WBBA is SPL === - console.log('🔄 Standard token/token pool - using SPL transfers') + // Combine all transfers + const liquidityTx = new Transaction().add(transferBBAIx, syncBBAIx, transferQuoteIx) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) + await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) + console.log('✅ BBA/Token liquidity transferred to pool accounts:', liquiditySig) + } else if (isBBAQuote) { + // Token is base, BBA is quote + console.log('💰 Token/BBA pool (BBA as quote)') + + // Check base token balance + const userBaseTokenAccount = await getAssociatedTokenAddress(baseMint, ownerAddress) + const userBaseInfo = await connection.getAccountInfo(userBaseTokenAccount) + + if (!userBaseInfo) { + throw new Error( + 'Base token account not found. Please ensure you have the required tokens.' + ) + } + + const userBaseBalance = new BN(userBaseInfo.data.slice(64, 72), 'le') + if (userBaseBalance.lt(new BN(baseAmountDaltons))) { + throw new Error( + `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userBaseBalance.div(new BN(1000000)).toString()}` + ) + } + + // Check BBA balance (native daltons) + const userBBABalance = await connection.getBalance(ownerAddress) + const requiredBBA = bbaTodaltons(liquidityQuoteAmount) + + if (userBBABalance < requiredBBA) { + throw new Error( + `Insufficient BBA balance. Required: ${liquidityQuoteAmount} BBA, Available: ${daltonsToBBA(userBBABalance)} BBA` + ) + } + + // Transfer base token (standard SPL transfer) + const { createTransferInstruction } = await import('@bbachain/spl-token') + const transferBaseIx = createTransferInstruction( + userBaseTokenAccount, + swapTokenAAccount, + ownerAddress, + baseAmountDaltons + ) - // First verify we have enough user balance - const userBaseTokenAccount = await getAssociatedTokenAddress(baseMint, ownerAddress) - const userQuoteTokenAccount = await getAssociatedTokenAddress(quoteMint, ownerAddress) + // Transfer BBA to pool (using special BBA handling) + console.log('🔄 Transferring BBA to pool account...') + const transferBBAIx = SystemProgram.transfer({ + fromPubkey: ownerAddress, + toPubkey: swapTokenBAccount, + daltons: requiredBBA + }) + + const { createSyncNativeInstruction } = await import('@bbachain/spl-token') + const syncBBAIx = createSyncNativeInstruction(swapTokenBAccount) + + // Combine all transfers + const liquidityTx = new Transaction().add(transferBaseIx, transferBBAIx, syncBBAIx) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) + await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) + console.log('✅ Token/BBA liquidity transferred to pool accounts:', liquiditySig) + } + } else { + // === Standard Token/Token Pool Logic === + console.log('🔄 Standard token/token pool - using SPL transfers') - // Check user balances - const [userBaseInfo, userQuoteInfo] = await Promise.all([ - connection.getAccountInfo(userBaseTokenAccount), - connection.getAccountInfo(userQuoteTokenAccount) - ]) + // First verify we have enough user balance + const userBaseTokenAccount = await getAssociatedTokenAddress(baseMint, ownerAddress) + const userQuoteTokenAccount = await getAssociatedTokenAddress(quoteMint, ownerAddress) - if (!userBaseInfo || !userQuoteInfo) { - throw new Error( - 'User token accounts not found. Please ensure you have the required tokens.' - ) - } + // Check user balances + const [userBaseInfo, userQuoteInfo] = await Promise.all([ + connection.getAccountInfo(userBaseTokenAccount), + connection.getAccountInfo(userQuoteTokenAccount) + ]) - // Parse user balances - const userBaseBalance = new BN(userBaseInfo.data.slice(64, 72), 'le') - const userQuoteBalance = new BN(userQuoteInfo.data.slice(64, 72), 'le') - - console.log('👤 User Token Balances:', { - baseBalance: userBaseBalance.toString(), - quoteBalance: userQuoteBalance.toString(), - baseBalanceFormatted: - userBaseBalance.div(new BN(1000000)).toString() + ` ${payload.baseToken.symbol}`, - quoteBalanceFormatted: - userQuoteBalance.div(new BN(1000000)).toString() + ` ${payload.quoteToken.symbol}`, - requiredBase: baseAmountDaltons, - requiredQuote: quoteAmountDaltons - }) + if (!userBaseInfo || !userQuoteInfo) { + throw new Error( + 'User token accounts not found. Please ensure you have the required tokens.' + ) + } - // Verify sufficient balance - if (userBaseBalance.lt(new BN(baseAmountDaltons))) { - throw new Error( - `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userBaseBalance.div(new BN(1000000)).toString()}` - ) - } + // Parse user balances + const userBaseBalance = new BN(userBaseInfo.data.slice(64, 72), 'le') + const userQuoteBalance = new BN(userQuoteInfo.data.slice(64, 72), 'le') + + console.log('👤 User Token Balances:', { + baseBalance: userBaseBalance.toString(), + quoteBalance: userQuoteBalance.toString(), + baseBalanceFormatted: + userBaseBalance.div(new BN(1000000)).toString() + ` ${payload.baseToken.symbol}`, + quoteBalanceFormatted: + userQuoteBalance.div(new BN(1000000)).toString() + ` ${payload.quoteToken.symbol}`, + requiredBase: baseAmountDaltons, + requiredQuote: quoteAmountDaltons + }) - if (userQuoteBalance.lt(new BN(quoteAmountDaltons))) { - throw new Error( - `Insufficient ${payload.quoteToken.symbol} balance. Required: ${liquidityQuoteAmount}, Available: ${userQuoteBalance.div(new BN(1000000)).toString()}` - ) - } + // Verify sufficient balance + if (userBaseBalance.lt(new BN(baseAmountDaltons))) { + throw new Error( + `Insufficient ${payload.baseToken.symbol} balance. Required: ${liquidityBaseAmount}, Available: ${userBaseBalance.div(new BN(1000000)).toString()}` + ) + } - // Transfer from user to pool accounts (standard SPL) - const { createTransferInstruction } = await import('@bbachain/spl-token') + if (userQuoteBalance.lt(new BN(quoteAmountDaltons))) { + throw new Error( + `Insufficient ${payload.quoteToken.symbol} balance. Required: ${liquidityQuoteAmount}, Available: ${userQuoteBalance.div(new BN(1000000)).toString()}` + ) + } - const transferBaseIx = createTransferInstruction( - userBaseTokenAccount, - swapTokenAAccount, - ownerAddress, - baseAmountDaltons - ) + // Transfer from user to pool accounts (standard SPL) + const { createTransferInstruction } = await import('@bbachain/spl-token') - const transferQuoteIx = createTransferInstruction( - userQuoteTokenAccount, - swapTokenBAccount, - ownerAddress, - quoteAmountDaltons - ) + const transferBaseIx = createTransferInstruction( + userBaseTokenAccount, + swapTokenAAccount, + ownerAddress, + baseAmountDaltons + ) - // Send initial liquidity transfer - const liquidityTx = new Transaction().add(transferBaseIx, transferQuoteIx) - const liquiditySig = await sendTransactionWithRetry( - liquidityTx, - connection, - sendTransaction - ) - await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) - console.log('✅ Standard token liquidity transferred to pool accounts:', liquiditySig) + const transferQuoteIx = createTransferInstruction( + userQuoteTokenAccount, + swapTokenBAccount, + ownerAddress, + quoteAmountDaltons + ) + + // Send initial liquidity transfer + const liquidityTx = new Transaction().add(transferBaseIx, transferQuoteIx) + const liquiditySig = await sendTransactionWithRetry( + liquidityTx, + connection, + sendTransaction + ) + await confirmTransactionWithTimeout(connection, liquiditySig, latestBlockhash) + console.log('✅ Standard token liquidity transferred to pool accounts:', liquiditySig) + } // Add delay to prevent wallet extension race condition console.log('⏳ Waiting 2 seconds before swap initialization...') diff --git a/src/features/swap/services.ts b/src/features/swap/services.ts index 9e5fbbe..fe12060 100644 --- a/src/features/swap/services.ts +++ b/src/features/swap/services.ts @@ -3,21 +3,29 @@ import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID, NATIVE_MINT, - createApproveInstruction + createApproveInstruction, + createAssociatedTokenAccountInstruction, + createSyncNativeInstruction, + createCloseAccountInstruction } from '@bbachain/spl-token' import { createSwapInstruction, PROGRAM_ID as TOKEN_SWAP_PROGRAM_ID } from '@bbachain/spl-token-swap' import { useConnection, useWallet } from '@bbachain/wallet-adapter-react' -import { PublicKey, Transaction, TransactionInstruction } from '@bbachain/web3.js' +import { + PublicKey, + SystemProgram, + Transaction, + type TransactionInstruction +} from '@bbachain/web3.js' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import axios from 'axios' import { TGetTokensResponse } from '@/app/api/tokens/route' import ENDPOINTS from '@/constants/endpoint' import SERVICES_KEY from '@/constants/service' -import { bbaTodaltons } from '@/lib/bbaWrapping' +import { bbaTodaltons, daltonsToBBA } from '@/lib/bbaWrapping' import { getTokenAccounts2 } from '@/lib/tokenAccount' import { isNativeBBA } from '@/staticData/tokens' @@ -731,17 +739,118 @@ export const useExecuteSwap = () => { requiresWrapping: isBBASwap }) - const preTxInstructions: TransactionInstruction[] = [] - const postTxInstructions: TransactionInstruction[] = [] + let userInputTokenAccount: PublicKey + let userOutputTokenAccount: PublicKey + let needsUnwrapping = false + // Track whether we created a temporary WBBA ATA for BBA → Token swaps + let createdWBBAInputAccount = false + let preTxInstructions: TransactionInstruction[] = [] + let postTxInstructions: TransactionInstruction[] = [] + + if (isBBASwap) { + console.log('🪙 BBA swap detected - using WBBA for native BBA') + + if (isInputBBA) { + // BBA → Token: Use WBBA account for input + console.log('💰 BBA → Token swap: Using WBBA account for input') + + // Check BBA balance + const userBBABalance = await connection.getBalance(publicKey) + const requiredBBA = bbaTodaltons(inputAmountNumber) + + if (userBBABalance < requiredBBA) { + throw new Error( + `Insufficient BBA balance. Required: ${inputAmountNumber} BBA, Available: ${daltonsToBBA(userBBABalance)} BBA` + ) + } - const userInputTokenAccount = await getAssociatedTokenAddress( - new PublicKey(inputMint), - publicKey - ) - const userOutputTokenAccount = await getAssociatedTokenAddress( - new PublicKey(outputMint), - publicKey - ) + // Use WBBA (NATIVE_MINT) associated token account + userInputTokenAccount = await getAssociatedTokenAddress(NATIVE_MINT, publicKey) + + // Create WBBA account if it doesn't exist and fund it + const wbbaAccountInfo = await connection.getAccountInfo(userInputTokenAccount) + if (!wbbaAccountInfo) { + console.log('📝 Creating WBBA account and funding with BBA...') + const createWBBAIx = createAssociatedTokenAccountInstruction( + publicKey, + userInputTokenAccount, + publicKey, + NATIVE_MINT + ) + preTxInstructions.push(createWBBAIx) + createdWBBAInputAccount = true + } + + // Add instructions to transfer BBA and sync + const transferBBAIx = SystemProgram.transfer({ + fromPubkey: publicKey, + toPubkey: userInputTokenAccount, + daltons: requiredBBA + }) + const syncBBAIx = createSyncNativeInstruction(userInputTokenAccount) + preTxInstructions.push(transferBBAIx, syncBBAIx) + + // For output, use standard token account + userOutputTokenAccount = await getAssociatedTokenAddress( + new PublicKey(outputMint), + publicKey + ) + + // If we created a temporary WBBA input ATA for this swap, close it after swap to reclaim rent + if (createdWBBAInputAccount) { + const closeWbbaInputIx = createCloseAccountInstruction( + userInputTokenAccount, + publicKey, + publicKey + ) + postTxInstructions.push(closeWbbaInputIx) + } + } else if (isOutputBBA) { + // Token → BBA: Swap to WBBA then unwrap + console.log('💰 Token → BBA swap: Will receive WBBA and unwrap to BBA') + + // For input, use standard token account + userInputTokenAccount = await getAssociatedTokenAddress( + new PublicKey(inputMint), + publicKey + ) + + // For output, use WBBA (NATIVE_MINT) associated token account + userOutputTokenAccount = await getAssociatedTokenAddress(NATIVE_MINT, publicKey) + + // Create WBBA account if it doesn't exist + const wbbaAccountInfo = await connection.getAccountInfo(userOutputTokenAccount) + if (!wbbaAccountInfo) { + console.log('📝 Creating WBBA account for output...') + const createWBBAIx = createAssociatedTokenAccountInstruction( + publicKey, + userOutputTokenAccount, + publicKey, + NATIVE_MINT + ) + preTxInstructions.push(createWBBAIx) + } + + // Add instruction to unwrap WBBA to BBA after swap + needsUnwrapping = true + const closeWBBAIx = createCloseAccountInstruction( + userOutputTokenAccount, + publicKey, + publicKey + ) + postTxInstructions.push(closeWBBAIx) + } else { + throw new Error('Unexpected BBA swap state') + } + } else { + // Standard token/token swap + console.log('🔄 Standard token/token swap') + userInputTokenAccount = await getAssociatedTokenAddress(new PublicKey(inputMint), publicKey) + userOutputTokenAccount = await getAssociatedTokenAddress( + new PublicKey(outputMint), + publicKey + ) + } // Get pool info from BBA Chain const poolInfo = pool.swapData From a5e5ad36da827996493415f6ca3463f4225fa3eb Mon Sep 17 00:00:00 2001 From: fiqriBTI Date: Mon, 25 Aug 2025 01:04:41 +0700 Subject: [PATCH 11/12] feat: wrap unwrap check done --- .../liquidity-pools/create-pool/page.tsx | 12 +- src/app/(walletConnected)/swap/page.tsx | 7 +- .../components/LPSuccessDialog.tsx | 31 +- src/features/liquidityPool/old-services.ts | 852 ------------------ .../swap/components/TokenListDialog.tsx | 3 +- src/features/swap/services.ts | 17 +- 6 files changed, 53 insertions(+), 869 deletions(-) delete mode 100644 src/features/liquidityPool/old-services.ts diff --git a/src/app/(walletConnected)/liquidity-pools/create-pool/page.tsx b/src/app/(walletConnected)/liquidity-pools/create-pool/page.tsx index 9ef50d6..92b8c35 100644 --- a/src/app/(walletConnected)/liquidity-pools/create-pool/page.tsx +++ b/src/app/(walletConnected)/liquidity-pools/create-pool/page.tsx @@ -530,23 +530,27 @@ export default function CreatePool() {

- WBBA Token Pool + BBA Native Token Pool

- You're creating a pool with WBBA (native token). This requires + You're creating a pool with BBA (native token). This requires special handling:

  • - Make sure you have converted enough BBA amount to WBBA + BBA will be automatically wrapped to WBBA for the pool
  • - Recommended fee tier: 0.3% for WBBA/{nonBBAToken?.symbol} pairs + Recommended fee tier: 0.3% for BBA/{nonBBAToken?.symbol} pairs
  • +
  • + + Pool will use NATIVE_MINT for WBBA representation +
diff --git a/src/app/(walletConnected)/swap/page.tsx b/src/app/(walletConnected)/swap/page.tsx index 358b930..e4c27a3 100644 --- a/src/app/(walletConnected)/swap/page.tsx +++ b/src/app/(walletConnected)/swap/page.tsx @@ -29,6 +29,9 @@ import { getCoinGeckoId } from '@/features/swap/utils' import { cn } from '@/lib/utils' import StaticTokens from '@/staticData/tokens' +const initialBaseToken = StaticTokens[1] +const initialQuoteToken = StaticTokens[2] + export default function Swap() { /** * Enhanced Swap Page with URL Parameter Support @@ -44,8 +47,8 @@ export default function Swap() { const searchParams = useSearchParams() const router = useRouter() const [amountIn, setAmountIn] = useState('') - const [fromTokenProps, setFromTokenProps] = useState(StaticTokens[1]) - const [toTokenProps, setToTokenProps] = useState(StaticTokens[2]) + const [fromTokenProps, setFromTokenProps] = useState(initialBaseToken) + const [toTokenProps, setToTokenProps] = useState(initialQuoteToken) const [isTokenDialogOpen, setIsTokenDialogOpen] = useState(false) const [maxSlippage, setMaxSlippage] = useState(0.5) const [timeLimit, setTimeLimit] = useState('0') diff --git a/src/features/liquidityPool/components/LPSuccessDialog.tsx b/src/features/liquidityPool/components/LPSuccessDialog.tsx index f27df15..3cffdad 100644 --- a/src/features/liquidityPool/components/LPSuccessDialog.tsx +++ b/src/features/liquidityPool/components/LPSuccessDialog.tsx @@ -1,11 +1,20 @@ import Image from 'next/image' +import Link from 'next/link' import { Dispatch, SetStateAction } from 'react' import { Button } from '@/components/ui/button' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose } from '@/components/ui/dialog' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogClose +} from '@/components/ui/dialog' interface LPSuccessDialogProps { isOpen: boolean + isNewTab?: boolean onOpenChange: Dispatch> title: string contents: string[] @@ -15,19 +24,27 @@ interface LPSuccessDialogProps { export default function LPSuccessDialog({ isOpen, + isNewTab, onOpenChange, title, contents, linkText, link }: LPSuccessDialogProps) { + const LinkWrapper = isNewTab ? 'a' : Link return ( - success parsed + success parsed

{title}

    {contents.map((content, index) => ( @@ -37,14 +54,14 @@ export default function LPSuccessDialog({ ))}
- {linkText} - +