# Optimizing Decentralized Applications

By [jackofcrypto](https://paragraph.com/@jackofcrypto) · 2022-01-25

---

Learn how to optimize a dApp by employing structs, events, and filters in Solidity and Web3.py as they relate to the ERC-721 standard. Also, learn about decentralized storage techniques.

By the end of this post, readers will be able to:

*   Explain what Solidity structs and events are and how they can be used to enhance a dApp.
    
*   Use filters in Web3.py to react to events from smart contracts.
    
*   Use the InterPlanetary File System (IPFS) to store immutable data off-chain in order to both save gas and ensure the decentralized nature of the dApp.
    

After defining terminology, we’ll build a smart contract that allows users to add appraisal values and comments to a tokenized piece of art. The focus is on using events and filters to build out the contract.

Structs and Events in Solidity
------------------------------

The contract needs a way to represent the artwork information, like the name of the artwork, the name of the artist, and the current appraisal value.

While it’s possible to create separate variables for each piece of information, we’ll instead group them together into a `struct`, which is short for “structure.” A `struct` is similar to a Python data class. Ethereum developers frequently use the `struct` to organize related pieces of data.

To define a `struct`, we name and define a unique data type, which consists of a structured collection of data. Inside the `struct`, we define the name and the type of each variable that belongs to the `struct`. In Solidity, the variables that exist inside a `struct` are called **members** (or, sometimes, **fields**).

For the ArtRegistry contract, we will use the keyword `struct` followed by the name Artwork. The struct will contain three pieces of data:

1.  A string named “name” that contains the name of the artwork.
    
2.  A string named “artist” that contains the name of the artist.
    
3.  An unsigned integer named “appraisalValue” that contains the currently appraised value of the artwork.
    

    struct Artwork {
        string name;
        string artist;
        uint256 appraisalValue;
    }
    

Organizing related pieces of data makes it easier to use them with other data types, such as mappings.

With Ethereum and many other blockchains, storing data in the contract and on-chain is expensive. To counter this issue, a new mechanism in Solidity has been created: the event. An **event** in Solidity is an inexpensive way to record data as a log entry on the blockchain. Historical changes in values can be documented without storing them directly in the contract.

Events work in a similar way as functions. First, an event is defined. In this case, an event named `Appraisal` is defined. The `Appraisal` event has three parameters: `token_id` (of type `uint256`) `appraisalValue` (of type `uint256`), `reportURI` (of type `string`).

The event can be accessed, or **fired**, from any function inside the contract. When the event is fired, the arguments that get passed to the event will be recorded as a log entry on the blockchain. The `Appraisal` event will be used to log new artwork appraisals to the blockchain.

Now that the code defines a `struct` that stores the metadata for the `Artwork`, and an `event` that catalogs the new `Appraisal` value, it’s time to focus on the code associated with registering a piece of artwork.

The complete code for the `ArtRegistry` contract is as follows:

    pragma solidity ^0.5.0;
    
    import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v2.5.0/contracts/token/ERC721/ERC721Full.sol";
    
    contract ArtRegistry is ERC721Full {
        constructor() public ERC721Full("ArtToken", "ART") {}
    
        struct Artwork {
            string name;
            string artist;
            uint256 appraisalValue;
        }
    
        mapping(uint256 => Artwork) public artCollection;
    
        event Appraisal(uint256 token_id, uint256 appraisalValue, string reportURI);
    
        function registerArtwork(
            address owner,
            string memory name,
            string memory artist,
            uint256 initialAppraisalValue,
            string memory tokenURI
        ) public returns (uint256) {
            uint256 tokenId = totalSupply();
    
            _mint(owner, tokenId);
            _setTokenURI(tokenId, tokenURI);
    
            artCollection[tokenId] = Artwork(name, artist, initialAppraisalValue);
    
            return tokenId;
        }
    
        function newAppraisal(
            uint256 tokenId,
            uint256 newAppraisalValue,
            string memory reportURI
        ) public returns (uint256) {
            artCollection[tokenId].appraisalValue = newAppraisalValue;
    
            emit Appraisal(tokenId, newAppraisalValue, reportURI);
    
            return artCollection[tokenId].appraisalValue;
        }
    }
    

Web3.py Filters
---------------

In this section, we’ll learn how to apply Web3.py filters to the front end of a decentralized application.

Since our contract can store data in the event log, we need to build a way to access that historical data. The ERC-721 standard provides the tools needed for managing these events.

Solidity automatically inherits all the code for managing events from the `ERC721Full` contract. To access the event data, a filter for the event needs to be created in the front end of a dApp.

Why a filter? The reason is that more than one type of event might get logged for the smart contract. We want to be able to choose the event type that we desire to access. Web3.py offers the `createFilter` function that can be used to create the necessary filter.

For the `ArtRegistry` contract, the dApp will require using only the `fromBlock` and `argument_filters` parameters of the Web3.py `createFilter` function.

Below is the complete code for the Streamlit dApp front end:

    import os
    import json
    from web3 import Web3
    from pathlib import Path
    from dotenv import load_dotenv
    import streamlit as st
    
    load_dotenv()
    
    # Define and connect a new Web3 provider
    w3 = Web3(Web3.HTTPProvider(os.getenv("WEB3_PROVIDER_URI")))
    
    ###
    # Load_Contract Function
    ###
    
    @st.cache(allow_output_mutation=True)
    def load_contract():
    
        # Load the contract ABI
        with open(Path('./contracts/compiled/artregistry_abi.json')) as f:
            contract_abi = json.load(f)
    
        # Set the contract address (this is the address of the deployed contract)
        contract_address = os.getenv("SMART_CONTRACT_ADDRESS")
    
        # Get the contract
        contract = w3.eth.contract(
            address=contract_address,
            abi=contract_abi
        )
    
        return contract
    
    
    # Load the contract
    contract = load_contract()
    
    
    st.title("Art Registry Appraisal System")
    st.write("Choose an account to get started")
    accounts = w3.eth.accounts
    address = st.selectbox("Select Account", options=accounts)
    st.markdown("---")
    
    ###
    # Register New Artwork
    ###
    st.markdown("## Register new Artwork")
    
    st.markdown("---")
    
    
    ###
    # Appraise Art
    ###
    st.markdown("## Appraise Artwork")
    tokens = contract.functions.totalSupply().call()
    token_id = st.selectbox("Choose an Art Token ID", list(range(tokens)))
    new_appraisal_value = st.text_input("Enter the new appraisal amount")
    report_uri = st.text_area("Enter notes about the appraisal")
    # if st.button("Appraise Artwork"):
        # Use the token_id and the report_uri to record the appraisal
    
    st.markdown("---")
    
    
    ###
    # Get Appraisals
    ###
    st.markdown("## Get the appraisal report history")
    art_token_id = st.number_input("Artwork ID", value=0, step=1)
    if st.button("Get Appraisal Reports"):
        appraisal_filter = contract.events.Appraisal.createFilter(
            fromBlock=0,
            argument_filters={"tokenId": art_token_id}
        )
        appraisals = appraisal_filter.get_all_entries()
    
        for appraisal in appraisals:
            report_dictionary = dict(appraisal)
            print(report_dictionary)
            print(report_dictionary["args"])
    

We have just built another NFT that is compliant with the ERC-721 standard, complete with on-chain, custom members and several linked token URIs.

We used Solidity events and URIs that allow you to connect both data and a front end to our Solidity smart contract from outside the blockchain.

We created a sophisticated dApp for this contract, but it’s not quite finished.

Decentralized Storage
---------------------

In this section, we’ll introduce **IPFS** technology and how it can be used to store immutable, hash-based, content-routed data. IPFS can store large datasets with the same level of integrity as on-chain data.

**IPFS** stands for **InterPlanetary File System**. It’s a protocol, a network, and a file system. But what exactly does this mean and how does it all fit together?

For two users to exchange data with one another across the internet, they need a common set of rules for how the information is sent between them; this is a communication protocol.

**Communication protocols** are usually organized as a **protocol suite**. For example, the internet protocol suite is widely used today, and of the protocols that make up that suite, **HyperText Transfer Protocol**, or **HTTP**, is the foundation for communication.

Another important piece is known as the system's **architecture**, or how the actual computers within the network can communicate with one another. Traditionally, this is done in a **client-server model**. IPFS uses a **peer-to-peer network model**.

Instead of using centralized storage, like a database, IPFS distributes each file across multiple nodes in its own network. That is, it breaks down the file into pieces of data and then distributes the pieces across multiple nodes. It does this by using a custom data structure and rules for storing and retrieving the data pieces.

Participants in the IPFS network can thus share their files. And, smart contracts and dApps can thus store and retrieve their files directly from the nodes that have the data pieces. This means that they store and access their data by using a decentralized technology—without the expense of storing that data on the chain.

### Pinata IPFS

One of the most popular ways for dApps to access IPFS services is through the [Pinata](https://pinata.cloud/) IPFS pinning service.

Pinata provides a web service, called a **gateway**, that allows access to files through a web browser without installing IPFS on a computer. This gateway acts as a bridge between any single computer and the IPFS network.

**Pinning** is the act of storing a file on the decentralized IPFS network. Files can be pinned through either the Pinata webpage or the Pinata API.

With Pinata, files can be hosted on the IPFS network and then referenced by a hash. The hash from the IPFS CID column in Pinata can be used in our dApps and smart contracts to refer to the pinned file. Storing this hash in a smart contract is more memory efficient (that is, it uses less space) than storing the entire file. Less required memory means smaller transaction fees associated with storing information on-chain.

![Pinata CID](https://storage.googleapis.com/papyrus_images/d1277dfa94d636890103eb5436b167bec4485f31ade4606cd3c15d1880e40150.png)

Pinata CID

Rather than using the Pinata webpage to upload files, we’ll reconfigure our dApp so that it pins the artwork files and appraisal reports through the Pinata API. The API will also return the hashes for the pinned files. So, the dApp will be able to permanently attach them to the tokens and events in the blockchain.

To ease pinning files and reports from the dApp, we’ll need to build a new Python file that has helper functions for interacting with the Pinata API. A **helper function** is a regular function that codes some of the complexity that using a service, like the Pinata API, requires. All the code for formatting and sending the Pinata API request will be added to helper functions. These helper functions, with the required data, can be called anytime that we need to use this code.

The final version of the `pinata.py` file appears as follows:

    import os
    import json
    import requests
    from dotenv import load_dotenv
    load_dotenv()
    
    
    file_headers = {
        "pinata_api_key": os.getenv("PINATA_API_KEY"),
        "pinata_secret_api_key": os.getenv("PINATA_SECRET_API_KEY"),
    }
    
    json_headers = {
        "Content-Type": "application/json",
        "pinata_api_key": os.getenv("PINATA_API_KEY"),
        "pinata_secret_api_key": os.getenv("PINATA_SECRET_API_KEY"),
    }
    
    def convert_data_to_json(content):
        data = {"pinataOptions": {"cidVersion": 1}, "pinataContent": content}
        return json.dumps(data)
    
    def pin_file_to_ipfs(data):
        r = requests.post(
            "https://api.pinata.cloud/pinning/pinFileToIPFS",
            files={'file': data},
            headers=file_headers
        )
        print(r.json())
        ipfs_hash = r.json()["IpfsHash"]
        return ipfs_hash
    
    def pin_json_to_ipfs(json):
        r = requests.post(
            "https://api.pinata.cloud/pinning/pinJSONToIPFS",
            data=json,
            headers=json_headers
        )
        print(r.json())
        ipfs_hash = r.json()["IpfsHash"]
        return ipfs_hash
    

Using the helper functions, we can now pin an artwork file to IPFS, build a JSON metadata file for the artwork, convert the metadata to the JSON string that Pinata requires, and then pin the JSON string to IPFS.

Until next time, here’s a twitter thread summary of this post:

[https://twitter.com/jackofcrypto/status/1486050652761440258?s=20](https://twitter.com/jackofcrypto/status/1486050652761440258?s=20)

---

*Originally published on [jackofcrypto](https://paragraph.com/@jackofcrypto/optimizing-decentralized-applications)*
