@tronweb3/tronwallet-adapter-react-hooks 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,275 @@
1
+ import { WalletNotSelectedError, AdapterState } from '@tronweb3/tronwallet-abstract-adapter';
2
+ import type { Adapter, WalletError, AdapterName, Transaction } from '@tronweb3/tronwallet-abstract-adapter';
3
+ import { TronLinkAdapter } from '@tronweb3/tronwallet-adapter-tronlink';
4
+ import type { FC, ReactNode } from 'react';
5
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
+ import type { Wallet } from './useWallet.js';
7
+ import { WalletContext } from './useWallet.js';
8
+ import { useLocalStorage } from './useLocalStorage.js';
9
+
10
+ export interface WalletProviderProps {
11
+ children: ReactNode;
12
+ adapters?: Adapter[];
13
+ onError?: (error: WalletError) => void;
14
+ localStorageKey?: string;
15
+ autoConnect?: boolean;
16
+ }
17
+
18
+ const initialState: {
19
+ wallet: Wallet | null;
20
+ address: string | null;
21
+ connected: boolean;
22
+ adapter: Adapter | null;
23
+ } = {
24
+ wallet: null,
25
+ address: null,
26
+ connected: false,
27
+ adapter: null,
28
+ };
29
+
30
+ export const WalletProvider: FC<WalletProviderProps> = function ({
31
+ children,
32
+ adapters: adaptersPro = null,
33
+ onError = (error) => console.error(error),
34
+ localStorageKey = 'tronAdapterName',
35
+ autoConnect = true,
36
+ }) {
37
+ const [name, setName] = useLocalStorage<AdapterName | null>(localStorageKey, null);
38
+ const [{ wallet, connected, address, adapter }, setState] = useState(initialState);
39
+ const [connecting, setConnecting] = useState(false);
40
+ const [disconnecting, setDisconnecting] = useState(false);
41
+ const isConnecting = useRef(false);
42
+ const isDisconnecting = useRef(false);
43
+
44
+ // set default supported adapters
45
+ const adapters = useMemo(() => {
46
+ if (adaptersPro === null) {
47
+ return [new TronLinkAdapter()];
48
+ }
49
+ return adaptersPro;
50
+ }, [adaptersPro]);
51
+ const [wallets, setWallets] = useState<Wallet[]>(() =>
52
+ adapters.map((adapter) => ({
53
+ adapter,
54
+ state: adapter.state,
55
+ }))
56
+ );
57
+ useEffect(
58
+ function () {
59
+ setWallets((prevWallets) =>
60
+ adapters.map((adapter, index) => {
61
+ const wallet = prevWallets[index];
62
+ if (wallet && wallet.adapter === adapter && wallet.state === adapter.state) {
63
+ return wallet;
64
+ }
65
+ return {
66
+ adapter,
67
+ state: adapter.state,
68
+ };
69
+ })
70
+ );
71
+
72
+ function handleStateChange(this: Adapter) {
73
+ setWallets((prevWallets) => {
74
+ const index = prevWallets.findIndex((wallet) => wallet.adapter === this);
75
+ if (index === -1) {
76
+ return prevWallets;
77
+ }
78
+ return prevWallets.map((wallet, idx) => {
79
+ if (idx === index) {
80
+ return {
81
+ ...wallet,
82
+ state: wallet.adapter.state,
83
+ };
84
+ }
85
+ return wallet;
86
+ });
87
+ });
88
+ }
89
+ adapters.forEach((adapter) => adapter.on('stateChanged', handleStateChange, adapter));
90
+ return () => adapters.forEach((adapter) => adapter.off('stateChanged', handleStateChange, adapter));
91
+ },
92
+ [adapters]
93
+ );
94
+
95
+ // Set state when choosen wallet changes
96
+ useEffect(
97
+ function () {
98
+ const wallet = name && wallets.find((item) => item.adapter.name === name);
99
+ if (wallet) {
100
+ setState({
101
+ wallet,
102
+ adapter: wallet.adapter,
103
+ connected: wallet.adapter.connected,
104
+ address: wallet.adapter.address,
105
+ });
106
+ } else {
107
+ setState(initialState);
108
+ }
109
+ },
110
+ [name, wallets]
111
+ );
112
+
113
+ const handleConnect = useCallback(
114
+ function () {
115
+ if (!adapter) {
116
+ return setName(null);
117
+ }
118
+ setState((state) => ({
119
+ ...state,
120
+ connected: adapter.connected,
121
+ address: adapter.address,
122
+ }));
123
+ },
124
+ [adapter, setName]
125
+ );
126
+
127
+ const handleDisconnect = useCallback(
128
+ function () {
129
+ setName(null);
130
+ },
131
+ [setName]
132
+ );
133
+
134
+ const handleError = useCallback(
135
+ function (error: WalletError) {
136
+ onError(error);
137
+ return error;
138
+ },
139
+ [onError]
140
+ );
141
+ const handleAccountChange = useCallback(function (address: string) {
142
+ setState((state) => ({ ...state, address }));
143
+ }, []);
144
+
145
+ useEffect(
146
+ function () {
147
+ if (adapter) {
148
+ adapter.on('connect', handleConnect);
149
+ adapter.on('disconnect', handleDisconnect);
150
+ adapter.on('error', handleError);
151
+ adapter.on('accountsChanged', handleAccountChange);
152
+ return () => {
153
+ adapter.off('connect', handleConnect);
154
+ adapter.off('disconnect', handleDisconnect);
155
+ adapter.off('error', handleError);
156
+ adapter.off('accountsChanged', handleAccountChange);
157
+ };
158
+ }
159
+ },
160
+ [adapter, handleConnect, handleDisconnect, handleError, handleAccountChange]
161
+ );
162
+ // disconnect the previous when wallet changes
163
+ useEffect(() => {
164
+ return () => {
165
+ adapter?.disconnect();
166
+ };
167
+ }, [adapter]);
168
+
169
+ // auto connect
170
+ useEffect(
171
+ function () {
172
+ if (
173
+ isConnecting.current ||
174
+ connected ||
175
+ !autoConnect ||
176
+ !adapter ||
177
+ adapter.state !== AdapterState.Disconnect
178
+ ) {
179
+ return;
180
+ }
181
+ (async function connect() {
182
+ isConnecting.current = true;
183
+ setConnecting(true);
184
+ try {
185
+ await adapter.connect();
186
+ } catch (error) {
187
+ setName(null);
188
+ } finally {
189
+ setConnecting(false);
190
+ isConnecting.current = false;
191
+ }
192
+ })();
193
+ },
194
+ [isConnecting, autoConnect, adapter, connected, setName]
195
+ );
196
+
197
+ const connect = useCallback(
198
+ async function () {
199
+ if (isConnecting.current || isDisconnecting.current || connected) {
200
+ return;
201
+ }
202
+ if (!adapter) throw handleError(new WalletNotSelectedError());
203
+ isConnecting.current = true;
204
+ setConnecting(true);
205
+ try {
206
+ await adapter.connect();
207
+ } catch (error: unknown) {
208
+ setName(null);
209
+ throw error;
210
+ } finally {
211
+ setConnecting(false);
212
+ isConnecting.current = false;
213
+ }
214
+ },
215
+ [isConnecting, isDisconnecting, adapter, connected, handleError, setName]
216
+ );
217
+
218
+ const disconnect = useCallback(
219
+ async function () {
220
+ if (isDisconnecting.current) return;
221
+ if (!adapter) return setName(null);
222
+
223
+ isDisconnecting.current = true;
224
+ setDisconnecting(true);
225
+ try {
226
+ await adapter.disconnect();
227
+ } catch (error: any) {
228
+ setName(null);
229
+ throw error;
230
+ } finally {
231
+ setDisconnecting(false);
232
+ isDisconnecting.current = false;
233
+ }
234
+ },
235
+ [adapter, isDisconnecting, setName]
236
+ );
237
+
238
+ const signTransaction = useCallback(
239
+ async function (transaction: Transaction, privateKey?: string) {
240
+ if (!adapter) throw handleError(new WalletNotSelectedError());
241
+ return await adapter.signTransaction(transaction, privateKey);
242
+ },
243
+ [adapter, handleError]
244
+ );
245
+
246
+ const signMessage = useCallback(
247
+ async function (message: string, privateKey?: string) {
248
+ if (!adapter) throw handleError(new WalletNotSelectedError());
249
+ return await adapter.signMessage(message, privateKey);
250
+ },
251
+ [adapter, handleError]
252
+ );
253
+
254
+ return (
255
+ <WalletContext.Provider
256
+ value={{
257
+ autoConnect,
258
+ wallets,
259
+ wallet,
260
+ address,
261
+ connecting,
262
+ connected,
263
+ disconnecting,
264
+
265
+ select: setName,
266
+ connect,
267
+ disconnect,
268
+ signTransaction,
269
+ signMessage,
270
+ }}
271
+ >
272
+ {children}
273
+ </WalletContext.Provider>
274
+ );
275
+ };
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './useLocalStorage.js';
2
+ export * from './useWallet.js';
3
+ export * from './WalletProvider.js';
@@ -0,0 +1,39 @@
1
+ import type { Dispatch, SetStateAction } from 'react';
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { isInBrowser } from '@tronweb3/tronwallet-abstract-adapter';
4
+
5
+ export function useLocalStorage<T>(key: string, defaultState: T): [T, Dispatch<SetStateAction<T>>] {
6
+ const [state, setState] = useState<T>(() => {
7
+ try {
8
+ const value = localStorage.getItem(key);
9
+ if (value) return JSON.parse(value) as T;
10
+ } catch (error: unknown) {
11
+ if (isInBrowser()) {
12
+ console.error(error);
13
+ }
14
+ }
15
+
16
+ return defaultState;
17
+ });
18
+
19
+ const isFirstRender = useRef(true);
20
+ useEffect(() => {
21
+ if (isFirstRender.current) {
22
+ isFirstRender.current = false;
23
+ return;
24
+ }
25
+ try {
26
+ if (state === null) {
27
+ localStorage.removeItem(key);
28
+ } else {
29
+ localStorage.setItem(key, JSON.stringify(state));
30
+ }
31
+ } catch (error: any) {
32
+ if (isInBrowser()) {
33
+ console.error(error);
34
+ }
35
+ }
36
+ }, [state, key]);
37
+
38
+ return [state, setState];
39
+ }
@@ -0,0 +1,80 @@
1
+ import type { Adapter, AdapterName, Transaction, AdapterState } from '@tronweb3/tronwallet-abstract-adapter';
2
+ import { createContext, useContext } from 'react';
3
+
4
+ export interface Wallet {
5
+ adapter: Adapter;
6
+ state: AdapterState;
7
+ }
8
+ export interface WalletContextState {
9
+ autoConnect: boolean;
10
+ wallets: Wallet[];
11
+ wallet: Wallet | null;
12
+ address: string | null;
13
+ connecting: boolean;
14
+ connected: boolean;
15
+ disconnecting: boolean;
16
+
17
+ select(adapterName: AdapterName): void;
18
+ connect(): Promise<void>;
19
+ disconnect(): Promise<void>;
20
+
21
+ signTransaction: Adapter['signTransaction'];
22
+ signMessage: Adapter['signMessage'];
23
+ }
24
+ function printError() {
25
+ console.error(`
26
+ You are using WalletContext without provide the WalletContext.Provider.
27
+ Please wrap your sub-components with a WalletProvider and provide required values.
28
+ `);
29
+ }
30
+ const DEFAULT_CONTEXT = {
31
+ autoConnect: false,
32
+ connecting: false,
33
+ connected: false,
34
+ disconnecting: false,
35
+ /* eslint-disable */
36
+ select(_name: AdapterName) {
37
+ printError();
38
+ },
39
+ connect() {
40
+ printError();
41
+ return Promise.reject();
42
+ },
43
+ disconnect() {
44
+ printError();
45
+ return Promise.reject();
46
+ },
47
+ signTransaction(_transaction: Transaction) {
48
+ printError();
49
+ return Promise.reject();
50
+ },
51
+ signMessage(_message: string) {
52
+ printError();
53
+ return Promise.reject();
54
+ },
55
+ /* eslint-enable */
56
+ } as WalletContextState;
57
+
58
+ Object.defineProperty(DEFAULT_CONTEXT, 'wallets', {
59
+ get() {
60
+ printError();
61
+ return [];
62
+ },
63
+ });
64
+ Object.defineProperty(DEFAULT_CONTEXT, 'wallet', {
65
+ get() {
66
+ printError();
67
+ return null;
68
+ },
69
+ });
70
+ Object.defineProperty(DEFAULT_CONTEXT, 'address', {
71
+ get() {
72
+ printError();
73
+ return null;
74
+ },
75
+ });
76
+
77
+ export const WalletContext = createContext<WalletContextState>(DEFAULT_CONTEXT as WalletContextState);
78
+ export function useWallet(): WalletContextState {
79
+ return useContext(WalletContext);
80
+ }