This example app showcases how to authenticate users via Farcaster (specifically Warpcast) and generate a unique reusable smart wallet from their Farcaster signature.
This project assumes some basic knowledge of TypeScript, Next.js App Router, and Connect SDK.
-
Create your
.env
by runningcp .env.example .env
in the project root. -
Create a client ID from the thirdweb dashboard and add it to your
.env
asNEXT_PUBLIC_THIRDWEB_CLIENT_ID
. -
Deploy an
AccountFactory
on your chain of choice from the thirdweb dashboard and paste the contract address in your.env
asNEXT_PUBLIC_FACTORY_ADDRESS
. -
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
asNEXT_PUBLIC_NFT_ADDRESS
. -
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
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.
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.
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.
Once you've implemented this flow into your own app, there are a few changes you'll need to make to go to production.
- 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
- 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 includehttps://
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.
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.
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.
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 theuseConnect
anduseActiveAccount
hooks to work.
Once our smart wallet is connected, the useActiveAccount
hook will return it, allowing us to enable 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;
}
For help or feedback, please visit our support site