@btc-vision/walletconnect
Version:
The OP_NET Wallet Connect library helps your dApp connect to any compatible wallet.
179 lines (178 loc) • 6.97 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { createContext, useCallback, useContext, useEffect, useRef, useState, } from 'react';
import WalletConnection, { SupportedWallets } from './WalletConnection';
const WalletContext = createContext(undefined);
const MAX_RETRIES = 10;
const RETRY_DELAY_MS = 2000;
function useDocumentComplete(fn) {
const fired = useRef(false);
useEffect(() => {
if (fired.current)
return;
const run = () => {
if (fired.current)
return;
fired.current = true;
fn();
};
if (document.readyState === 'complete') {
run();
return;
}
const handler = () => {
if (document.readyState === 'complete') {
document.removeEventListener('readystatechange', handler);
run();
}
};
document.addEventListener('readystatechange', handler);
return () => document.removeEventListener('readystatechange', handler);
}, [fn]);
}
export const WalletProvider = ({ children }) => {
const [walletConnection] = useState(() => new WalletConnection());
const [walletType, setWalletType] = useState(null);
const [walletWindowInstance, setWalletWindowInstance] = useState(null);
const [account, setAccount] = useState(null);
const listeners = useRef({});
const disconnect = useCallback(() => {
const inst = walletWindowInstance;
if (inst) {
if (listeners.current.disconnect) {
inst.removeListener?.('disconnect', listeners.current.disconnect);
listeners.current.disconnect = undefined;
}
if (listeners.current.accountsChanged) {
inst.removeListener?.('accountsChanged', listeners.current.accountsChanged);
listeners.current.accountsChanged = undefined;
}
}
walletConnection.disconnect();
setWalletType(null);
setWalletWindowInstance(null);
localStorage.removeItem('walletType');
setAccount(null);
}, [walletConnection, walletWindowInstance]);
const connect = useCallback(async (type, signal) => {
let attempt = 0;
const throwIfAborted = () => {
if (signal?.aborted)
throw new DOMException('Aborted', 'AbortError');
};
while (attempt < MAX_RETRIES) {
throwIfAborted();
try {
await walletConnection.connect(type);
if ((walletConnection.walletType !== SupportedWallets.OP_WALLET &&
!walletConnection.signer) ||
!walletConnection.walletWindowInstance) {
throw new Error('Wallet not fully loaded yet');
}
break;
}
catch (err) {
attempt += 1;
if (attempt >= MAX_RETRIES) {
console.warn(`Failed to connect after ${MAX_RETRIES} attempts.`, err);
disconnect();
return;
}
console.warn(`Connection attempt ${attempt} failed:`, err.message);
try {
await new Promise((res, rej) => {
const t = setTimeout(res, RETRY_DELAY_MS);
signal?.addEventListener('abort', () => {
clearTimeout(t);
rej(new DOMException('Aborted', 'AbortError'));
});
});
}
catch {
console.debug('Connection aborted during retry delay.');
return;
}
}
}
throwIfAborted();
setWalletType(type);
setWalletWindowInstance(walletConnection.walletWindowInstance);
localStorage.setItem('walletType', type);
try {
const [signer, address, addressTyped, network, provider] = await Promise.all([
walletConnection.signer,
walletConnection.getAddress(),
walletConnection.getAddressTyped(),
walletConnection.getNetwork(),
walletConnection.getProvider(),
]);
setAccount({
isConnected: true,
signer,
address,
addressTyped,
network,
provider,
});
const instance = walletConnection.walletWindowInstance;
if (instance) {
const onDisconnect = () => disconnect();
const onAccountsChanged = async () => {
try {
const [updatedAddr, updatedAddrTyped, updatedNet, updatedProv] = await Promise.all([
walletConnection.getAddress(),
walletConnection.getAddressTyped(),
walletConnection.getNetwork(),
walletConnection.getProvider(),
]);
setAccount((prev) => prev
? {
...prev,
address: updatedAddr,
addressTyped: updatedAddrTyped,
network: updatedNet,
provider: updatedProv,
}
: prev);
}
catch {
disconnect();
}
};
listeners.current.disconnect = onDisconnect;
listeners.current.accountsChanged = onAccountsChanged;
instance.on('disconnect', onDisconnect);
instance.on('accountsChanged', onAccountsChanged);
}
}
catch (err) {
console.warn('Unable to finalize wallet connection:', err);
disconnect();
}
}, [walletConnection, disconnect]);
useDocumentComplete(() => {
const stored = localStorage.getItem('walletType');
if (!stored)
return;
const controller = new AbortController();
connect(stored, controller.signal).catch((err) => {
if (err.name !== 'AbortError') {
console.warn('Failed to reconnect to wallet:', err);
}
});
return () => controller.abort();
});
const ctx = {
connect,
disconnect,
walletType,
walletWindowInstance,
account,
};
return _jsx(WalletContext.Provider, { value: ctx, children: children });
};
export const useWallet = () => {
const ctx = useContext(WalletContext);
if (!ctx)
throw new Error('useWallet must be used within a WalletProvider');
return ctx;
};