forked from solana-labs/solana
/
tokens.tsx
134 lines (115 loc) · 3.38 KB
/
tokens.tsx
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
import React from "react";
import { Connection, PublicKey, TokenAccountInfo } from "@solana/web3.js";
import { FetchStatus, useAccounts } from "./index";
import { useCluster, Cluster } from "../cluster";
interface AccountTokens {
status: FetchStatus;
tokens?: TokenAccountInfo[];
}
interface Update {
pubkey: PublicKey;
status: FetchStatus;
tokens?: TokenAccountInfo[];
}
type Action = Update | "clear";
type State = { [address: string]: AccountTokens };
type Dispatch = (action: Action) => void;
function reducer(state: State, action: Action): State {
if (action === "clear") {
return {};
}
const address = action.pubkey.toBase58();
let addressEntry = state[address];
if (addressEntry && action.status === FetchStatus.Fetching) {
addressEntry = {
...addressEntry,
status: FetchStatus.Fetching,
};
} else {
addressEntry = {
tokens: action.tokens,
status: action.status,
};
}
return {
...state,
[address]: addressEntry,
};
}
const StateContext = React.createContext<State | undefined>(undefined);
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
type ProviderProps = { children: React.ReactNode };
export function TokensProvider({ children }: ProviderProps) {
const [state, dispatch] = React.useReducer(reducer, {});
const { cluster, url } = useCluster();
const { accounts, lastFetchedAddress } = useAccounts();
React.useEffect(() => {
dispatch("clear");
}, [url]);
// Fetch history for new accounts
React.useEffect(() => {
if (lastFetchedAddress && cluster !== Cluster.MainnetBeta) {
const infoFetched =
accounts[lastFetchedAddress] &&
accounts[lastFetchedAddress].lamports !== undefined;
const noRecord = !state[lastFetchedAddress];
if (infoFetched && noRecord) {
fetchAccountTokens(dispatch, new PublicKey(lastFetchedAddress), url);
}
}
}, [accounts, lastFetchedAddress]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
const TOKEN_PROGRAM_ID = new PublicKey(
"TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
);
async function fetchAccountTokens(
dispatch: Dispatch,
pubkey: PublicKey,
url: string
) {
dispatch({
status: FetchStatus.Fetching,
pubkey,
});
let status;
let tokens;
try {
const { value } = await new Connection(
url,
"recent"
).getTokenAccountsByOwner(pubkey, { programId: TOKEN_PROGRAM_ID });
tokens = value.map((accountInfo) => accountInfo.account.data);
status = FetchStatus.Fetched;
} catch (error) {
status = FetchStatus.FetchFailed;
}
dispatch({ status, tokens, pubkey });
}
export function useAccountOwnedTokens(address: string) {
const context = React.useContext(StateContext);
if (!context) {
throw new Error(
`useAccountOwnedTokens must be used within a AccountsProvider`
);
}
return context[address];
}
export function useFetchAccountOwnedTokens() {
const dispatch = React.useContext(DispatchContext);
if (!dispatch) {
throw new Error(
`useFetchAccountOwnedTokens must be used within a AccountsProvider`
);
}
const { url } = useCluster();
return (pubkey: PublicKey) => {
fetchAccountTokens(dispatch, pubkey, url);
};
}