Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 122 additions & 40 deletions src/providers/WalletProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,87 +33,169 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }) => {
useState<Omit<WalletContextType, "isPending">>(initialState);
const [isPending, startTransition] = useTransition();
const popupLock = useRef(false);
const popupLock2 = useRef(false);

const signTransaction = wallet.signTransaction.bind(wallet);
const stateRef = useRef(state);
useEffect(() => {
stateRef.current = state;
}, [state]);

const nullify = () => {
setState(initialState);
updateState(initialState);
storage.setItem("walletId", "");
storage.setItem("walletAddress", "");
storage.setItem("walletNetwork", "");
storage.setItem("networkPassphrase", "");
};

const updateState = (newState: Omit<WalletContextType, "isPending">) => {
setState((prev: Omit<WalletContextType, "isPending">) => {
if (
prev.address !== newState.address ||
prev.network !== newState.network ||
prev.networkPassphrase !== newState.networkPassphrase
) {
return newState;
}
return prev;
});
};

const updateNetwork = async () => {
try {
const n = await wallet.getNetwork();
if (n.network) {
storage.setItem("walletNetwork", n.network);
storage.setItem("networkPassphrase", n.networkPassphrase);
return n;
} else {
storage.setItem("walletId", "");
storage.setItem("walletNetwork", "");
storage.setItem("networkPassphrase", "");
}
} catch (err) {
console.error(err);
nullify();
}
};

const updateAddress = async () => {
try {
const a = await wallet.getAddress();
if (a.address) {
storage.setItem("walletAddress", a.address);
return a;
} else {
storage.setItem("walletId", "");
storage.setItem("walletAddress", "");
}
} catch (err) {
console.error(err);
nullify();
}
};

const updateCurrentWalletState = async () => {
// There is no way, with StellarWalletsKit, to check if the wallet is
// installed/connected/authorized. We need to manage that on our side by
// checking our storage item.
const walletId = storage.getItem("walletId");
const walletNetwork = storage.getItem("walletNetwork");
const walletAddr = storage.getItem("walletAddress");
const passphrase = storage.getItem("networkPassphrase");

if (
!stateRef.current.address &&
walletAddr !== null &&
walletNetwork !== null &&
passphrase !== null
) {
setState({
address: walletAddr,
network: walletNetwork,
networkPassphrase: passphrase,
});
}

if (!walletId) {
nullify();
} else {
if (popupLock.current) return;
// If our storage item is there, then we try to get the user's address &
// network from their wallet. Note: `getAddress` MAY open their wallet
// extension, depending on which wallet they select!
// network from their wallet.
try {
popupLock.current = true;
wallet.setWallet(walletId);
if (walletId !== "freighter" && walletAddr !== null) return;
const [a, n] = await Promise.all([
wallet.getAddress(),
wallet.getNetwork(),
]);
if (!a.address) storage.setItem("walletId", "");
if (
a.address !== state.address ||
n.network !== state.network ||
n.networkPassphrase !== state.networkPassphrase
) {
storage.setItem("walletAddress", a.address);
setState({ ...a, ...n });
if (walletId !== "freighter" && walletId !== "hana") return;
let n;
if (walletId == "freighter") {
n = await updateNetwork();
}
const a = await updateAddress();

if (a) {
if (!a.address) storage.setItem("walletId", "");
if (n) {
if (
a.address !== state.address ||
n.network !== state.network ||
n.networkPassphrase !== state.networkPassphrase
) {
updateState({ ...a, ...n });
}
} else {
if (a.address !== state.address) {
updateState({
address: a.address,
network: "",
networkPassphrase: "",
});
}
}
}
} catch (e) {
// If `getNetwork` or `getAddress` throw errors... sign the user out???
nullify();
// then log the error (instead of throwing) so we have visibility
// into the error while working on Scaffold Stellar but we do not
// crash the app process
// crash the app process.
console.error(e);
} finally {
popupLock.current = false;
}
}
};

// Every time the page is refreshed this method asks the user to
// reconnect if they are using a wallet that triggers a popup
// when calling getAddress()
const refreshConnection = () => {
// Avoid being triggered by strict mode
if (popupLock2.current) return;
popupLock2.current = true;
const walletId = storage.getItem("walletId");
if (walletId !== null && walletId) {
wallet.setWallet(walletId);
} else return;
if (walletId == "freighter" || walletId == "hana") return;
if (walletId == "hot-wallet") {
updateNetwork().catch((err) => {
console.error(err);
});
}
updateAddress()
.catch((err) => {
console.error(err);
})
.finally(() => {
popupLock2.current = false;
});
};

useEffect(() => {
let timer: NodeJS.Timeout;
let isMounted = true;
refreshConnection();

// Create recursive polling function to check wallet state continuously
const pollWalletState = async () => {
if (!isMounted) return;

const walletNetwork = storage.getItem("walletNetwork");
const walletAddr = storage.getItem("walletAddress");
const passphrase = storage.getItem("networkPassphrase");

if (
!state.address &&
walletAddr !== null &&
walletNetwork !== null &&
passphrase !== null
) {
updateState({
address: walletAddr,
network: walletNetwork,
networkPassphrase: passphrase,
});
}
await updateCurrentWalletState();

if (isMounted) {
Expand All @@ -136,7 +218,7 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }) => {
isMounted = false;
if (timer) clearTimeout(timer);
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- it SHOULD only run once per component mount
}, [state]); // eslint-disable-line react-hooks/exhaustive-deps -- it SHOULD only run once per component mount

const contextValue = useMemo(
() => ({
Expand Down
7 changes: 5 additions & 2 deletions src/util/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import {
ISupportedWallet,
StellarWalletsKit,
WalletNetwork,
sep43Modules,
allowAllModules,
} from "@creit.tech/stellar-wallets-kit";
import { Horizon } from "@stellar/stellar-sdk";
import { networkPassphrase, stellarNetwork } from "../contracts/util";

const kit: StellarWalletsKit = new StellarWalletsKit({
network: networkPassphrase as WalletNetwork,
modules: sep43Modules(),
modules: allowAllModules(),
});

export const connectWallet = async () => {
Expand Down Expand Up @@ -51,6 +51,9 @@ export const connectWallet = async () => {
export const disconnectWallet = async () => {
await kit.disconnect();
storage.removeItem("walletId");
storage.removeItem("walletAddress");
storage.removeItem("walletNetwork");
storage.removeItem("networkPassphrase");
};

function getHorizonHost(mode: string) {
Expand Down