Minting von NFTs aus Ethereum oder OP Mainnet
0xa4D7…BA74
0xa4D7
April 26th, 2024

In diesem Gastbeitrag von Kiwi News wird erörtert, wie man Ethereum-Liquidität anzapfen kann, indem man die NFT-Prägung sowohl auf L1 als auch auf L2 verfügbar macht.

Das Ethereum Mainnet hat immer noch die meiste ETH-Liquidität - zum Zeitpunkt der Erstellung dieses Artikels etwa 100 Mal mehr als OP. Gleichzeitig wollen wir alle, dass sich die Nutzer mit L2-Smart Contracts beschäftigen, damit sie nicht für jede einfache Aktion eine 50-Dollar-Tankgebühr bezahlen müssen.

Wie könnten wir also die Liquidität des Mainnet anzapfen und gleichzeitig L2-Verträge für das Minting der NFT verwenden?

Wir standen genau vor diesem Dilemma mit unserem Projekt - Kiwi News. Es ist ein Hacker News-ähnlicher Link-Aggregator, der sich auf Krypto-Technologie, Produkte und Kultur konzentriert.

Kiwi ist sowohl eine App als auch ein Protokoll, das auf einem Algorithmus basiert, der dem von Farcaster ähnelt (er heißt „Set reconciliation“). Das bedeutet, dass jeder das Kiwi-Netzwerk aufspalten und seine eigene App betreiben kann, die auf unsere Inhalte mit unterschiedlichen Algorithmen, Moderation usw. zugreift.

Aber um die App für Upvotings, Links und Kommentare nutzen zu können, müssen unsere Nutzer zuerst unser NFT kaufen. Genauso wie Sie Gas bezahlen müssen, um eine Ethereum-Transaktion zu senden, oder wenn Sie ein Farcaster-Konto auf Kiwi News erstellen, müssen Sie eine OP NFT prägen, um sich zu engagieren.

Aber eigentlich haben wir unseren NFT-Verkauf nicht im OP Mainnet gestartet. Unser NFT war zunächst nur auf Ethereum verfügbar, aber wie sich herausstellte, kostete es während der $PEPE-Manie 19 $, um unsere 15 $ NFT zu prägen. Also beschlossen wir, zum OP Mainnet zu wechseln.

Und es schien eine Win-Win-Situation zu sein: Unsere Nutzer zahlten weniger, und wir verdienten den gleichen Betrag und konnten technisch sogar die Preise erhöhen.

Aber wir lernten schnell, dass die meisten Leute - sogar langjährige Ethereum-Nutzer - kein Geld für OP Mainnet zur Verfügung hatten. Natürlich könnten sie zu einer Brücke gehen, aber sie von unserer Website zu nehmen, bedeutete eine insgesamt geringere Umsatzkonversion. Und wie das alte Vertriebsmantra sagt, muss man „dort fischen, wo die Fische sind“.

Also beschlossen wir, dieses Problem zu lösen, indem wir unsere NFT-Prägung sowohl auf L1 als auch auf L2 verfügbar machten.

Technische Einzelheiten

Hier ist das Problem: Unser NFT-Kontrakt befindet sich auf L2, und nur dort gibt es Zoras mintWithRewards(...) Funktion, die aufgerufen werden kann, um die NFT zu prägen. Ein neuer Nutzer von Kiwi News kann jedoch nur über Geldmittel im Ethereum Mainnet verfügen.

Wenn er über Geldmittel im OP Mainnet verfügt, ist das großartig; er kann die NFT einfach direkt kaufen und prägen.

Wenn sie jedoch über Geldmittel im Ethereum-Mainnet verfügen, müssen wir sie dazu bringen, diese Geldmittel zuerst zum OP-Mainnet zu überbrücken und idealerweise die NFT während des Überbrückungsprozesses zu kaufen.

Unter keinen Umständen möchten wir, dass der Nutzer mehrere Transaktionen unterzeichnet, da dies zu höheren Abwanderungsraten führen und den gesamten Prozess umständlicher machen würde. Wenn mehrere Transaktionen hintereinander bestätigt werden müssen, ist die Wahrscheinlichkeit höher, dass etwas schief geht. Deshalb haben wir nach Möglichkeiten gesucht, die Überbrückung und den Kauf des NFT atomar zu kombinieren.

Lassen Sie uns also jetzt in den eigentlichen Code eintauchen.

Zunächst einmal müssen Sie verstehen, dass das Optimism-System sowohl auf L1 als auch auf L2 über APIs verfügt. Sie können z. B. einen Vertrag auf L2 aufrufen, um Ihr Geld auf L1 abzuheben. Oder Sie können eine Funktion auf L1 aufrufen, um direkt auf L2 einzuzahlen.

Diese Schnittstellen sind in unserem Fall besonders nützlich, da das OptimismPortal (Etherscan, Quellcode) im Ethereum Mainnet eine Funktion namens OptimismPortal.depositTransaction(...) hat:

/// @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
)
public
payable;

Ist Ihnen aufgefallen, dass die Argumente von depositTransaction(...) der Namensgebung einer normalen Ethereum-Transaktion ähneln? Es gibt die Adresse to, den uint256-Wert und einen Bytespeicherwert, der die Details des Funktionsaufrufs kodiert. uint64 gasLimit ist, auch hier keine Überraschung, und definiert das maximale Gas, das die Transaktion verwenden kann, und bool isCreation, ob der Bytespeicherwert einen neuen Vertrag erstellen soll.

Die Argumente von depositTransaction(...) sind ähnlich, weil der OP-Mainnet-Knoten während des Überbrückungsprozesses intern eine Transaktion mit den entsprechenden Parametern an das OP-Mainnet sendet, wenn der Überbrückungsprozess des L1-Aufrufers erfolgreich ist.

Dies ist besonders nützlich für uns, wenn wir die NFT mit Zoras mintWithRewards(...) Funktion im OP Mainnet prägen wollen, aber auch, um den Nutzern zu ermöglichen, diese Funktion vom Ethereum Mainnet aus aufzurufen, ohne separate Bridging- und Minting-Transaktionen.

Lassen Sie uns hier also tief in den Code eintauchen. Nachfolgend sehen Sie die Schnittstelle für unseren NFT-Sammelvertrag im OP Mainnet, der über Zora bereitgestellt wird:

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

Die Funktion benötigt eine Adresse recipient, den Empfänger der NFT, eine uint256 quantity, um zu definieren, wie viele NTFs geprägt werden sollen, einen String calldata comment für Onchain-Kommentare und eine Adresse mintReferral, um einen Referrer für die Empfehlung der Münzstätte zu belohnen.

Angenommen, wir übernehmen die Aufgabe eines Front-End-Ingenieurs, der einen NFT-Mint-Button erstellen soll, der sowohl im OP-Mainnet als auch im ETH-Mainnet funktioniert, dann würden wir hier Schritt für Schritt erklären, was wir berechnen müssten:

  • Überprüfen Sie das ETH-Guthaben des Benutzers im OP Mainnet. Wenn der Benutzer es sich leisten kann, die NFT zu prägen, fordern Sie den Benutzer auf, mintWithRewards(...) direkt auf Optimism aufzurufen.

  • Wenn der Benutzer nicht genug ETH auf dem OP Mainnet hat, prüfen Sie den Ethereum Mainnet Saldo des Benutzers. Wenn der Benutzer sich den Kauf der NFT im ETH-Mainnet nicht leisten kann, informieren Sie ihn darüber; andernfalls fahren Sie mit Schritt 3 fort.

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”);
}
  • Da wir nun wissen, dass der Benutzer die NFT über das OptimismPortal aus dem ETH-Hauptnetz prägen wird, müssen wir zwei ETH-Aufrufe vorbereiten. Einer ist für den Aufruf von depositTransaction(...) auf L1, und einer für den Aufruf von mintWithRewards(...) auf L2. Den zweiten Aufruf, der auf dem OP-Mainnet ausgeführt werden soll, übergeben wir in die Speicherdaten von depositTransaction(...). So machen wir das:

  • Wir erstellen die Funktionsaufrufdaten für mintWithRewards(...), indem wir die Eingaben für die Funktion mintWithRewards(...) auf L2 sammeln. Wir verwenden interface.encodeFunctionData(name, [...inputs]) von ethers, um den Aufruf als Hex-String zu verpacken.

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,
  ]);
}
  • Für depositTransaction(...) wählen wir dann das Ziel des Funktionsaufrufs als Adresse an und setzen uint256 value auf den Wert von ETH, den wir an Adresse an weitergeben wollen. Wie bei uint64 gasLimit sollen wir die Kosten des ETH-Aufrufs auf Optimism mit dem Guthaben des Benutzers simulieren. Das ETH-Guthaben des Benutzers ist jedoch nicht ausreichend, erinnern Sie sich? Daher wird jeder estimateGas Aufruf mit dem OP-Mainnet-Provider und der Adresse des Benutzers fehlschlagen.

Daher schlagen wir vor, eine statische Schätzung zu ermitteln, indem Sie z.B. die Funktion mintWithRewards(...) manuell aufrufen und diesen Wert im Code verwenden.

Was die anderen Argumente betrifft, so ist bool isCreation falsch und bytes memory data enthält schließlich die in Schritt 3.1 erzeugten Aufrufdaten.

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,
  });
}
  • Wichtig ist, dass wir mindestens den Betrag von uint256 value oder mehr als msg.value beim L1-Aufruf hinterlegen müssen. Dadurch wird sichergestellt, dass einige Ether in Optimism hinterlegt und für den Aufruf von mintWithRewards(...) verfügbar gemacht werden. Wir tun dies, indem wir msg.value auf den Wert von price setzen.

  • Nachdem alle Argumente zusammengetragen wurden, fordern wir den Benutzer auf, diese Transaktion für das Ethereum Mainnet zu signieren.

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>
  );
}

Und das war's! Das ist es, was Sie tun müssen, wenn Sie eine NFT auf Optimism atomar mint, während Sie den Aufruf direkt aus dem Ethereum-Mainnet bereitstellen wollen.

Die Codeschnipsel sind zwar nicht genau der Produktionscode, den wir in Kiwi News verwenden, aber sie sind sehr nah an dem Code, den wir offengelegt haben. Sie können die vollständige Vorbereitungssequenz und mehr auf unserem GitHub finden. Und Sie können dies auch direkt ausprobieren, indem Sie auf unsere NFT-Minting Seite gehen.

Natürlich ist die Funktion depositTransaction(...) des OptimismPortal nicht die einzige Möglichkeit, um von L1 zu L2 zu wechseln. Zurzeit gibt es viele Börsen, die ähnliche Dienste anbieten. Eine Besonderheit des OP-Portals ist jedoch, dass es den msg.sender der Adresse beibehält, die die Transaktion im ETH-Mainnet unterzeichnet und gesendet hat. Dies kann besonders wichtig sein, wenn Ihre Dapp darauf angewiesen ist, dass diese Adresse legitim ist.

Fazit

Als wir unser NFT von Ethereum auf das OP Mainnet migrierten, brauchten wir eine Möglichkeit, die L1-Liquidität anzuzapfen und gleichzeitig all unseren Nutzern zu ermöglichen, es auch vom OP Mainnet aus zu mint.

Das OptimismPortal bietet eine bequeme Funktionalität, um sowohl Geldmittel zu überbrücken als auch einen ETH-Aufruf in einer einzigen atomaren Transaktion auszuführen, was eine hohe Erfolgswahrscheinlichkeit ermöglicht, wenn ein Benutzer den Aufruf sendet.

Wir haben unseren React.js-Code detailliert beschrieben, der sowohl den Ethereum- als auch den L2-Saldo prüft, um den richtigen Aufruf für jeden Nutzer bereitzustellen. Wenn ein Nutzer nicht über OP ETH verfügt, wird er direkt über das Portal an Bord geholt.

In Zukunft freuen wir uns auf weitere spannende technische Möglichkeiten. Zum Beispiel würden wir gerne in der Lage sein, die Liquidität der Base-Chain mit einer ähnlichen Portal-Transaktion atomar anzuzapfen, um unseren Code für alle Ethereum-Nutzer leichter aufrufbar zu machen.

Wir hoffen, dass Sie diesen Beitrag hilfreich für Ihre Arbeit gefunden haben. Bei Kiwi News arbeiten wir immer daran, das Ökosystem mit so viel Nutzen wie möglich zu versorgen - sei es mit den neuesten Nachrichten, Blogbeiträgen oder GitHub-Repositories. Wir würden uns freuen, Sie als Leser an Bord zu haben! Besuchen Sie uns unter https://kiwinews.xyz.

Subscribe to 0xa4D7…BA74
Receive the latest updates directly to your inbox.
Nft graphic
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from 0xa4D7…BA74

Skeleton

Skeleton

Skeleton