5 Wege zur Erstellung Blockchain-übergreifender Anwendungen mit CCIP

Lukas Leys
Chainlink Community
20 min readJul 23, 2023

20. Juli 2023 — Harry Papacharissiou (übersetzt von Lukas Leys)

Das Cross-Chain Interoperability Protocol (CCIP) von Chainlink ist ein neues standardisiertes Cross-Chain-Kommunikationsprotokoll, das Entwicklern von Smart Contracts die Möglichkeit bietet, Daten und Token über Blockchain-Netzwerke hinweg auf eine vertrauensminimierte Weise zu übertragen.

Derzeit leiden Anwendungen, die über mehrere Blockchains hinweg eingesetzt werden, unter der Fragmentierung von Vermögenswerten, Liquidität und Benutzern. Mit dem CCIP können Entwickler Token-Transfers und beliebige Nachrichtenübermittlung nutzen, um dezentrale Anwendungen zu erstellen, die aus mehreren verschiedenen Smart Contracts bestehen, die über mehrere verschiedene Blockchain-Netzwerke bereitgestellt werden und zusammen eine einzige, einheitliche Anwendung bilden. Dieses Web3-Designmuster ist als Blockchain-übergreifender Smart Contract bekannt.

Im Folgenden sind einige Beispiele für CCIP-Anwendungsfälle für Blockchain-übergreifende Anwendungen aufgeführt, die von DeFi und Blockchain-übergreifenden ENS über die Prägung von NFTs auf mehreren Blockchains bis hin zu Blockchain-übergreifenden Games reichen. Diese Anwendungsbeispiele zeigen das Potenzial von CCIP, herkömmliche Single-Chain- oder Multi-Chain-Anwendungen in leistungsstarke neue Blockchain-übergreifende dApps zu verwandeln.

Alle Beispiele sind auf dem Chainlink Labs GitHub zu finden und können ab sofort eingesetzt und genutzt werden.

DeFi: Blockchain-übergreifende Kompatibilität

DeFi ist eine Kategorie von Web3-Anwendungen, die reif für eine Transformation durch Blockchain-übergreifende Smart Contracts ist. In der heutigen DeFi-Welt werden viele Anwendungen entweder auf einer einzigen Blockchain oder auf mehreren Blockchains eingesetzt, wobei jede Instanz ihre eigene Gruppe von Benutzern und Liquidität benötigt. Innerhalb jeder Blockchain gibt es ein Konzept der DeFi-Zusammensetzbarkeit (Composability) und “Money Legos”, bei dem Entwickler ohne Erlaubnis verschiedene Protokolle, die in einem bestimmten Netzwerk eingesetzt werden, verbinden und integrieren können, um neue Anwendungsfälle und Finanzprodukte zu schaffen.

Durch die Ermöglichung Blockchain-übergreifender Smart Contracts und Blockchain-übergreifender Token-Transfers nimmt CCIP das Konzept der DeFi-Zusammensetzbarkeit auf und vervielfacht dieses exponentiell. Denn anstatt dass die Zusammensetzbarkeit auf jede Blockchain und auf die DeFi-Protokolle auf dieser Blockchain beschränkt ist, können nun alle DeFi-Anwendungen auf allen Blockchains auf verschiedene Weise zusammengesetzt werden, um neue Finanzprodukte zu schaffen. Anwendungen und Protokolle sind nicht mehr auf jene Blockchain beschränkt, auf der sie sich befinden.

Diese Blockchain-übergreifende Zusammensetzbarkeit von DeFi-Anwendungen ermöglicht ein weniger fragmentiertes und stärker vernetztes DeFi-Ökosystem, bei dem Liquidität, Nutzer und Finanzprodukte auf allen Blockchains für alle Protokolle verfügbar sind. In Anlehnung an die Geld-Legos ermöglicht CCIP es Ihnen, alle Ihre verschiedenen Lego-Sets zusammenzubringen und mit ihnen Finanzprotokolle zu bauen, als wären sie ein einziges, einheitliches Set.

Eine spezifische DeFi-Finanzdienstleistung, die von CCIP sehr profitieren würde, ist das Lending und Borrowing. In der heutigen Welt verlangen die meisten DeFi-Protokolle, dass Sie Sicherheiten auf einer Blockchain hinterlegen, auf der das Protokoll, das Sie verwenden möchten, eingesetzt wird. Viele DeFi-Nutzer verwenden jedoch mehrere DeFi-Protokolle auf mehreren Blockchains und haben Vermögenswerte, die über all diese Chains verstreut sind. Diese Nutzer jagen oft nach den besten Renditen und passen ihre Positionen an, um die Rendite zu maximieren. In vielen Fällen haben sie Vermögenswerte aber in Protokollen auf einer Blockchain gelocked, obwohl es bessere Renditemöglichkeiten gibt, diese Vermögenswerte auf einer anderen Blockchain zu nutzen. Wenn sie an diesen besseren Renditechancen teilhaben wollten, müssten sie ihre Position auf einer Blockchain auflösen, den Vermögenswert manuell auf die neue Blockchain übertragen, diesen Vermögenswert auf dem Protokoll der neuen Blockchain hinterlegen und dann denselben Prozess in umgekehrter Reihenfolge durchführen, wenn sie ihren Vermögenswert auf die Ursprungs-Blockchain zurückbringen wollen — eine Menge Schritte, nur um Vermögenswerte in ein neues Protokoll zu verschieben, um Renditechancen zu nutzen.

In Szenarien wie diesen kann CCIP dazu beitragen, dass DeFi-Protokolle wirklich Blockchain-übergreifend eingesetzt werden können, und es den Nutzern ermöglichen, digitale Vermögenswerte auf einer Blockchain nahtlos als Sicherheit für ein DeFi-Protokoll auf einer anderen Blockchain zu verwenden, ohne dass der Nutzer manuelle Schritte durchführen muss oder durch die Verwendung einer Brücke eines Drittanbieters zusätzliche Vertrauensannahmen treffen muss. Mithilfe von CCIP kann ein DeFi-Protokoll es einem Kreditnehmer ermöglichen, Vermögenswerte auf einer (Quell-)Blockchain zu hinterlegen oder sie direkt auf eine Ziel-Blockchain zu übertragen, und diese Vermögenswerte dann auf der Ziel-Blockchain zur Kreditaufnahme bereitzustellen. Wenn er dann die Nutzung seiner Vermögenswerte auf der Ziel-Blockchain beenden möchte, kann das DeFi-Protokoll CCIP verwenden, um seine Position zurückzuziehen und seine Vermögenswerte zurück auf die Ursprungskette zu verschieben. Das ist die Stärke von CCIP-aktiviertem DeFi.

In diesem Beispiel sehen wir einen DeFi Smart Contract Sender.sol im Avalanche Fuji Testnet. Dieser Vertrag akzeptiert Benutzereinlagen in Form eines Tokens—es kann sich dabei um wrapped ETH, einen Stablecoin oder einen beliebigen Token handeln, das einen realen Wert hat. Sender.sol hat eine sendMessage-Funktion, die CCIP verwendet, um einen programmierbaren Token-Transfer der angegebenen Token sowie eine Nachricht an eine Ziel-Blockchain auszuführen. In diesem Fall senden wir die angegebenen Token an das Ethereum Sepolia Testnet, wobei die Nachricht den EOA (Externally Owned Account) des Endnutzers enthält:

// Sender.sol
Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
receiver: abi.encode(receiver), // ABI-encoded receiver contract address
data: data,
tokenAmounts: tokenAmounts,
extraArgs: Client._argsToBytes(
Client.EVMExtraArgsV1({gasLimit: 200_000, strict: false}) // Additional arguments, setting gas limit and non-strict sequency mode
),
feeToken: address(linkToken) // Setting feeToken to LinkToken address, indicating LINK will be used for fees
});


// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());


// Get the fee required to send the message. Fee paid in LINK.
uint256 fees = router.getFee(destinationChainSelector, evm2AnyMessage);


// Approve the Router to pay fees in LINK tokens on contract's behalf.
linkToken.approve(address(router), fees);


// Approve the Router to transfer the tokens on contract's behalf.
IERC20(tokenToTransfer).approve(address(router), transferAmount);


// Send the message through the router and store the returned message ID
messageId = router.ccipSend(destinationChainSelector, evm2AnyMessage);


// Emit an event with message details
emit MessageSent(messageId, destinationChainSelector, receiver, msg.sender, tokenAmount, fees);

Hinweis: Alle Code-Beispiele in diesem Beitrag dienen der Veranschaulichung und werden ohne jegliche Zusicherungen, Gewährleistungen, Zusagen oder Bedingungen zur Verfügung gestellt. Die Verwendung dieser Codeschnipsel unterliegt unseren Allgemeinen Geschäftsbedingungen, die Sie unter chain.link/terms finden.

Im Ethereum-Sepolia-Netzwerk haben wir einen Smart Contract Protocol.sol implementiert. Dieser Vertrag empfängt die programmierbare CCIP-Token-Übertragungsnachricht und führt Folgendes aus

  • Er minted und kontrolliert Stablecoins, die gegen hinterlegte Sicherheiten ausgeliehen werden können.
  • Liest aus der CCIP-Nachricht die angegebene Token-Vertragsadresse (auf der Ziel-Blockchain), für die Vermögenswerte von der Quell-Blockchain (als Sicherheit für die Ausleihe) gesendet wurden, sowie den hinterlegten Betrag.
  • Aus dem Inhalt der CCIP-Nachricht wird auch die Wallet-Adresse des Endnutzers (Einzahler/Kreditnehmer) ausgelesen. Der Stablecoin wird auf diese Adresse geminted, und diese Adresse wird zudem verwendet, um Einzahlungen und Ausleihen zu verfolgen.
  • Speichert diese Informationen im Smart Contract.
// Protocol.sol
bytes32 messageId = any2EvmMessage.messageId; // fetch the messageId
uint64 sourceChainSelector = any2EvmMessage.sourceChainSelector; // fetch the source chain identifier (aka selector)
address sender = abi.decode(any2EvmMessage.sender, (address)); // abi-decoding of the sender address

// Collect tokens transferred. This increases this contract's balance for that Token.
Client.EVMTokenAmount[] memory tokenAmounts = any2EvmMessage.destTokenAmounts;
address token = tokenAmounts[0].token;
uint256 amount = tokenAmounts[0].amount;

address depositor = abi.decode(any2EvmMessage.data, (address)); // abi-decoding of the depositor's address

receivedMessages.push(messageId);
MessageIn memory detail = MessageIn(sourceChainSelector, sender, depositor, token, amount);
messageDetail[messageId] = detail;

emit MessageReceived(messageId, sourceChainSelector, sender, depositor, tokenAmounts[0]);

// Store depositor data.
deposits[depositor][token] = amount;

Sobald diese programmierbare KKP-Token-Übertragungsnachricht von Protocol.sol empfangen und erfolgreich verarbeitet wurde, kann der Nutzer dann manuell eine Aktion zum Ausleihen von Geldern einleiten, indem er die Funktion borrowUSDC ausführt. Diese Funktion ermöglicht es dem Benutzer, die übertragenen Token als Sicherheit zu verwenden, um einen gleichwertigen Betrag eines Stablecoins, wie z.B. USDC, für den EOA des Kreditnehmers zu prägen und zu leihen. In diesem Beispiel gehen wir von einer Besicherungsquote von 70% aus, was bedeutet, dass das Protokoll nicht mehr als 70% des Wertes des hinterlegten Tokens verleihen wird:

uint256 borrowed = borrowings[msg.sender][address(usdcToken)];
require(borrowed == 0, "Caller has already borrowed USDC");

address transferredToken = messageDetail[msgId].token;
require(transferredToken != address(0), "Caller has not transferred this token");

uint256 deposited = deposits[msg.sender][transferredToken];
uint256 borrowable = (deposited * 70) / 100; // 70% collateralization ratio.

// LINK/USD on Sepolia (https://sepolia.etherscan.io/address/0xc59E3633BAAC79493d908e63626716e204A45EdF)
// Docs: https://docs.chain.link/data-feeds/price-feeds/addresses#Sepolia%20Testnet
AggregatorV3Interface priceFeed = AggregatorV3Interface(0xc59E3633BAAC79493d908e63626716e204A45EdF);

(, int256 price, , , ) = priceFeed.latestRoundData();
uint256 price18decimals = uint256(price * (10 ** 10)); // make USD price 18 decimal places from 8


uint256 borrowableInUSDC = borrowable * price18decimals;

// MintUSDC
usdcToken.mint(msg.sender, borrowableInUSDC);

// Update state.
borrowings[msg.sender][address(usdcToken)] = borrowableInUSDC;


return borrowableInUSDC;

Sobald der Nutzer erfolgreich UDSC auf Sepolia gegen die hinterlegten Sicherheiten ausgeliehen hat, kann er die Mittel nach Belieben für jedes DeFi-Protokoll im Sepolia-Netzwerk verwenden. Wenn sie fertig sind, können sie die Gelder an Protocol.sol zurückzahlen, was dazu führt, dass die Stablecoin-Token geburnt werden. Eine programmierbare CCIP-Token-Transfer-Nachricht wird dann zurück an den Sender.sol-Vertrag im Fuji-Netzwerk gesendet, der die gesperrten Token an die angegebene Adresse im Fuji-Netzwerk zurückschickt. Beachten Sie, dass der Nutzer Protocol.solzunächst als “Spender” der geliehenen Stablecoins des Nutzers genehmigen muss, damit das Protokoll den geliehenen Betrag burnen kann, so wie die Rückzahlung implementiert ist:

require(amount >= borrowings[msg.sender][address(usdcToken)], "Repayment amount is less than amount borrowed");


// Get the deposit details, so it can be transferred back.
address transferredToken = messageDetail[msgId].token;
uint256 deposited = deposits[msg.sender][transferredToken];


uint256 mockUSDCBal = usdcToken.balanceOf(msg.sender);
require(mockUSDCBal >= amount, "Caller's USDC token balance insufficient for repayment");


if (usdcToken.allowance(msg.sender, address(this)) < borrowings[msg.sender][address(usdcToken)]) {
revert("Protocol allowance is less than amount borrowed");
}


usdcToken.burnFrom(msg.sender, mockUSDCBal);


borrowings[msg.sender][address(usdcToken)] = 0;
// send transferred token and message back to Sepolia Sender contract
sendMessage(destinationChain, receiver, transferredToken, deposited);

Der vollständige Quellcode und die Anweisungen für dieses Beispiel sind im CCIP-DeFi Lending GitHub-Repository zu finden.

DeFi: Blockchain-übergreifender Liquidationsschutz

Um beim Thema DeFi und Lending- und Kreditprotokolle zu bleiben: Viele DeFi-Nutzer halten mehrere Positionen über mehrere DeFi-Protokolle und mehrere Blockchains. Dies macht es schwierig, den Überblick über Portfolios und DeFi-Positionen zu behalten. Es gibt mehrere Plattformen von Drittanbietern, Tracker und sogar Rendite-Aggregatoren. DeFi-Nutzer können einfach Sicherheiten bereitstellen und diese Plattformen von Drittanbietern die Bereitstellung und Bewegung von Vermögenswerten übernehmen lassen, um die Rendite für den Endnutzer zu optimieren. Während diese Tools eine großartige Möglichkeit darstellen, einige der Komplexitäten von DeFi zu abstrahieren, so dass Benutzer einfach Rendite erzielen können, sind sie nicht vertrauensminimierend. Der Nutzer vertraut dem Protokoll an, dass es Rendite erwirtschaftet und sicherstellt, dass die Positionen besichert bleiben, um eine Liquidation zu vermeiden. Wenn der Endnutzer eine Art von Liquidationsschutz wünscht, muss er darüber hinaus auf allen Blockchains, auf denen er DeFi-Positionen hält, native Vermögenswerte bereithalten, um die Besicherung von Krediten auf den Positionen, die er auf der jeweiligen Blockchain hält, zu gewährleisten.

Mit CCIP und Blockchain-übergreifenden Token-Transfers und Nachrichtenübermittlung können DeFi-Protokolle, Positionsüberwachungs-Apps und Renditeaggregatoren so erweitert werden, dass sie über einen Blockchain-übergreifenden Liquidationsschutz verfügen. Das bedeutet, dass ein Nutzer offene Positionen auf mehreren DeFi-Protokollen über mehrere Blockchains hinweg haben kann und dann Vermögenswerte auf einer einzigen Blockchain zuweisen kann, die als zusätzliche Sicherheiten verwendet werden, falls einer oder mehrere der Kredite zusätzliche Mittel benötigen, um die Besicherung zu gewährleisten. So funktioniert das in groben Zügen:

  • Ein DeFi-Endnutzer hat Schuldpositionen über mehrere Protokolle auf mehreren Blockchains (z.B. Ethereum, Avalanche, Polygon), aber er bewahrt seine Liquidität sicher in einem digitalen Tresor auf einer Blockchain auf (z.B. Aave auf Ethereum).
  • Auf jeder Blockchain, auf der der Nutzer eine Schuldenposition hat, überwacht die Implementierung von Chainlink Automation das Schuldenverhältnis der Positionen.
  • Stellt die Automatisierung fest, dass sich einer ihrer Kredite der Liquidationsschwelle nähert, sendet sie eine CCIP-Nachricht an die Liquiditäts-Blockchain des Nutzers (z.B. Ethereum), um die Überweisung von Mitteln zur Aufstockung der Schuldenposition anzufordern.
  • Wenn der Vertrag auf der Liquiditäts-Blockchain die CCIP-Nachricht erhält, zieht er Liquidität von Aave ab und sendet eine neue CCIP-Nachricht mit den Mitteln zurück an die anfordernde Blockchain. Die Nachricht enthält genügend Informationen und Token, um die Position zu finanzieren und ein Liquidationsszenario zu vermeiden.

Das Ergebnis ist, dass der Benutzer Schuldpositionen auf mehreren Blockchains haben kann, während er die Liquidität auf einer einzigen Blockchain behält. Der gesamte Prozess ist vertrauensminimiert, wobei der Nutzer immer noch 100% Kontrolle über seine Schuldenpositionen hat und keine manuellen Abhebungen und Geldverschiebungen zwischen den Blockchains vornehmen muss. Und so funktioniert es:

Chainlink Automation überwacht alle Blockchains, in denen ein Benutzer eine Sollposition hat, und bestimmt, ob eine Nachricht zur Finanzierung gesendet werden muss. Falls erforderlich, sendet die performUpkeep Funktion dann eine CCIP-Nachricht an den Tresor auf der Blockchain, der über Liquidität verfügt, und fordert die Übermittlung von Mitteln an.

function checkUpkeep(
bytes calldata checkData
)
external
view
override
returns (bool upkeepNeeded, bytes memory performData)
{
upkeepNeeded =
IMockLending(i_lendingAddress).healthFactor(i_onBehalfOf) <
i_minHealthFactor &&
!_isCcipMessageSent;
}

function performUpkeep(bytes calldata performData) external override {
require(
!_isCcipMessageSent,
"CCIP Message already sent, waiting for a response"
);
require(
IMockLending(i_lendingAddress).healthFactor(i_onBehalfOf) <
i_minHealthFactor,
"Account can't be liquidated!"
);

// Ask for funds from LPSC on the source blockchain
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(i_lpsc),
data: abi.encode(
i_tokenAddress,
IMockLending(i_lendingAddress).getBorrowedAmount(i_onBehalfOf)
),
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: "",
feeToken: i_link
});

bytes32 messageId = IRouterClient(i_sourceChainRouter).ccipSend(
i_sourceChainSelector,
message
);
_isCcipMessageSent = true;

emit MessageSent(messageId);
}

Der Tresor auf der Blockchain, der über die Liquidität verfügt, empfängt dann die Anfrage nach Geldmitteln und prüft, ob er über genügend Geldmittel verfügt, um sie an die anfragende Blockchain zurückzusenden, oder ob er etwas Liquidität von einem DeFi-Protokoll (z.B. Aave) abheben sollte, um sicherzustellen, dass er genügend Geldmittel zum Senden hat. Anschließend initiiert sie einen programmierbaren CCIP-Token-Transfer, der die angeforderten Mittel sowie die Nachrichten-ID der ursprünglich empfangenen Nachricht enthält (damit der Zielvertrag auf der Blockchain, der die Mittel angefordert hat, weiß, für welche Anforderung die Mittel bestimmt sind):

function _ccipReceive(
Client.Any2EVMMessage memory receivedMessage
) internal override {
bytes32 messageId = receivedMessage.messageId;
uint64 sourceChainSelector = receivedMessage.sourceChainSelector;
address sender = abi.decode(receivedMessage.sender, (address));
(address tokenAddress, uint256 amount) = abi.decode(
receivedMessage.data,
(address, uint256)
);

address tokenToReturn = s_destinationToSourceMap[
keccak256(abi.encodePacked(tokenAddress, sourceChainSelector))
];

uint256 currentBalance = IERC20(tokenToReturn).balanceOf(address(this));

// If there are not enough funds in LPSC, withdraw additional from Aave vault
if (currentBalance < amount) {
withdrawFromVault(tokenToReturn, amount - currentBalance);
}

Client.EVMTokenAmount[] memory tokenAmounts;
tokenAmounts[1] = (Client.EVMTokenAmount(tokenToReturn, amount));

Client.EVM2AnyMessage memory messageReply = Client.EVM2AnyMessage({
receiver: abi.encode(sender),
data: abi.encode(msgId),
tokenAmounts: tokenAmounts,
extraArgs: "",
feeToken: LINK
});

bytes32 replyMessageId = IRouterClient(i_router).ccipSend(
sourceChainSelector,
messageReply
);

// emit ReplySent(replyMessageId,sourceChainSelector, messageId, sender, tokenToReturn, amount);
}

Schließlich erhält der Smart Contract auf der Blockchain, der Mittel zur Aufstockung einer Schuldenposition angefordert hat, den Transfer des programmierbaren CCIP-Tokens, gleicht die Anforderungs-ID mit seiner ursprünglichen Anforderung ab und nimmt dann die Mittel entgegen und zahlt die übertragenen Mittel in die Schuldenposition ein, um die Besicherung des Kredits zu erhöhen und eine Liquidation zu vermeiden:

function _ccipReceive(
Client.Any2EVMMessage memory receivedMessage
) internal override {
_isCcipMessageSent = false;
bytes32 requestMessageId = abi.decode(receivedMessage.data, (bytes32));
uint256 amountToRepay = requested[requestMessageId];
IMockLending(i_lendingAddress).repay(
i_tokenAddress,
amountToRepay,
i_onBehalfOf
);
}

Dieses Beispiel zeigt, wie CCIP in DeFi-Protokollen und DeFi-Positionsüberwachungsanwendungen eingesetzt werden kann, um Nutzern einen vertrauensminimierten Liquidationsschutz ihrer Schuldenpositionen über mehrere Blockchains hinweg zu bieten und ihnen gleichzeitig zu ermöglichen, ihre Mittel und Liquidität auf einer einzigen Blockchain zu halten.

Der vollständige Quellcode und die Anweisungen für dieses Beispiel können im CCIP Liquidation Protector GitHub Repository gefunden werden.

Blockchain-übergreifender Domänennamen-Dienst

Dezentrale Domain-Namen-Dienste wie ENS sind im Web3 extrem beliebt, da sie die Übersetzung von menschlich lesbaren Namen in Wallet-Adressen erleichtern. In einer idealen Welt sollten Domain-Namen-Dienste nicht spezifisch für eine Chain sein, sondern jede registrierte Domain sollte sich über alle Ethereum Chains, Side Chains, Layer 2s und App Chains hinweg verbreiten und leben. Dies würde es Nutzern ermöglichen, eine einzige, einheitliche Identität im gesamten Ethereum-Ökosystem zu haben, im Gegensatz zu der Notwendigkeit, Domains über mehrere Namensdienste zu registrieren oder Interoperabilitätslösungen zu verwenden, die nicht vertrauensminimiert sind.

Um dies zu erreichen, müssten die Domainnamen-Dienste jedoch mit anderen Blockchains kommunizieren. Jede Instanz des Benennungsdienstes auf einer Blockchain müsste benachrichtigt werden, wenn neue Domains registriert werden, und es müsste eine Möglichkeit geben, “Lookups” gegen ein globales Namensregister über alle Blockchains hinweg durchzuführen.

Dieses Beispiel zeigt, wie man eine vereinfachte, Blockchain-übergreifende Namensdienstanwendung erstellen könnte, bei der Benutzer Domains auf einer Blockchain registrieren und diese Registrierung auf mehrere andere Blockchains übertragen lassen sowie Namen in Adressen auf jeder Blockchain auflösen könnten.

Überblick über die Blockchain-übergreifende Namensgebungsarchitektur

Der erste Schritt besteht darin, die Verträge CrossChainNameServiceRegisterund CrossChainNameServiceLookup auf dem Ethereum-Sepolia-Netzwerk bereitzustellen. Dieses Netzwerk wird als “Heimat”-Netzwerk fungieren, in dem alle Registrierungen stattfinden und sich dann auf andere Chains ausbreiten.

Wenn Sie ein neues .ccns-Handle registrieren, wird der CrossChainNameServiceRegister-Vertrag CCIP verwenden, um eine Nachricht an andere unterstützte Blockchains zu senden, die Informationen über das registrierte .ccns-Handle enthält:

uint256 length = s_chains.length;
for (uint256 i; i < length; ) {
Chain memory currentChain = s_chains[i];

Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(currentChain.ccnsReceiverAddress),
data: abi.encode(_name, msg.sender),
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: Client._argsToBytes(
Client.EVMExtraArgsV1({
gasLimit: currentChain.gasLimit,
strict: currentChain.strict
})
),
feeToken: address(0) // We leave the feeToken empty indicating we'll pay raw native.
});

i_router.ccipSend{
value: i_router.getFee(currentChain.chainSelector, message)
}(currentChain.chainSelector, message);

unchecked {
++i;
}
}

i_lookup.register(_name, msg.sender);

Auf allen unterstützten empfangenden Blockchains würde der CrossChainNameServiceReceiver-Vertrag eingesetzt werden. Dieser Vertrag empfängt registrierte .ccns-Domänen vom CrossChainNameServiceRegister-Vertrag und speichert sie im CrossChainNameServiceLookup-Vertrag, der auf dieser Blockchain bereitgestellt wird:

constructor(
address router,
address lookup,
uint64 sourceChainSelector
) CCIPReceiver(router) {
i_router = IRouterClient(router);
i_lookup = ICrossChainNameServiceLookup(lookup);
i_sourceChainSelector = sourceChainSelector;
}

function _ccipReceive(
Client.Any2EVMMessage memory message
) internal override onlyFromSourceChain(message.sourceChainSelector) {
(string memory _name, address _address) = abi.decode(
message.data,
(string, address)
);

i_lookup.register(_name, _address);
}

Mit diesem einfachen Entwurfsmuster ist es möglich, einen einfachen Blockchain-übergreifenden Domain-Namen-Service zu erstellen, bei dem Benutzer eine Domain einmal registrieren und dann über mehrere Blockchains hinweg besitzen und nutzen können.

Der vollständige Quellcode und die Anweisungen für dieses Beispiel sind im Cross-Chain Name Service GitHub-Repository zu finden.

Cross-Chain NFTs

NFTs sind einer der beliebtesten Anwendungsfälle im Web3. Jedes NFT-Projekt befindet sich in der Regel auf einer einzigen Blockchain, oder das Projekt selbst hat mehrere Implementierungen auf mehreren Blockchains, wobei die Endnutzer die NFT mehr als einmal prägen müssen, wenn sie sie auf mehreren Blockchains besitzen wollen.

Mit CCIP Arbitrary Messaging können NFT-Projekte es ermöglichen, dass ihre Vermögenswerte einmal auf einer einzigen Blockchain geprägt werden, einmal vom Minter bezahlt werden und dann an den Nutzer auf anderen Blockchains weitergegeben werden. Dies bedeutet, dass die Nutzer ihre NFT besitzen und teilen können, unabhängig davon, welches Netzwerk sie verwenden. CCIP kann auch zum “Burnen und Minten” von NFTs über Blockchains hinweg verwendet werden, so dass Nutzer ihre NFTs von einer Blockchain auf eine andere übertragen können. Hier ein Beispiel dafür, wie das erste Szenario funktioniert:

Der MyNFT-Vertrag enthält einen einfachen NFT-Smart-Contract mit einer Münzfunktion:

function mint(address to) public {
unchecked {
tokenId++;
}
_safeMint(to, tokenId);
}

Der sourceMinter-Vertrag wird auf der Quell-Blockchain eingesetzt und enthält eine Logik in seiner Mint-Funktion, um eine CCIP-Cross-Chain-Nachricht mit der ABI-kodierten Mint-Funktionssignatur vom MyNFT.sol-Smart Contract an die Ziel-Blockchain zu senden:

function mint(
uint64 destinationChainSelector,
address receiver,
PayFeesIn payFeesIn
) external {
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(receiver),
data: abi.encodeWithSignature("mint(address)", msg.sender),
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: "",
feeToken: payFeesIn == PayFeesIn.LINK ? i_link : address(0)
});

uint256 fee = IRouterClient(i_router).getFee(
destinationChainSelector,
message
);

bytes32 messageId;

if (payFeesIn == PayFeesIn.LINK) {
LinkTokenInterface(i_link).approve(i_router, fee);
messageId = IRouterClient(i_router).ccipSend(
destinationChainSelector,
message
);
} else {
messageId = IRouterClient(i_router).ccipSend{value: fee}(
destinationChainSelector,
message
);
}

emit MessageSent(messageId);

Der DestinationMinter-Smart-Contract empfängt die CCIP-Cross-Chain-Nachricht mit der ABI-codierten Signatur der mint-Funktion als Nutzdaten und ruft damit die mint-Funktion in der MyNFT-Smart-Contract-Funktion auf. Der MyNFT-Smart-Contract mintet dann die neue NFT auf das msg.sender-Konto aus der mint()-Funktion des SourceMinter-Smart-Contracts, d. h. auf dieselbe Kontoadresse, für die die NFT auf der Quell-Blockchain gemint wurde:

function _ccipReceive(
Client.Any2EVMMessage memory message
) internal override {
(bool success, ) = address(nft).call(message.data);
require(success);
emit MintCallSuccessfull();
}

Das Endergebnis ist, dass der Nutzer, der das NFT geprägt hat, das NFT nun auf mehreren Blockchains besitzt, wobei er sie nur einmal prägen und bezahlen musste. Wenn das NFT-Projekt auch über Blockchains hinweg strikt fälschungssicher bleiben will, kann diese Lösung auch leicht so modifiziert werden, dass die NFT auf einer Ziel-Blockchain geprägt und anschließend auf der Ursprungs-Blockchain geburned wird, wodurch sichergestellt wird, dass es auf allen Blockchains nur eine Version davon gibt.

Der vollständige Quellcode und die Anweisungen für dieses Beispiel sind im Cross-Chain NFT GitHub Repository zu finden.

Gaming: Cross-Chain Tic-Tac-Toe

In den letzten Jahren hat das Web3-Gaming massiv an Popularität gewonnen. Wie bei DeFi ist das Gaming jedoch sehr fragmentiert, wobei die Spiele und ihre Inhalte in der Regel für eine bestimmte Blockchain spezifisch gebaut sind. Aber wie beim traditionellen Gaming ist das ultimative Ziel oder die beste Erfahrung, wenn die Spieler ein Spiel gemeinsam spielen können, unabhängig von der Hardware oder Software, mit der sie spielen. So wie ein PC-Spieler mit Besitzern einer Xbox-Konsole spielen kann, sollte es keinen Grund geben, warum jemand ein Spiel auf Polygon nicht mit jemandem spielen kann, der auf Avalanche spielt. Dies ist als plattformübergreifendes Gaming bekannt.

Dies lässt sich perfekt auf rundenbasierte Web3-Spiele und andere Spiele übertragen, die keine schnelle Echtzeit-Interaktion erfordern. Web3-Spiele leiden unter der Zersplitterung der Nutzer, da die Spieler es vorziehen, auf der Blockchain ihrer Wahl zu spielen und ihre bevorzugten digitalen Ressourcen zu verwenden. CCIP ermöglicht es Web3-Spielen, wirklich Blockchain-übergreifend zu sein, indem es die Übertragung von Vermögenswerten über Blockchains hinweg erleichtert und einen gemeinsamen Spielstatus über mehrere Blockchains hinweg ermöglicht, so dass Spieler unabhängig von der Blockchain, die sie verwenden möchten, gegeneinander spielen können. Wenn Sie so viele Spieler wie möglich erreichen wollen, dann ist es sinnvoll, Ihr Spiel auf mehreren Chains zu erstellen und es so zu gestalten, dass alle Spieler gegeneinander oder miteinander spielen können.

Eine einfache Demonstration dieses Blockchain-übergreifenden Spiel-Designmusters kann anhand eines rundenbasierten Strategiespiels wie Tic-Tac-Toe gezeigt werden. In diesem Beispiel haben wir einen Spiel-Smart-Contract, der auf mehreren Blockchains eingesetzt wird. Die Nutzer können dann ein Spiel auf der Blockchain ihrer Wahl starten und die ID der Spielsitzung mit ihren Freunden teilen. Ihre Freunde können dann dem Spiel von einer anderen Blockchain aus beitreten, wenn sie dies wünschen. Bei der Erstellung des Spiels hat CCIP die Spieldetails und den Anfangszustand mit allen anderen Chains geteilt:

struct GameSession {
bytes32 sessionId;
address player_1; // player who starts the game
address player_2; // the other player in the game
address winner; // winner of game
address turn; // check who takes action in next step
uint8[9] player1Status; // current status for player 1
uint8[9] player2Status; // current status for player 2
}
mapping(bytes32 => GameSession) public gameSessions;
bytes32[] public sessionIds;


function start(uint64 destinationChainSelector, address receiver) external {
bytes32 uniqueId = keccak256(abi.encodePacked(block.timestamp, msg.sender));
sessionIds.push(uniqueId);
gameSessions[uniqueId]= GameSession(
uniqueId,
msg.sender,
address(0),
address(0),
msg.sender,
initialCombination,
initialCombination
);

sendMessage(destinationChainSelector, receiver, gameSessions[uniqueId]);
}

Sobald der erste Spieler nach der Initiierung des Spiels an der Reihe ist, sieht der zweite Spieler auf einer anderen Blockchain den aktualisierten Spielstatus auf seinem Spiel-Smart-Contract, sobald die CCIP-Nachricht erfolgreich verarbeitet wurde. Spieler 2 ist dann am Zug, was eine CCIP-Nachricht erzeugt, die an Spieler 1 zurückgeschickt wird und den Spielstatus auf dessen Blockchain aktualisiert:

function _ccipReceive(
Client.Any2EVMMessage memory any2EvmMessage
) internal override {
bytes32 messageId = any2EvmMessage.messageId; // fetch the messageId
uint64 sourceChainSelector = any2EvmMessage.sourceChainSelector; // fetch the source chain identifier (aka selector)
address sender = abi.decode(any2EvmMessage.sender, (address)); // abi-decoding of the sender address
GameSession memory message = abi.decode(any2EvmMessage.data, (GameSession)); // abi-decoding of the sent string message
receivedMessages.push(messageId);
Message memory detail = Message(sourceChainSelector, sender, message);
messageDetail[messageId] = detail;
gameSessions[message.sessionId] = message;
sessionIds.push(message.sessionId);

emit MessageReceived(messageId, sourceChainSelector, sender, message);
}


function move(
uint256 x,
uint256 y,
uint256 player,
bytes32 sessionId,
uint64 destinationChainSelector,
address receiver)
public
{
GameSession memory gs = gameSessions[sessionId];
// make sure the game session setup and not over.
require(gs.player_1 != address(0), "the session is not setup, please start game first!");
require(gs.winner == address(0), "the game is over");

// make sure the player is in the game session
require(player == 1 || player == 2, "you must be player1 or player2"); //this is used to when player has the same address

if(player == 1) {
// make sure it is player1's turn to move
require(gs.player_1 == msg.sender && gs.turn == msg.sender, "it is not your turn");

// 1. if the position is not taken by the opponent, then take the position
if(gs.player1Status[x * 3 + y] == 0 && gs.player2Status[x * 3 + y] == 0) {
gameSessions[sessionId].player1Status[x * 3 + y] = 1;

// 2. check if player1 wins or make the turn to the opponent, send the message
if(checkWin(keccak256(abi.encodePacked(gameSessions[sessionId].player1Status)))) {
gameSessions[sessionId].winner = gameSessions[sessionId].player_1;
} else {
gameSessions[sessionId].turn = gameSessions[sessionId].player_2;
}
sendMessage(destinationChainSelector, receiver, gameSessions[sessionId]);
} else {
revert("the position is occupied");
}
} else if(player == 2) {
// make sure it is player2's turn to move, this is the first step for player2
require((gs.player_2 == msg.sender && gs.turn == msg.sender) || gs.player_2 == address(0), "it is not your turn");

if(gs.player_2 == address(0)) {
gameSessions[sessionId].player_2 = msg.sender;
}

// 1. if the position is not taken by the opponent, then take the position
if(gs.player1Status[x * 3 + y] == 0 && gs.player2Status[x * 3 + y] == 0) {
gameSessions[sessionId].player2Status[x * 3 + y] = 1;

// 2. check if player1 wins or make the turn to the opponent, send the message
if(checkWin(keccak256(abi.encodePacked(gameSessions[sessionId].player2Status)))) {
gameSessions[sessionId].winner = gameSessions[sessionId].player_2;
} else {
gameSessions[sessionId].turn = gameSessions[sessionId].player_1;
}
sendMessage(destinationChainSelector, receiver, gameSessions[sessionId]);
} else {
revert("the position is occupied");
}
}
}

Spieler 1 sieht dann den aktualisierten Spielstand und macht seinen Zug noch einmal. Dieses Hin und Her von CCIP-Nachrichten zwischen den Chains wird fortgesetzt, während die Spieler ihre Züge machen, bis das Spiel zu Ende ist und ein Gewinner erklärt wird. Wichtig ist hierbei, dass die Smart Contracts auf beiden Chains den Spielstatus beibehalten, wobei CCIP zum Senden und Empfangen von Nachrichten verwendet wird, um sicherzustellen, dass der Spielstatus auf beiden Blockchains beibehalten wird:

function checkWin(bytes32 combination) public view returns (bool) {
return wcs[combination];
}

/// @notice Sends data to receiver on the destination chain.
/// @dev Assumes your contract has sufficient native asset (e.g, ETH on Ethereum, MATIC on Polygon...).
/// @param destinationChainSelector The identifier (aka selector) for the destination blockchain.
/// @param receiver The address of the recipient on the destination blockchain.
/// @param message The string message to be sent.
/// @return messageId The ID of the message that was sent.
function sendMessage(
uint64 destinationChainSelector,
address receiver,
GameSession memory message
) public returns (bytes32 messageId) {
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
receiver: abi.encode(receiver), // ABI-encoded receiver address
data: abi.encode(msg), // ABI-encoded string message
tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array indicating no tokens are being sent
extraArgs: Client._argsToBytes(
Client.EVMExtraArgsV1({gasLimit: 400_000, strict: false}) // Additional arguments, setting gas limit and non-strict sequency mode
),
feeToken: address(0) // Setting feeToken to zero address, indicating native asset will be used for fees
});

// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(_router);

// Get the fee required to send the message
uint256 fees = router.getFee(destinationChainSelector, evm2AnyMessage);

// Send the message through the router and store the returned message ID
messageId = router.ccipSend{value: fees}(
destinationChainSelector,
evm2AnyMessage
);

// Emit an event with message details
emit MessageSent(
messageId,
destinationChainSelector,
receiver,
message,
fees
);

// Return the message ID
return messageId;
}

Der vollständige Quellcode und die Anweisungen für dieses Beispiel sind im CCIP Tic-Tac-Toe GitHub-Repository zu finden.

Fazit

Von Blockchain-übergreifenden DeFi und NFTs bis hin zu Spielen, die über mehrere Blockchains laufen, ermöglicht CCIP die Realisierung von Blockchain-übergreifenden Smart Contracts, echte DeFi-Kompositionsfähigkeit über alle Blockchains hinweg und ein viel einheitlicheres Web3.

Wenn Sie eine Blockchain-übergreifende Anwendung entwickeln, die CCIP nutzt, würden wir uns freuen, davon zu hören. Bitte kontaktieren Sie uns per E-Mail an chaindevhub@chainlinklabs.com. Wenn Sie daran interessiert sind, Ihre CCIP-fähige Anwendung ins Mainnet zu bringen, setzen Sie sich bitte mit uns in Verbindung.

Haftungsausschluss: Dieser Beitrag dient nur zu Informationszwecken und enthält Aussagen über die Zukunft, einschließlich voraussichtlicher Produktfunktionen, Entwicklung und Zeitpläne für die Einführung dieser Funktionen. Diese Aussagen sind nur Vorhersagen und spiegeln die aktuellen Überzeugungen und Erwartungen in Bezug auf zukünftige Ereignisse wider; sie basieren auf Annahmen und unterliegen Risiken, Unsicherheiten und Änderungen zu jeder Zeit. Chainlink CCIP befindet sich in der “Early Access”-Phase der Entwicklung, was bedeutet, dass Chainlink CCIP derzeit über Funktionen verfügt, die sich in der Entwicklung befinden und in späteren Versionen geändert werden können. Es kann nicht garantiert werden, dass die tatsächlichen Ergebnisse nicht wesentlich von den in diesen Aussagen ausgedrückten abweichen, obwohl wir glauben, dass sie auf vernünftigen Annahmen beruhen. Alle Aussagen gelten nur für das Datum, an dem sie erstmals veröffentlicht wurden. Diese Aussagen spiegeln möglicherweise nicht die zukünftigen Entwicklungen aufgrund von Benutzer-Feedback oder späteren Ereignissen wider, und wir aktualisieren diesen Beitrag möglicherweise nicht als Reaktion darauf. Chainlink CCIP ist ein Messaging-Protokoll, das keine Vermögenswerte hält oder überträgt. Bitte lesen Sie die Chainlink Terms of Service, die wichtige Informationen und Offenlegungen enthalten.

Disclaimer: This post is for informational purposes only and contains statements about the future, including anticipated product features, development, and timelines for the rollout of these features. These statements are only predictions and reflect current beliefs and expectations with respect to future events; they are based on assumptions and are subject to risk, uncertainties, and changes at any time. Chainlink CCIP is in the “Early Access” stage of development, which means that Chainlink CCIP currently has functionality which is under development and may be changed in later versions. There can be no assurance that actual results will not differ materially from those expressed in these statements, although we believe them to be based on reasonable assumptions. All statements are valid only as of the date first posted. These statements may not reflect future developments due to user feedback or later events and we may not update this post in response. Chainlink CCIP is a messaging protocol which does not hold or transfer any assets. Please review the Chainlink Terms of Service which provides important information and disclosures.

--

--