本文介绍了Kiwi News项目如何利用OptimismPortal的depositTransaction(...)
函数,实现在以太坊主网上发起交易,同时在Optimism L2上原子性地铸造NFT。通过检查用户在L1和L2上的ETH余额,并结合React.js代码,为用户提供无缝的NFT铸造体验,解决了用户在OP Mainnet上缺乏资金的问题。
以太坊主网仍然拥有最多的 ETH 流动性——截至撰写本文时,比 OP 多约 100 倍。 同时,我们都希望用户与 L2 智能合约交互,这样他们就不必为每个简单的操作支付 50 美元的 gas 费。
那么,我们如何在利用 L2 合约铸造 NFT 的同时利用主网的流动性呢?
我们的项目——Kiwi News 正面临着同样的困境。 这是一个类似于 Hacker News 的链接聚合器,专注于加密技术、产品和文化内容。
Kiwi 既是一个应用程序,也是一个建立在类似于 Farcaster 算法之上的协议(它被称为 “集合协调”)。 这意味着每个人都可以 fork Kiwi 网络并运行自己的应用程序,通过不同的算法、审核等方式访问我们的内容。
但要真正使用该应用程序进行投票、提交链接和评论,我们的用户必须首先购买我们的 NFT。 就像你需要支付 gas 才能发送以太坊交易,或者你需要在 Kiwi News 上创建 Farcaster 帐户时付费一样,你需要铸造一个 OP NFT 才能参与。
但实际上我们并没有在 OP Mainnet 上开始我们的 NFT 销售。 我们的 NFT 最初仅在以太坊上可用,但事实证明,在 $PEPE 狂热期间,铸造我们 15 美元的 NFT 花费了 19 美元。 因此,我们决定转移到 OP Mainnet。
这看起来确实是一个双赢的局面:我们的用户支付的费用更少,而我们获得的收入相同,甚至可以在技术上提高价格。
但我们很快发现,大多数人——即使是长期使用以太坊的用户——也没有将资金桥接到 OP Mainnet。 当然,他们可以去桥接,但将他们从我们的网站上带走意味着整体销售转化率降低。 正如旧的发行格言所说,你必须“在有鱼的地方钓鱼”。
因此,我们决定通过使我们的 NFT 铸造在 L1 和 L2 上都可用来解决这个问题。
问题如下:我们的 NFT 合约位于 L2 上,并且只有 Zora 的 mintWithRewards(...)
函数,可以调用它来铸造 NFT。 然而,新的 Kiwi News 用户可能只有以太坊主网上的资金。
如果他们在 OP Mainnet 上有资金,那就太好了; 他们可以直接购买和铸造 NFT。
但是,如果他们在以太坊主网上有资金,我们必须首先让他们将这些资金桥接到 OP Mainnet,并且理想情况下,在桥接过程中购买 NFT。
现在,在任何情况下,我们都不希望用户签署多个交易,因为这会导致更高的流失率,并且会使整个过程更加繁琐。 必须连续确认多个交易意味着事情出错的几率更高,这就是为什么我们开始寻找将桥接和购买 NFT 原子性结合起来的方法。
所以,现在让我们深入研究实际代码。
首先,你必须了解 Optimism 系统在 L1 和 L2 上都有 API。 例如,你可以调用 L2 上的合约将你的资金提取到 L1 中。 或者你可以调用 L1 上的函数直接存入 L2。
这些接口在我们的案例中特别有用,因为以太坊主网上的 OptimismPortal ( Etherscan, 源代码) 有一个名为 [OptimismPortal.depositTransaction(...)](https://github.com/ethereum-optimism/optimism/blob/6120d22fc8382f92c990d2c81231fa8e981ed7dd/packages/contracts-bedrock/src/L1/OptimismPortal.sol?ref=blog.oplabs.co#L365-L415)
的函数:
/// @notice 接受 ETH 和数据的存款,并发出 TransactionDeposited 事件,用于
/// 推导存款交易。 请注意,如果存款是由合约进行的,则其
/// 地址在使用 `tx.origin` 或 `msg.sender` 检索时将被别名。 考虑
/// 使用 CrossDomainMessenger 合约以获得更简单的开发者体验。
/// @param _to L2 上的目标地址。
/// @param _value 要发送给接收者的 ETH 值。
/// @param _gasLimit 通过在 L1 上燃烧 gas 来购买的 L2 gas 量。
/// @param _isCreation 该交易是否为合约创建。
/// @param _data 触发接收者的数据。
function depositTransaction(
address to,
uint256 value,
uint64 gasLimit,
bool isCreation,
bytes memory data
)
public
payable;
请注意 depositTransaction(...)
的参数与常规以太坊交易的命名方式非常相似吗? 有 address to
、uint256 value
和一个 bytes memory data
编码函数调用详细信息。 uint64 gasLimit
毫不奇怪,定义了交易可以使用的最大 gas,bool isCreation
定义了 bytes memory data
是否应创建新合约。
depositTransaction(...)
的参数相似,因为在内部,在桥接过程中,当 L1 调用方的桥接过程成功时,OP mainnet 节点将在 OP Mainnet 上发送一个带有相应参数的交易。
当我们想要使用 Zora 的 mintWithRewards(...)
函数在 OP mainnet 上铸造 NFT,但也允许用户从以太坊主网调用此函数而无需单独的桥接和铸造交易时,这对于我们特别有用。
所以,让我们深入研究一下这里的代码。 以下是通过 Zora 部署在 OP mainnet 上的 NFT 集合合约的接口:
function mintWithRewards(
address recipient,
uint256 quantity,
string calldata comment,
address mintReferral
) external payable returns (uint256);
该函数需要一个 address recipient
,即 NFT 的接收者,一个 uint256 quantity
来定义应该铸造多少个 NTF,一个用于链上评论的 string calldata comment
,以及一个 address mintReferral
来奖励推荐铸造的推荐人。
因此,假设我们承担了一项前端工程师的工作,负责构建一个在 OP mainnet 和 ETH mainnet 上都能工作的 NFT 铸造按钮,以下是我们必须计算的逐步过程:
检查用户在 OP Mainnet 上的 ETH 余额。 如果用户能够负担得起铸造 NFT 的费用,则提示用户直接在 Optimism 上调用 mintWithRewards(...)
。
如果用户在 OP Mainnet 上没有足够的 ETH,则检查用户的以太坊主网余额。 如果用户无法在 ETH 主网上购买 NFT,则通知用户; 否则,继续执行步骤 3。
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) {
// 在 OP mainnet 上铸造
} else if (balance.mainnet >= mintPriceETH) {
// 在 ETH mainnet 上铸造
} else {
throw new Error(“余额不足”);
}
depositTransaction(...)
,另一个是在 L2 上调用 mintWithRewards(...)
。 我们将传递第二个旨在在 OP mainnet 上执行的调用到 depositTransaction(...)
的 bytes memory data
中。 这是我们这样做的方式:3.1. 我们通过收集 L2 上 mintWithRewards(...)
函数的输入来构建 mintWithRewards(...)
的函数调用数据。 我们使用 ethers 的 [interface.encodeFunctionData(name, \[...inputs\])](https://docs.ethers.org/v5/api/utils/abi/interface/?ref=blog.oplabs.co#Interface--encoding)
将调用打包为十六进制字符串。
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 = “从主网铸造!”
const referrer = null;
return contract.interface.encodeFunctionData("mintWithRewards", [\
recipient,\
quantity,\
comment,\
referrer,\
]);
}
3.2. 对于 depositTransaction(...)
,然后我们选择函数调用的目标作为 address to
,并将 uint256 value
设置为我们要传递给 address to
的 ETH 的值。 至于 uint64 gasLimit
,我们应该使用用户的余额模拟 ETH 调用在 Optimism 上的成本。 但是,用户的 ETH 余额不足,还记得吗? 因此,使用 OP mainnet 提供商和用户地址进行的任何 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,
});
}
3.3. 重要的是,我们将必须至少存入 uint256 value
或更多的金额作为 msg.value
到 L1 调用。 这确保了一些以太坊被存入 Optimism,并可用于 mintWithRewards(...)
调用。 我们通过将 msg.value
设置为 price
的值来实现这一点。
3.4. 最后,在组装好所有参数后,我们提示用户签署此以太坊主网的交易。
import { useContractWrite } from "wagmi";
// …
function prepareL2Call(...) {...}
async function writeToDeposit(...) {...}
// …
const BuyButton = (props) => {
// …
const { write } = useContractWrite(config);
// …
return (
<button disabled={!write} onClick={() => write?.()}>
购买 NFT
</button>
);
}
就是这样! 这就是你想要在 Optimism 上原子性地铸造 NFT,同时直接从以太坊主网提供调用时必须做的事情。
虽然代码片段与我们在 Kiwi News 中使用的生产代码并不完全相同,但它们与我们开源的代码非常接近。 你可以在我们的 GitHub 上找到完整的准备序列和更多信息。 你实际上可以通过访问我们的 NFT 铸造页面 直接尝试一下。
现在,当然,使用 OptimismPortal 的 depositTransaction(...)
函数并不是从 L1 到 L2 的唯一选择。 目前,也有许多交易所提供类似的服务。 但是,OP Portal 的一个特点是它保留了在该地址上签名并从 ETH 主网发送交易的 msg.sender
。 当你的 dapp 依赖于该地址必须是合法的时,这尤其重要。
在将我们的 NFT 从以太坊迁移到 OP Mainnet 时,我们需要一种利用 L1 流动性的方法,同时仍然允许我们所有的用户也从 OP Mainnet 铸造它。
OptimismPortal 提供了一种方便的功能,可以在单个原子交易中桥接资金和执行 ETH 调用,从而在使用户发送调用时能够获得很高的成功机会。
我们详细介绍了我们的 React.js 代码,该代码检查以太坊和 L2 余额,以便为每个用户提供正确的调用。 如果用户没有 OP ETH,他们将通过 Portal 桥接直接加入。
将来,我们期待更多此类令人兴奋的技术机会。 例如,我们很希望能够像类似的 Portal 交易一样原子性地利用 Base 链的流动性,从而使我们的代码对于所有以太坊用户来说都更容易调用。
我们希望你发现这篇文章对你的工作有所帮助。 在 Kiwi News,我们始终致力于为生态系统提供尽可能多的实用程序——无论是最新的新闻、博客文章还是 GitHub 存储库。 我们很乐意你成为我们的读者! 请访问 https://kiwinews.xyz 查看我们的信息。
- 原文链接: optimism.io/blog/minting...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!