5 Ways To Build Cross-Chain Applications With CCIP

https://blog.chain.link/how-to-use-ccip/

Chainlink’s Cross-Chain Interoperability Protocol (CCIP) is a new generalized cross-chain communication protocol that provides smart contract developers with the ability to transfer data and tokens across blockchain networks in a trust-minimized manner.

Currently, applications deployed across multiple blockchains suffer from the fragmentation of assets, liquidity, and users. With CCIP, developers can utilize token transfers and arbitrary messaging to enable the creation of decentralized applications that are composed of multiple different smart contracts deployed across multiple different blockchain networks that interoperate to create a single unified application. This Web3 design pattern is known as the cross-chain smart contract.

Below are a few examples of CCIP use cases for cross-chain applications, ranging from DeFi and cross-chain ENS to minting NFTs on multiple chains to cross-chain gaming. These use case examples demonstrate the potential for CCIP to transform traditional single-chain or multi-chain applications into powerful new cross-chain dApps.

All examples can be found on the Chainlink Labs GitHub and can be deployed and interacted with now.

DeFi: Cross-Chain Composability

DeFi is a category of Web3 applications that is ripe for transformation by cross-chain smart contracts. In the world of DeFi today, many applications are either deployed on a single chain or on multiple chains, with each instance requiring its own set of users and liquidity. Within each chain, there is a concept of DeFi composability and “money Legos,” with developers able to permissionless-ly connect and integrate with various protocols deployed on a particular network to create new use cases and financial products.

By enabling cross-chain smart contracts and cross-chain token transfers, CCIP takes the concept of DeFi composability and exponentially multiplies it. Because now, instead of composability being restricted to each chain and limited to the DeFi protocols on that chain, all DeFi applications on all blockchains can now be composed together in various ways to create new financial products. Applications and protocols are no longer confined to the chain they’re on.

This cross-chain composability of DeFi applications enables a less fragmented and more connected DeFi ecosystem, with liquidity, users, and financial products on all chains being available to all protocols. Using the money Legos analogy, CCIP enables you to bring all of your different Lego sets together and build financial protocols with them as if they were a single, unified set.

One particular DeFi financial service that would greatly benefit from CCIP is lending and borrowing. In today’s world, most DeFi lending and borrowing protocols require you to deposit collateral on the chain the protocol you want to use is deployed on. But many DeFi users use multiple DeFi protocols across multiple blockchains and have assets scattered across all these chains. These users often chase the best returns, adjusting their positions to maximize yield, but in many cases they have assets locked up in protocols on one chain when there are better yield-earning opportunities to use those assets on another chain. If they wanted to participate in those superior yield opportunities, they’d need to liquidate their position on one chain, manually bridge the asset to the new chain, deposit that asset on to the protocol on the new chain, and then perform the same process in reverse when they want to return their asset to the originating chain—a lot of steps just to move assets into a new protocol to chase yield opportunities.

Scenarios like this are where CCIP can help enable DeFi protocols to truly go cross-chain, and allow users to seamlessly utilize digital assets on one chain as collateral for a DeFi protocol on another chain, all handled by CCIP at the protocol level, without the user having to perform manual steps, or adding trust assumptions due to using a third party bridge. Using CCIP, a DeFi protocol can enable a borrower to deposit assets on one (source) chain, or transfer them directly to a destination chain, and then have those assets made available on the destination chain to borrow against. Then when they wish to stop using their assets on the destination chain, the DeFi protocol can use CCIP to withdraw their position and move their assets back to the originating chain. This is the power of CCIP-enabled DeFi.

In this example, we have a DeFi smart contract Sender.sol on the Avalanche Fuji testnet. This contract accepts user deposits in the form of a token; it could be wrapped ETH, a stablecoin, or any token that has a real value. Sender.sol has a sendMessage function, which uses CCIP to execute a programmable token transfer of the specified tokens as well as a message to a destination chain. In this case, we are sending the specified tokens to the Ethereum Sepolia testnet, with the message containing the end-user’s EOA (externally owned account):

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

Note: All code examples in this post are illustrative and are provided on an “as-is” basis without any representations, warranties, covenants, or conditions of any kind. Use of these code snippets is governed by our Terms of Service found at chain.link/terms.

On the Ethereum Sepolia network, we have deployed a Protocol.sol smart contract. This contract receives the CCIP programmable token transfer message and does the following:

  • Mints and controls stablecoins that can be borrowed against deposited collateral.
  • From the CCIP message, reads the specified token contract address (on the destination chain) for which funds were sent from the source chain (as collateral for borrowing) and the amount deposited.
  • Also from the CCIP message contents, reads the end-user’s (depositor/borrower’s) wallet address. The stablecoin is minted to this address, and this address is also used to track deposits and borrowing.
  • Stores this information in the 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;

Once this CCIP programmable token transfer message has been received and successfully processed by Protocol.sol, the user can then manually initiate an action to borrow funds by executing the borrowUSDC function. This function allows the user to use the transferred tokens as collateral to mint and borrow an equivalent amount of a stablecoin, such as USDC, to the borrower’s EOA. In this example, we assume a 70% collateralization ratio, meaning that the protocol will lend no more than 70% of the value of the deposited token:

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;

Once the user has successfully borrowed UDSC on Sepolia against their deposited collateral, they can then use the funds as they wish on any DeFi protocol on the Sepolia network. Then, when they are done, they can repay the funds to Protocol.sol, which will cause the stablecoin tokens to be burnt, and a CCIP programmable token transfer message is then sent back to the Sender.sol contract on the Fuji network, which will return the locked up tokens to the specified address on the Fuji network. Note that the user must first approve Protocol.sol as a “spender” of the user’s borrowed stablecoin for the protocol to be able to burn the borrowed amount, which is how repayment is implemented:

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

The complete source code and instructions for this example can be found in the CCIP-DeFi Lending GitHub repository.

DeFi: Cross-Chain Liquidation Protection

Continuing with the theme of DeFi and lending and borrowing protocols, many DeFi users have multiple positions held across multiple DeFi protocols across multiple blockchains. This makes keeping track of portfolios and DeFi positions difficult. There are multiple third-party platforms, trackers, and even yield aggregators available, with DeFi users able to simply deploy collateral and let these third-party platforms handle the deploying and moving around of assets to optimize yield for the end-user. While these tools are a great way to abstract away some of the complexities of DeFi so users can simply earn yield, they are not trust-minimized. The user is entrusting the protocol to earn yield and to ensure positions are kept collateralized to avoid liquidation. In addition to this, if the end-user wants to have some sort of liquidation protection, they are required to have native assets deployed across all blockchains they have DeFi positions in, ready to be deployed to ensure collateralization of loans on the positions they have on each respective chain.

With CCIP and cross-chain token transfers and messaging, DeFi protocols, position monitoring apps, and yield aggregators can be enhanced to have cross-chain liquidation protection. This means that a user can have open positions on multiple DeFi protocols across multiple blockchains, and they can then allocate assets on a single chain to be used as extra collateral in the event that one or more of the loans requires extra funds to ensure collateralization. Here’s how that works at a high level:

  • A DeFi end-user has debt positions across multiple protocols across multiple chains (e.g., Ethereum, Avalanche, Polygon), but they keep their liquidity safely in a vault on one chain (e.g., Aave on Ethereum).
  • On each chain where the user has a debt position, a user’s implementation of Chainlink Automation monitors the debt ratio of the positions.
  • If Automation detects that any of their loans are approaching the liquidation threshold, then Automation will send a CCIP message to the user’s liquidity chain (e.g., Ethereum) to request funds to be sent to top up the debt position.
  • When the contract on the liquidity chain receives the CCIP message, it withdraws liquidity from Aave and sends a new CCIP message back to the requesting chain with the funds. The message includes enough information and tokens to fund the position and avoid a liquidation scenario.

The result of this is the user can have debt positions on multiple chains while still keeping liquidity on a single chain. The entire process is trust-minimized, with the user still 100% in control of their debt positions, and they do not have to manually withdraw and move funds across chains. Here’s how it works:

Chainlink Automation monitors all chains where a user has a debit position and determines if a message for funding needs to be sent. If required, the performUpkeep function will then send a CCIP message to the vault on the chain that has liquidity, requesting funds to be sent.

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

The vault on the chain that has the liquidity then receives the request for funds and checks if it has enough funds to send back to the requesting chain or if it should withdraw some liquidity from a DeFi protocol (such as Aave) to ensure it has enough funds to send. Following this, it then initiates a CCIP programmable token transfer containing the funds requested as well as the message ID of the initially received message (so the destination contract on the blockchain that requested funds knows which request the funds are for):

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

Finally, the smart contract on the chain that requested funds to top up a debt position receives the CCIP programmable token transfer, matches up the request ID with it’s original request, and then takes the funds and deposits the transferred funds into the debt position in order to increase the collateralization of the loan and avoid a liquidation:

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

This example demonstrates how CCIP can be utilized in DeFi protocols and DeFi position monitoring applications to provide users with trust-minimized liquidation protection of their debt positions across multiple blockchains while allowing them to keep their funds and liquidity on a single blockchain.

The complete source code and instructions for this example can be found in the CCIP Liquidation Protector GitHub repository.

Cross-Chain Domain Name Service

Decentralized domain name services like ENS are extremely popular in Web3, as they facilitate the translation of human readable names to wallet addresses. In an ideal world, domain name services should not be specific to one chain, but each registered domain should propagate and live across all Ethereum chains, side chains, layer 2s, and app chains. This would allow users to have a single, unified identity across the entire Ethereum ecosystem, as opposed to having to register domains across multiple naming services or using interoperability solutions that aren’t trust-minimized.

However, to achieve this, domain name services would be required to communicate with other blockchains. Each instance of the naming service on a blockchain would need to be notified when new domains are registered, and there would need to be a way to perform “lookups” against a global name registry across all blockchains.

This example showcases how you could build a simplified cross-chain naming service application, where users could register domains on one chain and have that registration propagate across multiple other blockchains as well as resolve names to addresses across any blockchain.

Overview of cross-chain naming architecture

The first step is to deploy the CrossChainNameServiceRegister and CrossChainNameServiceLookup contracts to the Ethereum Sepolia network. This network will act as the “home” network where all registrations will occur, and then propagate to other chains.

When you register a new .ccns handle, the CrossChainNameServiceRegister contract will use CCIP to send a message to other supported blockchains containing information about the registered .ccns handle:

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

On all the supported receiving blockchains, the CrossChainNameServiceReceiver contract would be deployed. This contract would receive registered .ccns domains from the CrossChainNameServiceRegister contract, and would store them in the CrossChainNameServiceLookup contract deployed on that blockchain:

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

Finally, the CrossChainNameServiceLookup contract would be deployed on all blockchains, including the registering blockchain (in this case, Sepolia), as well as all destination blockchains. This contract would be used to store all registered .ccns handles and act as an interface to perform lookups to translate names to addresses:

function register(
        string memory _name,
        address _address
    ) external onlyCrossChainNameService {
        if (lookup[_name] != address(0)) revert AlreadyTaken();
        lookup[_name] = _address;
    }

Using this simple design pattern, it becomes possible to create a simple cross-chain domain name service where users can register a domain once, then own and use it across multiple blockchains.

The complete source code and instructions for this example can be found in the Cross-Chain Name Service GitHub repository.

Cross-Chain NFTs

NFTs are one of the most popular use cases in Web3. Each NFT project is usually on a single blockchain, or the project itself has multiple deployments across multiple chains, with end-users having to mint the NFT more than once if they want to own it on multiple blockchains.

With CCIP Arbitrary Messaging, NFT projects can allow for their assets to be minted once on a single chain, paid for once by the minter, and then have them propagated to the user on other blockchains. This means users can own and share their NFT, irrespective of which network they are using. CCIP can also be used to “burn and mint” NFTs across chains, allowing users to move their NFTs from one chain to another. Here is an example of how the first scenario works:

The MyNFT contract contains a simple NFT smart contract with a mint function:

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

The sourceMinter contract is deployed on the source chain, and contains logic in its mint function to send a CCIP Cross-Chain Message with the ABI-encoded mint function signature from the MyNFT.sol smart contract to the destination blockchain:

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

The DestinationMinter smart contract will receive the CCIP Cross-Chain Message with the ABI-encoded mint function signature as a payload, and call the mint function in the MyNFT smart contract function using it. The MyNFT smart contract will then mint the new NFT to the msg.sender account from the mint() function of the SourceMinter smart contract, i.e. to the same account address for which the NFT was minted on the source chain:

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

The end result is that the user that minted the NFT now owns the NFT on multiple chains, and they only had to mint and pay for it once. If the NFT project wants to remain strictly non-fungible even across blockchains, this solution can also be easily modified to mint the NFT on a destination blockchain and burn it on the source chain afterwards, ensuring there is only ever one version of it across all blockchains.

The complete source code and instructions for this example can be found in the Cross-Chain NFT GitHub repository.

Gaming: Cross-Chain Tic-Tac-Toe

In the past couple of years, Web3 gaming has become massively popular. However, like DeFi, gaming is very fragmented, with games and their assets usually being specific to a certain chain. But like traditional gaming, the ultimate goal or best experience is when you have gamers that can play a game together, irrespective of the hardware or software they’re playing on. Just like how a PC gamer can play games with Xbox console owners, there should be no reason why someone can’t play a game on Polygon with someone playing on Avalanche. This is known as cross-platform gaming.

This translates perfectly to turn-based Web3 games and other games that do not require fast real-time interaction. Web3 gaming suffers from user fragmentation, with gamers preferring to play on their chain of choice and use their preferred digital assets. CCIP enables Web3 games to be truly cross-chain, facilitating the transferring of assets across chains and enabling a shared game state across multiple blockchains, allowing gamers to play each other regardless of the chain they want to use. If you want to reach as many gamers as possible, then it makes sense to have your game deployed across multiple chains and design it in a way where all players can play against or with each other.

A simple demonstration of this cross-chain gaming design pattern can be shown with a turn-based strategy game, such as Tic-Tac-Toe. In this example, we have a game smart contract that is deployed on multiple blockchains. Users can then start a game on their chain of choice and then share the game session ID with their friends. Their friends can then join the game from another chain if they wish. On game creation, CCIP will have shared the game details and initial state with all the other chains:

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

Once the first player has taken a turn after initiating the game, the second player on another blockchain will see the updated game state on their game smart contract once the CCIP message is successfully processed. Player 2 will then take their turn, which will generate a CCIP message to get sent back to player 1 and update the state of the game on their chain:

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

Player 1 will then see the updated game state and make their move once again. This back and forth of CCIP messages between chains will continue to happen as the gamers make their moves until the game reaches a conclusion and a winner is declared. The important thing to note here is that the game smart contracts on both chains both maintain the game state, with CCIP being used to send and receive messages to ensure game state is maintained across both blockchains:

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

Cross-chain Tic-Tac-Toe Game

The complete source code and instructions for this example can be found in the CCIP Tic-Tac-Toe GitHub repository.

Conclusion

From cross-chain DeFi and NFTs to games that run across multiple blockchains, CCIP enables the realization of cross-chain smart contracts, true DeFi composability across all blockchains, and a much more unified Web3.

If you are building a cross-chain application that uses CCIP, we would love to hear about it. Please reach out and email us at chaindevhub@chainlinklabs.com. If you are interested in taking your CCIP-enabled application to mainnet, please reach out to us.

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.

The post 5 Ways To Build Cross-Chain Applications With CCIP appeared first on Chainlink Blog.

5 Ways To Use Chainlink Functions in Your Decentralized Applications

https://blog.chain.link/?p=5681

Chainlink Functions is a serverless developer platform for Web3 that enables smart contracts to connect to any API and get decentralized and verifiable consensus on the result of any code you want to run. 

This is extremely powerful, because traditionally smart contracts running on a blockchain cannot easily access data and systems off-chain, and doing complex computation on-chain can be expensive. 

Below are a few sample use cases, ranging from NFT giveaways to crypto payments for music streams, that walk through how Chainlink Functions can connect decentralized applications to any API. These technical tutorials with code examples were built in collaboration with leading cloud and Web2 platforms such as Meta, Twilio, AWS, and Google Cloud to demonstrate Chainlink Functions’ extensive connectivity and showcase the new possibilities when the expansive, feature-rich world of Web2 is combined with the highly automated and secure infrastructure of Web3. 

All examples can be found on the Chainlink Labs GitHub and can be deployed today.

Meta: Connecting Social Media to Web3

Social media platforms like Instagram and Facebook serve billions of users, enabling them to stay connected with their peers and consume content. Most businesses have a heavy online presence on these platforms, with many of them driving a large majority of their business via these channels.

One use case that combines the powerful connectivity and reach of social media applications with the unique properties of the Web3 world is social media promotions and campaigns. Millions of businesses worldwide leverage the reach of social media platforms like Facebook and Instagram to share and promote new product launches via competitions, campaigns, and giveaways, with customers able to win special prizes by interacting with content shared by business pages.

In addition to this, digital collectibles were integrated into Instagram in 2022, offering creators the opportunity to share and showcase their art, images, videos, and music with their network in the form of NFTs. This provides an avenue for businesses and creators to leverage new tools to earn income, and to take more control over their work and their relationships with their fans and customers.

With Instagram’s integration of digital collectibles now being rolled out and being used by millions of people, combining digital collectibles with social media competitions and giveaways presents an incredible opportunity to showcase how social media and Web3 technologies like Chainlink Functions can be used together to empower businesses to conduct social media campaigns and competitions that leverage digital collectibles in an automated, scalable, and trust-minimized way.

The Use Case: New Product Launch NFT Giveaway

This use case example starts with a business advertising that it has a new product or service launching through its Facebook or Instagram business page, and, to celebrate the occasion, it has a limited number of unique digital collectibles in the form of NFTs to offer customers. To be eligible for one of these special NFTs, the customers need to send the business page a private message containing their wallet address and the specific hashtag for the promotion.

Business advertising a promotion via their Facebook page
Customer participating in the promotion by sending a private message to the business
Customer participating in the promotion by sending a private message to the business

Once the timeframe for the promotion has expired, the business interacts with a smart contract to handle the NFT minting. The smart contract requires a list of eligible wallet addresses as an input, and once it receives the eligible wallet addresses, it will mint an NFT for each of them. In this example, it assumes there are three winners:

 function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override {
    latestResponse = response;
    latestError = err;
    emit OCRResponse(requestId, response, err);

    address winner1;
    address winner2;
    address winner3;
    assembly {
      winner1 := mload(add(response, 20))
      winner2 := mload(add(response, 40))
      winner3 := mload(add(response, 60))
    }
    nft.mint(winner1);
    nft.mint(winner2);
    nft.mint(winner3);
  }

 

Flow of events for New Product Launch NFT Giveaway
Flow of events for New Product Launch NFT Giveaway

The business uses Chainlink Functions together with a smart contract to find the eligible list of customer wallet addresses. In this case, the smart contract initiates the call to Chainlink Functions, passing in the JavaScript code to be executed. This JavaScript code reaches out to the Meta Messaging API, extracts and filters out conversations that contain the relevant hashtag, and then figures out which customers should be eligible to receive an NFT (in this case, the first three that responded).

async function getEligibleConversations(isoStartDate) {
    const conversationsResponse = await Functions.makeHttpRequest({
        url: `https://graph.facebook.com/v16.0/${secrets.FACEBOOK_PAGE_ID}/conversations?fields=messages.limit(1){message,from},updated_time&access_token=${secrets.FACEBOOK_GRAPH_API_KEY}`
    })

    if (conversationsResponse.status === 200) {
        const conversationsObject = conversationsResponse.data;
        const conversations = conversationsObject.data;

        const eligibleConversations = conversations.filter(conversation => new Date(conversation.updated_time) > isoStartDate);
        return eligibleConversations;
    } else {
        throw Error(conversationsResponse.statusText);
    }
}
async function chooseWinners(eligibleConversations, numberOfWinners) {
    let winnersArray = [];
    const length = eligibleConversations.length;
    for (let i = 0; i < length;) {
        // we are getting only the latest received message from the conversation with the user
        const current = eligibleConversations[i].messages.data[0].message;
        if (current.includes("#giveaway")) {
            const walletAddress = current.substr(current.indexOf("0x"), 42);
            if (isAddress(walletAddress)) {
                winnersArray.push({
                    walletAddress: walletAddress,
                    senderId: eligibleConversations[i].messages.data[0].from.id
                });
                if (winnersArray.length == numberOfWinners) {
                    return winnersArray;
                }
            }
        }
        ++i;
    }
    throw Error("No eligible addresses");
}

Once it builds up a list of wallet addresses eligible to receive an NFT, the function uses the Meta API to send a message back to each chosen user informing them that they were successful. From here, it simply returns the list of addresses to the smart contract in the form of a byte array. Once the smart contract receives the output from Chainlink Functions, it will use it to mint an NFT to each of the specified wallet addresses.

 

Customer receiving a notification after being chosen to receive an NFT as part of the promotion
Customer receiving a notification after being chosen to receive an NFT as part of the promotion
  async function sendNotification(recipientId) {
    await Functions.makeHttpRequest({
        method: 'POST',
        url: `https://graph.facebook.com/v16.0/${secrets.FACEBOOK_PAGE_ID}/messages?recipient={'id':'${recipientId}'}&messaging_type=MESSAGE_TAG&message={'text':'Congratulations, you were successful in winning one of our special unique NFTs to celebrate the launch of our new product! Please check your wallet address that you specified in this conversation, you should now be able to see your NFT there, or in the Instagram Digital Collectibles album if you have linked the specified wallet address to your Instagram account.'}&tag=CONFIRMED_EVENT_UPDATE&access_token=${secrets.FACEBOOK_GRAPH_API_KEY}`
    })
}
async function main() {
    const isoStartDate = new Date(args[0]);
    const numberOfWinners = args[1];
    const testerAccounts = JSON.parse(args[2]);
    const eligibleConversations = await getEligibleConversations(isoStartDate);
    if (eligibleConversations === undefined || eligibleConversations.length === 0) {
        throw Error("No eligible conversations");
    }
    // conversations are stored based on the latest update:
    // 1. the newest
    // 2. old
    // 3. the oldest
    // 
    // we want to find the fastest eligible address to award it with an NFT
    const sortedEligibleConversations = eligibleConversations.reverse();
    const chosenWinners = await chooseWinners(sortedEligibleConversations, numberOfWinners);
    const winners = chosenWinners.map(({ walletAddress }) => Buffer.from(walletAddress.slice(2), 'hex'))
    chosenWinners.forEach(async ({ senderId }) => {
        if (testerAccounts.includes(senderId)) {
            await sendNotification(senderId);
        }
    });
    return winners;
}

The final step is each customer who won the competition receiving a private message informing them that they were successful. They will be able to see their newly minted digital collectible in their Instagram Digital Collectibles gallery or in their account on OpenSea:

Viewing the NFT in Instagram Digital Collectibles once minted
Viewing the NFT in Instagram Digital Collectibles once minted
Viewing the NFT in OpenSea once minted
Viewing the NFT in OpenSea once minted

Please see the example repository for the full source code and complete instructions on how to deploy and execute this use case.

Twilio: Generating Alerts Based on On-Chain Events

Twilio is a cloud communication platform that offers a set of APIs and tools that enable developers to build and integrate messaging, voice, and video capabilities into their applications. With Twilio, developers can easily add real-time communication features to their web and mobile applications without the need for any complex infrastructure setup or maintenance.

With decentralized applications not natively able to access off-chain data and APIs, they have been limited in their functionality and ability to interact with off-chain services. With Chainlink Functions, dApp builders can now easily set up complex custom workflows that decentralize the execution of arbitrary code, run complex compute, and also consume services like Twilio’s email, WhatsApp, and SMS alerting services. This opens up a number of new features and use cases, from alerting users when a DeFi position is in danger of being liquidated to sending automated email invoices based on on-chain payments made as the result of a digital agreement.

The Use Case: Music Artist and Record Label Streaming Income Digital Agreement

This use case revolves around a digital agreement between a record label and a music artist in the form of a smart contract. The smart contract has been set up to pay out the music artist based on the number of Spotify music streams they received in the past month based on an agreed formula of X USD per Y streams. The smart contract has a function that, when invoked, executes custom logic to fetch and compare the latest streaming count to the last fetched count, calculates the payment required, and then pays out the required amount to the specified artist’s wallet address in the form of a stablecoin such as USDC. This smart contract can act as a single source of truth for the digital agreement and its state, and the data can be integrated and referenced by other systems as required.

The ability for the smart contract to reach out and find the artist’s Spotify streams and generate an email alert to the artist is not something that smart contracts can do themselves. In this example, the smart contract uses Chainlink Functions to connect to a music data API to come to consensus on the number of Spotify streams the artist has.

const URL = `https://sandbox.api.soundcharts.com/api/v2/artist/${artistId}/streaming/spotify/listeners`
const soundchartsResponse = await Functions.makeHttpRequest({
    url: URL,
    // Get a free sandbox API key from https://doc.api.soundcharts.com/api/v2/doc
    headers: { "x-app-id": secrets.soundchartAppId, "x-api-key": secrets.soundchartApiKey },
  })

It then does the off-chain computation to calculate the payment amount, generates an email alert for the artist to inform them of the streams and payment amount using the Twilio email API, then returns the latest listener count back to the smart contract.

 const emailData = {
    personalizations: [
      {
        to: [
          {
            email: artistEmail,
            name: artistName,
          },
        ],
        subject: "A payout is coming your way!",
      },
    ],
    content: [
      {
        type: "text/plain",
        value: `Hey ${artistName}! 
You've got ${latestListenerCount} listeners which is ${
          latestListenerCount - lastListenerCount
        } more than when we last checked!
So you can expect some cool crypto to be sent to your wallet soon!
Best,
TwiLink Records
            `,
      },
    ],
    from: {
      email: VERIFIED_SENDER,
      name: "TwiLink Records",
    },
    reply_to: {
      email: "sam.smith+noreply@example.com",
      name: "Sam Smith",
    },
  }

 

Twilio email notification
Twilio email notification

Once the smart contract receives the listener count, it calculates and sends the required payment of USDC to the saved artists’ wallet address, then stores the latest number of streams in the digital agreement to be used in the next month’s calculation. 

For this particular use case, having a process in place using trust-minimized smart contracts as digital agreements has multiple advantages:

  • The artist knows they will always be paid and can be sure that the record label can’t alter the conditions of the agreement.
  • The record label’s process for paying its artists is more efficient, removing the need for manual payments.
  • The solution scales well, and is automated and efficient regardless of how many artists are onboarded to the record label.

In this example, the Twilio email is being used as a simple alert for the artist. But it can also be used in conjunction with a fully designed invoice email using the Twilio SendGrid Design editor, giving the on-chain smart contract full functionality for sending professional-looking invoices to the artist, as the payments are made in real-time.

Please see the example repository for the full source code and complete instructions on how to deploy and execute this use case.

Amazon Web Services (AWS): Integrating Web3 With Cloud Systems and APIs

Cloud platforms such as Amazon Web Services (AWS) offer a wide range of cloud-based computing services to developers, including scalable and reliable infrastructure, storage solutions, database management, machine learning tools, and serverless computing. These services are used to power much of the digital world today, thanks to them being easy to integrate into developer workflows in an efficient and cost-effective way. 

Integrating these cloud services into the world of Web3 and smart contracts opens up a large number of potential use cases that fuse together the capabilities and scalability of Web2 cloud computing with the high security and trust-minimization properties of Web3 decentralized applications. 

The Use Case: A Universal Connector to Consume AWS Data APIs

This use case involves creating a universal connector Chainlink Function that can be used to connect to any AWS Data Exchange data, allowing developers to seamlessly integrate third-party data in AWS together with smart contracts. This enables the creation of advanced Web3 applications that can make use of the vast array of data sets available in AWS.

In this specific example, the universal connector will be used to connect and obtain currency exchange data from the Rearc Currency Exchange API, then return the data to an on-chain smart contract.

The Rearc currency exchange API in AWS
The Rearc currency exchange API in AWS

The universal connector has been built as a JavaScript function for all nodes in the Chainlink Functions decentralized oracle network (DON) to execute before coming to consensus on the result of the API call.

The HTTP request that Functions executes is built up programmatically, taking in a number of encoded environment variables, such as the data set ID, revision ID, and asset ID from the Rearc data set.

The authorization header is built up using the AWS access key and secret key as well as a signature. The signature is generated in the request header containing a concatenated string of the request that is then hashed with the SHA-256 hashing algorithm. For more information on generating signatures for the authorization header, see the Amazon Simple Storage Service API Reference.

const signature = buildSignature(method, url, host, secrets.secretKey, secrets.securityToken, date, payload, region, service)
const config = {
  url: `https://${host}${url}`,
  headers: {
    'x-amzn-dataexchange-data-set-id': secrets.dataSetID,
    'x-amzn-dataexchange-revision-id': secrets.revisionID,
    'x-amzn-dataexchange-asset-id': secrets.assetID,
    'X-Amz-Date': date,
    'Authorization': `AWS4-HMAC-SHA256 Credential=${secrets.accessKey}/${shortDate(date)}/${region}/${service}/aws4_request, SignedHeaders=${buildSignedHeaders(secrets.securityToken)}, Signature=${signature}`
  }
}
const response = await Functions.makeHttpRequest(config)

To calculate the signature, the AWS secret key is used to derive a signing key. The derived signing key is specific to the date, service, and region. The final signature is the HMAC-SHA256 hash of the string to sign (concatenated string of the request), using the derived signing key as the key.

  /**
   * To calculate a signature, a special string has to be signed. Canonical request is part of that string. This functions takes various request parts and returns special shaped string that will be hashed later on. Since queries are passed separately we need to remove them from url parameter (if there is any)
   * @param {string} method - request method
   * @param {string} url - absolute url of the request WITHOUT query parameters
   * @param {string} host - host of the request url
   * @param {string} securityToken - optional security token when temporary credentials are used
   * @param {string} queries - encoded and sorted query parameters
   * @param {string} date - current date (ISO 8601)
   * @param {string} payload - request body for POST/PUT request, empty string for GET requests
   * @returns canonical request string
   */
  const buildCanonicalRequest = (method, url, host, securityToken, queries, date, payload) => {
    url = url.split('?')[0]
    return method + 'n'
      + encodeURI(url) + 'n'
      + queries + 'n'
      + 'host:' + host + 'n'
      + 'x-amz-date:' + date + 'n'
      + (securityToken ? 'x-amz-security-token:' + securityToken + 'n' : '')
      + 'n'
      + buildSignedHeaders(securityToken) + 'n'
      + crypto.createHash('sha256').update(payload).digest('hex')
  }

Once each node in the Chainlink Functions DON has executed the AWS API call and the DON has come to consensus on the result, it is then encoded as a uint256 and posted back to the consuming smart contract. The complete source code for this example can be found in the AWS Data Exchange GitHub repository.

This example demonstrates just one of many ways of integrating smart contracts with AWS. Developers can easily modify the universal connector to accommodate some of the other capabilities of AWS that can be accessed via APIs, such as querying and updating data on an Amazon Relational Database Service (RDS) or enabling a smart contract to execute a Lambda Function hosted on AWS. 

Google: Integrating Decentralized Applications With BigQuery and Analytics

Google Analytics is a popular web analytics service that allows website owners to track and analyze traffic and user behavior. While it was designed to be used with traditional websites, it can also be used with decentralized applications (dApps) and smart contracts.

Google BigQuery is a cloud-native data warehouse that enables businesses to analyze and query large datasets in real-time. 

One particular example of integrating Google Analytics and BigQuery with smart contracts is to use Chainlink Functions and Chainlink Automation to provide live updates on-chain about website or dApp user statistics. This demo was created in collaboration with Google’s Web3 Developer Advocate Allen Day.

The Use Case: Using Analytics Data To Trigger On-Chain Logic

This use case showcases how a developer can use website data collected by Google Analytics to drive and influence logic in an on-chain smart contract. For this particular example, a user is directed to a website where they can vote for a dog or cat, with their selection being sent to Google Analytics. The collected data is then automatically uploaded to a dataset in Google Cloud’s BigQuery. From here, Chainlink Automation will be used to regularly call a Chainlink Function to pull the vote totals from Google BigQuery and then return them on-chain. The smart contract will use this analytics data to keep track of vote totals. Once the voting period has ended, a function in the smart contract will determine who the winner is.

Before a request to the Google BigQuery API can be made, Chainlink Functions must request an access token:

const jwtClaimSetObj = {
    "iss": iss,
    "scope": "https://www.googleapis.com/auth/cloud-platform.read-only",
    "aud": "https://oauth2.googleapis.com/token",
    "exp": currentTimeInSeconds + 3500,
    "iat": currentTimeInSeconds
  }
  const jwtBase64ClaimSet = Buffer.from(JSON.stringify(jwtClaimSetObj)).toString('base64')
  const stringToSign = `${jwtBase64Headers}.${jwtBase64ClaimSet}`
  const jwtBase64Signature = crypto.sign('RSA-SHA256', stringToSign, privateKey).toString('base64')
  const jwtRequest = {
    grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
    assertion: `${jwtBase64Headers}.${jwtBase64ClaimSet}.${jwtBase64Signature}`
  }
  const jwtRequestString = querystring.stringify(jwtRequest)
  const tokenResponse = await Functions.makeHttpRequest({
    url: 'https://oauth2.googleapis.com/token',
    method: 'post',
    data: jwtRequestString
  })

Once this token has been obtained, it can be used in the built-up request to obtain the data from Google BigQuery for each selection:

const getSQLQuery = (propertyId) => {
  return `SELECT COUNT(DISTINCT user_pseudo_id) AS votes FROM `${secrets.projectId}.analytics_${propertyId}.events_intraday_*` WHERE event_name = 'page_view' OR event_name = 'user_engagement'`
}
const requestConfig = {
  method: 'post',
  url: `https://bigquery.googleapis.com/bigquery/v2/projects/${secrets.projectId}/queries`,
  headers: {
    "Authorization": `Bearer ${await getOauthToken(secrets.iss, secrets.key)}`,
    "Accept": 'application/json',
    "Content-Type": 'application/json'
  },
  data: {
    "query": getSQLQuery(secrets.property1),
    "useLegacySql": false
  }
}
const request1 = Functions.makeHttpRequest(requestConfig)
requestConfig.data.query = getSQLQuery(secrets.property2)
const request2 = Functions.makeHttpRequest(requestConfig)
const responses = await Promise.all([ request1, request2 ])

The returned results are then combined and sent back on-chain to the consuming smart contract:

let item1Votes
try {
  item1Votes = parseInt(responses[0].data.rows[0].f[0].v)
} catch {
  item1Votes = 0
}
let item2Votes
try {
  item2Votes = parseInt(responses[1].data.rows[0].f[0].v)
} catch {
  item2Votes = 0
}
console.log(`Item 1 votes: ${item1Votes}nItem 2 votes: ${item2Votes}`)
return Buffer.concat([ Functions.encodeUint256(item1Votes), Functions.encodeUint256(item2Votes) ])

Once the voting period is complete, the declareWinner function will determine which of the two animals is the winner:

function declareWinner() public onlyOwner {
    if (charity1Votes == charity2Votes) {
      winner = 'Charity #1 and #2 tied!';
    }
    if (charity1Votes > charity2Votes) {
      winner = 'Charity #1 won!';
    }
    if (charity1Votes > charity2Votes) {
      winner = 'Charity #2 won!';
    }
    emit WinnerDeclared(winner);
  }

The complete source code for this example can be found in the Google BigQuery Demo GitHub repository. This simple example is limited to specific analytics data based upon two events, but acts as a vehicle for demonstrating the possibilities that can be explored when you combine decentralized applications with services like Google Analytics and Google BigQuery.

Decentralized Insurance: Leveraging Off-Chain Weather Data for Parametric Insurance 

Decentralized insurance involves using blockchain technology and smart contracts to replace traditional insurance agreements, offering a superior insurance product for both insurance companies and their clients thanks to data-driven automation, highly secure, tamper-proof digital agreements in the form of smart contracts, and instant and automated claims processing.

The biggest issue for bringing the world of parametric insurance to Web3 is the availability of high-quality, verifiable data. With Chainlink Functions, developers can easily create their own off-chain data feed by accessing and aggregating data from multiple sources and then have Chainlink Functions reach consensus on what the result of the off-chain data aggregation is before reporting the final result on-chain.

The Use Case: Parametric Insurance Contract Using Multiple Data Sources

This use case showcases how a developer can pull weather data from three different data sources, aggregate the three results off-chain, then have each node in the Chainlink Functions DON reach a consensus on the median value returned by each node before posting it on-chain.

This data will be used to determine if an insurance contract should be paid out to a client. The insurance contract ParametricInsurance will check to see what the temperature is in a given city (in this case, New York). If the temperature falls below 60 degrees Fahrenheit for three consecutive days, the contract will payout the agreed value to the client. The ParametricInsurance contract defines the terms of the agreement, such as agreed value, and the parametric parameters for the paying out of the contract:

function fulfillRequest(
        bytes32 requestId,
        bytes memory response,
        bytes memory err
    ) internal override {
      latestResponse = response;
      latestError = err;
      emit OCRResponse(requestId, response, err);
      // once callback happens, mark the timestamp
      currentTempDateChecked = block.timestamp;
      currentTemperature = uint256(bytes32(response));
      // if current temperature is under temperature which considered as cold, number of cold days increment
      if (currentTemperature > coldTemp) {
          consecutiveColdDays = 0;
      } else {
          consecutiveColdDays += 1;
      }
      // pay the client and shut down the contract
      if(consecutiveColdDays >= COLD_DAYS_THRESHOLD) {
          payoutContract();
      }
    }

The Parametric-insurance-example.js Chainlink Function performs API calls to three different weather data sources, aggregates the result, then come to consensus on the median result of each node before passing the weather data back to the ParametricInsurance smart contract.

const openWeatherRequest = Functions.makeHttpRequest({
  url: `http://api.openweathermap.org/data/2.5/weather?lat=${cityLat}&lon=${cityLon}&appid=${secrets.openWeatherApiKey}&units=imperial`,
})
const worldWeatherRequest = Functions.makeHttpRequest({
  url: `http://api.worldweatheronline.com/premium/v1/weather.ashx?key=${secrets.worldWeatherApiKey}&q=${cityName}&format=json`,
})
const ambeeDataRequest = Functions.makeHttpRequest({
  url: `http://api.ambeedata.com/weather/latest/by-lat-lng?lat=${cityLat}&lng=${cityLon}`,
  headers: { "x-api-key": `${secrets.ambeeWeatherApiKey}` }
})
// wait data returned by multiple APIs
const [openWeatherResponse, worldWeatherResponse, ambeeDataResponse] = await Promise.all([
    openWeatherRequest, 
    worldWeatherRequest,
    ambeeDataRequest])

The complete source code for this example can be found in the Parametric Insurance GitHub repository. This example is specific to parametric insurance, but the idea showcases how easy it is to consume data from multiple sources, perform off-chain computation such as aggregation on it, and then use Chainlink Functions’ OCR consensus protocol to further verify the result of the computation before delivering it on-chain.

Conclusion

From social media to cloud computing and insurance protocols, Chainlink Functions opens up a plethora of smart contract use cases by enabling developers to easily and securely access off-chain data and computation in a highly secure and trust-minimized way. 

To see more use cases, or to submit your own, we encourage you to visit ​​Use Chainlink Functions, a website that displays community-submitted Chainlink Functions use cases and examples. 

Bringing all the world’s data and APIs to decentralized applications will unleash an explosion of new use cases, similar to how Chainlink Price Feeds was the catalyst for the growth of DeFi. But the difference with Chainlink Functions is that Chainlink isn’t just a price data oracle—it becomes an any data oracle—enabling developers to access any data on-chain and unlocking exciting use cases across the entire Web3 space.

The post 5 Ways To Use Chainlink Functions in Your Decentralized Applications appeared first on Chainlink Blog.