[Testnet] Open Action | Cross-chain Zora Mint (v2)

Mint Zora NFTs on Base, with Bonsai on Polygon

Overview

This is a technical guide on how to integrate our ZoraLzMintActionV2 open action. More details about the open action can be found here: Cross-chain Zora Mint (v2)

Our ZoraLzMintActionV2 action module is deployed and verified on Polygon

Our contract which receives the messages is ZoraLzCreatorV2 - deployed and verified on Base.

🚧 Currently on testnet (Amoy / Base Sepolia)

💰 Any client that processes our Zora open action is eligible for 2.5% (variable) protocol fee on mints

Initialize with the open action

To initialize a publication with the ZoraLzMintActionV2 module, you must provide the details for a Zora NFT where

  • the chain is Base

  • the currency is Bonsai (Base)

  • the sale is active

import { testnet, LensClient } from "@lens-protocol/client";
import { parseEther } from "viem";

// polygon amoy
const ZORA_LZ_MINT_ACTION_V2 = "0xA5F19D5953B7777537014653e6219983cE82001c";

// 1. Fetch module metadata
const lensClient = new LensClient({ environment: testnet });
const data = await lensClient.modules.fetchMetadata({ implementation: ZORA_LZ_MINT_ACTION_V2 });
const { metadata } = data;

// 2. Prepare init data
// https://testnet.zora.co/collect/bsep:0x313bc79382af2fe2fceee28601cae2a49d4c2be1/3
const params = {
  token: 0x313bc79382af2fe2fceee28601cae2a49d4c2be1,
  tokenId: ,
  salePrice: parseEther("100")
};

// 3. Encode init data
const data = encodeData(
  JSON.parse(metadata.initializeCalldataABI),
  [params.token, params.tokenId, params.salePrice]
);

// 4. Post onchain (assuming authenticated lensClient)
const broadcastResult = await lensClient.publication.postOnchain({ 
  contentURI: "mint this", 
  openActionModules: [{ unknownOpenAction: { address: ZORA_LZ_MINT_ACTION_V2, data } }]
});

Process the open action

To process a publication with the ZoraLzMintActionV2 module, you must

  • Get the total sale price for the mint

  • Have the actor approve the $BONSAI token transfer to the module

  • Encode the data needed to process the act

  • Send the act transaction

ℹī¸ Remember that the open action is to pay for mints in Bonsai on Polygon - even though the Zora NFT is on Base and priced in Base Bonsai.

Prepare the act transaction

// 1. Fetch module metadata
import { testnet, LensClient } from "@lens-protocol/client";
import { constants } from "viem";

const ZORA_LZ_MINT_ACTION_V2 = "0xA5F19D5953B7777537014653e6219983cE82001c";
const BONSAI = "0x3d2bD0e15829AA5C362a4144FdF4A1112fa29B5c";

const lensClient = new LensClient({ environment: testnet });
const data = await lensClient.modules.fetchMetadata({ implementation: ZORA_LZ_MINT_ACTION_V2 });
const { metadata } = data;

// 2. Get the total sale price
const pointedProfileId = 697; 
const pointedPubId = 3; 
const quantity = 1;

const actionModule = getActionModule(ZORA_LZ_MINT_ACTION_V2); // ethers Contract
const totalSalePrice = await actionModule.getTotalSalePrice(pointedProfileId, pointedPubId, qty);

// 3. Approve the token transfer
const tx = await getTokenContract(BONSAI).approve(ZORA_LZ_MINT_ACTION_V2, totalSalePrice);
await tx.wait();

// 4. Encode act data
const params = {
  quantity,
  clientAddress: constants.AddressZero, // client address to earn protocol fee
  comment: "minted with $BONSAI" // user-provided comment to surface on Zora UI
};

const actionModuleData = encodeData(
  JSON.parse(metadata.processCalldataABI),
  [params.quantity, params.clientAddress, params.comment]
);

Send the act transaction (gasless)

There's a lot of steps involved here, so we'll defer to the Lens docs - but here is the general code.

⚠ī¸ This function assumes your app domain is whitelisted to use gasless

⚠ī¸ This function assumes that lensClient is authenticated with a profile

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 gasless
export const actWithSignedTypedata = async (
  lensClient: LensClient,
  walletClient: WalletClient,
  publicationId: string,
  actionModule: `0x${string}`,
  actionModuleData: string,
  referrers?: OnchainReferrer[] // profile to earn mint referral fees
): Promise<any> => {
  try {
    // get typed data
    const typedDataResult = await lensClient.publication.actions.createActOnTypedData({
      actOn: {
        unknownOpenAction: {
          address: actionModule,
          data: actionModuleData
        }
      },
      for: publicationId,
      referrers: referrers || []
    });

    const { id, typedData } = typedDataResult.unwrap();

    // sign it
    const [account] = await walletClient.getAddresses();
    const signedTypedData = await walletClient.signTypedData({
      account,
      domain: omit(typedData.domain, "__typename"),
      types: omit(typedData.types, "__typename"),
      primaryType: "Act",
      message: omit(typedData.value, "__typename"),
    });

    // broadcast onchain, gasless
    const broadcastResult = await lensClient.transaction.broadcastOnchain({ id, signature: signedTypedData });
    const broadcastResultValue = broadcastResult.unwrap();

    if (broadcastResultValue.__typename === "RelayError") throw new Error("RelayError");

    // return the tx hash to link to layerzero scan
    return (broadcastResultValue as RelaySuccessFragment).txHash;
  } catch (error) {
    console.log(error);
  }
}

💰 A note on mint 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.

Last updated