on 4b0ca21 help sign in
bridge/src/lib/useWalletConnect.ts
238 lines 4.9 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 } 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 } = 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 const authToken = await getAuthToken({
85 address,
86 connector,
87 walletType: WALLET_TYPES.WALLET_CONNECT,
88 });
90 const wallet: WalletConnectWallet = {
91 address,
92 };
94 setAuthToken(Just(authToken));
95 setWallet(Just(wallet));
96 };
98 const isConnected = () => {
99 if (!connector) {
100 return false;
101 }
103 return connector.connected;
104 };
106 const peerIcon = useMemo(() => {
107 if (!peerMeta?.icons) {
108 return null;
109 }
111 // Some peers return a list of empty string(s) :)
112 const iconCandidates = peerMeta.icons.filter(pm => pm !== '');
114 return iconCandidates.length > 0 ? iconCandidates[0] : null;
115 }, [peerMeta]);
117 const signTransaction = async ({
118 from,
119 to,
120 gas,
121 gasPrice,
122 value,
123 data,
124 nonce,
125 }: ITxData): Promise<string> => {
126 return new Promise((resolve, reject) => {
127 if (!connector || !isConnected()) {
128 reject(new Error('No connected wallet available for signing'));
129 return;
130 }
132 return connector
133 .signTransaction({
134 from,
135 to,
136 gas,
137 gasPrice,
138 value,
139 data,
140 nonce,
141 })
142 .then((txHash: string) => resolve(txHash))
143 .catch((error: Error) => reject(error));
144 });
145 };
147 // Event Handlers
148 const initConnectHandler = () => {
149 if (!connector) {
150 return;
151 }
153 connector.on('connect', async (error, payload: ConnectEvent) => {
154 if (error) {
155 throw error;
156 }
158 const address = payload.params[0].accounts[0];
159 setAddress(address);
160 setPeerMeta(payload.params[0].peerMeta);
161 });
162 };
164 const initDisconnectHandler = () => {
165 if (!connector) {
166 return;
167 }
169 connector.on('disconnect', (error, _payload) => {
170 if (error) {
171 throw error;
172 }
174 setAddress(null);
175 setPeerMeta(null);
176 resetWallet();
177 });
178 };
180 const initSessionUpdateHandler = () => {
181 if (!connector) {
182 return;
183 }
185 connector.on('session_update', (error, payload) => {
186 if (error) {
187 throw error;
188 }
190 disconnect();
191 });
192 };
194 const initModalClosedHandler = () => {
195 if (!connector) {
196 return;
197 }
199 connector.on('modal_closed', () => {
200 resetWallet();
201 resetConnector();
202 });
203 };
205 // Init and clean up
206 useEffect(() => {
207 if (!connector) {
208 return;
209 }
211 initConnectHandler();
212 initDisconnectHandler();
213 initSessionUpdateHandler();
214 initModalClosedHandler();
216 return () => {
217 // Clean up listeners
218 connector.off('connect');
219 connector.off('disconnect');
220 connector.off('session_update');
221 connector.off('modal_closed');
222 };
223 // eslint-disable-next-line react-hooks/exhaustive-deps
224 }, [connector]);
226 return {
227 address,
228 authenticate,
229 connect,
230 connector,
231 disconnect,
232 isConnected,
233 peerIcon,
234 peerMeta,
235 resetConnector,
236 signTransaction,
237 };
238};