Comments
The Comments contract allows for comments to be made on any Zora 1155 token. Only 1155 token owners or holders can comment on that token. If the commenter is an owner, they must pay a Spark to comment. If the commenter is a creator, they can comment for free.
Comments can be Sparked by anyone, meaning that the Sparker must send a Spark as a form of liking a comment.
Contracts
The protocol consists of a single upgradeable contract called Comments
, that is deployed deterministically to the same address on all chains. There is also a helper contract called CallerAndCommenter
that enables minting and commenting in a single transaction.
Contract | Deterministic Address |
---|---|
Comments | 0x7777777C2B3132e03a65721a41745C07170a5877 |
CallerAndCommenter | 0x77777775C5074b74540d9cC63Dd840A8c692B4B5 |
Spark value distribution
When a commenter pays a Spark to comment on a token, the Spark value (less a protocol fee) is sent to the token's creator reward recipient. When a commenter pays a Spark to reply to a comment, the Spark value (less a protocol fee) is sent to the original commenter as a reward. When a Spark is used to Spark a comment, the Spark value (less a protocol fee) is sent to the commenter.
For each Spark value transaction, a 30% protocol fee is taken. If a referrer is specified, 20% goes to the referrer and 10% goes to Zora. Otherwise, 30% goes to Zora. A referrer can be a third-party developer that surfaces the ability to comment on a site or app, and the referrer address is specified as an argument when commenting or Sparking.
Building on comments and earning referral rewards
Developers can integrate the Comments contract into on their platform from day one and earn referral rewards when users when users spark a comment on their platform.
When a referral address is specified when minting or sparking, 20% of the total Spark value is paid out to the referrer.
To earn referral rewards, developers should specify a referrer address in the function calls
What is a Spark?
A Spark is a fundamental concept in the Zora ecosystem. It serves as a unit of value and can be used to pay for mints and other interactions:
- Sparks are 1155 tokens on the Zora network. They can be purchased with credit/debit cards or ETH, primarily used to cover minting fees for NFTs on Zora.
- Each Spark has an immutable value of 0.000001 ETH
- In the context of the Comments contract, Sparks are used to pay for comments (for non-creators) and to "like" or endorse comments made by others.
- Sparks can be unwrapped by their owner, allowing the underlying ETH value to be used for other transactions.
Backfilled legacy comments
Before the Comments contract's deployment, comments were made on other contracts that emitted MintComment
events. To enable users to reply to or Spark these older comments, we backfill the new Comments contract with legacy comment data. This process:
- Saves onchain unique IDs for the legacy comments.
- Allows users to interact with pre-existing comments since they have an onchain ID.
Usage
Commenting
Commenting can be done by calling the comment
function, paying with the equivalent value in Sparks:
interface IComments {
struct CommentIdentifier {
address commenter;
address contractAddress;
uint256 tokenId;
bytes32 nonce;
}
/// @notice Creates a new comment. Equivalent Sparks value in ETH must be sent with the transaction. Must be a holder or creator of the referenced 1155 token.
/// If not the owner, must send at least 1 Spark. Sparks are transferred from the commenter to the Sparks recipient (either the creator when there is no replyTo, or the replyTo commenter).
/// @param contractAddress The address of the contract
/// @param tokenId The token ID
/// @param commenter The address of the commenter
/// @param text The text content of the comment
/// @param replyTo The identifier of the comment being replied to (if any)
/// @return commentIdentifier The identifier of the created comment, including the nonce
function comment(
address commenter,
address contractAddress,
uint256 tokenId,
string calldata text,
CommentIdentifier calldata replyTo,
address referrer
) external payable returns (CommentIdentifier memory commentIdentifier) {
}
}
Example usage with @zoralabs/protocol-deployments
and viem
:
import {
commentsABI,
commentsAddress,
emptyCommentIdentifier,
} from "@zoralabs/protocol-deployments";
import { zeroAddress, parseEther, parseEventLogs } from "viem";
import {
publicClient,
walletClient,
chainId,
commenterAccount,
contractAddress1155,
tokenId1155,
} from "./config";
// if no referrer, use zero address
const referrer = zeroAddress;
// if no smart wallet owner, use zero address
const smartWallet = zeroAddress;
const sparkValue = parseEther("0.000001");
// comment that we are replying to. If there is no reply, use emptyCommentIdentifier() from @zoralabs/protocol-deployments
const replyTo = emptyCommentIdentifier();
// comment on token, paying the value of 1 spark to the contract
const hash = await walletClient.writeContract({
abi: commentsABI,
address: commentsAddress[chainId],
functionName: "comment",
args: [
// account that is attributed with the comment; must match the account that is executing the transaction
commenterAccount,
// 1155 contract address to comment on. Must be an admin or owner of the token.
contractAddress1155,
// tokenId of the token to comment on
tokenId1155,
// text content of the comment
"This is a test comment",
// empty reply to, since were not replying to any other comment
replyTo,
// optional smart wallet. smart wallet can be an owner or creator of the 1155 token.
// and eoa that is the owner of the smart wallet can comment.
smartWallet,
// Optional referrer address to receive a portion of the sparks value
referrer,
],
// account that is executing the transaction. Must match the commenterAccount argument above.
account: commenterAccount,
// pay the value of 1 spark to the contract
value: sparkValue,
});
// wait for comment to complete - make sure it succeeds
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") {
throw new Error("Transaction failed");
}
// we can get the comment identifier from the Commented event in the receipt logs
const commentedEvent = parseEventLogs({
abi: commentsABI,
eventName: "Commented",
logs: receipt.logs,
})[0]!;
const commentIdentifier = commentedEvent.args.commentIdentifier;
export { commentIdentifier };
Note: The getSparksValue
function is used to calculate the equivalent ETH value for a given number of sparks. It's implementation is not shown here but is crucial for determining the correct payment amount.
Replying to a comment
When a comment is created, it is associated with a unique identifier. This identifier is used to reply to the comment. The unique identifier contains an autoincrementing nonce generated by the contract that is used to ensure that the identifier is unique for a given commenter, contract, and tokenId.
When replying to a comment, the replyTo argument is the identifier of the comment being replied to.
interface IComments {
struct CommentIdentifier {
address commenter;
address contractAddress;
uint256 tokenId;
bytes32 nonce;
}
function comment(
address commenter,
address contractAddress,
uint256 tokenId,
string calldata text,
// this identifies the comment that we are replying to
CommentIdentifier calldata replyTo,
address referrer
) external payable returns (CommentIdentifier memory commentIdentifier) {
}
}
Example usage with @zoralabs/protocol-deployments
and viem
:
import {
commentsABI,
commentsAddress,
CommentIdentifier,
} from "@zoralabs/protocol-deployments";
import {
commenterAccount,
contractAddress1155,
publicClient,
tokenId1155,
walletClient,
chainId,
} from "./config";
import { commentIdentifier } from "./comment";
import { zeroAddress, parseEther } from "viem";
const sparkValue = parseEther("0.000001");
// this identifies the comment that we are replying to
// it can be gotten from the `Commented` event when commenting,
// or from the subgraph when querying for comments
const replyTo: CommentIdentifier = {
commenter: commentIdentifier.commenter,
contractAddress: commentIdentifier.contractAddress,
tokenId: commentIdentifier.tokenId,
nonce: commentIdentifier.nonce,
};
const referrer = zeroAddress;
const smartWallet = zeroAddress;
const hash = await walletClient.writeContract({
abi: commentsABI,
address: commentsAddress[chainId],
functionName: "comment",
args: [
commenterAccount,
contractAddress1155,
tokenId1155,
"This is a test reply",
// when replying to a comment, we must pass the comment identifier of the comment we are replying to
replyTo,
smartWallet,
referrer,
],
account: commenterAccount,
value: sparkValue,
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") {
throw new Error("Transaction failed");
}
Sparking a comment
Sparking a comment is done by calling the sparkComment
function, paying with the equivalent value in Sparks.
Sparking a comment is similar to liking a comment, except it is liked with the value of Sparks attached. The Spark value gets sent to the commenter, with a fee taken out.
interface IComments {
struct CommentIdentifier {
address commenter;
address contractAddress;
uint256 tokenId;
// nonce is a unique value that is generated when a comment is created. It is used to ensure that the comment identifier is unique
// for a given commenter, contract, and tokenId.
bytes32 nonce;
}
/// @notice Sparks a comment. Equivalent Sparks value in ETH to sparksQuantity must be sent with the transaction. Sparking a comment is
/// similar to liking it, except it is liked with the value of Sparks attached. The Spark value gets sent to the commenter, with a fee taken out.
/// @param commentIdentifier The identifier of the comment to Spark
/// @param sparksQuantity The quantity of Sparks to send
/// @param referrer The referrer of the comment
function sparkComment(CommentIdentifier calldata commentIdentifier, uint64 sparksQuantity, address referrer) public payable;
}
Example usage with @zoralabs/protocol-deployments
and viem
:
import {
commentsABI,
commentsAddress,
CommentIdentifier,
} from "@zoralabs/protocol-deployments";
import {
commenterAccount,
contractAddress1155,
publicClient,
tokenId1155,
walletClient,
chainId,
sparkerAccount,
} from "./config";
import { keccak256, toBytes, zeroAddress, parseEther } from "viem";
// quantity of sparks to spark (like) the comment with
const sparksQuantity = 1n;
const sparkValue = parseEther("0.000001");
const commentIdentifier: CommentIdentifier = {
commenter: commenterAccount,
contractAddress: contractAddress1155,
tokenId: tokenId1155,
nonce: keccak256(toBytes(1)),
};
const referrer = zeroAddress;
const hash = await walletClient.writeContract({
abi: commentsABI,
address: commentsAddress[chainId],
functionName: "sparkComment",
args: [commentIdentifier, sparksQuantity, referrer],
account: sparkerAccount,
value: sparksQuantity * sparkValue,
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") {
throw new Error("Transaction failed");
}
Minting and commenting
When minting with the ZoraTimedSaleStrategy
, which is the default way to mint on Zora,
a comment can be included at no additional cost by calling the function timedSaleMintAndComment()
on the CallerAndCommenter
helper contract. While the comment itself is free, the standard mint fee still needs to be sent with the transaction.
// Deployed to 0x77777775C5074b74540d9cC63Dd840A8c692B4B5 on all chains supported by Zora.
interface ICallerAndCommenter {
/// @notice Mints tokens and adds a comment, without needing to pay a spark for the comment.
/// @dev The payable amount should be the total mint fee. No spark value should be sent.
/// @param commenter The address of the commenter
/// @param quantity The number of tokens to mint
/// @param collection The address of the 1155 collection to mint from
/// @param tokenId The 1155 token Id to mint
/// @param mintReferral The address to receive mint referral rewards, if any
/// @param comment The comment to be added. If empty, no comment will be added.
/// @return The identifier of the newly created comment
function timedSaleMintAndComment(
address commenter,
uint256 quantity,
address collection,
uint256 tokenId,
address mintReferral,
string calldata comment
) external payable returns (IComments.CommentIdentifier memory);
}
Example usage with @zoralabs/protocol-deployments
and viem
:
import {
callerAndCommenterABI,
callerAndCommenterAddress,
} from "@zoralabs/protocol-deployments";
import {
publicClient,
walletClient,
chainId,
minterAccount,
contractAddress1155,
tokenId1155,
} from "./config";
import { parseEther, zeroAddress } from "viem";
const mintFee = parseEther("0.000111");
const commenter = minterAccount;
const quantityToMint = 3n;
const collection = contractAddress1155;
const tokenId = tokenId1155;
const mintReferral = zeroAddress;
const comment = "This is a test comment";
// minting and commenting in one transaction, calling the `timedSaleMintAndComment` function
// on the `CallerAndCommenter` contract
const hash = await walletClient.writeContract({
abi: callerAndCommenterABI,
address: callerAndCommenterAddress[chainId],
functionName: "timedSaleMintAndComment",
args: [commenter, quantityToMint, collection, tokenId, mintReferral, comment],
account: commenter,
// when minting and commenting, only the mint fee needs to be paid;
// no additional ETH is required to pay for commenting
value: mintFee * quantityToMint,
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") {
throw new Error("Transaction failed");
}
Specifying a Referrer
When calling the comment
, sparkComment
, or related functions, a referrer address can be specified. This allows third-party developers to earn a portion of the protocol fee when users interact with the Comments contract through on their platform.
To specify a referrer, simply include the referrer's address as the last argument in the function call:
import {
commentsABI,
commentsAddress,
emptyCommentIdentifier,
} from "@zoralabs/protocol-deployments";
import { zeroAddress, parseEther } from "viem";
import {
publicClient,
walletClient,
chainId,
commenterAccount,
contractAddress1155,
tokenId1155,
} from "./config";
// referrer is the address that will receive a portion of the Sparks value
const referrer = "0x1234567890123456789012345678901234567890";
const sparkValue = parseEther("0.000001");
const replyTo = emptyCommentIdentifier();
// comment on token, paying the value of 1 spark to the contract
const hash = await walletClient.writeContract({
abi: commentsABI,
address: commentsAddress[chainId],
functionName: "comment",
args: [
commenterAccount,
contractAddress1155,
tokenId1155,
"This is a test comment",
replyTo,
zeroAddress,
// Optional referrer address to receive a portion of the Sparks value
referrer,
],
// account that is executing the transaction. Must match the commenterAccount argument above.
account: commenterAccount,
// pay the value of 1 spark to the contract
value: sparkValue,
});
// wait for comment to complete - make sure it succeeds
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") {
throw new Error("Transaction failed");
}
Commenting as a smart wallet owner
An account that is a smart wallet owner can comment on a token, if a smart wallet
is an owner or creator of the token. In this case, the smart wallet address should
be passed as the smartWallet
argument when calling the comment
function. The function
will check if the smart wallet or the account that is creating the comment is an owner or creator of the token,
but will attribute the comment to the account that is calling the comment function. Correspondingly,
the commenter
argument must match the account that is creating the comment.
Example usage with @zoralabs/protocol-deployments
and viem
:
import {
zoraTimedSaleStrategyABI,
zoraTimedSaleStrategyAddress,
commentsABI,
commentsAddress,
emptyCommentIdentifier,
sparkValue,
} from "@zoralabs/protocol-deployments";
import {
publicClient,
walletClient,
chainId,
smartWalletOwner,
contractAddress1155,
tokenId1155,
bundlerClient,
smartWalletAccount,
} from "./config";
import { zeroAddress } from "viem";
// 1. Mint as the smart wallet
// fist perform the mint as the smart wallet via the bundler, making the
// smart wallet the owner of the token; we simulate it first.
const userOperationResponse = await bundlerClient.prepareUserOperation({
account: smartWalletAccount,
calls: [
{
abi: zoraTimedSaleStrategyABI,
to: zoraTimedSaleStrategyAddress[chainId],
functionName: "mint",
args: [
smartWalletAccount.address,
1n,
contractAddress1155,
tokenId1155,
zeroAddress,
"0",
],
},
],
});
// send the user operation with the bundler
const hash = await bundlerClient.sendUserOperation(userOperationResponse);
// ensure the user operation is accepted
const mintReceipt = await bundlerClient.waitForUserOperationReceipt({ hash });
if (!mintReceipt.success) {
throw new Error("Mint failed");
}
// 2. Comment as the smart wallet owner
// We comment as an owner of the smart wallet. The contract allows for commenting
// when a smart wallet owned by an account is the owner of the token.
const referrer = zeroAddress;
// now perform the comment as a smart wallet owner
const commentHash = await walletClient.writeContract({
abi: commentsABI,
address: commentsAddress[chainId],
functionName: "comment",
args: [
// commenter account is an account that owns the smart wallet
smartWalletOwner.address,
contractAddress1155,
tokenId1155,
"This is a test reply",
// when replying to a comment, we must pass the comment identifier of the comment we are replying to
emptyCommentIdentifier(),
// we set the smart wallet parameter. the smart wallet can be checked to see if it is the owner of the token
smartWalletAccount.address,
referrer,
],
account: smartWalletOwner,
value: sparkValue(),
});
const receipt = await publicClient.waitForTransactionReceipt({
hash: commentHash,
});
if (receipt.status !== "success") {
throw new Error("Transaction failed");
}
Cross-Chain commenting, sparking, and minting with comments
An account can sign a permit to comment, spark a comment, or mint and comment on their behalf, and that permit can be used to execute the action onchain. This enables cross-chain functionality for these actions by validating that the signer of the message is the original commenter, sparker, or minter. Here's how it works:
-
When creating the permit, the user specifies two chain IDs:
sourceChainId
: The chain ID where the permit is being signed.destinationChainId
: The chain ID where the permit should be executed.
-
To enable cross-chain functionality:
- Set
sourceChainId
to the current chain where you're signing the permit. - Set
destinationChainId
to the chain ID of the target blockchain where you want the action to be executed.
- Set
-
The permit can then be signed on a source chain and submitted to the appropriate contract on the destination chain, allowing the action to be executed there.
For example, if you're signing a permit on Base, but want the action to occur on Zora Network, you would set:
sourceChainId
to 8453destinationChainId
to 7777777
This process works for:
- Commenting (using the
Comments
contract) - Sparking a comment (using the
Comments
contract) - Minting and commenting (using the
CallerAndCommenter
helper contract)
- Example cross-chain commenting with Relay:
import {
commentsABI,
commentsAddress,
emptyCommentIdentifier,
permitCommentTypedDataDefinition,
PermitComment,
sparkValue,
} from "@zoralabs/protocol-deployments";
import { getClient } from "@reservoir0x/relay-sdk";
import { zeroAddress, keccak256, toBytes, encodeFunctionData } from "viem";
import { base, zora } from "viem/chains";
import {
walletClient,
chainId,
commenterAccount,
contractAddress1155,
tokenId1155,
} from "./config";
// 1. Create and sign a cross-chain permit comment.
// Calculate a timestamp 30 seconds from now
const thirtySecondsFromNow =
BigInt(Math.round(new Date().getTime() / 1000)) + 30n;
// Generate a random nonce
const randomNonce = () => keccak256(toBytes(Math.round(Math.random() * 1000)));
// Get the comments contract address for the current chain
const commentsContractAddress = commentsAddress[chainId];
// Define the permit comment object
const permitComment: PermitComment = {
sourceChainId: base.id, // The chain where the transaction originates (Base)
destinationChainId: zora.id, // The chain where the comment will be stored (Zora)
contractAddress: contractAddress1155, // The address of the 1155 contract
tokenId: tokenId1155, // The 1155 token ID being commented on
commenter: commenterAccount, // The account making the comment.
text: "hello world", // The content of the comment
deadline: thirtySecondsFromNow,
nonce: randomNonce(),
referrer: zeroAddress, // No referrer in this case
commenterSmartWallet: zeroAddress, // Not using a smart wallet for commenting
replyTo: emptyCommentIdentifier(), // This is not a reply to another comment
};
// Generate the typed data for the permit comment using the helper
// method from @zoralabs/protocol-deployments
const permitCommentTypedData = permitCommentTypedDataDefinition(permitComment);
// Sign the permit
const permitCommentSignature = await walletClient.signTypedData(
permitCommentTypedData,
);
// 2. Execute the cross-chain transaction with relay
// Initialize the relay client
const relayClient = getClient();
// Get a quote from relay for the cross-chain transaction
const quote = await relayClient.actions.getQuote({
wallet: walletClient,
chainId: permitComment.sourceChainId, // The origin chain (Base)
toChainId: permitComment.destinationChainId, // The destination chain (Zora)
amount: sparkValue().toString(), // The value to send to the comments contract on the destination chain
tradeType: "EXACT_OUTPUT",
currency: zeroAddress, // ETH
toCurrency: zeroAddress, // ETH
txs: [
{
to: commentsContractAddress,
value: sparkValue().toString(),
// we will call permitComment on the destination chain
data: encodeFunctionData({
abi: commentsABI,
functionName: "permitComment",
args: [permitComment, permitCommentSignature],
}),
},
],
});
// Execute the cross-chain transaction
await relayClient.actions.execute({
quote,
wallet: walletClient, // The wallet initiating the transaction
});
- Example cross-chain sparking with Relay:
import {
commentsABI,
commentsAddress,
PermitSparkComment,
sparkValue,
permitSparkCommentTypedDataDefinition,
} from "@zoralabs/protocol-deployments";
import { getClient } from "@reservoir0x/relay-sdk";
import { zeroAddress, keccak256, toBytes, encodeFunctionData } from "viem";
import { base, zora } from "viem/chains";
import { walletClient, chainId, sparkerAccount } from "./config";
import { commentIdentifier } from "./comment";
// 1. Create and sign a cross-chain permit spark comment.
// Calculate a timestamp 30 seconds from now
const thirtySecondsFromNow =
BigInt(Math.round(new Date().getTime() / 1000)) + 30n;
// Generate a random nonce
const randomNonce = () => keccak256(toBytes(Math.round(Math.random() * 1000)));
// Get the comments contract address for the current chain
const commentsContractAddress = commentsAddress[chainId];
// Define the number of sparks to add
const sparksQuantity = 3n;
// Define the permit spark comment object
const permitSparkComment: PermitSparkComment = {
comment: commentIdentifier, // The comment to spark
deadline: thirtySecondsFromNow,
nonce: randomNonce(),
sparker: sparkerAccount, // The account sparking the comment
sparksQuantity: sparksQuantity, // The number of sparks to add
sourceChainId: base.id, // The chain where the transaction originates (Base)
destinationChainId: zora.id, // The chain where the spark will be stored (Zora)
referrer: zeroAddress, // No referrer in this case
};
// Generate the typed data for the permit spark comment using the helper
// method from @zoralabs/protocol-deployments
const permitSparkCommentTypedData =
permitSparkCommentTypedDataDefinition(permitSparkComment);
// Sign the permit
const permitSparkCommentSignature = await walletClient.signTypedData(
permitSparkCommentTypedData,
);
// 2. Execute the cross-chain transaction with relay
// Initialize the relay client
const relayClient = getClient();
// Get a quote from relay for the cross-chain transaction
const quote = await relayClient.actions.getQuote({
wallet: walletClient,
chainId: permitSparkComment.sourceChainId, // The origin chain (Base)
toChainId: permitSparkComment.destinationChainId, // The destination chain (Zora)
amount: (sparkValue() * sparksQuantity).toString(), // The total value to send to the comments contract on the destination chain
tradeType: "EXACT_OUTPUT",
currency: zeroAddress, // ETH
toCurrency: zeroAddress, // ETH
txs: [
{
to: commentsContractAddress,
value: sparkValue().toString(),
// We will call permitSparkComment on the destination chain
data: encodeFunctionData({
abi: commentsABI,
functionName: "permitSparkComment",
args: [permitSparkComment, permitSparkCommentSignature],
}),
},
],
});
// Execute the cross-chain transaction
await relayClient.actions.execute({
quote,
wallet: walletClient, // The wallet initiating the transaction
});
- Example cross-chain minting and commenting with Relay:
import {
callerAndCommenterAddress,
callerAndCommenterABI,
permitMintAndCommentTypedDataDefinition,
PermitMintAndComment,
} from "@zoralabs/protocol-deployments";
import { getClient } from "@reservoir0x/relay-sdk";
import {
zeroAddress,
keccak256,
toBytes,
encodeFunctionData,
parseEther,
} from "viem";
import { base, zora } from "viem/chains";
import {
walletClient,
minterAccount,
contractAddress1155,
tokenId1155,
} from "./config";
// 1. Create and sign a cross-chain permit mint and comment.
// Calculate a timestamp 30 seconds from now
const thirtySecondsFromNow =
BigInt(Math.round(new Date().getTime() / 1000)) + 30n;
// Generate a random nonce
const randomNonce = () => keccak256(toBytes(Math.round(Math.random() * 1000)));
// Define the number of 1155 tokens to mint
const quantityToMint = 3n;
// Define the permit mint and comment object
const permit: PermitMintAndComment = {
commenter: minterAccount,
comment: "This is a test comment",
deadline: thirtySecondsFromNow,
mintReferral: zeroAddress, // No mint referral in this case
quantity: quantityToMint,
collection: contractAddress1155,
tokenId: tokenId1155,
nonce: randomNonce(),
sourceChainId: base.id, // The chain where the transaction originates (Base)
destinationChainId: zora.id, // The chain where the mint and comment will be executed (Zora)
};
// Generate the typed data for the permit mint and comment using the helper
// method from @zoralabs/protocol-deployments
const typedData = permitMintAndCommentTypedDataDefinition(permit);
// Sign the permit
const signature = await walletClient.signTypedData(typedData);
const mintFee = parseEther("0.000111");
// 2. Execute the cross-chain transaction with relay
// Initialize the relay client
const relayClient = getClient();
// Value to send to the CallerAndCommenter contract on the destination chain
// is the mint fee multiplied by the quantity of tokens to mint
const valueToSend = mintFee * quantityToMint;
// Get a quote from relay for the cross-chain transaction
const quote = await relayClient.actions.getQuote({
wallet: walletClient,
chainId: permit.sourceChainId, // The origin chain (Base)
toChainId: permit.destinationChainId, // The destination chain (Zora)
amount: valueToSend.toString(), // The total value to send to the CallerAndCommenter contract on the destination chain
tradeType: "EXACT_OUTPUT",
currency: zeroAddress, // ETH
toCurrency: zeroAddress, // ETH
txs: [
{
to: callerAndCommenterAddress[
permit.destinationChainId as keyof typeof callerAndCommenterAddress
],
value: valueToSend.toString(),
// We will call permitTimedSaleMintAndComment on the destination chain
data: encodeFunctionData({
abi: callerAndCommenterABI,
functionName: "permitTimedSaleMintAndComment",
args: [permit, signature],
}),
},
],
});
// Execute the cross-chain transaction
await relayClient.actions.execute({
quote,
wallet: walletClient, // The wallet initiating the transaction
});
Events
The Comments
contract emits the following events:
interface IComments {
/// @notice Event emitted when a comment is created
event Commented(
bytes32 indexed commentId, // Unique ID for the comment, generated from a hash of the commentIdentifier
CommentIdentifier commentIdentifier, // Identifier for the comment, containing details about the comment
bytes32 replyToId, // Unique ID of the comment being replied to (if any)
CommentIdentifier replyTo, // Identifier of the comment being replied to (if any)
uint256 sparksQuantity, // Number of sparks associated with this comment
string text, // The actual text content of the comment
uint256 timestamp, // Timestamp when the comment was created
address referrer // Address of the referrer who referred the commenter, if any
);
// Event emitted when a comment is backfilled
event BackfilledComment(
bytes32 indexed commentId, // Unique identifier for the backfilled comment
CommentIdentifier commentIdentifier, // Identifier for the comment
string text, // The actual text content of the backfilled comment
uint256 timestamp, // Timestamp when the original comment was created
bytes32 originalTransactionId // Transaction ID of the original comment (before backfilling)
);
// Event emitted when a comment is Sparked
event SparkedComment(
bytes32 indexed commentId, // Unique identifier of the comment being sparked
CommentIdentifier commentIdentifier, // Struct containing details about the comment and commenter
uint256 sparksQuantity, // Number of sparks added to the comment
address sparker, // Address of the user who sparked the comment
uint256 timestamp, // Timestamp when the spark action occurred
address referrer // Address of the referrer who referred the sparker, if any
);
}
When minting and commenting, the MintedAndCommented
event is emitted from the caller and commenter contract, containing
more contextual information about the mint and comment, as well as a link to the comment via the comment identifier. When buying or selling on secondary and commenting, the SwappedOnSecondaryAndCommented
event is emitted, containing the same contextual information as the minted and commented event, as well as the quantity of tokens bought or sold.
interface ICallerAndCommenter {
/// @notice Emitted when tokens are minted and a comment is added
/// @param commentId The unique identifier of the comment
/// @param commentIdentifier The struct containing details about the comment
/// @param quantity The number of tokens minted
/// @param text The content of the comment
event MintedAndCommented(
bytes32 indexed commentId,
IComments.CommentIdentifier commentIdentifier,
uint256 quantity,
string text
);
/// @notice Emitted when tokens are bought or sold on the secondary market and a comment is added
/// @param commentId The unique identifier of the comment
/// @param commentIdentifier The struct containing details about the comment
/// @param quantity The number of tokens bought
/// @param comment The content of the comment
/// @param swapDirection The direction of the swap (BUY or SELL)
event SwappedOnSecondaryAndCommented(
bytes32 indexed commentId,
IComments.CommentIdentifier commentIdentifier,
uint256 indexed quantity,
string comment,
SwapDirection indexed swapDirection
);
}