|
1 | 1 | "use client"; |
2 | 2 |
|
| 3 | +import { useQuery } from "@tanstack/react-query"; |
3 | 4 | import type { Account, Address, FetchAccountConfig, Simplify } from "gill"; |
4 | 5 | import type { Token } from "gill/programs/token"; |
5 | 6 | import { decodeToken, TOKEN_PROGRAM_ADDRESS } from "gill/programs/token"; |
6 | | -import { useQuery } from "@tanstack/react-query"; |
7 | 7 | import { GILL_HOOK_CLIENT_KEY } from "../const"; |
8 | 8 | import { useSolanaClient } from "./client"; |
9 | 9 | import type { GillUseRpcHook } from "./types"; |
10 | 10 |
|
11 | 11 | type RpcConfig = Simplify<Omit<FetchAccountConfig, "abortSignal">>; |
12 | 12 |
|
| 13 | +/** |
| 14 | + * Utility type for accounts that exist on-chain |
| 15 | + */ |
| 16 | +type ExistingAccount<TData extends object | Uint8Array, TAddress extends Address = Address> = Account< |
| 17 | + TData, |
| 18 | + TAddress |
| 19 | +> & { exists: true }; |
| 20 | + |
13 | 21 | /** |
14 | 22 | * Token account with its associated token account address |
15 | 23 | */ |
16 | 24 | type TokenAccountWithAddress<TAddress extends Address = Address> = { |
17 | 25 | /** |
18 | 26 | * The token account data |
19 | 27 | */ |
20 | | - account: Account<Token, TAddress> & { exists: true }; |
| 28 | + account: ExistingAccount<Token, TAddress>; |
21 | 29 | /** |
22 | 30 | * The address of the token account |
23 | 31 | */ |
@@ -92,61 +100,51 @@ export function useTokenAccountsByOwner<TConfig extends RpcConfig = RpcConfig, T |
92 | 100 | const { data, ...rest } = useQuery({ |
93 | 101 | ...options, |
94 | 102 | enabled: !!owner, |
95 | | - queryFn: async (): Promise<TokenAccountsQueryResult<TAddress>> => { |
96 | | - const allAccounts: TokenAccountWithAddress<TAddress>[] = []; |
97 | | - |
98 | | - try { |
99 | | - let filter; |
100 | | - if (tokenMint) { |
101 | | - filter = { mint: tokenMint }; |
102 | | - } else { |
103 | | - filter = { programId: TOKEN_PROGRAM_ADDRESS }; |
104 | | - } |
| 103 | + queryFn: async (): Promise<TokenAccountsQueryResult<Address>> => { |
| 104 | + const allAccounts: TokenAccountWithAddress<Address>[] = []; |
105 | 105 |
|
106 | | - const response = await rpc.getTokenAccountsByOwner(owner, filter, { encoding: "base64", ...config }).send(); |
107 | | - |
108 | | - for (const accountInfo of response.value) { |
109 | | - try { |
110 | | - const rawAccount: Account<Uint8Array, TAddress> & { exists: true } = { |
111 | | - address: accountInfo.pubkey as TAddress, |
112 | | - data: new Uint8Array(Buffer.from(accountInfo.account.data[0], "base64")), |
113 | | - executable: accountInfo.account.executable, |
114 | | - exists: true, |
115 | | - lamports: accountInfo.account.lamports, |
116 | | - programAddress: accountInfo.account.owner, |
117 | | - space: accountInfo.account.space, |
118 | | - }; |
119 | | - |
120 | | - const decodedAccount = decodeToken(rawAccount); |
121 | | - |
122 | | - if (!includeZeroBalance && decodedAccount.data.amount === 0n) { |
123 | | - continue; |
124 | | - } |
125 | | - |
126 | | - allAccounts.push({ |
127 | | - account: decodedAccount as Account<Token, TAddress> & { exists: true }, |
128 | | - address: accountInfo.pubkey as TAddress, |
129 | | - }); |
130 | | - } catch (decodeError) { |
131 | | - console.warn(`Failed to decode token account ${accountInfo.pubkey}:`, decodeError); |
132 | | - } |
| 106 | + const filter = tokenMint ? { mint: tokenMint } : { programId: TOKEN_PROGRAM_ADDRESS }; |
| 107 | + |
| 108 | + const response = await rpc.getTokenAccountsByOwner(owner, filter, { encoding: "base64", ...config }).send(); |
| 109 | + |
| 110 | + for (const accountInfo of response.value) { |
| 111 | + const rawAccount: ExistingAccount<Uint8Array, Address> = { |
| 112 | + address: accountInfo.pubkey, |
| 113 | + data: new Uint8Array(Buffer.from(accountInfo.account.data[0], "base64")), |
| 114 | + executable: accountInfo.account.executable, |
| 115 | + exists: true, |
| 116 | + lamports: accountInfo.account.lamports, |
| 117 | + programAddress: accountInfo.account.owner, |
| 118 | + space: accountInfo.account.space, |
| 119 | + }; |
| 120 | + |
| 121 | + const decodedAccount = decodeToken(rawAccount); |
| 122 | + |
| 123 | + if (!includeZeroBalance && decodedAccount.data.amount === 0n) { |
| 124 | + continue; |
133 | 125 | } |
134 | | - } catch (queryError) { |
135 | | - console.warn(`Failed to query token accounts:`, queryError); |
136 | | - throw queryError; |
| 126 | + |
| 127 | + const tokenAccount: ExistingAccount<Token, Address> = { |
| 128 | + ...decodedAccount, |
| 129 | + exists: true, |
| 130 | + }; |
| 131 | + |
| 132 | + allAccounts.push({ |
| 133 | + account: tokenAccount, |
| 134 | + address: accountInfo.pubkey, |
| 135 | + }); |
137 | 136 | } |
138 | 137 |
|
139 | 138 | return { |
140 | 139 | accounts: allAccounts, |
141 | 140 | total: allAccounts.length, |
142 | | - } satisfies TokenAccountsQueryResult<TAddress>; |
| 141 | + }; |
143 | 142 | }, |
144 | 143 | queryKey: [GILL_HOOK_CLIENT_KEY, "getTokenAccountsByOwner", owner, tokenMint, includeZeroBalance], |
145 | 144 | }); |
146 | 145 |
|
147 | 146 | return { |
148 | 147 | ...rest, |
149 | 148 | accounts: (data as TokenAccountsQueryResult<TAddress> | undefined)?.accounts || [], |
150 | | - total: (data as TokenAccountsQueryResult<TAddress> | undefined)?.total || 0, |
151 | 149 | }; |
152 | 150 | } |
0 commit comments