So you want to write and deploy a smart contract, huh? 

Go for it and do it as a professional, use the best practices common for the best software out there. There is no need to reinvent the wheel and change processes that work.  Sometimes we just need to adjust the tooling – and that’s what this article is about.

Let’s set up this environment for a sample project from scratch.

Setting up the repository

Starting with two repositories is considered to be a good practice. One is public for the main activity, and the second is private and also holds the codebase because some activity on the project should not be discussed publicly, for example, live vulnerabilities/security patches that could affect users. This approach helps us to avoid hackers who could exploit a disclosed bug in pull requests before it is fixed for the released code.

To avoid complicating this tutorial, we will stick only to a public repository (on GitHub).

Let’s start going step by step through repository settings and consider changing crucial parameters, such as branch protection rules or access controls for the team.

Initialize the project

Clone your empty repository and enter it. In the repository folder, initialize the template for your project with:

This command will prepare all needed folders, including the .gitignore file.

Write the code

Now, we are ready to write the code. Let’s start with the following one generated by OpenZeppelin Solidity Wizard with a few changes (bugs).

This code includes some packages, so we must add them with npm (don’t forget to add node_modules to .gitignore).

When we have installed the packages, we can test the compilation.

In the current state, we have a working project with the following structure.

Write tests

Firstly, we need to initialize pytypes against the source code:

Then we will write some basic test:

And we can run the tests with:

Write deployment scripts

We will be deploying our contract to Holesky testnet. Writing deployment scripts is very similar to writing tests.

Before deploying, we need to set the private key in .env file and retrieve it using dotenv library, and then we can test deployment.

Everything works as expected: tests, deployment, and compilation, so we can proceed to writing the pipeline to have these steps automated.

Write pipeline

We will start with creating a folder for our pipeline:

Then, in this folder, we will create the following file: pipeline.yml.

We have two jobs. One is for pull requests to master testing and analyzing the new code that will be merged. The second job is for deployment, and it’s triggered only on merge/push to master. We are using Wake Setup action to set up the environment for tests and deployment. Then, we are using specialized Wake Detect action for code scanning for vulnerabilities. In the deployment part, we must pass a private key with dotenv via GitHub secrets. That secret has to be set in repository settings.

Once we set a secret, we are good to go.


Everything is set. We can check out to another branch and push our codebase. After push, nothing is triggered because we don’t have a pipeline defined for this behavior. That’s expected.

After creating a pull request, we can see the pipeline is triggered (skipping deploy).

Apparently, since we didn’t run static analysis before, we can see the pipeline detected bugs and attached them to our pull request.

So, with our repository policies, we may not be able to merge to master until it is fully resolved.

We go back to the code to fix it. Remove unused import and replace tx.origin with msg.sender.

Now we can see we are ready to deploy.

Let’s merge it!

We successfully deployed the contract via GitHub actions.

Final remarks

This tutorial explained how to use GitHub actions to enhance your CI/CD processes. 

These actions will help you make your project more durable and efficient. The provided example is purely informational, and now it’s on you to find the best match for your project.