Skip to content

Commit

Permalink
SmartWallet react integration
Browse files Browse the repository at this point in the history
  • Loading branch information
MananTank committed Feb 14, 2024
1 parent bb55646 commit 2ddaf48
Show file tree
Hide file tree
Showing 13 changed files with 461 additions and 44 deletions.
29 changes: 25 additions & 4 deletions packages/thirdweb/src/react/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,32 @@ export {
export { createContractQuery } from "./utils/createQuery.js";

// wallets
export { metamaskConfig } from "./wallets/metamask/metamaskConfig.js";
export {
metamaskConfig,
type MetamaskConfigOptions,
} from "./wallets/metamask/metamaskConfig.js";

export { coinbaseConfig } from "./wallets/coinbase/coinbaseConfig.js";
export { rainbowConfig } from "./wallets/rainbow/rainbowConfig.js";
export { walletConnectConfig } from "./wallets/walletConnect/walletConnectConfig.js";
export { zerionConfig } from "./wallets/zerion/zerionConfig.js";

export {
rainbowConfig,
type RainbowConfigOptions,
} from "./wallets/rainbow/rainbowConfig.js";

export {
walletConnectConfig,
type WalletConnectConfigOptions,
} from "./wallets/walletConnect/walletConnectConfig.js";

export {
zerionConfig,
type ZerionConfigOptions,
} from "./wallets/zerion/zerionConfig.js";

export {
smartWalletConfig,
type SmartWalletConfigOptions,
} from "./wallets/smartWallet/smartWalletConfig.js";

export type { SupportedTokens } from "./ui/ConnectWallet/defaultTokens.js";
export { defaultTokens } from "./ui/ConnectWallet/defaultTokens.js";

Check warning on line 89 in packages/thirdweb/src/react/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/index.tsx#L2-L89

Added lines #L2 - L89 were not covered by tests
3 changes: 2 additions & 1 deletion packages/thirdweb/src/react/ui/ConnectWallet/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ export const ConnectedWalletDetails: React.FC<{
// const wrapperWalletConfig =
// wrapperWallet && walletContext.getWalletConfig(wrapperWallet);

const disableSwitchChain = false;
const disableSwitchChain = !activeAccount?.wallet.switchChain;

// const disableSwitchChain = !!personalWallet;

// const isActuallyMetaMask =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import type { ConnectUIProps, WalletConfig } from "../../types/wallets.js";
import { HeadlessConnectUI } from "../headlessConnectUI.js";
import { SmartWallet, type Account } from "../../../wallets/index.js";
import { useThirdwebProviderProps } from "../../hooks/others/useThirdwebProviderProps.js";
import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
import { useTWLocale } from "../../providers/locale-provider.js";
import { ModalConfigCtx } from "../../providers/wallet-ui-states-provider.js";
import { Spacer } from "../../ui/components/Spacer.js";
import { Spinner } from "../../ui/components/Spinner.js";
import { Container, ModalHeader } from "../../ui/components/basic.js";
import { Button } from "../../ui/components/buttons.js";
import { iconSize, spacing, fontSize } from "../../ui/design-system/index.js";
import { Text } from "../../ui/components/text.js";

/**
* @internal
*/
export const SmartConnectUI = (props: {
connectUIProps: ConnectUIProps;
personalWalletConfig: WalletConfig;
smartWalletChainId: bigint;
}) => {
const [personalAccount, setPersonalAccount] = useState<Account | null>(null);
// const { personalWalletConnection } = useWalletContext();
const { personalWalletConfig } = props;

const { client, dappMetadata } = useThirdwebProviderProps();

if (!personalAccount) {
const _props: ConnectUIProps = {
walletConfig: personalWalletConfig,
screenConfig: props.connectUIProps.screenConfig,
createInstance() {
return props.personalWalletConfig.create({
client: client,
dappMetadata: dappMetadata,
});
},
done(account: Account) {
setPersonalAccount(account);
},
chains: [],
chainId: props.smartWalletChainId,
};

if (personalWalletConfig.connectUI) {
return <personalWalletConfig.connectUI {..._props} />;
}

return <HeadlessConnectUI {..._props} />;
}

return (
<SmartWalletConnecting
connectUIProps={props.connectUIProps}
personalWalletConfig={personalWalletConfig}
personalAccount={personalAccount}
smartWalletChainId={props.smartWalletChainId}
// onBack={props.goBack}
// onConnect={() => {
// props.connected();
// }}
// smartWallet={walletConfig}
// personalWalletConfig={personalWalletConfig}
// personalWallet={personalWalletConnection.activeWallet}
// personalWalletChainId={personalWalletConnection.chainId || 1}
// switchChainPersonalWallet={personalWalletConnection.switchChain}
/>
);
};

const SmartWalletConnecting = (props: {
connectUIProps: ConnectUIProps;
personalAccount: Account;
personalWalletConfig: WalletConfig;
smartWalletChainId: bigint;
}) => {
const locale = useTWLocale().wallets.smartWallet;
// const { personalWallet, personalWalletChainId, switchChainPersonalWallet } =
// props;
const createSmartWalletInstance = props.connectUIProps.createInstance;
const wrongNetwork =
props.personalAccount.wallet.chainId !== props.smartWalletChainId;
const { personalAccount } = props;
const { done } = props.connectUIProps;

const [smartWalletConnectionStatus, setSmartWalletConnectionStatus] =
useState<"connecting" | "connect-error" | "idle">("idle");
const [personalWalletChainSwitchStatus, setPersonalWalletChainSwitchStatus] =
useState<"switching" | "switch-error" | "idle">("idle");

const connectStarted = useRef(false);

const modalSize = useContext(ModalConfigCtx).modalSize;

const handleConnect = useCallback(async () => {
if (!personalAccount) {
throw new Error("No personal account");
}

setSmartWalletConnectionStatus("connecting");

try {
const smartWallet = createSmartWalletInstance() as SmartWallet; // TODO: fix this type
const smartAccount = await smartWallet.connect({
personalAccount,
});

done(smartAccount);
setSmartWalletConnectionStatus("idle");
} catch (e) {
console.error(e);
setSmartWalletConnectionStatus("connect-error");
}
}, [createSmartWalletInstance, done, personalAccount]);

useEffect(() => {
if (!wrongNetwork && !connectStarted.current) {
handleConnect();
connectStarted.current = true;
}
}, [handleConnect, wrongNetwork]);

if (smartWalletConnectionStatus === "connecting") {
return (
<Container
fullHeight
flex="column"
center="both"
style={{
minHeight: "300px",
}}
>
<Text color="primaryText" multiline center>
{locale.connecting}
</Text>
<Spacer y="lg" />
<Spinner color="accentText" size="lg" />
</Container>
);
}

if (smartWalletConnectionStatus === "connect-error") {
return (
<Container
fullHeight
animate="fadein"
flex="column"
center="both"
p="lg"
style={{
minHeight: "300px",
}}
>
<Text color="danger">{locale.failedToConnect}</Text>
</Container>
);
}

return (
<Container fullHeight animate="fadein" flex="column">
<Container p="lg">
<ModalHeader
title={props.personalWalletConfig.metadata.name}
imgSrc={props.personalWalletConfig.metadata.iconUrl}
onBack={props.connectUIProps.screenConfig.goBack}
/>
</Container>

{modalSize === "compact" && <Spacer y="lg" />}

<Container expand flex="column" center="both" p="lg">
<Container p={modalSize === "wide" ? "lg" : undefined}>
<Container flex="row" center="x" color="danger">
<ExclamationTriangleIcon width={iconSize.lg} height={iconSize.lg} />
</Container>

<Spacer y="md" />

<Text size="lg" color="primaryText" center weight={500}>
{locale.wrongNetworkScreen.title}
</Text>

<Spacer y="lg" />

<Text multiline center>
{locale.wrongNetworkScreen.subtitle}
</Text>

<Spacer y="xl" />

<Container flex="column" gap="md">
<Button
type="button"
fullWidth
variant="accent"
style={{
display: "flex",
alignItems: "center",
gap: spacing.sm,
}}
onClick={async () => {
// setConnectError(false);
// setSwitchError(false);
// setSwitchingNetwork(true);
const switchPersonalWalletChain =
personalAccount.wallet.switchChain;
if (!switchPersonalWalletChain) {
setPersonalWalletChainSwitchStatus("switch-error");
throw new Error("No switchChain method");
}

try {
await switchPersonalWalletChain(props.smartWalletChainId);
setPersonalWalletChainSwitchStatus("idle");
} catch (e) {
setPersonalWalletChainSwitchStatus("switch-error");
}
}}
>
{" "}
{personalWalletChainSwitchStatus === "switching"
? "Switching"
: "Switch Network"}
{personalWalletChainSwitchStatus === "switching" && (
<Spinner size="sm" color="accentButtonText" />
)}
</Button>

<Container
flex="row"
gap="sm"
center="both"
color="danger"
style={{
textAlign: "center",
fontSize: fontSize.sm,
opacity:
personalWalletChainSwitchStatus === "switch-error" ? 1 : 0,
transition: "opacity 200ms ease",
}}
>
<ExclamationTriangleIcon
width={iconSize.sm}
height={iconSize.sm}
/>
<span>{locale.wrongNetworkScreen.failedToSwitch}</span>
</Container>
</Container>
</Container>
</Container>
</Container>
);
};

Check warning on line 255 in packages/thirdweb/src/react/wallets/smartWallet/SmartWalletConnectUI.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/wallets/smartWallet/SmartWalletConnectUI.tsx#L2-L255

Added lines #L2 - L255 were not covered by tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
smartWallet,
type SmartWalletOptions,
} from "../../../wallets/index.js";
import type { WalletConfig } from "../../types/wallets.js";
import { SmartConnectUI } from "./SmartWalletConnectUI.js";

export type SmartWalletConfigOptions = Omit<
SmartWalletOptions,
"personalAccount" | "client"
>;

/**
* Integrate Smart wallet connection into your app.
* @param walletConfig - WalletConfig object of a personal wallet to use with the smart wallet.
* @param options - Options for configuring the Smart wallet.
* @example
* ```tsx
* <ThirdwebProvider
* client={client}>
* wallets={[
* smartWalletConfig(metamaskConfig(), smartWalletOptions),
* smartWalletConfig(coinbaseConfig(), smartWalletOptions)
* ]}
* <App />
* </ThirdwebProvider>
* ```
* @returns WalletConfig object to be passed into `ThirdwebProvider`
*/
export const smartWalletConfig = (
walletConfig: WalletConfig,
options: SmartWalletConfigOptions,
): WalletConfig => {
const config: WalletConfig = {
metadata: walletConfig.metadata,
create(createOptions) {
const wallet = smartWallet({ ...options, client: createOptions.client });
wallet.metadata = walletConfig.metadata;
return wallet;
},
connectUI(props) {
const chain = options.chain;
const chainId =
typeof chain === "bigint"
? chain
: typeof chain === "number"
? BigInt(chain)
: BigInt(chain.id);

return (
<SmartConnectUI
connectUIProps={props}
personalWalletConfig={walletConfig}
smartWalletChainId={chainId}
/>
);
},
};

return config;
};

Check warning on line 61 in packages/thirdweb/src/react/wallets/smartWallet/smartWalletConfig.tsx

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/wallets/smartWallet/smartWalletConfig.tsx#L2-L61

Added lines #L2 - L61 were not covered by tests
10 changes: 4 additions & 6 deletions packages/thirdweb/src/wallets/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
export type {
Wallet,
Account,
WalletConnectionOptions,
} from "./interfaces/wallet.js";
export type { Wallet, Account } from "./interfaces/wallet.js";
export type { WalletEventListener } from "./interfaces/listeners.js";
export type { WalletMetadata } from "./types.js";

Expand All @@ -28,6 +24,7 @@ export type {
WalletRDNS,
InjectedWalletOptions,
SpecificInjectedWalletOptions,
InjectedWalletConnectOptions,
} from "./injected/types.js";
export { injectedProvider } from "./injected/mipdStore.js";

Expand Down Expand Up @@ -64,4 +61,5 @@ export {
export type { WalletConnectConnectionOptions } from "./wallet-connect/types.js";

// smart
export { smartWallet } from "./smart/index.js";
export { smartWallet, SmartWallet } from "./smart/index.js";
export type { SmartWalletOptions } from "./smart/types.js";
Loading

0 comments on commit 2ddaf48

Please sign in to comment.