on ebdc206 help sign in
bridge/src/lib/useWalletConnect.ts
283 lines 6.0 kB history raw
1import WalletConnect from '@walletconnect/client';
2import QRCodeModal from '@walletconnect/qrcode-modal';
3import { ITxData } from '@walletconnect/types';
5import { useEffect, useMemo, useState } from 'react';
6import { Just, Nothing } from 'folktale/maybe';
8import { useWallet } from 'store/wallet';
9import { getAuthToken } from './authToken';
10import { WALLET_TYPES } from './constants';
11import WalletConnectWallet from './types/WalletConnectWallet';
13type PeerMeta = {
14 description: string;
15 icons?: string[];
16 name: string;
17 url: string;
18};
20type ConnectEvent = {
21 event: 'connect';
22 params: [
23 {
24 accounts: string[];
25 chainId: number;
26 peerId: string;
27 peerMeta: PeerMeta;
28 }
29 ];
30};
32export const useWalletConnect = () => {
33 const { setWallet, setAuthToken, resetWallet }: any = useWallet();
34 const [connector, setConnector] = useState<WalletConnect | null>(null);
35 const [address, setAddress] = useState<string | null>(null);
36 const [peerMeta, setPeerMeta] = useState<PeerMeta | null>(null);
38 const resetConnector = () => {
39 const newConnector = new WalletConnect({
40 bridge: 'https://bridge.walletconnect.org',
41 qrcodeModal: QRCodeModal,
42 });
44 setConnector(newConnector);
46 // restore state from cached connection
47 if (newConnector.accounts.length > 0) {
48 setAddress(newConnector.accounts[0]);
49 }
51 if (newConnector.peerMeta) {
52 setPeerMeta(newConnector.peerMeta);
53 }
54 };
56 // Init connector
57 useEffect(() => {
58 resetConnector();
59 // eslint-disable-next-line react-hooks/exhaustive-deps
60 }, []);
62 // Behavior
63 const connect = async () => {
64 if (!connector || connector.connected) {
65 return;
66 }
68 await connector.createSession();
69 };
71 const disconnect = async () => {
72 if (!connector) {
73 return;
74 }
76 await connector.killSession();
77 };
79 const authenticate = async () => {
80 if (!(address && connector)) {
81 return;
82 }
84 let authToken = Nothing();
85 try {
86 const token = await getAuthToken({
87 address,
88 connector,
89 walletType: WALLET_TYPES.WALLET_CONNECT,
90 });
91 authToken = Just(token);
92 } catch (e) {
93 if (e.message === 'METHOD_NOT_SUPPORTED') {
94 console.warn(
95 'wallet does not support message signing. proceeding without auth token.'
96 );
97 } else {
98 throw e;
99 //TODO should errors with this *really* prevent login?
100 }
101 }
103 const wallet: WalletConnectWallet = {
104 address,
105 };
107 setAuthToken(authToken);
108 setWallet(Just(wallet));
109 };
111 const isConnected = () => {
112 if (!connector) {
113 return false;
114 }
116 return connector.connected;
117 };
119 const peerIcon = useMemo(() => {
120 if (!peerMeta?.icons) {
121 return null;
122 }
124 // Some peers return a list of empty string(s) :)
125 const iconCandidates = peerMeta.icons.filter(pm => pm !== '');
127 return iconCandidates.length > 0 ? iconCandidates[0] : null;
128 }, [peerMeta]);
130 const signTransaction = async ({
131 from,
132 to,
133 gas,
134 gasPrice,
135 value,
136 data,
137 nonce,
138 }: ITxData): Promise<string> => {
139 return new Promise((resolve, reject) => {
140 if (!connector || !isConnected()) {
141 reject(new Error('No connected wallet available for signing'));
142 return;
143 }
145 return connector
146 .signTransaction({
147 from,
148 to,
149 gas,
150 gasPrice,
151 value,
152 data,
153 nonce,
154 })
155 .then((signature: string) => resolve(signature))
156 .catch((error: Error) => reject(error));
157 });
158 };
160 const sendTransaction = async ({
161 from,
162 to,
163 gas,
164 gasPrice,
165 value,
166 data,
167 nonce,
168 }: ITxData): Promise<string> => {
169 return new Promise((resolve, reject) => {
170 if (!connector || !isConnected()) {
171 reject(new Error('No connected wallet available for sending'));
172 return;
173 }
175 //REVIEW .then path untested
176 return connector
177 .sendTransaction({
178 from,
179 to,
180 gas,
181 gasPrice,
182 value,
183 data,
184 nonce,
185 })
186 .then((txHash: string) => resolve(txHash))
187 .catch((error: Error) => reject(error));
188 });
189 };
191 // Event Handlers
192 const initConnectHandler = () => {
193 if (!connector) {
194 return;
195 }
197 connector.on('connect', async (error, payload: ConnectEvent) => {
198 if (error) {
199 throw error;
200 }
202 const address = payload.params[0].accounts[0];
203 setAddress(address);
204 setPeerMeta(payload.params[0].peerMeta);
205 });
206 };
208 const initDisconnectHandler = () => {
209 if (!connector) {
210 return;
211 }
213 connector.on('disconnect', (error, _payload) => {
214 if (error) {
215 throw error;
216 }
218 setAddress(null);
219 setPeerMeta(null);
220 resetWallet();
221 });
222 };
224 const initSessionUpdateHandler = () => {
225 if (!connector) {
226 return;
227 }
229 connector.on('session_update', (error, payload) => {
230 if (error) {
231 throw error;
232 }
234 disconnect();
235 });
236 };
238 const initModalClosedHandler = () => {
239 if (!connector) {
240 return;
241 }
243 connector.on('modal_closed', () => {
244 resetWallet();
245 resetConnector();
246 });
247 };
249 // Init and clean up
250 useEffect(() => {
251 if (!connector) {
252 return;
253 }
255 initConnectHandler();
256 initDisconnectHandler();
257 initSessionUpdateHandler();
258 initModalClosedHandler();
260 return () => {
261 // Clean up listeners
262 connector.off('connect');
263 connector.off('disconnect');
264 connector.off('session_update');
265 connector.off('modal_closed');
266 };
267 // eslint-disable-next-line react-hooks/exhaustive-deps
268 }, [connector]);
270 return {
271 address,
272 authenticate,
273 connect,
274 connector,
275 disconnect,
276 isConnected,
277 peerIcon,
278 peerMeta,
279 resetConnector,
280 signTransaction,
281 sendTransaction,
282 };
283};