Building DevDaoStory: A Dynamic Visual Novel
Tags: web3, aws, renpy
Published: 2022-03-01 22:26:40

---
title: Building DevDaoStory: A Dynamic Visual Novel
published: true
description: 
tags: web3,aws,renpy
cover_image: https://devdaodevs.s3.amazonaws.com/assets/devdaologo.jpg
---

Composability is the general ability to reuse the components of a system to develop applications. For example Legos are the building blocks for many type of different structures that can be created.

In web3 world text based NFTs which are just list of words which are akin to Lego blocks that allow the community to build applications around these composable parts.

![Dev Dao NFT](https://storage.opensea.io/files/55635e01e96f55677e85f081f06e5ee6.svg)

This post will walk through how I built [DevDaoStory](https://www.devdaostory.com/) a dynamic visual novel based on the [DevDao NFT](https://opensea.io/collection/devs-for-revolution) your wallet holds. I will highlight how I built the following sections

* Front End using server side rendering
* Utilizing OpenSea API for Identity
* Generating a unique game with the Ren'py Engine


![DevDaoStory Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m10c95403jzdja2xzlmi.png)


## Front end

The user interface is a website built using HTML, [Bootstrap](https://getbootstrap.com/), JavaScript, [jinja2](https://palletsprojects.com/p/jinja/) and [JQuery](https://jquery.com/). I find it simplest to build using primitives instead of being locked into a framework like React for simple interfaces.

![DevDaoStory Home Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w2xequ8we9fnj2c6059o.png)

Since I'm leveraging jinja2 for server side rending, the application is hosted on [AWS Lambda](https://aws.amazon.com/lambda/) and [API Gateway](https://aws.amazon.com/api-gateway/) which provides one million events for free to use each month which provides a cost-effective way to host the application.

There are only two pages for this app, the home page and the select your Dev page. Here is a snippet of code for rendering Devs in the user's wallet.

```html
{% for dev in devs %}
<div class="col-md-5 offset-md-1">

    <div class="card mb-3" style="max-width: 540px;">
        <h3 class="card-title mt-3 mx-auto dark-text">Dev #{{dev['id']}}</h3>
        <div class="row g-0">
            <div class="col-md-5">
                <div class="card-body">

                    <ul class="list-group list-group-flush">
                        <li class="list-group-item">Os: {{dev['os']}}</li>
                        <li class="list-group-item">Text Editor: {{dev['textEditor']}}</li>
                        <li class="list-group-item">Language: {{dev['language']}}</li>
                        <li class="list-group-item">Vibe: {{dev['vibe']}}</li>
                        <li class="list-group-item">Location: {{dev['location']}}</li>
                        <li class="list-group-item">Mind: {{dev['mind']}}</li>
                        <li class="list-group-item">Industry: {{dev['industry']}}</li>
                        <li class="list-group-item">Clothing: {{dev['clothing']}}</li>
                        <li class="list-group-item">Background: {{dev['background']}}</li>

                    </ul>
                </div>
            </div>  
...
```

The next section will focus on how to build mechanisms to get identity details from a user.
 
## Identity

In order to know what Devs, a user has connect to their wallet I utilized the [ethers.js](https://docs.ethers.io/v5/) library to provide a way for users to sign a "Login" message with their MetaMask Wallet.

```javascript
async function connectWallet() {

    if (window.ethereum) {
        try {
            const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
            console.log("accounts")
            console.log(accounts)

            walletProvider = new ethers.providers.Web3Provider(window.ethereum)
            walletSigner = walletProvider.getSigner(); 
            const base_message = "Login to DevDaoStory"
            const signature = await walletSigner.signMessage(base_message)
            console.log(signature)

            login_info = {}
            login_info["address"] = accounts[0]
            login_info["signature"] = signature
            login_info["message"] = base_message

            var login_info_string = JSON.stringify(login_info)
```

This code provides users a way to allow the application to read their public key, so their Devs can be verified. 

![Login to DevDaoStory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hecgwk4w6p5b8n3mej8e.png)
 
Once the User accepts a POST request is sent to the server to gather the details of the user

```javascript

            $.ajax({
                type: "POST",
                url: "/web3_login",
                dataType: "json",
                data: login_info_string,
                contentType: "application/json",
                success: function(data) {
                    console.log("login finished")
                    console.log(data)
                    ...

                },
                error: function(xhr, textStatus, thrownError, data) {
                    alert("Error: " + thrownError);
                    $("body").css("opacity", "1");
                    $("#load_spinner").toggle();

                }
            })
```

The message can be verified leveraging the [web3](https://web3py.readthedocs.io/en/stable/) python module. 

```python
from web3 import Web3
from eth_account.messages import encode_defunct


def verify_signature(address, signature, message, w3):
    """
    Purpose:
        Verify user signed the message
    Args:
        address - address of user
        signature - the signature from user
        message - message to check
    Returns:
        boolean - True if verify, false if not
    """
    encoded_message = encode_defunct(text=message)
    pub_key = w3.eth.account.recover_message(encoded_message, signature=signature)

    logging.info(pub_key)

    # make sure same case
    if w3.toChecksumAddress(pub_key.lower()) == w3.toChecksumAddress(address.lower()):
        return True
    else:
        return False


...

@application.route("/web3_login", methods=["POST"])
def web3_login():
    """
    Purpose:
        login
    Args:
        N/A
    Returns:
        login object
    """

    jsonResp = {}
    data = request.data

    try:
        login_info = json.loads(data.decode("utf-8"))

        logging.info(login_info)

        address = w3.toChecksumAddress(str(login_info["address"]).lower())
        message = str(login_info["message"])
        signature = str(login_info["signature"])
        valid = web3_utils.verify_signature(address, signature, message, w3)
...

```

For more information on setting up your python web3 environment check out the [docs](https://web3py.readthedocs.io/en/stable/providers.html)

### OpenSea API

Once the message has been verified we can now use the [OpenSea API](https://docs.opensea.io/reference/api-overview) to get the Devs for the user.

```python

def get_opensea_assets(userAddress: str, contract: str):
    """
    Purpose:
        Get assets from opensea
    Args:s
        userAddress: user to get
        contract: contract to get
    Returns:
        json_obj - Opensea assets
    """

    API_KEY = os.environ["OPENSEA_API"]

    HEADERS = {"x-api-key": API_KEY, "Accept": "application/json"}

    url = "https://api.opensea.io/api/v1/assets"

    querystring = {
        "owner": userAddress,
        "order_direction": "desc",
        "offset": "0",
        "limit": "20",
        "asset_contract_address": contract,
    }

    json_obj = requests.get(url, params=querystring, headers=HEADERS).json()

    return json_obj["assets"]

assets = web3_utils.get_opensea_assets(address, DEVS_CONTRACT)

```

The API call will get all the information about the item, including the `tokenid`. By building upon the [pixel-avatars project](https://github.com/Developer-DAO/pixel-avatars) we can now get an image, and the data that defines your Dev, so it can be rendered on the front end.

![Dev 2096](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kzliyo87w1bf6lmoxhdz.png)
 
The next section will focus on how the actual game was built, now that all the setup is complete. 


## Ren'py Game Generation

[Ren'Py](https://www.renpy.org/) is an open-source visual novel engine used by thousands of creators from around the world. It provides a scripting language that allow users to build interactive stories with words, images, and sounds. 

For Ren'py to work for DevDaoStory I created a custom docker image with a modified version of Ren'py to compile only the web version of the game. I also leverage AWS Lambda to provide an endpoint to run the docker image with the necessary data to generate the story.

Once the game is compiled, it is uploaded to an [S3 bucket](https://aws.amazon.com/s3/) for users to play. Here is sample code highlight the process

```python
def main():
    """
    Purpose:
        Main driver
    Args:
        N/A
    Returns:
        N/A
    """

    test_dev = {
        "id": "2",
        "os": "Windows 95",
        "textEditor": "Emacs",
        "clothing": "Pink Hoodie",
        "language": "C",
        "industry": "Traveling Consultant",
        "location": "London",
        "mind": "Concrete",
        "vibe": "Optimist",
        "background": "Blue",
    }

    wallet = "0x00000000000"

    logging.info("Building Dynamic Game")
    gen_game_script(test_dev, wallet)
    build_renpy_game("/tmp/stories/devdaostory/")
    upload_to_s3_bucket("dev2")
    logging.info("Done and done")
```

Once the game is uploaded to s3, the UI renders an iframe with the game so users can begin the story with their Dev.

![gm fren](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nt1r1rp5hoy07uzhxv6g.png)
 

## Conclusion

DevDaoStory was fun to build, and provides a use case for building an application for a community leveraging NFTs. The game is also playable without a Dev in your wallet to ensure other builders can be inspired by the story. You can play here: https://www.devdaostory.com/

If you want to make your own story, or update the existing one, you can checkout the [github repo] (https://github.com/banjtheman/devdaostory_script).

*Follow Banjo on Twitter at [@banjtheman](https://twitter.com/banjtheman) and [@developer_dao](https://twitter.com/developer_dao) for more useful tips and tricks about building web3 applications.*