从以太坊或 OP 主网铸造 NFT

OP 中文力量
·
·
IPFS

译者的话

作为一名译者和前端开发工程师,我有幸阅读并翻译了这篇文章。本文将涉及一系列前端开发技术,特别是如何在不同区块链平台上实现NFT铸造的原理,深入浅出的讲解了核心的代码,读者将能够获得关于 NFT 铸造方面的很多实用知识。希望这篇文章能为那些希望深入了解 OP 和 NFT 铸造的前端工程师提供参考,无论是区块链技术的新手还是有经验的开发者,我相信都能在本文中有所收获。

本文一共有 3000 字,阅读完本文预计需要 20 分钟。

原文链接:

Minting NFTs from Ethereum or OP Mainnet

前言

这篇来自 Kiwi News 的客座文章讨论了如何通过在 L1 和 L2 上提供 NFT 铸造功能,以此充分挖掘以太坊的流动性。

截至撰写时,以太坊主网仍然拥有最多的 ETH 流动性——大约是 OP 的 100 倍[2]。同时,我们都希望用户能够与 L2 智能合约互动,这样他们就不需要为每一个简单的操作支付 50 美元的 Gas 费。

那么,我们如何在使用 L2 合约铸造 NFT 的同时,挖掘利用主网的流动性呢?

我们的项目— Kiwi News[3] 就面临着这样的困境。它是一个专注于加密技术、产品及文化内容的类似于 Hacker News 的链接聚合器。

Kiwi 既是一个应用程序,也是一个建立在类似 Farcaster 的算法(这种算法也被称为 集合对账[4]上的协议。这意味着每个人都可以克隆 Kiwi 网络并且运行自己的程序,使用不同的算法、内容审查方式等来访问我们的内容。

但是,要实际使用该应用程序进行点赞、提交链接和评论时,我们的用户必须首先购买我们的 NFT。就像你需要支付 Gas 费来发送以太坊交易,或者在 Kiwi News 上创建 Farcaster 账户必须支付费用一样,你需要铸造一个 OP NFT 来进行互动。

实际上我们并没有在 OP 主网上销售我们的 NFT。我们的 NFT 最初只在以太坊上可用,但实际上,在 Pepe 的交易热潮期间,铸造我们的 15 美元 NFT 足足花费了 19 美元[5]。所以我们决定转移到 OP 主网。

这看起来确实是一个双赢的局面:我们的用户支付的少了,而我们赚的还是一样的金额,甚至可以从技术上提高价格。

但我们很快就发现,即使是以太坊的长期用户,大多数人也没有将资金转移到 OP 主网。当然,他们可以去桥接,但这会使得他们离开我们的网站,这意味着销售转化率总体上会变低。正如传统销售法则所讲,必须“到鱼儿多的地方去钓鱼。”

因此,我们决定通过在 L1 和 L2 上都支持 NFT 铸造来解决这个问题

技术细节

这里有一个问题:我们的 NFT 合约在 L2 上,只有在那里才能调用 Zora 的 mintWithRewards(...) 函数来铸造 NFT。然而,一个新的 Kiwi News 用户可能只在以太坊主网上有资金。

如果他们在 OP 主网上有资金,那就太好了,他们可以直接购买并铸造 NFT。

然而,如果他们的资金在以太坊主网上,我们首先要让他们将这些资金桥接到 OP 主网,理想情况下接着在桥接过程中购买 NFT。

现在,在任何情况下我们都不希望用户签署多个交易,因为这会导致更高的用户流失率,并且会使整个过程更加繁琐。不得不连续确认多个交易意味着更高的出错概率,这也是我们开始探索将桥接和购买 NFT 原子化结合的原因。

那么,现在让我们深入实际代码。

首先,你必须理解的是,Optimism 系统在 L1 和 L2 都有 API。例如,你可以在 L2 调用一个合约来将你的资金提取到 L1。或者在 L1 调用一个函数直接存款到 L2。

在我们的案例中,这些接口特别有用,因为以太坊主网上的 OptimismPortal(Etherscan,源代码[6])有一个叫做 OptimismPortal.depositTransaction(...) 的函数:

/// @notice Accepts deposits of ETH and data, and emits a TransactionDeposited event for use in///         deriving deposit transactions. Note that if a deposit is made by a contract, its///         address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider///         using the CrossDomainMessenger contracts for a simpler developer experience./// @param _to         Target address on L2./// @param _value      ETH value to send to the recipient./// @param _gasLimit   Amount of L2 gas to purchase by burning gas on L1./// @param _isCreation Whether or not the transaction is a contract creation./// @param _data       Data to trigger the recipient with.function depositTransaction(    address to,    uint256 value,    uint64 gasLimit,    bool isCreation,    bytes memory data)publicpayable;

注意到 depositTransaction(...) 参数的命名方式与常规以太坊交易很相似了吗?有 address to,uint256 value,以及 bytes memory data 用来编码函数调用的详细信息。uint64 gasLimit 毫无疑问定义了该交易可以使用的最大 Gas 费,而 bool isCreation 则决定 bytes memory data 是否可以创建一个新合约。

depositTransaction(...) 的参数之所以与常规以太坊交易类似,是因为在桥接过程中,一旦 L1 调用者的桥接过程成功,OP 主网节点将在 OP 主网上发送一个带有相应参数的交易。

这对我们特别有用,因为我们希望在 OP 主网上使用 Zora 的 mintWithRewards(...) 函数来铸造 NFT,同时允许用户从以太坊主网调用此函数,无需分开进行桥接和铸造交易。

现在,让我们实际深入代码,下面是我们在 OP 主网通过 Zora 部署的 NFT 收藏合约的接口:

function mintWithRewards(    address recipient,    uint256 quantity,    string calldata comment,    address mintReferral) external payable returns (uint256);

这个功能需要一个 address recipient 参数作为 NFT 的接收方,一个 uint256 quantity 参数来定义应当铸造多少 NFT,一个 string calldata comment 参数用于链上评论,以及一个 address mintReferrall 参数用于为推荐铸造 NFT 的推荐人记账。

假设我们是前端工程师,负责构建一个同时在 OP 主网和 ETH 主网上工作的 NFT 铸造按钮,以下是我们必须进行的步骤:

2.1 在 OP 主网上检查用户的 ETH 余额。如果用户有足够的资金铸造 NFT,提示用户直接在 Optimism 上调用 mintWithRewards(…)。

2.2 如果用户在 OP 主网上的 ETH 余额不足,检查用户在以太坊主网的余额;如果用户在 ETH 主网上也无法负担 NFT 的购买费用,向用户说明此情况;否则,继续执行第三步。

import { fetchBalance, getAccount } from "@wagmi/core";import { mainnet, optimism } from "wagmi/chains";Import { utils } from “ethers”;const { address } = getAccount();if (!address) {  throw new Error("Account not available");}const balance = {  mainnet: (await fetchBalance({ address, chainId: mainnet.id })).value,  optimism: (await fetchBalance({ address, chainId: optimism.id })).value,};const mintPriceETH = utils.parseEther(“0.00256”);if (balance.optimism >= mintPriceETH) {  // mint on OP mainnet} else if (balance.mainnet >= mintPriceETH) {  // mint on ETH mainnet} else {  throw new Error(“Insufficient balance”);}

2.3 既然我们知道用户将通过 OptimismPortal 在 ETH 主网上铸造 NFT,我们将需要准备两个以太坊调用。一个是在 L1 调用 depositTransaction(...),另一个是在 L2 调用 mintWithRewards(...)。我们将把要在 OP 主网执行的第二个调用的数据传递到 depositTransaction(...) 的 bytes memory data 参数中。以下是操作方法:

2.3.1 我们通过收集 L2 上 mintWithRewards(...) 函数的输入来构建该函数的调用数据,然后使用 Ethers.js 的 interface.encodeFunctionData(name, [...inputs]) 函数将该调用打包成十六进制字符串。

import { getAccount } from "@wagmi/core";import { Contract } from "@ethersproject/contracts";import { mainnet, optimism } from "wagmi/chains";import { getProvider } from “./viem-adapter.mjs”;const nftAddress = 0xabc…;const nftABI = [{...}];function prepareL2Call() {  const { address } = getAccount();  const opProvider = getProvider({ chainId: optimism.id });  const contract = new Contract(nftAddress, nftABI, opProvider);  const recipient = address;  const quantity = 1;  const comment = “minting this from mainnet!”  const referrer = null;  return contract.interface.encodeFunctionData("mintWithRewards", [    recipient,    quantity,    comment,    referrer,  ]);}

2.3.2 对于 depositTransaction(...),我们接着选择函数调用的目标地址作为 address to 参数,并将 uint256 value 参数设置为想要传递给目标地址的 ETH 数额。至于参数 uint64 gasLimit,原计划是模拟在 Optimism 上进行 ETH 调用时的成本,但还记得吗?用户的 ETH 余额不足,因此,任何来自 OP 主网提供者或用户地址的 estimateGas 调用都会出错。

所以,我们建议通过手动调用 mintWithRewards(...) 函数来发现一个静态估计值,并在代码中使用这个静态值。

至于其他参数,bool isCreation 设置为 false,而 bytes memory data 则包含了在步骤 3.1 中生成的调用数据。

import { prepareWriteContract } from "@wagmi/core";import { mainnet } from "wagmi/chains";const optimismPortalAddress = 0x…;const optimismPortalABI = [{...}, …];async function writeToDeposit(nftAddress, price, data) {  const isCreation = false;  const gasLimit = 170000;  return await prepareWriteContract({    address: optimismPortalAddress,    abi: optimismPortalABI,    functionName: "depositTransaction",    args: [nftAddress, price, gasLimit, isCreation, data],    value: price,    chainId: mainnet.id,  });}

2.3.3 重要的是,我们必须确保将等同于 uint256 value 或更多的金额作为 msg.value 存入到 L1 的调用中,这种操作确保了一定数额的以太币被存入 Optimism,并且可以用于 mintWithRewards(...) 的调用。我们通过将 msg.value 设置为 price 参数的值来实现。

2.3.4 最后,所有参数准备就绪,我们将提示用户为以太坊主网签署这笔交易。

import { useContractWrite } from "wagmi";// …function prepareL2Call(...) {...}async function writeToDeposit(...) {...}// …const BuyButton = (props) => {  // …  const { write } = useContractWrite(config);  // …  return (    <button disabled={!write} onClick={() => write?.()}>    Buy NFT    </button>  );}

就是这样!如果你想在 Optimism 上直接用以太坊主网原子性地铸造 NFT,你就需要这么做。

虽然这些代码片段并非我们在 Kiwi News 使用的生产代码,但与我们已经开源的代码非常接近。你可以在我们的 GitHub[7] 上找到完整的准备过程和其他相关信息,甚至还可以通过直接访问我们的 NFT 铸造页面[8]来尝试这一功能。

当然,使用 OptimismPortal 的 depositTransaction(...) 功能并不是实现从 L1 到 L2 的唯一选项。目前,许多交易所也提供类似的服务。然而,OP Portal 的特殊之处在于它能保留在 ETH 主网上签名并发送交易的地址的 msg.sender 参数。当你的 dapp 依赖于验证该地址的合法性时,这一点可能尤其重要。

总结

在将我们的 NFT 从以太坊迁移到 OP 主网时,我们需要一种方式,既能利用 L1 层的流动性,又能允许所有用户从 OP 主网铸造它。

OptimismPortal 提供了一种便利的功能,可以在一次原子交易中既桥接资金又执行 ETH 调用,从而在用户发起调用时增加成功的可能性。

我们详细地介绍了我们的 React.js 代码,这些代码可以检查以太坊和 L2 的余额,为每个用户提供正确的调用。如果用户在 OP 上没有 ETH,他们将通过 Portal 桥接直接上线。

未来,我们期待更多这样激动人心的技术机会。例如,我们希望能够以类似 Portal 交易的方式原子性地利用 Base 链的流动性,使我们的代码更容易被所有的以太坊用户调用。

我们希望这篇文章对您的工作有所帮助。在 Kiwi News,我们一直在努力为生态系统提供最大的实用性,无论是通过最新新闻、博客文章还是 GitHub 仓库。我们非常欢迎您成为我们的读者!请访问 我们的网站[9] 来查看更多关于我们的信息

找到我们

OP 中文力量是由 GCC、LXDAO、PlanckerDAO,登链社区和 TraDAO 共同发起的 Optimism 中文开发者社区,是一个传播 Optimism 技术和公共物品理念的组织,旨在成为链接华语社区和 Optimism 生态的桥梁,促进 Optimism 生态和华语社区内的双向交流,促进公共物品的繁荣。

OP 中文力量是 GCC 中文力量计划旗下专注 OP 生态的中文社区,由 GCC 捐助孵化成立。

微信公众号: Optimism 中文

Twitter: x.com/Optimismzh

Notionwww.notion.so/lxdao/...

Telegramt.me/optimism_cn

Mirror: mirror.xyz/optimismc...

微信群:公众号后台回复 【加群】

CC BY-NC-ND 4.0 授权

喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!

OP 中文力量Optimism中文社区 微信公众号:Optimism 中文 TG:https://t.me/optimism_cn Twitter:https://x.com/Optimismzh
  • 来自作者
  • 相关推荐

Optimism 中文周刊 #41

Optimism 与 Uniswap 共同提出的 ERC-7802 是什么?

Super Accounts: 加速超级链上的有意义的贡献