Typical dapp project structure

There’s been some discussion about how to best structure the codebase of a Flow application. Often this means finding a home for the following:

  • Cadence smart contracts
  • Cadence transactions and scripts
  • Frontend web app (JS)
  • Backend app (optional, any language)

While we don’t want to prescribe a rigid structure for dapps, we also want to make sure that the Flow tooling and examples demonstrate common patterns that promote composability and ease-of-use for developers.

@flowjosh previously defined a pattern for Cadence-only repositories, which you can see on display here: GitHub - onflow/flow-core-contracts: Cadence smart contracts that define core functionality of the Flow protocol

Kitty Items is an example of a fully-fledged dapp that includes both Cadence and JavaScript code in the same repository. While a single repo may not be scalable for larger development teams (e.g. teams with separate smart-contract and frontend engineers), it works quite well for small projects.

My opinion is that our tools should define patterns that work for both mono and multi-repo applications, and I’d love to explore what that looks like.

2 Likes

Excited to check out Kitty Items!

Would love to get your thoughts on DappStarter. We try to do exactly what you’re describing here ‒ provide code with best practices and common patterns to make it easier to build on Flow :slight_smile:

3 Likes

Thanks for sharing Chase! I haven’t looked at DappStarter recently – I’ll spin up a new project with it and post my thoughts here

2 Likes

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:

2 Likes

That’d be awesome, I appreciate that!

I’m particularly interested in hearing your thoughts in the context of this conversation. We’re always looking for feedback and thoughts on how we can make it easier for devs to build on Flow (and a lot of that is based in this idea of demonstrating common patterns and best practices that devs can copy / learn from). So we’re open to exploring anything and everything you think would simplify the development experience!

2 Likes

@chase @pete @qvvg @JeffreyDoyle @robmyers @hlee @bjartek @ebreuers @MaxStarka @daniel

What do you think about my proposal above? I’ve been thinking about it a lot.

1 Like

I like it!

Another issue that I think is worth looking at here is the accounts for the contracts. Right bow the tooling in vscode has to have hard coded address imto the contracts to work properly. However that scales poorly to a model where rhe accounts are differnet on emulator/testnet/mainnet.

In the examples st the start of the thread placeholders that are replaced in are used. However that does nor work with the tooling.

So can the vscode extension get support for a lookup of an account from an alias or something?

1 Like

Hey all, I added an issue to the VSCode extension repo re this:

Feel free to ‘flesh it out’ with the details.

1 Like

@flowjosh I’ve been thinking about this more while building the CLI deployment tool and I think your proposed structure makes a lot of sense.

IMO it feels a bit cleaner to have scripts pulled out into their own directory:

/contracts
/transactions
/scripts

Regarding multi- vs mono-repo, I think that’s up to the developer! This structure supports both, and I think our tooling should, too.

I’m also realizing that we shouldn’t be too prescriptive when it comes to file structure. Developers will have their own preferences regarding code organization, and our tools should be flexible enough to support any project structure. However, I still think there’s value in defining a canonical format for us to follow, especially in the early days when so many people are learning.

1 Like

Hey @chase! I created a new DappStarter project and took some notes:

  • I think the cadence directory here can be flattened so that transactions and scripts are at the same level as contracts: diligent-tusoupum/packages/dapplib at master · DappStarter/diligent-tusoupum · GitHub
  • The dapplib/src folder is really cool! I had no idea you were transpiling the Cadence code like that. We’ve been working on something a bit similar lately – it’d be great to collaborate
  • Out of curiosity, did you consider making dapplib/src a reusable library rather than embedding it directly into each project?
  • I was a bit confused when I saw the src directory. At first I assumed that it held the Cadence source files, but it looks like it contains JS files that aren’t supposed to be edited. Is that correct?
  • I think dapplib itself could even just be called cadence for simplicity. Does dapplib have a special meaning in the context of DappStarter?

This is awesome stuff, looking forward to exploring it more!

2 Likes