Skip to content

thirdweb-example/thirdweb-siwf

Repository files navigation

banner

thirdweb Sign in with Farcaster Example

thirdweb SDK Discord

This example app showcases how to authenticate users via Farcaster (specifically Warpcast) and generate a unique reusable smart wallet from their Farcaster signature.

Getting Started

This project assumes some basic knowledge of TypeScript, Next.js App Router, and Connect SDK.

Environment Variables

  1. Create your .env by running cp .env.example .env in the project root.

  2. Create a client ID from the thirdweb dashboard and add it to your .env as NEXT_PUBLIC_THIRDWEB_CLIENT_ID.

  3. Deploy an AccountFactory on your chain of choice from the thirdweb dashboard and paste the contract address in your .env as NEXT_PUBLIC_FACTORY_ADDRESS.

  4. Deploy a new OpenEditionERC721 contract on your chain of choice from the thirdweb dashboard and add a claim period with no restrictions. Paste the contract's address in your .env as NEXT_PUBLIC_NFT_ADDRESS.

  5. Set your chain ID, block explorer base url (this should be the base url up until the transaction hash, for etherscan mainnet it would be https://etherscan.io/tx/), and an encryption key of your choice in your .env

Set authentication endpoint

This project uses an incredibly powerful thirdweb feature called Authentication Endpoints. It uses your own API endpoint to generate a wallet for users on successful authentication. All the code for this is written for you in this project, you'll just need to set the endpoint in your thirdweb dashboard.

To use Custom Authentication Endpoints, you'll need to be on the Growth Plan. If you have questions about the plan options or want to try it out, reach out to our team.

Navigate to the In-App Wallets page on the dashboard and select your project from the dropdown. This should be the same project your clientId is from. Then click the "Configuration" tab and scroll down to "Custom Authentication Endpoint" and enable the toggle. You'll then see a field to enter your endpoint.

Screenshot 2024-04-25 at 10 46 11 PM

While testing the project locally, you'll need a publicly exposed endpoint to authenticate through. We recommend using a tool like ngrok to create a public endpoint that forwards traffic to your local server. Forward your traffic to http://localhost:3000 (where your app will run locally).

Once you have your ngrok or similar endpoint, add it to the Authentication Endpoint field as [YOUR FORWARDING ENDPOINT]/api/authenticate, the route this app uses to perform authentication.

You're now ready to run the project!

When you deploy to production (or any live URL), you'll modify this authentication endpoint to be your actual live URL. You could also create a separate thirdweb project for local development and production.

Run the project

You're now ready to test the project! First, install the dependencies:

pnpm install

Then, run the app locally:

pnpm run dev

You should see the app at http://localhost:3000. Try signing in with Warpcast and minting the NFT!

Check the users tab in In-App Wallets dashboard. You should see your created users appear.

Going to production

Once you've implemented this flow into your own app, there are a few changes you'll need to make to go to production.

  1. Modify the NEXT_PUBLIC_DOMAIN in your production .env to be your production domain.

    Don't prepend your domain with https:// when setting it in your .env

  2. Remember to go to your project in the In-App Wallets configuration tab and update the auth endpoint to be [YOUR PRODUCTION URL]/api/authenticate. In this case, do include https:// in the URL.

You might also want to (but don't have to) use a different account factory, NFT, and chain for production.

Now, you're ready to deploy your app to Vercel or a similar service.

How it works

All the logic for this example can be found in page.tsx. The most important areas for authentication are handleSuccess(), mint, and the useConnect hook from the thirdweb React SDK.

Authenticating the user

We use Farcaster AuthKit to handle the connection with Warpcast. We use the useSignIn hook to trigger the handleSuccess as a success callback when the sign in data is available. When the user authenticates with Warpcast, this function will be called with the user's signature.

In handleSuccess, we optimistically set the fid (it will unset if the signature verification or wallet generation fail), then connect using the "auth_endpoint" strategy. This strategy needs a payload and encryptionKey that will be sent to the authentication endpoint we specified in the dashboard. The code below is simplified from the actual project.

const handleSuccess = async (res: StatusAPIResponse) => {
	await wallet.connect({
		client: thirdwebClient,
		chain: defineChain(Number(process.env.NEXT_PUBLIC_CHAIN_ID)),
		strategy: "auth_endpoint",
		payload: JSON.stringify({
			signature: res.signature,
			message: res.message,
			nonce: res.nonce,
		}),
		encryptionKey: process.env.NEXT_PUBLIC_ENCRYPTION_KEY!,
	});
	await connect(wallet);
};

Then in the /api/authenticate/route.ts file we specify a POST handler that accepts the payload and verifies the signature. If this route returns a userId, it's considered to be successful and generates and/or connects the user's in-app wallet.

Using a smart wallet

Since this user's wallet is generated the first time they sign into our app, it won't have any funds for gas. Instead, we'll wrap this generated wallet in a smart wallet, which will allow the user to execute gasless transactions.

We use the useConnect hook from the React SDK to specify the client and account abstraction options (gasless enabled, factory address, and chain). This hook returns a connect function that will wrap our in-app wallet and set the app's currently active wallet to this smart wallet.

const { connect } = useConnect({
	client: thirdwebClient,
	accountAbstraction: {
		gasless: true,
		chain: defineChain(Number(process.env.NEXT_PUBLIC_CHAIN_ID)),
		factoryAddress: process.env.NEXT_PUBLIC_FACTORY_ADDRESS as Address,
	},
});

Note; We setup a ThirdwebProvider in Providers.tsx for the useConnect and useActiveAccount hooks to work.

Once our smart wallet is connected, the useActiveAccount hook will return it, allowing us to enable minting the NFT.

Minting the NFT

Once the smart account is ready we enable the minting button. When clicked, it calls mint, a simple function that uses the Thirdweb SDK's ERC721 extension to generate a claimTo transaction, then we send and await the transaction result in one sendAndConfirmTransaction call. With extensions, we don't need to worry about ABIs, argument arrays, calldata, or any other "low-level" concepts. All the complicated elements are abstracted away from the frontend code.

async function mint(account: Account, recipient: Address) {
	const contract = getContract({
		address: process.env.NEXT_PUBLIC_NFT_ADDRESS as Address,
		chain: defineChain(Number(process.env.NEXT_PUBLIC_CHAIN_ID)),
		client: thirdwebClient,
	});

	const mintTx = claimTo({
		contract,
		to: recipient,
		quantity: BigInt(1),
	});

	const res = await sendAndConfirmTransaction({
		account,
		transaction: mintTx,
	});

	return res.transactionHash;
}

Documentation

Support

For help or feedback, please visit our support site