Typical dapp project structure

I’ve spent some time thinking about this, and with as complicated as Cadence smart contract interactions can be, I believe that that contracts and transactions should be front and center in a typical repo/project, with application code and language-specific packages either being of equal importance, or of lesser importance in the project hierarchy. The contracts and transactions are the core of how the projects function, are agnostic to whatever language everyone is choosing, and are fairly critical for a developer to understand and know where to find when first encountering a project.

This reasoning informed how I structured the repos for the Flow smart contracts, specifically flow-ft, flow-nft, dapperlabs/nba-smart-contracts, and flow-core-contracts which I will use for my example. (my reasoning applies to all).

The important directories are:

contracts/
transactions/
lib/

contracts/: This should house the main contracts for the project. Easily accessible from the top level.
transactions/: This should house all of the transactions and scripts that interact with the contracts and act as a single source of truth. The contents of this directory can be organized any way the developer wants, but would probably have a directory for each contract, with subdirectories for different types of users of the contract, and scripts, as I did with the staking contract:

idTableStaking/
    admin/
    delegation/
    node/
    scripts/

I’m not sold on the transactions name, so if there is a better idea for the name, I’m all ears.

Last is the lib/ directory. This is where I believe language-specific packages and libraries should live. As you can imagine, developers coming from many different backgrounds will be wanting to learn how to interact with the contracts and repos, and exposing access to the contracts isn’t as simple as publishing an ABI that every language can import like in Solidity. Each language needs a set of packages for generating common transactions and scripts for the contracts.
In a mature contract repo, I would imagine something like this for lib/

lib/
    js/
    python/
    cplusplus/
    go/
    rust/

Then each language could have a contracts, templates, and test package, or whatever organization makes sense for the specific language or project. These packages would take the templates in contracts/ and transactions/ (source of truth top level directories) and package them for their specific language.
Right now, too many projects are just copying and pasting transactions into their application code, which works fine short term, but if there is ever a breaking change in Cadence or in the contract or transaction code, updating that copied and pasted code will be a huge hassle for projects. Like any other software projects, contracts should publish releases for their transaction and contract packages in the languages they want to support, then projects that depend on them can simply update their versions.

This is how I have it for my contract repos, but unfortunately I haven’t gotten enough help in building packages for languages besides Go. If anyone is interested in helping, I would appreciate it very much!

I could imagine that another top level directory could contain tests/ that are written in Cadence once the Cadence testing framework is ready.

Lastly, the application code needs a place. This is where I have the least experience from app development best practices, but I think a project could choose between two options.

  • Option 1: Use a different repo for the application: Since an application isn’t directly tied to a smart contract, an argument can be made that it should be in a different repo, and then import the packages from the main contract repo. This encourages the decentralized mindset, allows projects to protect proprietary application code, and de-clutters the main contract repo so that the focus can be on the most important part, the contracts.
  • Option 2: Use the same repo, but put the application in one or more top level directories. This would make testing and running the application easier, but could complicate the organization of the repository.

I lean towards option 1, but I might be a little biased because I am primarily a smart contract developer. I acknowledge that I am probably missing some pros and cons for the different options for everything I’ve talked about, so I am definitely open for feedback. Even if my structure is deemed the preferred, it still needs a lot of streamlining to be the best it can be.

I’m excited to hear what everyone thinks! I’d like to start coming to consensus soon so apps can start standardizing this. :slight_smile:

3 Likes