Mint Zora NFTs from any chain, right from your Lens feed or website
Overview
This is a technical guide on how to integrate our ZoraLzMintActionV1 open action. More details about the open action can be found here: Crosschain Zora Mint
EASIER: with the Publication component exported from @madfi/widgets-react
MORE WORK: fetching the open action metadata to act on the publication via the Lens API
Using the Lens Widgets SDK
Our exported Publication component handles rendering the social post and processing the act.
This is what it looks like to import and render the component
import { production } from"@lens-protocol/client";import { Publication, Theme, ProfileFragment } from"@madfi/widgets-react";// within your nextjs page / react component<Publication publicationId={"0x01a6-0x01ae"} theme={Theme.dark} environment={production} walletClient={walletClient || undefined} authenticatedProfile={authenticatedProfile asProfileFragment || undefined} appDomainWhitelistedGasless={true}/>
That will render the component seen at the top of this guide. Notice we pass in three extra props
walletClient is the connected wallet client returned from wagmi's useWalletClient hook
authenticatedProfile is an object of type ProfileFragment which is the lens profile that is authenticated in your app; check the Lens docs for authenticating
Of course if you want to handle all reactions, render whether the profile has reacted, etc - you can pass in more props. The full Publication component source code can be found on github.
Process a Publication with the Lens API
Using the Lens module metadata and broadcast APIs to integrate our open action offers you complete control when it comes to rendering your own components and processing the act. It's arguably more work, so we'll illustrate the steps required.
Generally speaking, it looks something like this
Fetch the open action module metadata from the Lens API (using @lens-protocol/client)
Fetch the onchain data necessary to process from polygon and the remote chain (ie Zora, Base)
Get the desired payment currency from the user (or default to WETH) and fetch an onchain quote for the swap to pay the sales price + fees in the desired currency
Have the user approve the fee transfer to the module contract
Encode the data needed to process the act
Send the act transaction using typed data + the Lens broadcast API
Since this open action enables NFT sales on another chain, we have to fetch all the relevant information. Also, since the sale price is defined in ETH, and the user can pay in any Lens whitelisted currency - we must fetch a quote for the swap of payment currency to the sale price in ETH.
This is the data we must fetch
the remote chain data for the NFT
the sale price
total quoted price (includes mint, relay, swap)
To make this easier, we export a useful hook from the widgets SDK called useSupportedActionModule that returns a self-contained client with all the state and contract data needed to encode data
import { PostFragment, production } from"@lens-protocol/client"import { useSupportedActionModule } from"@madfi/widgets-react";import { useAccount, useWalletClient, useContractWrite, usePrepareContractWrite } from"wagmi";constPOLYGON_CURRENCY_WETH="0x7ceb23fd6bc0add59e62ac25578270cff1b9f619";// within your nextjs page / react component// actionModuleHandler is initalized with the data / public clients we needconst {isActionModuleSupported,actionModuleHandler, isLoading: isLoadingActionModuleState,} =useSupportedActionModule( production, publication asPostFragment);// useful wagmi hooksconst { address } =useAccount();const { data: walletClient } =useWalletClient();const { config } =usePrepareContractWrite({ address:POLYGON_CURRENCY_WETH, abi: IERC20Abi, functionName:"approve",});const { data,isLoading,isSuccess,write } =useContractWrite(config);// fetch the quote, which includes the sale price and fees in whatever currencylet quotePriceToRender;if (isActionModuleSupported &&!isLoadingActionModuleState) {const { quotedAmountIn } =awaitactionModuleHandler.getQuotesForCreatorPublication( address,// connected wallet to simulate fromPOLYGON_CURRENCY_WETH,// whitelisted currency1// quantity of NFTs to mint ); quotePriceToRender = quotedAmountIn;}// user approves the fee for `ZoraLzMintActionV1` to transfer fromwrite({ args: [actionModuleHandler.address, quotePriceToRender] });
3. Encode the process action module data
Now that you have the module metadata, quote and params - you can encode the necessary calldata
import { encodeData } from'@lens-protocol/client';constZORA_LZ_MINT_ACTION="0x5f377e3e9BE56Ff72588323Df6a4ecd5cEedc56A";constparams= { currency:POLYGON_CURRENCY_WETHas`0x${string}`,// get from user quantity:'1',// get from user quotedAmountIn,// from step 2 uniFee:'500'// configurable, see uniswapv3 docs};constcalldata=encodeData(JSON.parse(metadata.processCalldataABI),// got `metadata` from step 1 [params.currency,params.quantity,params.quotedAmountIn,params.uniFee]);
4. Send the act transaction using the Lens API
There's a lot of steps involved here, so we'll defer to the Lens docs - but here is the general code.
import { WalletClient } from"viem";import { OnchainReferrer, RelaySuccessFragment, LensClient } from"@lens-protocol/client";import { omit } from"lodash/object";// NOTE: this assume the given `actionModule` has `metadata.sponsoredApproved` = true// NOTE: this assumes that the passed in `lensClient` is authenticated (see: https://docs.lens.xyz/docs/login)// NOTE: this assumes the app is whitelisted to use gaslessexportconstactWithSignedTypedata=async ( lensClient:LensClient, walletClient:WalletClient, publicationId:string, actionModule:`0x${string}`, actionModuleData:string, referrers?: OnchainReferrer[] // clients wishing to earn referral fees should mirror the `publicationId` and pass in that data
):Promise<any> => {try {// get typed dataconsttypedDataResult=awaitlensClient.publication.actions.createActOnTypedData({ actOn: { unknownOpenAction: { address: actionModule, data: actionModuleData } }, for: publicationId, referrers: referrers || [] });const { id,typedData } =typedDataResult.unwrap();// sign itconst [account] =awaitwalletClient.getAddresses();constsignedTypedData=awaitwalletClient.signTypedData({ account, domain:omit(typedData.domain,"__typename"), types:omit(typedData.types,"__typename"), primaryType:"Act", message:omit(typedData.value,"__typename"), });// broadcast onchain, gaslessconstbroadcastResult=awaitlensClient.transaction.broadcastOnchain({ id, signature: signedTypedData });constbroadcastResultValue=broadcastResult.unwrap();if (broadcastResultValue.__typename ==="RelayError") thrownewError("RelayError");// return the tx hash to link to layerzero scanreturn (broadcastResultValue asRelaySuccessFragment).txId; } catch (error) {console.log(error); }}
Conclusion
Enabling crosschain Zora mints through a social post is only possible with Lens v2, and we think it's a great distribution channel for creators to monetize their work - and for brands that want to run crypto-native social campaigns. We built this as the first step towards our onchain ads product (coming out in q1 2024).
If you have any questions, doubts, or suggestions, please email us at contact@madfinance.xyz.
It is arguably easier to simply process the open action on your client or website, and rely on the MadFi web app to initialize these kinds of posts (MadFi v2 out early Jan. 2024). A future guide will be released for initializing, but this guide will focus on two ways to process our open action
Any client that processes our Zora open action is eligible for Zora Mint Referral Rewards as long as the correct referrer data is passed in. Details around this are in step 4.
That's it! As long as those props are passed in, and the publication was initialized with this open action, the `Mint` button will be rendered and will handle the act - onchain or with the gasless API
If you don't want to rely on the madfi/widgets-react sdk, then you can see how we fetch the necessary data by checking the fetchActionModuleData and getQuotesForCreatorPublication function found here.
This function assumes your app domain is whitelisted to use gasless
This function assumes that lensClient is authenticated with a profile
There you go! You have all the steps necessary in order to process the open action yourself.
A note on client referral rewards. The function above shows how to pass in referrers data - which when processed by our open action - will include the referrers[0].profileId profile owner as the recipient for Zora Mint Referral rewards. The catch is that the given profile must have mirrored the original post (the one initialized with the open action) and the resulting publicationId provided in the object for referrers. We know this is not an ideal flow and will improve this in v2! For lens clients with more traffic, we will share details around our plans in due time.