Skip to content

Commit

Permalink
feat: use hooks from aa-alchemy
Browse files Browse the repository at this point in the history
  • Loading branch information
moldy530 committed Apr 11, 2024
1 parent cd024cd commit 53ba7e0
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 318 deletions.
17 changes: 8 additions & 9 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@

import { LoginSignupCard } from "@/components/LoginSignupCard";
import { UserCard } from "@/components/UserCard";
import { AccountContextProvider } from "@/context/AccountContext";
import { useSignerContext } from "@/context/SignerContext";
import { useAccount, useSignerStatus } from "@alchemy/aa-alchemy/react";

export default function Home() {
const { signer, account, isLoadingUser, refetchUserDetails } =
useSignerContext();
const signerStatus = useSignerStatus();
const { account } = useAccount({
type: "MultiOwnerModularAccount",
});

return (
<main className="flex min-h-screen flex-col items-center p-24 gap-4 justify-center">
{isLoadingUser ? (
{signerStatus.isInitializing ? (
<span className="loading loading-ring loading-lg"></span>
) : account == null ? (
<LoginSignupCard signer={signer} onLogin={refetchUserDetails} />
<LoginSignupCard />
) : (
<AccountContextProvider account={account}>
<UserCard />
</AccountContextProvider>
<UserCard />
)}
</main>
);
Expand Down
32 changes: 15 additions & 17 deletions app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
"use client";
import { SignerContextProvider } from "@/context/SignerContext";
import { createConfig } from "@alchemy/aa-alchemy/config";
import { AlchemyAccountProvider } from "@alchemy/aa-alchemy/react";
import { sepolia } from "@alchemy/aa-core";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { PropsWithChildren, Suspense, useState } from "react";
import { PropsWithChildren, Suspense } from "react";

const TurnkeyIframeContainerId = "turnkey-iframe-container-id";
const TurnkeyIframeElementId = "turnkey-iframe-element-id";
const config = createConfig({
// required
rpcUrl: "/api/rpc",
chain: sepolia,
// optional
rootOrgId: "3121a8a0-c548-4d14-a313-630c3b739858",
});

export const Providers = (props: PropsWithChildren) => {
const [queryClient] = useState(() => new QueryClient());
const [clientConfig] = useState({
connection: {
rpcUrl: "/api/rpc",
},
iframeConfig: {
iframeContainerId: TurnkeyIframeContainerId,
iframeElementId: TurnkeyIframeElementId,
},
});
const queryClient = new QueryClient();

export const Providers = (props: PropsWithChildren<{}>) => {
return (
<Suspense>
<QueryClientProvider client={queryClient}>
<SignerContextProvider client={clientConfig}>
<AlchemyAccountProvider config={config} queryClient={queryClient}>
{props.children}
</SignerContextProvider>
</AlchemyAccountProvider>
</QueryClientProvider>
</Suspense>
);
Expand Down
7 changes: 0 additions & 7 deletions client.ts

This file was deleted.

22 changes: 0 additions & 22 deletions components/EmailBundleForm.tsx

This file was deleted.

42 changes: 17 additions & 25 deletions components/LoginSignupCard.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,56 @@
"use client";
import { AlchemySigner } from "@alchemy/aa-alchemy";
import { useMutation } from "@tanstack/react-query";
import { useAccount, useAuthenticate } from "@alchemy/aa-alchemy/react";
import { useState } from "react";
import EmailBundleForm from "./EmailBundleForm";
import EmailForm from "./EmailForm";

type Props = {
signer?: AlchemySigner;
onLogin: () => void;
};

export const LoginSignupCard = ({ signer, onLogin }: Props) => {
export const LoginSignupCard = () => {
const [email, setEmail] = useState<string | undefined>(undefined);

const { mutate, isPending } = useMutation({
mutationFn: signer?.authenticate,
onSuccess: onLogin,
onError: (e) => {
console.error("Failed to login", e);
},
const { authenticate, isPending } = useAuthenticate();
const { isLoadingAccount } = useAccount({
type: "MultiOwnerModularAccount",
skipCreate: true,
});

return (
<div className="card bg-base-100 shadow-xl w-[500px] max-w-[500px]">
<div className="card-body gap-4">
<h2 className="card-title">Login / Signup</h2>
{email && isPending ? (
// OTP bundle input
<EmailBundleForm
onSubmit={(bundle) => {
// resolve(bundle);
}}
/>
<div>Check your email and click the link to complete login</div>
) : (
// email input
<>
<EmailForm
buttonDisabled={isLoadingAccount || isPending}
onSubmit={(email) => {
setEmail(email);
mutate({
authenticate({
type: "email",
email,
});
}}
/>
<div className="flex flex-row gap-2">
<button
className="btn btn-ghost btn-sm"
onClick={() =>
mutate({
authenticate({
type: "passkey",
createNew: true,
username: "Test User",
})
}
disabled={isLoadingAccount || isPending}
>
Use New Passkey
</button>
<div className="divider divider-horizontal"></div>
<button
onClick={() => mutate({ type: "passkey", createNew: false })}
className="btn btn-ghost btn-sm"
onClick={() =>
authenticate({ type: "passkey", createNew: false })
}
disabled={isLoadingAccount || isPending}
>
Use Existing Passkey
</button>
Expand Down
65 changes: 34 additions & 31 deletions components/UserCard.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"use client";
import { publicClient } from "@/client";
import { useAccountContext } from "@/context/AccountContext";
import { useSignerContext } from "@/context/SignerContext";
import {
useBundlerClient,
useSigner,
useSmartAccountClient,
useUser,
} from "@alchemy/aa-alchemy/react";
import { useForm } from "@tanstack/react-form";
import { useMutation } from "@tanstack/react-query";
import { zodValidator } from "@tanstack/zod-form-adapter";
import { z } from "zod";
import { useExportWallet } from "./useExportWallet.js";

const TurnkeyExportWalletContainerId = "turnkey-export-wallet-container-id";
const TurnkeyExportWalletElementId = "turnkey-export-wallet-element-id";
Expand All @@ -24,20 +28,26 @@ iframe {
`;

export const UserCard = () => {
const { signer, user, account } = useSignerContext();
const { provider } = useAccountContext();
const bundlerClient = useBundlerClient();
const signer = useSigner();
const { client, isLoadingClient } = useSmartAccountClient({
type: "MultiOwnerModularAccount",
});
const user = useUser();

const { mutate: signMessage, data: { signature, isValid } = {} } =
useMutation({
mutationFn: async (msg: string) => {
return provider
if (!client) throw new Error("Provider not found");

return client
.signMessageWith6492({ message: msg })
.then(async (signature) => {
return {
signature,
isValid: await publicClient
isValid: await bundlerClient
.verifyMessage({
address: provider.getAddress(),
address: client.getAddress(),
message: msg,
signature,
})
Expand All @@ -50,26 +60,25 @@ export const UserCard = () => {
},
});

const { mutate, isPending, data } = useMutation({
mutationFn: async () =>
signer.exportWallet({
iframeContainerId: TurnkeyExportWalletContainerId,
iframeElementId: TurnkeyExportWalletElementId,
}),
});
// TODO: we need to add this as a hook
const { ExportWalletComponent, exportWallet, isExporting, isExported } =
useExportWallet({
iframeCss,
iframeContainerId: TurnkeyExportWalletContainerId,
iframeElementId: TurnkeyExportWalletElementId,
});

// TODO: we need to add this as a hook
const { mutate: addPasskey } = useMutation({
mutationFn: async () => signer.addPasskey({}),
mutationFn: async () => signer?.addPasskey({}),
onSuccess: (data) => {
console.log(data);
},
});

// TODO: we need to add this as a hook
const { mutate: logout } = useMutation({
mutationFn: async () => signer.disconnect(),
onSuccess: () => {
window.location.reload();
},
mutationFn: async () => signer?.disconnect(),
});

const form = useForm({
Expand Down Expand Up @@ -98,7 +107,7 @@ export const UserCard = () => {
</div>
<div className="flex flex-col">
<strong>Account Address</strong>
<code className="break-words">{account!.address}</code>
<code className="break-words">{client?.account.address}</code>
</div>
<div className="flex flex-col">
<strong>Signer Address</strong>
Expand Down Expand Up @@ -144,7 +153,7 @@ export const UserCard = () => {
{({ canSubmit, isSubmitting }) => (
<button
className="btn"
disabled={!canSubmit || isSubmitting}
disabled={!canSubmit || isSubmitting || isLoadingClient}
type="submit"
>
Submit
Expand All @@ -166,20 +175,14 @@ export const UserCard = () => {
</>
)}
<div className="flex flex-col gap-2">
{!data ? (
<button onClick={() => mutate()} disabled={isPending}>
{!isExported ? (
<button onClick={() => exportWallet()} disabled={isExporting}>
Export Wallet
</button>
) : (
<strong>Seed Phrase</strong>
)}
<div
className="w-full"
style={{ display: !data ? "none" : "block" }}
id={TurnkeyExportWalletContainerId}
>
<style>{iframeCss}</style>
</div>
<ExportWalletComponent className="w-full" isExported={isExported} />
</div>
</div>
</div>
Expand Down
51 changes: 51 additions & 0 deletions components/useExportWallet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useSigner } from "@alchemy/aa-alchemy/react";
import { useMutation } from "@tanstack/react-query";
import { createElement, useCallback } from "react";

export const useExportWallet = ({
iframeContainerId,
iframeElementId,
iframeCss,
}: {
iframeContainerId: string;
iframeElementId?: string;
iframeCss?: string;
}) => {
const signer = useSigner();

const { mutate, isPending, data } = useMutation({
mutationFn: async () =>
signer?.exportWallet({
iframeContainerId: iframeContainerId,
iframeElementId: iframeElementId,
}),
});

const ExportWalletComponent = useCallback(
({
className,
isExported,
}: {
isExported: boolean;
className?: string;
}) => {
return createElement(
"div",
{
className,
style: { display: !isExported ? "none" : "block" },
id: iframeContainerId,
},
createElement("style", {}, iframeCss)
);
},
[iframeContainerId, iframeCss]
);

return {
isExported: !!data,
exportWallet: mutate,
isExporting: isPending,
ExportWalletComponent,
};
};
Loading

0 comments on commit 53ba7e0

Please sign in to comment.