feat: add optional wallet.getAdminAccount(), new useAdminWallet() hook and auto auth when using inapp+smart accounts (#4601)

Fixes CNCT-1833

## PR-Codex overview
This PR introduces a new `useAdminWallet()` hook for smart wallets and adds functionality for automatic authentication with in-app and smart accounts.

### Detailed summary
- Added `getAdminAccount` method to `Wallet` interface
- Created `useAdminWallet()` hook to get admin wallet for smart wallets
- Automatic authentication for in-app and smart accounts
- Updated various files to implement the new hook and method

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
This commit is contained in:
joaquim-verges
2024-09-15 20:34:38 +00:00
parent eba1c60895
commit a4a41364cd
8 changed files with 61 additions and 6 deletions
+16
View File
@@ -0,0 +1,16 @@
---
"thirdweb": minor
---
`useAdminWallet()` Hook + automatically auth when using inapp + smart accounts
### Add `useAdminWallet()` hook to get the admin wallet for a smart wallet
```ts
const activeWallet = useActiveWallet(); // smart wallet
const adminWallet = useAdminWallet(); // the personal wallet that controls the smart wallet
```
### Automatically auth when using inapp + smart accounts
When using auth with an inapp + smart wallet, ConnectButton and ConnectEmebed will automatically auth without having to click sign in.
@@ -9,6 +9,7 @@ export type {
// wallet hooks
export { useActiveWallet } from "../react/core/hooks/wallets/useActiveWallet.js";
export { useAdminWallet } from "../react/core/hooks/wallets/useAdminAccount.js";
export { useActiveWalletChain } from "../react/core/hooks/wallets/useActiveWalletChain.js";
export { useActiveWalletConnectionStatus } from "../react/core/hooks/wallets/useActiveWalletConnectionStatus.js";
export { useActiveAccount } from "../react/core/hooks/wallets/useActiveAccount.js";
+1
View File
@@ -40,6 +40,7 @@ export type { MediaRendererProps } from "../react/web/ui/MediaRenderer/types.js"
// wallet hooks
export { useActiveWallet } from "../react/core/hooks/wallets/useActiveWallet.js";
export { useAdminWallet } from "../react/core/hooks/wallets/useAdminAccount.js";
export { useActiveWalletChain } from "../react/core/hooks/wallets/useActiveWalletChain.js";
export { useActiveWalletConnectionStatus } from "../react/core/hooks/wallets/useActiveWalletConnectionStatus.js";
export { useActiveAccount } from "../react/core/hooks/wallets/useActiveAccount.js";
@@ -0,0 +1,25 @@
import { useActiveWallet } from "./useActiveWallet.js";
import { useConnectedWallets } from "./useConnectedWallets.js";
/**
* Get the admin wallet for the active wallet
* Useful for smart wallets to get the underlying personal account
* @returns The admin wallet for the active wallet, or the active wallet if it doesn't have an admin account
*/
export function useAdminWallet() {
const activeWallet = useActiveWallet();
const connectedWallets = useConnectedWallets();
const adminAccount = activeWallet?.getAdminAccount?.();
if (!adminAccount) {
// If the active wallet doesn't have an admin account, return the active wallet
return activeWallet;
}
// If the active wallet has an admin account, find the admin wallet in connected wallets and return it
return connectedWallets.find(
(wallet) =>
wallet.getAccount()?.address?.toLowerCase() ===
adminAccount?.address?.toLowerCase(),
);
}
@@ -13,6 +13,7 @@ import { useSiweAuth } from "../../../../core/hooks/auth/useSiweAuth.js";
import type { ConnectButtonProps } from "../../../../core/hooks/connection/ConnectButtonProps.js";
import { useActiveAccount } from "../../../../core/hooks/wallets/useActiveAccount.js";
import { useActiveWallet } from "../../../../core/hooks/wallets/useActiveWallet.js";
import { useAdminWallet } from "../../../../core/hooks/wallets/useAdminAccount.js";
import { useDisconnect } from "../../../../core/hooks/wallets/useDisconnect.js";
import { wait } from "../../../../core/utils/wait.js";
import { LoadingScreen } from "../../../wallets/shared/LoadingScreen.js";
@@ -46,12 +47,12 @@ export const SignatureScreen: React.FC<{
connectLocale,
} = props;
const activeWallet = useActiveWallet();
const wallet = useActiveWallet();
const adminWallet = useAdminWallet();
const activeAccount = useActiveAccount();
const siweAuth = useSiweAuth(activeWallet, activeAccount, props.auth);
const siweAuth = useSiweAuth(wallet, activeAccount, props.auth);
const [status, setStatus] = useState<Status>("idle");
const { disconnect } = useDisconnect();
const wallet = useActiveWallet();
const locale = connectLocale.signatureScreen;
const signIn = useCallback(async () => {
@@ -66,12 +67,15 @@ export const SignatureScreen: React.FC<{
}
}, [onDone, siweAuth]);
// this should not happen
if (!wallet) {
return <LoadingScreen />;
}
if (wallet.id === "inApp" || wallet.id === "embedded") {
if (
wallet.id === "inApp" ||
wallet.id === "embedded" ||
(wallet.id === "smart" && adminWallet?.id === "inApp")
) {
return (
<HeadlessSignIn
signIn={signIn}
@@ -305,7 +305,6 @@ export class IFrameWallet {
return signedMessage as Hex;
},
async signTypedData(_typedData) {
console.log("signTypedData", _typedData);
const parsedTypedData = parseTypedData(_typedData);
// deleting EIP712 Domain as it results in ambiguous primary type on some cases
// this happens when going from viem to ethers via the iframe
@@ -141,6 +141,12 @@ export type Wallet<TWalletId extends WalletId = WalletId> = {
* Can be used to execute any pre-connection actions like showing a modal, etc.
*/
onConnectRequested?: () => Promise<void>;
/**
* Get the admin account of this wallet
* This is useful for smart wallets to get the underlying personal account
*/
getAdminAccount?: () => Account | undefined;
};
/**
@@ -102,6 +102,7 @@ export function smartWallet(
): Wallet<"smart"> {
const emitter = createWalletEmitter<"smart">();
let account: Account | undefined = undefined;
let adminAccount: Account | undefined = undefined;
let chain: Chain | undefined = undefined;
let lastConnectOptions: WalletConnectionOption<"smart"> | undefined;
@@ -118,6 +119,7 @@ export function smartWallet(
},
getConfig: () => createOptions,
getAccount: () => account,
getAdminAccount: () => adminAccount,
autoConnect: async (options) => {
const { connectSmartWallet } = await import("./index.js");
const [connectedAccount, connectedChain] = await connectSmartWallet(
@@ -145,6 +147,7 @@ export function smartWallet(
createOptions,
);
// set the states
adminAccount = options.personalAccount;
lastConnectOptions = options;
account = connectedAccount;
chain = connectedChain;