-
Notifications
You must be signed in to change notification settings - Fork 106
Expand file tree
/
Copy pathtoken-accounts-by-owner.ts
More file actions
150 lines (132 loc) · 4.41 KB
/
Copy pathtoken-accounts-by-owner.ts
File metadata and controls
150 lines (132 loc) · 4.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"use client";
import { useQuery } from "@tanstack/react-query";
import type { Account, Address, FetchAccountConfig, Simplify } from "gill";
import type { Token } from "gill/programs/token";
import { decodeToken, TOKEN_PROGRAM_ADDRESS } from "gill/programs/token";
import { GILL_HOOK_CLIENT_KEY } from "../const";
import { useSolanaClient } from "./client";
import type { GillUseRpcHook } from "./types";
type RpcConfig = Simplify<Omit<FetchAccountConfig, "abortSignal">>;
/**
* Utility type for accounts that exist on-chain
*/
type ExistingAccount<TData extends object | Uint8Array, TAddress extends Address = Address> = Account<
TData,
TAddress
> & { exists: true };
/**
* Token account with its associated token account address
*/
type TokenAccountWithAddress<TAddress extends Address = Address> = {
/**
* The token account data
*/
account: ExistingAccount<Token, TAddress>;
/**
* The address of the token account
*/
address: TAddress;
};
/**
* Response type for the token accounts query
*/
type TokenAccountsQueryResult<TAddress extends Address = Address> = {
accounts: TokenAccountWithAddress<TAddress>[];
total: number;
};
/**
* Input parameters for fetching token accounts by owner with optional filtering
*/
type UseTokenAccountsByOwnerInput<TConfig extends RpcConfig = RpcConfig> = GillUseRpcHook<TConfig> & {
/**
* Whether to include accounts with zero balance
* @default true
*/
includeZeroBalance?: boolean;
/**
* The owner address to fetch token accounts for
*/
owner: Address;
/**
* Optional specific token mint to filter by.
* If provided, only returns accounts for this specific token.
* If not provided, returns all token accounts from the legacy token program.
*/
tokenMint?: Address;
};
/**
* Get token accounts owned by a specific address.
*
* - If no `tokenMint` is provided: returns all token accounts from the legacy token program
* - If `tokenMint` is provided: returns only accounts for that specific token mint
*
* @example
* ```tsx
* // Get all legacy token accounts for an owner
* const { accounts } = useTokenAccountsByOwner({ owner: ownerAddress });
*
* // Get token accounts for a specific mint (e.g., USDC)
* const { accounts } = useTokenAccountsByOwner({
* owner: ownerAddress,
* tokenMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" // USDC
* });
* ```
*/
export function useTokenAccountsByOwner<TConfig extends RpcConfig = RpcConfig, TAddress extends Address = Address>({
options,
config,
abortSignal,
owner,
tokenMint,
includeZeroBalance = true,
}: UseTokenAccountsByOwnerInput<TConfig>) {
const { rpc } = useSolanaClient();
if (abortSignal) {
// @ts-expect-error the `abortSignal` was stripped from the type but is now being added back in
config = {
...(config || {}),
abortSignal,
};
}
const { data, ...rest } = useQuery({
...options,
enabled: !!owner,
queryFn: async (): Promise<TokenAccountsQueryResult<Address>> => {
const allAccounts: TokenAccountWithAddress<Address>[] = [];
const filter = tokenMint ? { mint: tokenMint } : { programId: TOKEN_PROGRAM_ADDRESS };
const response = await rpc.getTokenAccountsByOwner(owner, filter, { encoding: "base64", ...config }).send();
for (const accountInfo of response.value) {
const rawAccount: ExistingAccount<Uint8Array, Address> = {
address: accountInfo.pubkey,
data: new Uint8Array(Buffer.from(accountInfo.account.data[0], "base64")),
executable: accountInfo.account.executable,
exists: true,
lamports: accountInfo.account.lamports,
programAddress: accountInfo.account.owner,
space: accountInfo.account.space,
};
const decodedAccount = decodeToken(rawAccount);
if (!includeZeroBalance && decodedAccount.data.amount === 0n) {
continue;
}
const tokenAccount: ExistingAccount<Token, Address> = {
...decodedAccount,
exists: true,
};
allAccounts.push({
account: tokenAccount,
address: accountInfo.pubkey,
});
}
return {
accounts: allAccounts,
total: allAccounts.length,
};
},
queryKey: [GILL_HOOK_CLIENT_KEY, "getTokenAccountsByOwner", owner, tokenMint, includeZeroBalance],
});
return {
...rest,
accounts: (data as TokenAccountsQueryResult<TAddress> | undefined)?.accounts || [],
};
}