Writing an NFT Contract with inbuilt Push Notifications Support ~ Push Protocol

SiddeshSiddesh
6 min read

Smart Contracts typically operate independently, meaning if you deploy an NFT contract and users mint NFTs, you might not have a straightforward way to monitor these actions. While you can create custom event listeners or services to send email notifications to stakeholders, it can be a complex and resource-intensive process.

However, in this guide, I'll demonstrate how, in just 5 minutes, you can effortlessly enable push notifications within your smart contracts for any function call, utilizing the Push Protocol Notification Service, without the need for additional server setups or unnecessary complexities.

Goal

To write an ERC-721 NFT smart contract and give it the ability to trigger Notifications directly to wallet addresses using Push Protocol's Notification Service.

TLDR; The NFT Contract Owner will receive a Notification to his wallet whenever someone mints his NFT.

Prerequisites

Before we start writing our NFT contract, there are a few things we need to setup,

  1. Metamask Wallet or any other Ethereum Wallet.

  2. Some Goerli ETH tokens since we will be deploying our NFT in Goerli Testnet. You can acquire gETH from this PoW Faucet or Push Protocol Discord.

    %[https://youtu.be/OtKLAKYjWh0]

  3. So now we have acquired the gETH tokens, we should create a Channel on Push Staging network to use for sending notifications in Push Network from your smart contract.

I will be using the ERC-721 NFT contract code from OpenZeppelin Library which you can copy from Openzeppelin website or Just copy from the code below.

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts@5.0.0/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@5.0.0/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@5.0.0/access/Ownable.sol";

contract MyTokenb is ERC721, ERC721URIStorage, Ownable {
    uint256 private _nextTokenId;

    constructor()
        ERC721("MyToken", "MTK")
        Ownable(msg.sender)
    {}

    function safeMint() public {
        uint256 tokenId = _nextTokenId++;
        _safeMint(msg.sender, tokenId);
        _setTokenURI(tokenId, "https://bafybeiezmpzzg2wfapt5cjaxttfsgmi3q5hi5oypqxwfbh6fikclrwgtdu.ipfs.w3s.link/metadata.json");
    }
    // The following functions are overrides required by Solidity.

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

 }

Boom, We already have a fully functional NFT contract with metadata defined, here I've passed an IPFS file in Line number 19 where you can pass your own NFT Metadata if you wish.

Now, All we need to do is add a block of code within the safeMint function to trigger Notifications whenever someone mints your NFT.

    IPUSHCommInterface(EPNS_COMM_ADDRESS).sendNotification(
            CHANNEL_ADDRESS, // from channel
            TO_ADDRESS, // to recipient, put address(this) in case you want Broadcast or Subset. For Targetted put the address to which you want to send
            bytes(
                string(
                    // We are passing identity here: https://docs.epns.io/developers/developer-guides/sending-notifications/advanced/notification-payload-types/identity/payload-identity-implementations
                    abi.encodePacked(
                        "0", // this is notification identity: https://docs.epns.io/developers/developer-guides/sending-notifications/advanced/notification-payload-types/identity/payload-identity-implementations
                        "+", // segregator
                        "3", // this is payload type: https://docs.epns.io/developers/developer-guides/sending-notifications/advanced/notification-payload-types/payload (1, 3 or 4) = (Broadcast, targetted or subset)
                        "+", // segregator
                        "Woohoo, We're making moooneiz!", // this is notificaiton title
                        "+", // segregator       
                        addressToString(msg.sender), // notification body
                        "just minted an NFT"
                    )
                )
            )
        );

In the above code snippet, You will need to replace EPNS_COMM_ADDRESS, CHANNEL_ADDRESS, TO_ADDRESS with corresponding values.

  1. EPNS_COMM_ADDRESS - 0xb3971BCef2D791bc4027BbfedFb47319A4AAaaAa

    Refer Push Contract Addresses Page to find EPNS_COMM_ADDRESS for the chain that you're using.

  2. CHANNEL_ADDRESS - Wallet address that you used for creating a channel in Push Staging Site.

  3. TO_ADDRESS - Recipient Address(es)

Apart from this, You can edit the title and body text of your notification.

The above code is all you need to trigger the notification, paste this block of code inside safeMint function. The above code uses one helper function called addressToString and also uses IPUSHCommInterface interface which we should define in our contract.

addressToString

Helper function to convert an Ethereum address to a string.

      // Helper function to convert address to string
    function addressToString(
        address _address
    ) internal pure returns (string memory) {
        bytes32 _bytes = bytes32(uint256(uint160(_address)));
        bytes memory HEX = "0123456789abcdef";
        bytes memory _string = new bytes(42);
        _string[0] = "0";
        _string[1] = "x";
        for (uint i = 0; i < 20; i++) {
            _string[2 + i * 2] = HEX[uint8(_bytes[i + 12] >> 4)];
            _string[3 + i * 2] = HEX[uint8(_bytes[i + 12] & 0x0f)];
        }
        return string(_string);
    }

IPUSHCommInterface

// PUSH Comm Contract Interface
interface IPUSHCommInterface {
    function sendNotification(
        address _channel,
        address _recipient,
        bytes calldata _identity
    ) external;
}

We now paste this interface code above the contract keyword in our solidity file.

Hooray! This is what my final contract code looks like👀

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts@5.0.0/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@5.0.0/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@5.0.0/access/Ownable.sol";
// PUSH Comm Contract Interface
interface IPUSHCommInterface {
    function sendNotification(
        address _channel,
        address _recipient,
        bytes calldata _identity
    ) external;
}

contract MyTokenb is ERC721, ERC721URIStorage, Ownable {
    uint256 private _nextTokenId;

    constructor()
        ERC721("MyToken", "MTK")
        Ownable(msg.sender)
    {}

    function safeMint() public {
        uint256 tokenId = _nextTokenId++;
        _safeMint(msg.sender, tokenId);
        _setTokenURI(tokenId, "https://bafybeiezmpzzg2wfapt5cjaxttfsgmi3q5hi5oypqxwfbh6fikclrwgtdu.ipfs.w3s.link/metadata.json");
          IPUSHCommInterface(0xb3971BCef2D791bc4027BbfedFb47319A4AAaaAa).sendNotification(
            0xb3fE9B4b68Eff6aB07D349296a9a80a0F02B55f1, // from channel
            0xb3fE9B4b68Eff6aB07D349296a9a80a0F02B55f1, // to recipient, put address(this) in case you want Broadcast or Subset. For Targetted put the address to which you want to send
            bytes(
                string(
                    // We are passing identity here: https://docs.epns.io/developers/developer-guides/sending-notifications/advanced/notification-payload-types/identity/payload-identity-implementations
                    abi.encodePacked(
                        "0", // this is notification identity: https://docs.epns.io/developers/developer-guides/sending-notifications/advanced/notification-payload-types/identity/payload-identity-implementations
                        "+", // segregator
                        "3", // this is payload type: https://docs.epns.io/developers/developer-guides/sending-notifications/advanced/notification-payload-types/payload (1, 3 or 4) = (Broadcast, targetted or subset)
                        "+", // segregator
                        "Woohoo, We're making moooneiz!", // this is notificaiton title
                        "+", // segregator       
                        addressToString(msg.sender), // notification body
                        "just minted an NFT"
                    )
                )
            )
        );
    }

    // The following functions are overrides required by Solidity.

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
        // Helper function to convert address to string
    function addressToString(
        address _address
    ) internal pure returns (string memory) {
        bytes32 _bytes = bytes32(uint256(uint160(_address)));
        bytes memory HEX = "0123456789abcdef";
        bytes memory _string = new bytes(42);
        _string[0] = "0";
        _string[1] = "x";
        for (uint i = 0; i < 20; i++) {
            _string[2 + i * 2] = HEX[uint8(_bytes[i + 12] >> 4)];
            _string[3 + i * 2] = HEX[uint8(_bytes[i + 12] & 0x0f)];
        }
        return string(_string);
    }
}

Now it's time to Deployyy our contract and test out our notifications, Follow this guide if you are not aware of how to deploy your contract to goerli network from Remix IDE

Now that We have deployed our Contract to Goerli Network before our contract can start sending notifications, we should add our contract as a delegate in our Push Channel.

Adding a delegate

Navigate to Push Channel Dashboard and click add a delegate, paste your NFT contract address. Then Sign the transaction on your Metamask/Wallet.

Testing

The best part about Remix, is you can test by calling function in your contract right from that interface.

Let's call safeMint function and mint our cool NFT......

Woooho, I got a notification from my contract as soon as someone called safeMint function.

Woooohoo, Now my boss will get an notification when someone mints his NFT, How cool is that?

Conclusion

While this is a sample use case of how quickly you can add push notifications to any contract, you can come up with any creative use cases and add Push Notification in a few lines powered by Push Protocol.

0
Subscribe to my newsletter

Read articles from Siddesh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Siddesh
Siddesh