Skip to content

Commit

Permalink
widget-v2: set required chain addresses (#265)
Browse files Browse the repository at this point in the history
  • Loading branch information
codingki authored Sep 19, 2024
1 parent 63c1686 commit ace78f8
Show file tree
Hide file tree
Showing 10 changed files with 454 additions and 40 deletions.
177 changes: 177 additions & 0 deletions packages/widget-v2/src/hooks/useAutoSetAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { skipChainsAtom } from "@/state/skipClient";
import { ChainAddress, chainAddressEffectAtom, chainAddressesAtom, swapExecutionStateAtom } from "@/state/swapExecutionPage";
import { walletsAtom } from "@/state/wallets";
import { useQuery } from "@tanstack/react-query";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { useCreateCosmosWallets } from "./useCreateCosmosWallets";
import { useCreateEvmWallets } from "./useCreateEvmWallets";
import { useCreateSolanaWallets } from "./useCreateSolanaWallets";
import { useEffect, useMemo } from "react";
import { getClientOperations } from "@/utils/clientType";
import { getSignRequiredChainIds } from "@/utils/operations";

export const useAutoSetAddress = () => {
const setChainAddresses = useSetAtom(chainAddressesAtom);
const { route } = useAtomValue(swapExecutionStateAtom);
const requiredChainAddresses = route?.requiredChainAddresses;
const { data: chains } = useAtomValue(skipChainsAtom);
const sourceWallet = useAtomValue(walletsAtom);

useAtom(chainAddressEffectAtom);

const { createCosmosWallets } = useCreateCosmosWallets();
const { createEvmWallets } = useCreateEvmWallets();
const { createSolanaWallets } = useCreateSolanaWallets();

const signRequiredChains = useMemo(() => {
if (!route?.operations) return;
const operations = getClientOperations(route.operations);
const signRequiredChains = getSignRequiredChainIds(operations);
return signRequiredChains;
}, [route?.operations]);

useEffect(() => {
const res: Record<number, ChainAddress> = {};
if (!route?.operations) return;
route?.requiredChainAddresses?.forEach((chainID, index) => {
res[index] = {
chainID,
};
});
setChainAddresses(res);
// we only want to run this once for preserve the chainAddresses chainID
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const connectRequiredChains = async () => {
if (!requiredChainAddresses) return;
requiredChainAddresses.forEach(async (chainID, index) => {
const chain = chains?.find((c) => c.chainID === chainID);
if (!chain) {
return;
}

const isSignRequired = signRequiredChains?.includes(chainID);
const chainType = chain.chainType;
switch (chainType) {
case "cosmos":
{
const wallets = createCosmosWallets(chainID);
const wallet = wallets.find(w => w.walletName === sourceWallet.cosmos?.walletName);
if (!wallet) {
return;
}
try {
const address = await wallet?.getAddress?.({
signRequired: isSignRequired,
});
if (!address) {
return;
}
setChainAddresses((prev) => ({
...prev,
[index]: {
chainID,
address,
chainType: "cosmos",
source: "wallet",
wallet: {
walletName: wallet?.walletName,
walletPrettyName: wallet?.walletPrettyName,
walletChainType: wallet?.walletChainType,
walletInfo: wallet?.walletInfo,
},
},
}));
} catch (_) {
return;
}
break;
}
case "svm":
{
const wallets = createSolanaWallets();
const wallet = wallets.find(w => w.walletName === sourceWallet.svm?.walletName);
if (!wallet) {
return;
}
try {
const address = await wallet?.getAddress?.({
signRequired: isSignRequired,
});
if (!address) {
return;
}
setChainAddresses((prev) => ({
...prev,
[index]: {
chainID,
address,
chainType: "svm",
source: "wallet",
wallet: {
walletName: wallet?.walletName,
walletPrettyName: wallet?.walletPrettyName,
walletChainType: wallet?.walletChainType,
walletInfo: wallet?.walletInfo,
},
},
}));
} catch (_) {
return;
}

break;
}
case "evm":
{
const wallets = createEvmWallets(chainID);
const wallet = wallets.find(w => w.walletName === sourceWallet.evm?.walletName);
if (!wallet) {
return;
}
try {
const address = await wallet?.getAddress?.({
signRequired: isSignRequired,
});
if (!address) {
return;
}
setChainAddresses((prev) => ({
...prev,
[index]: {
chainID,
address,
chainType: "evm",
source: "wallet",
wallet: {
walletName: wallet?.walletName,
walletPrettyName: wallet?.walletPrettyName,
walletChainType: wallet?.walletChainType,
walletInfo: wallet?.walletInfo,
},
},
}));
} catch (_) {
return;
}
break;
}
default:
break;
}
});
};

useQuery({
queryKey: ["auto-set-address", { requiredChainAddresses, chains, sourceWallet, signRequiredChains }],
enabled: !!requiredChainAddresses && !!chains && (!!sourceWallet.cosmos || !!sourceWallet.evm || !!sourceWallet.svm),
queryFn: async () => {
if (!requiredChainAddresses) return;
await connectRequiredChains();
return null;
},
});

return { connectRequiredChains };
};
5 changes: 3 additions & 2 deletions packages/widget-v2/src/hooks/useCreateCosmosWallets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const useCreateCosmosWallets = () => {

for (const wallet of cosmosWallets) {
const getAddress = async ({ signRequired }: { signRequired?: boolean; context?: "recovery" | "destination" }) => {
if (wallet !== currentWallet) {
if (wallet !== currentWallet || !currentAddress) {
if (!chainInfo) throw new Error(`getAddress: Chain info not found for chainID: ${chainID}`);
await getWallet(wallet).experimentalSuggestChain(chainInfo);
await connect({
Expand All @@ -84,7 +84,8 @@ export const useCreateCosmosWallets = () => {
} else if (currentAddress && isConnected && signRequired) {
setCosmosWallet({ walletName: wallet, chainType: "cosmos" });
}
return currentAddress;
const address = (await getWallet(wallet).getKey(chainID)).bech32Address;
return address;
};
const walletInfo = getCosmosWalletInfo(wallet);
const minimalWallet: MinimalWallet = {
Expand Down
2 changes: 1 addition & 1 deletion packages/widget-v2/src/hooks/useCreateEvmWallets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const useCreateEvmWallets = () => {
const evmGetAddress: MinimalWallet["getAddress"] = async ({
signRequired,
}) => {
if (connector.id !== currentEvmConnector?.id) {
if (connector.id !== currentEvmConnector?.id || !isEvmConnected) {
await connectAsync({ connector, chainId: Number(chainID) });
setEvmWallet({ walletName: connector.id, chainType: "evm" });
} else if (evmAddress && isEvmConnected && signRequired) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@ import { txState } from "./SwapExecutionPageRouteDetailedRow";
import { useModal } from "@/components/Modal";
import { currentPageAtom, Routes } from "@/state/router";
import { ClientOperation, getClientOperations } from "@/utils/clientType";
import { swapExecutionStateAtom } from "@/state/swapExecutionPage";
import {
chainAddressesAtom,
skipSubmitSwapExecutionAtom,
swapExecutionStateAtom,
} from "@/state/swapExecutionPage";
import { useAutoSetAddress } from "@/hooks/useAutoSetAddress";

enum SwapExecutionState {
recoveryAddressUnset,
destinationAddressUnset,
unconfirmed,
broadcasted,
ready,
pending,
confirmed,
}

Expand All @@ -31,16 +37,16 @@ export const SwapExecutionPage = () => {
const theme = useTheme();
const setCurrentPage = useSetAtom(currentPageAtom);
const { route } = useAtomValue(swapExecutionStateAtom);
const chainAddresses = useAtomValue(chainAddressesAtom);

const { connectRequiredChains } = useAutoSetAddress();

const clientOperations = useMemo(() => {
if (!route?.operations) return [] as ClientOperation[];
return getClientOperations(route.operations);
}, [route?.operations]);

const [_destinationWallet] = useAtom(destinationWalletAtom);
const [swapExecutionState, _setSwapExecutionState] = useState(
SwapExecutionState.unconfirmed
);

const [simpleRoute, setSimpleRoute] = useState(true);
const modal = useModal(ManualAddressModal);
Expand All @@ -51,8 +57,45 @@ export const SwapExecutionPage = () => {
2: "pending",
});

const { mutate, isPending, isSuccess } = useAtomValue(
skipSubmitSwapExecutionAtom
);

const swapExecutionState = useMemo(() => {
if (!chainAddresses) return;
const requiredChainAddresses = route?.requiredChainAddresses;
if (!requiredChainAddresses) return;
const allAddressesSet = requiredChainAddresses.every(
(_chainId, index) => chainAddresses[index]?.address
);
const lastChainAddress =
chainAddresses[requiredChainAddresses.length - 1]?.address;

if (!isPending) {
if (allAddressesSet) {
return SwapExecutionState.ready;
}
if (!lastChainAddress) {
return SwapExecutionState.destinationAddressUnset;
}
return SwapExecutionState.recoveryAddressUnset;
}
if (isSuccess) {
return SwapExecutionState.confirmed;
}
return SwapExecutionState.pending;
}, [chainAddresses, isPending, isSuccess, route?.requiredChainAddresses]);

const renderMainButton = useMemo(() => {
switch (swapExecutionState) {
case SwapExecutionState.recoveryAddressUnset:
return (
<MainButton
label="Set recovery address"
icon={ICONS.rightArrow}
onClick={connectRequiredChains}
/>
);
case SwapExecutionState.destinationAddressUnset:
return (
<MainButton
Expand All @@ -61,14 +104,15 @@ export const SwapExecutionPage = () => {
onClick={() => modal.show()}
/>
);
case SwapExecutionState.unconfirmed:
case SwapExecutionState.ready:
return (
<MainButton
label="Confirm swap"
icon={ICONS.rightArrow}
onClick={mutate}
/>
);
case SwapExecutionState.broadcasted:
case SwapExecutionState.pending:
return (
<MainButton
label="Swap in progress"
Expand All @@ -86,7 +130,14 @@ export const SwapExecutionPage = () => {
/>
);
}
}, [clientOperations.length, modal, swapExecutionState, theme.success.text]);
}, [
clientOperations.length,
connectRequiredChains,
modal,
mutate,
swapExecutionState,
theme.success.text,
]);

const SwapExecutionPageRoute = useMemo(() => {
if (simpleRoute) {
Expand All @@ -105,7 +156,7 @@ export const SwapExecutionPage = () => {
leftButton={{
label: "Back",
icon: ICONS.thinArrow,
onClick: () => setCurrentPage(Routes.SwapPage)
onClick: () => setCurrentPage(Routes.SwapPage),
}}
rightButton={{
label: simpleRoute ? "Details" : "Hide details",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { SmallText } from "@/components/Typography";
import { ClientOperation, OperationType } from "@/utils/clientType";
import { skipBridgesAtom, skipSwapVenuesAtom } from "@/state/skipClient";
import { useAtom } from "jotai";
import { getIsOperationSignRequired } from "@/utils/operations";

export type SwapExecutionPageRouteDetailedProps = {
operations: ClientOperation[];
Expand Down Expand Up @@ -79,6 +80,8 @@ export const SwapExecutionPageRouteDetailed = ({
chainID={firstOperation.fromChainID}
txState={"pending"}
key={`first-row-${firstOperation?.denomIn}`}
context="source"
index={0}
/>
{operations.map((operation, index) => {
const simpleOperationType =
Expand All @@ -100,6 +103,8 @@ export const SwapExecutionPageRouteDetailed = ({
};

const bridgeOrSwapVenue = getBridgeSwapVenue();
const nextOperation = operations[index + 1];
const isSignRequired = getIsOperationSignRequired(index, operations, nextOperation, operation);

const asset = {
tokenAmount: operation.amountOut,
Expand Down Expand Up @@ -127,6 +132,9 @@ export const SwapExecutionPageRouteDetailed = ({
</Row>
<SwapExecutionPageRouteDetailedRow
{...asset}
index={index}
context={index === operations.length - 1 ? "destination" : "intermediary"}
isSignRequired={isSignRequired}
txState={txStateMap[index]}
explorerLink={
txStateMap[index] !== "pending"
Expand Down
Loading

0 comments on commit ace78f8

Please sign in to comment.