From 9de31318d24ff130cbd521343e5a1e1e7d8fb046 Mon Sep 17 00:00:00 2001 From: Ankush Swarnakar Date: Mon, 12 Aug 2024 11:51:20 -0400 Subject: [PATCH] Add example of linking smart account to user --- hooks/SmartAccountContext.tsx | 39 ++++++++++++++++++++++------------- package-lock.json | 30 +++++++++++++-------------- package.json | 2 +- pages/_app.tsx | 5 +---- pages/dashboard.tsx | 31 +++++++++++++++++++++++++++- 5 files changed, 72 insertions(+), 35 deletions(-) diff --git a/hooks/SmartAccountContext.tsx b/hooks/SmartAccountContext.tsx index e8f092a..734a3e4 100644 --- a/hooks/SmartAccountContext.tsx +++ b/hooks/SmartAccountContext.tsx @@ -1,19 +1,17 @@ import React, { useState, useEffect, useContext } from "react"; -import { ConnectedWallet, useWallets } from "@privy-io/react-auth"; +import { ConnectedWallet, usePrivy, useWallets } from "@privy-io/react-auth"; import { createPublicClient, createWalletClient, custom, http } from "viem"; import { baseSepolia } from "viem/chains"; -import { Address, Chain, Transport } from "viem"; -import { SmartAccount } from "permissionless/accounts"; +import { Chain, Transport } from "viem"; +import { signerToSafeSmartAccount, SmartAccount } from "permissionless/accounts"; import { walletClientToSmartAccountSigner, type SmartAccountClient, createSmartAccountClient, - ENTRYPOINT_ADDRESS_V06, + ENTRYPOINT_ADDRESS_V07, } from "permissionless"; import { EntryPoint } from "permissionless/types"; -import { signerToSimpleSmartAccount } from "permissionless/accounts"; -import { createPimlicoPaymasterClient } from "permissionless/clients/pimlico"; -import { SMART_ACCOUNT_FACTORY_ADDRESS } from "../lib/constants"; +import { createPimlicoBundlerClient, createPimlicoPaymasterClient } from "permissionless/clients/pimlico"; /** Interface returned by custom `useSmartAccount` hook */ interface SmartAccountInterface { @@ -50,11 +48,14 @@ export const SmartAccountProvider = ({ }) => { // Get a list of all of the wallets (EOAs) the user has connected to your site const { wallets } = useWallets(); + const {ready} = usePrivy(); // Find the embedded wallet by finding the entry in the list with a `walletClientType` of 'privy' const embeddedWallet = wallets.find( (wallet) => wallet.walletClientType === "privy" ); + console.log({wallets}); + // States to store the smart account and its status const [eoa, setEoa] = useState(); const [smartAccountClient, setSmartAccountClient] = useState< @@ -69,6 +70,10 @@ export const SmartAccountProvider = ({ >(); const [smartAccountReady, setSmartAccountReady] = useState(false); + useEffect(() => { + if (!ready) return; + }, [ready, embeddedWallet]); + useEffect(() => { // Creates a smart account given a Privy `ConnectedWallet` object representing // the user's EOA. @@ -89,28 +94,34 @@ export const SmartAccountProvider = ({ transport: http(), }); - const simpleSmartAccount = await signerToSimpleSmartAccount( + const safeAccount = await signerToSafeSmartAccount( publicClient, { - entryPoint: ENTRYPOINT_ADDRESS_V06, signer: customSigner, - factoryAddress: SMART_ACCOUNT_FACTORY_ADDRESS! as Address, + safeVersion: '1.4.1', + entryPoint: ENTRYPOINT_ADDRESS_V07 } ); const pimlicoPaymaster = createPimlicoPaymasterClient({ chain: baseSepolia, transport: http(process.env.NEXT_PUBLIC_PIMLICO_PAYMASTER_URL), - entryPoint: ENTRYPOINT_ADDRESS_V06, + entryPoint: ENTRYPOINT_ADDRESS_V07, }); + const pimlicoBundler = createPimlicoBundlerClient({ + transport: http(process.env.NEXT_PUBLIC_PIMLICO_PAYMASTER_URL), + entryPoint: ENTRYPOINT_ADDRESS_V07, + }) + const smartAccountClient = createSmartAccountClient({ - account: simpleSmartAccount, - entryPoint: ENTRYPOINT_ADDRESS_V06, + account: safeAccount, + entryPoint: ENTRYPOINT_ADDRESS_V07, chain: baseSepolia, // Replace this with the chain for your app bundlerTransport: http(process.env.NEXT_PUBLIC_PIMLICO_BUNDLER_URL), middleware: { - sponsorUserOperation: pimlicoPaymaster.sponsorUserOperation, // If your app uses a paymaster for gas sponsorship + sponsorUserOperation: pimlicoPaymaster.sponsorUserOperation, + gasPrice: async () => (await pimlicoBundler.getUserOperationGasPrice()).fast, }, }); diff --git a/package-lock.json b/package-lock.json index f4d59a6..7bc40fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "dependencies": { "@headlessui/react": "^1.7.19", "@heroicons/react": "^2.1.3", - "@privy-io/react-auth": "^1.77.0", + "@privy-io/react-auth": "^1.78.1", "@tailwindcss/forms": "^0.5.7", "abitype": "^1.0.2", "next": "latest", @@ -1995,9 +1995,9 @@ } }, "node_modules/@privy-io/js-sdk-core": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/@privy-io/js-sdk-core/-/js-sdk-core-0.26.0.tgz", - "integrity": "sha512-pEdyi7VpuxSn0YaN3SpmiZcQ5N/YAGcIfpHKJ1kOm29DAwuhOtqCVEWsb+GynVYJjt/Ec2KKgPTBkQjF+JsWbQ==", + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/@privy-io/js-sdk-core/-/js-sdk-core-0.26.1.tgz", + "integrity": "sha512-RjjSF7xTWAhIY+FJddkkiNogfubf1lIt3MUboG71+Eotf8pGS21vLJZ/XZO3gydeIU+oIk/dMBXyNQT61TXyiA==", "dependencies": { "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", @@ -2006,7 +2006,7 @@ "@ethersproject/transactions": "^5.7.0", "@ethersproject/units": "^5.7.0", "@privy-io/api-base": "^1.2.2", - "@privy-io/public-api": "2.8.3", + "@privy-io/public-api": "2.8.4", "eventemitter3": "^5.0.1", "fetch-retry": "^5.0.6", "jose": "^4.15.5", @@ -2016,9 +2016,9 @@ } }, "node_modules/@privy-io/public-api": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@privy-io/public-api/-/public-api-2.8.3.tgz", - "integrity": "sha512-CYmITfRNnAOpbUaRE9ehPPGHV3hDQuI4soOYJdmFra568QWGmhFRCu7Q+7vpezq/i3dr5eLk9ljZuYHw9ZwxWg==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@privy-io/public-api/-/public-api-2.8.4.tgz", + "integrity": "sha512-vnosLKj7N+MT3v/n7uZg+4mmG8Y0NpuT+17x76Ozv64b72392wtFB467rfZvR+8LyVNch8Kp4+05xz0i/j7gOA==", "dependencies": { "@privy-io/api-base": "1.2.2", "ethers": "^5.7.2", @@ -2031,9 +2031,9 @@ } }, "node_modules/@privy-io/react-auth": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@privy-io/react-auth/-/react-auth-1.77.0.tgz", - "integrity": "sha512-EbrRZhstEHAMJVH7ilpLoYJW1ZtiMPmEZUrjPX7+rEa0ecwyLpyCGfRG/9/Wqj5l74gRSyIJEtS2CQDCMDm4sg==", + "version": "1.78.1", + "resolved": "https://registry.npmjs.org/@privy-io/react-auth/-/react-auth-1.78.1.tgz", + "integrity": "sha512-GcZVUymaiK2HsgFovbaafdWJOXn2QUad46fV0Dqb28HjRudwiCDEcSP/1cyGTtTymbz1e8KhEQkIWJbEzTNo8Q==", "dependencies": { "@coinbase/wallet-sdk": "4.0.3", "@ethersproject/abstract-signer": "^5.7.0", @@ -2050,7 +2050,7 @@ "@heroicons/react": "^2.1.1", "@marsidev/react-turnstile": "^0.4.1", "@metamask/eth-sig-util": "^6.0.0", - "@privy-io/js-sdk-core": "0.26.0", + "@privy-io/js-sdk-core": "0.26.1", "@simplewebauthn/browser": "^9.0.1", "@walletconnect/ethereum-provider": "^2.13.3", "@walletconnect/modal": "^2.6.2", @@ -6144,9 +6144,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.10.48", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.48.tgz", - "integrity": "sha512-Vvcgt4+o8+puIBJZLdMshPYx9nRN3/kTT7HPtOyfYrSQuN9PGBF1KUv0g07fjNzt4E4GuA7FnsLb+WeAMzyRQg==" + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.5.tgz", + "integrity": "sha512-TwHR5BZxGRODtAfz03szucAkjT5OArXr+94SMtAM2pYXIlQNVMrxvb6uSCbnaJJV6QXEyICk7+l6QPgn72WHhg==" }, "node_modules/lilconfig": { "version": "2.1.0", diff --git a/package.json b/package.json index 3f3b09f..4ac3600 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dependencies": { "@headlessui/react": "^1.7.19", "@heroicons/react": "^2.1.3", - "@privy-io/react-auth": "^1.77.0", + "@privy-io/react-auth": "^1.78.1", "@tailwindcss/forms": "^0.5.7", "abitype": "^1.0.2", "next": "latest", diff --git a/pages/_app.tsx b/pages/_app.tsx index b334814..1408b8d 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -2,15 +2,12 @@ import "../styles/globals.css"; import type { AppProps } from "next/app"; import Head from "next/head"; import { PrivyProvider } from "@privy-io/react-auth"; -import { useRouter } from "next/router"; import { baseSepolia } from "viem/chains"; import { SmartAccountProvider } from "../hooks/SmartAccountContext"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; function MyApp({ Component, pageProps }: AppProps) { - const router = useRouter(); - return ( <> @@ -49,6 +46,7 @@ function MyApp({ Component, pageProps }: AppProps) { router.push("/dashboard")} > diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index d91a987..74628e3 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -1,6 +1,6 @@ import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; -import { usePrivy } from "@privy-io/react-auth"; +import { useLinkWithSiwe, usePrivy } from "@privy-io/react-auth"; import Head from "next/head"; import { useSmartAccount } from "../hooks/SmartAccountContext"; import { BASE_SEPOLIA_SCAN_URL, NFT_ADDRESS } from "../lib/constants"; @@ -13,6 +13,7 @@ import { baseSepolia } from "viem/chains"; export default function DashboardPage() { const router = useRouter(); const { ready, authenticated, user, logout } = usePrivy(); + const {generateSiweMessage, linkWithSiwe} = useLinkWithSiwe(); const { smartAccountAddress, smartAccountClient, eoa } = useSmartAccount(); // If the user is not authenticated, redirect them back to the landing page @@ -79,6 +80,27 @@ export default function DashboardPage() { setIsMinting(false); }; + const onLink = async () => { + // The link button is disabled if either of these are undefined + if (!smartAccountClient || !smartAccountAddress) return; + const chainId = `eip155:${baseSepolia.id}`; + + const message = await generateSiweMessage({ + address: smartAccountAddress, + chainId + }); + + const signature = await smartAccountClient.signMessage({message}); + + await linkWithSiwe({ + signature, + message, + chainId, + walletClientType: 'privy_smart_account', + connectorType: 'safe' + }); + } + return ( <> @@ -108,6 +130,13 @@ export default function DashboardPage() { > Mint NFT +

Your Smart Wallet Address