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 24, 2024
1 parent 82b3b74 commit ccd2aad
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 304 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.

38 changes: 17 additions & 21 deletions components/LoginSignupCard.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +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 />
<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
133 changes: 67 additions & 66 deletions components/UserCard.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,86 @@
"use client";
import { publicClient } from "@/client";
import { useAccountContext } from "@/context/AccountContext";
import { useSignerContext } from "@/context/SignerContext";
import {
useAddPasskey,
useBundlerClient,
useExportAccount,
useLogout,
useSigner,
useSignMessage,
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 React, { useState } from "react";
import { isHex } from "viem";
import { z } from "zod";

const TurnkeyExportWalletContainerId = "turnkey-export-wallet-container-id";
const TurnkeyExportWalletElementId = "turnkey-export-wallet-element-id";

const iframeCss = `
iframe {
box-sizing: border-box;
width: 100%;
height: 120px;
border-radius: 8px;
border-width: 1px;
border-style: solid;
border-color: rgba(216, 219, 227, 1);
padding: 20px;
}
`;
const iframeCss: React.CSSProperties = {
boxSizing: "border-box",
width: "100%",
height: "120px",
borderRadius: "8px",
borderWidth: "1px",
borderStyle: "solid",
borderColor: "rgba(216, 219, 227, 1)",
padding: "20px",
};

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 [isValid, setIsValid] = useState<boolean | undefined>(undefined);

const { mutate: signMessage, data: { signature, isValid } = {} } =
useMutation({
mutationFn: async (msg: string) => {
return provider
.signMessageWith6492({ message: msg })
.then(async (signature) => {
return {
signature,
isValid: await publicClient
.verifyMessage({
address: provider.getAddress(),
message: msg,
signature,
})
.catch((e: any) => {
console.log("error verifying signature, ", e);
return false;
}),
};
});
},
});
const { signMessage, signedMessage } = useSignMessage({
client,
onSuccess: async (signature, msg) => {
setIsValid(
await bundlerClient
.verifyMessage({
address: client!.getAddress(),
message: msg.message,
signature,
})
.catch((e: any) => {
console.log("error verifying signature, ", e);
return false;
})
);
},
});

const { mutate, isPending, data } = useMutation({
mutationFn: async () =>
signer.exportWallet({
const { ExportAccountComponent, exportAccount, isExporting, isExported } =
useExportAccount({
params: {
iframeContainerId: TurnkeyExportWalletContainerId,
iframeElementId: TurnkeyExportWalletElementId,
}),
});
},
});

const { mutate: addPasskey } = useMutation({
mutationFn: async () => signer.addPasskey({}),
const { addPasskey } = useAddPasskey({
onSuccess: (data) => {
console.log(data);
},
});

const { mutate: logout } = useMutation({
mutationFn: async () => signer.disconnect(),
onSuccess: () => {
window.location.reload();
},
});
const { logout } = useLogout();

const form = useForm({
defaultValues: {
message: "",
},
validatorAdapter: zodValidator,
onSubmit: ({ value }) => signMessage(value.message),
onSubmit: ({ value }) =>
signMessage({
message: isHex(value.message) ? { raw: value.message } : value.message,
}),
});

return (
Expand All @@ -98,7 +101,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 +147,7 @@ export const UserCard = () => {
{({ canSubmit, isSubmitting }) => (
<button
className="btn"
disabled={!canSubmit || isSubmitting}
disabled={!canSubmit || isSubmitting || isLoadingClient}
type="submit"
>
Submit
Expand All @@ -153,11 +156,11 @@ export const UserCard = () => {
</form.Subscribe>
</form>
</form.Provider>
{signature && (
{signedMessage && (
<>
<div className="flex flex-col">
<strong>Signature</strong>
<code className="break-words">{signature}</code>
<code className="break-words">{signedMessage}</code>
</div>
<div className="flex flex-col">
<strong>Is Valid?</strong>
Expand All @@ -166,20 +169,18 @@ export const UserCard = () => {
</>
)}
<div className="flex flex-col gap-2">
{!data ? (
<button onClick={() => mutate()} disabled={isPending}>
{!isExported ? (
<button onClick={() => exportAccount()} disabled={isExporting}>
Export Wallet
</button>
) : (
<strong>Seed Phrase</strong>
)}
<div
<ExportAccountComponent
iframeCss={iframeCss}
className="w-full"
style={{ display: !data ? "none" : "block" }}
id={TurnkeyExportWalletContainerId}
>
<style>{iframeCss}</style>
</div>
isExported={isExported}
/>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit ccd2aad

Please sign in to comment.