A Practical Introduction to Developing Web3 Apps Using Solidity and React - Part 1
Tags: blockchain, web3, solidity, smartcontract
Published: 2022-03-16 21:34:47

---
title: A Practical Introduction to Developing Web3 Apps Using Solidity and React - Part 1
published: true
description: Learn how to create the smart contract that can be deployed on the Ethereum blockchain using Solidity and following a TDD methodology. 
tags: blockchain, web3, solidity, smartcontract
canonical_url: https://www.partnerly.co/learn
//cover_image: https://direct_url_to_image.jpg
---

## Developing a Smart Contract

Imagine building a project with someone. You are planning on monetizing this project by accepting payments in cryptocurrency. You would want to distribute the earnings fairly in between yourselves. [Partnerly.co](https://www.partnerly.co/) helps you do this. It is an app that helps you create a smart contract on the blockchain that can distribute the collected payments between two (or more) parties in a predetermined split ratio.

I will show you how I built **Partnerly** from scratch. In this part of this walkthrough, you will learn how to create the smart contract that can be deployed on the **Ethereum blockchain** using **Solidity**.

The code for the smart contract and the front end application is available at [https://github.com/hibernationTheory/partnerly](https://github.com/hibernationTheory/partnerly).

## Disclaimer

I am not a Blockchain developer. In fact, I am just learning this stuff myself. However, I am a software developer and an educator who taught programming from scratch to thousands of students. I can't claim that you will become a Blockchain development expert by just following this walkthrough, but I am pretty confident that you will learn practical skills that will make this a valuable stepping stone in your journey.

Let's get started!

## Getting Started

This walkthrough assumes you have a working knowledge of **JavaScript**, **Node.js** and **React**. We will be using the **Solidity** language to develop the smart contract, which is relatively similar to **JavaScript**. The user interface we will be building for this contract will use **React**, **Next.js** and **Tailwind** libraries.

I use Visual Studio Code (VS Code) for coding. VS Code has an extension that makes working with Solidity (`sol`) files easy, so I recommend you [download](https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity) that for a better development experience.

If you are yet to install Node.js, make sure to install it using [nvm](https://github.com/nvm-sh/nvm). **nvm** makes it easy to manage multiple Node versions on the same system.

We will first need to install a library called [Hardhat](https://hardhat.org/). **Hardhat** is a local development tool that makes developing, testing and deploying smart contracts easy.

Let's create a folder for our smart contract and initialize **npm** to install packages from the node registry.

```bash
mkdir partnerly-contract
cd partnerly-contract
npm init -y
npm install --save-dev hardhat
npx hardhat
```

When you run `npx hardhat` you will be presented with some options. Select `> Create a basic sample project` and say `yes` to all the questions. This will set up a sample project with Hardhat, further dependencies will get installed.

## Initializing the Contract

We will be developing our contract inside the `contracts` folder. Let's delete the sample contract in there and create a new file called `Partnership.sol`. Below is what the skeleton of our contract looks like. I will explain every line that is in there.

```sol
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;

import "hardhat/console.sol";

contract Partnership {
    constructor() {
        console.log("Contract is deployed");
    }
}
```

The first line inside the contract is a comment that defines the licence for this contract. We will just leave it to be **unlicensed**.

```sol
//SPDX-License-Identifier: Unlicense
```

The second line is for the Solidity version you want for this contract. We will be using version `0.8.4`.

```bash
pragma solidity ^0.8.4;
```

Hardhat created a config file called `hardhat.config.js` when we initiated this sample project. You should set the version declaration in there to match the contract version.

```js
module.exports = {
  solidity: "0.8.4",
};
```

Next, we are importing another contract into our contract called `hardhat/console.sol`. This is similar to how you would import libraries when coding in Node.js to enhance the functionality of your programs. `console.sol` gives our contract the ability to call the `console.log` function, which will make debugging the contract easier as we are developing it. We will remove this function before deploying the contract to a live network since it costs some **gas** to execute the `console.log` statements on the Ethereum network. You can think of **gas** as the price you need to pay for things to get executed on the Ethereum blockchain.

Next, we define a contract called `Partnership` that console.log's the message `Contract is deployed` when it is deployed.

```sol
contract Partnership {
    constructor() {
        console.log("Contract is deployed");
    }
}
```

Notice the definition of a contract is pretty similar to the definition of a **class** in JavaScript. Just like a class, a contract has a **constructor** function that gets executed when the contract is initialized (**deployed** in our case). It potentially can have other attributes and methods that will help us define the contract's data and behaviour. But for now, let's just `console.log` a string to indicate that the contract is deployed.

## Running the Contract

Using Hardhat, we can define a script that will simulate the deployment of a contract. Let's create a script called `run.js` inside the `scripts` folder.

```js
const hre = require("hardhat");

async function main() {
  await hre.run("compile");

  const Contract = await hre.ethers.getContractFactory("Partnership");
  const contract = await Contract.deploy();

  await contract.deployed();
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
```

We can execute this script by running this statement inside the terminal:

```bash
 npx hardhat run scripts/run.js
```

We should now see our `console.log` message get executed. The contract gets deployed successfully.

This `run.js` file has a bunch of boilerplate that we will skim over. You won't need to adjust some of the statements in there.

```sol
const hre = require("hardhat");
```

The above statement imports the `hardhat` library into the `run` script. You might see some other examples where this line is omitted. That is because this dependency gets automatically injected when you call this script using `npx hardhat`.

It is the same with the first statement inside the `main` function. You might see it getting omitted in some examples. I have included in my examples for the sake of completeness.

```sol
async function main() {
  await hre.run("compiler");
}
```

The following statement executes the async `main` function and exits from the script once the execution is completed. It additionally logs errors if there are any.

```sol
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
```

What we need to care about are these three lines of code.

```sol
// We get the contract to deploy
const Contract = await hre.ethers.getContractFactory("Partnership");
const contract = await Contract.deploy();

await contract.deployed();
```

We first compile the smart contract with the given name and then deploy the contract using the `deploy` method. Then, we ensure that the contract is deployed using the `deployed` method. Note that all these operations are **asynchronous**, so we use the **async-await** pattern.

We can continue developing the contract now that we have a script to execute it.

## Terms of the Contract

Let's think of what we would like to do with this contract.

- We want to create a contract that requires two or more **wallet addresses** that can accept payments.
- The contract will also require split ratios to be defined for each given address. 1 to 1 split ratio means an equal split. 2 to 1 split means that the first address will get double the amount of the second one.
- The contract will store these given addresses and split ratios.
- The contract should be able to receive and store payments.
- When a withdrawal is requested, the stored amount should be transferred to the contract's parties in the designated split ratio.

At this point, I should mention that I am not a lawyer. In fact, I am just a simple programmer, and as a programmer, I write bugs all the time. This poses some problems when it comes to developing smart contracts. It is impossible to ensure that a contract (or a program, for that matter) doesn't have a bug, and once a contract is deployed, you can't change it. The implication is that it is possible to deploy a buggy program that you can't alter after the fact.

The code for the smart contract is available on the blockchain for anyone to see. This transparency is great since you can verify that the contract is working as intended by reading the code. However, it also means that any actor could be dissecting the contract to take advantage of its vulnerabilities.

Have a look at the above terms. Does it look airtight? When I started to code this, it looked pretty good to me until I realized the users could pass a split ratio equivalent to `0`, which would cause the contract to fail (You can't divide a number with `0`). This is only a single oversight I could catch, and I can't guarantee there aren't others!

So let's ensure we update our requirements to include that the split ratios should be bigger than 0.

- The contract will also need split ratios to be defined for each given address. Each split ratio should be bigger than 0.

## Passing and Storing Values inside the Smart Contract

You can store data (state) inside the contract by defining variables inside the contract body. Any value passed into these variables will be stored inside the contract **permanently**.

Let's define a contract that has some state variables.

```sol
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;

import "hardhat/console.sol";

contract Partnership {
    string private deploymentMessage = "Contract is deployed";
    uint256 private partnerAmount = 2;
    address[] public addresses;

    constructor(address[] memory _addresses) {
        require(
            _addresses.length == partnerAmount,
            "You can't have more than 2 partners"
        );

        addresses = _addresses;

        console.log(deploymentMessage);
    }
}
```

Solidity is a **typed** language meaning that we need to define our variable types.

We can also define the **visibility** and **accessibility** of these variables by using the keywords `public` and `private`. A **private** variable is meant to be accessible only inside the contract. In contrast, Solidity creates a **getter** function for a **public** variable, meaning that you could read the value of this variable by calling an automagically generated method of the same name (`partnerAmount()` and `addresses()` in this case).

We have defined three variables at the top of this contract. A private `string` variable called `deploymentMessage`, a public `uint256` (an unsigned 256 bit integer) variable called `partnerAmount`, and a public array of `address` type called `addresses`. The `addresses` variable doesn't have a value yet. It is going to be populated by the caller of this contract.

We have updated the `constructor` function to accept `_addresses` as an argument. It is conventional to prefix function arguments with an underscore (`_`) to differentiate them from the state variables.

We use a keyword called `memory` before the `_addresses` argument. The `memory` keyword instructs Solidity that this data doesn't need to be part of the permanent storage. The function arguments are always declared with the `memory` keyword.

Constructor function makes use of a built-in function called `require` that checks to see if the given conditional is `true`. If not, it reverts the operation (an operation is called `transaction` in Blockchain terms) with the given message.

In our case, we require the given address amount to be equal to the defined `partnerAmount`. If that is not the case, the transaction will not go through. If there aren't any errors, our contract will deploy, and the passed arguments will permanently get stored inside this smart contract.

If we tried running the `run` script as is, we would get an error since our script now expects an array of two addresses to be passed in. Let's update our `run` script to pass addresses inside this contract.

```js
const hre = require("hardhat");

async function main() {
  await hre.run("compile");
  const [owner, person1] = await hre.ethers.getSigners();
  const addresses = [owner.address, person1.address];

  const Contract = await hre.ethers.getContractFactory("Partnership");
  const contract = await Contract.deploy(addresses);

  await contract.deployed();

  await console.log(await contract.partnerAmount());
  await console.log(await contract.addresses(0));
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
```

We use the `hre.ethers.getSigners` function to get objects representing Ethereum accounts. We create an array of addresses by using the addresses of these accounts by using their `address` property.

```sol
const [owner, person1] = await hre.ethers.getSigners();
const addresses = [owner.address, person1.address];
```

We then pass these addresses into our contract during the initialization. Our contract successfully deploys when provided with two addresses.

Another way of ensuring that our contract would only accept two addresses would have been to define the array to have a fixed size. That way, we wouldn't need the `require` statement since the condition would be enforced at a data type level.

```sol
address[2] public addresses;
```

We are also logging the `partnerAmount` and the first address inside the `addresses` array by using the getter functions that Solidity exposes since these are defined as **public** variables.

While the `run` script is helpful to us in understanding the runtime behaviour of our contract, it is not a reliable way to ensure that our contract behaves correctly for our use cases. To develop confidence in our contract, we should write some **tests** that cover its behaviour.

Create a file called `Partnership.js` inside the test folder. This is what it will look like:

```js
describe("Partnership", () => {
  it("can be deployed by providing two addresses on initialization", async () => {
    const Contract = await hre.ethers.getContractFactory("Partnership");

    const [owner, person1] = await hre.ethers.getSigners();

    const addresses = [owner.address, person1.address];

    const contract = await Contract.deploy(addresses);
    await contract.deployed();
  });
});
```

We will call this file using `npx hardhat test test/Partnership.js`. You should see a nicely formatted terminal output that tells us that 1 test has passed. As you can see, this file is super similar to our **run** script. The only difference is that it includes a **test suite** that gets executed whenever this script is called. The `describe` function describes what this test suite is for. The test block that starts with the function name `it` defines a test block that tests one aspect of our contract. In this case, it just tests to see if the contract is deploying successfully.

Let's write a test to see if our contract is failing as expected when no addresses are provided. First, we will need to import an `expect` method to help us make **assertations**.

```js
const { expect } = require("chai");
```

And then, we will add this test block right after the previous one.

```js
it("can NOT be deployed when NOT providing addresses on initialization", async () => {
  const Contract = await hre.ethers.getContractFactory("Partnership");

  let error;
  try {
    const contract = await Contract.deploy();
    await contract.deployed();
  } catch (_error) {
    error = _error;
  } finally {
    expect(error).to.be.ok;
  }
});
```

You would get an error thrown when a contract that expects arguments is deployed without being provided with those arguments. Here we start by initializing an empty `error` variable.

```js
let error;
```

And then, we use the **try-catch** block to see if the error is populated or not. The error gets populated because the deployment fails when addresses are not provided. We check for the error to exist (to be truthy) inside the `finally` block:

```js
expect(error).to.be.ok;
```

Tests are a super important part of any software development process. They not only help you ascertain the behaviour of a program (or a smart contract), but they can also give you confidence that the existing behaviour of the contract doesn't break when you modify it. You can ensure this by running the test suite whenever you update the contract.

Tests are an essential part of the software development process. There is a software development paradigm called **Test-Driven Development (TDD)**, where we first write a failing test for each feature. We then implement the bare minimum code that will make this test pass. This methodology forces us to be iterative with our development process. We will try to follow this methodology while developing our smart contract.

## Smart Contract Continued

In the previous section, we wrote a contract that takes two addresses. But we would like our eventual contract to work with two or more addresses and equivalent amounts of split ratios.

Let's change our tests to encode this expectation. We will be deleting the tests from before and writing new ones. Here is the first test block we will have. It simply tests if the contract deploys without errors when provided with the required amount of addresses and split ratios.

```js
it("can be deployed by providing at least two addresses and equal amounts of split ratios on initialization", async () => {
  const Contract = await hre.ethers.getContractFactory("Partnership");

  const [owner, person1] = await hre.ethers.getSigners();
  const addresses = [owner.address, person1.address];

  const splitRatios = [1, 1];

  const contract = await Contract.deploy(addresses, splitRatios);
  await contract.deployed();
});
```

Our second test will check to see if our contract fails as expected when the address amount and the split ratio amount are different. In this test, we will have two addresses but three split ratios.

```js
it("can NOT be deployed when the split ratios are not equivalent to the address amount", async () => {
  const Contract = await hre.ethers.getContractFactory("Partnership");

  const [owner, person1] = await hre.ethers.getSigners();
  const addresses = [owner.address, person1.address];

  const splitRatios = [1, 1, 1];

  await expect(Contract.deploy(addresses, splitRatios)).to.be.revertedWith(
    "The address amount and the split ratio amount should be equal"
  );
});
```

We use a new method on the `expect` function called `revertedWith` that checks to see if the transactions are reverted with the expected message.

We will add one more test block to ensure that the contract fails to deploy when it is called with less than two addresses.

```js
it("can NOT be deployed when the the address amount is less than two", async () => {
  const Contract = await hre.ethers.getContractFactory("Partnership");

  const [owner] = await hre.ethers.getSigners();
  const addresses = [owner.address];

  const splitRatios = [1];

  await expect(Contract.deploy(addresses, splitRatios)).to.be.revertedWith(
    "More than one address should be provided to establish a partnership"
  );
});
```

All these tests will currently fail when called. Let's update our contract to make them pass. Our new contract will look like this.

```sol
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;

import "hardhat/console.sol";

contract Partnership {
    address[] public addresses;
    uint256[] public splitRatios;

    constructor(address[] memory _addresses, uint256[] memory _splitRatios) {
        require(
            _addresses.length > 1,
            "More than one address should be provided to establish a partnership"
        );

        require(
            _splitRatios.length == _addresses.length,
            "The address amount and the split ratio amount should be equal"
        );
        addresses = _addresses;
        splitRatios = _splitRatios;
        console.log("Contract is Deployed");
    }
}
```

We now have two `require` statements. The first one is to ensure that more than one address is passed into the contract, and the second is to ensure that an equal amount of split ratios is provided. We get the `_addresses` and `_splitRatios` and store them inside the contract.

If we want to update our `run` script, we can do so by including `splitRatios` as an argument.

```js
const hre = require("hardhat");

async function main() {
  await hre.run("compile");
  const [owner, person1] = await hre.ethers.getSigners();
  const addresses = [owner.address, person1.address];
  const splitRatios = [1, 1];

  const Contract = await hre.ethers.getContractFactory("Partnership");
  const contract = await Contract.deploy(addresses, splitRatios);

  await contract.deployed();

  await console.log(await contract.addresses(0));
  await console.log(await contract.splitRatios(0));
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
```

We are now defining and passing split ratios to the contract as well inside the `run` script.

## Requiring Valid Arguments

We also need to ensure that there are no split ratios that are less than or equal to 0. We should iterate through every split ratio to see if they are all above one. The test for this functionality would look like this.

```js
it("can NOT be deployed when any of the split ratios is less than one", async () => {
  const Contract = await hre.ethers.getContractFactory("Partnership");

  const [owner, person1] = await hre.ethers.getSigners();
  const addresses = [owner.address, person1.address];

  const splitRatios = [1, 0];

  await expect(Contract.deploy(addresses, splitRatios)).to.be.revertedWith(
    "Split ratio can not be 0 or less"
  );
});
```

As mentioned previously, this test will initially fail. Let's implement the code that would make it pass.

```sol
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;

import "hardhat/console.sol";

contract Partnership {
    address[] public addresses;
    uint256[] public splitRatios;
    uint256 private splitRatiosTotal;

    constructor(address[] memory _addresses, uint256[] memory _splitRatios) {
        require(
            _addresses.length > 1,
            "More than one address should be provided to establish a partnership"
        );

        require(
            _splitRatios.length == _addresses.length,
            "The address amount and the split ratio amount should be equal"
        );
        addresses = _addresses;
        splitRatios = _splitRatios;
        splitRatiosTotal = getSplitRatiosTotal(splitRatios);

        console.log("Contract is Deployed");
    }

    function getSplitRatiosTotal(uint256[] memory _splitRatios)
        private
        pure
        returns (uint256)
    {
        uint256 total = 0;

        for (uint256 i = 0; i < _splitRatios.length; i++) {
            require(_splitRatios[i] > 0, "Split ratio can not be 0 or less");
            total += _splitRatios[i];
        }

        return total;
    }
}
```

We now added a new variable called `splitRatiosTotal` that is `private`. This means that it won't be accessible from outside of the contract.

```sol
uint256 private splitRatiosTotal;
```

We have defined a new function called `getSplitRatiosTotal` that goes through every item in a given `uint256[]` and checks to see if they are bigger than `0`. It also calculates the sum of those items and stores it inside a state variable `splitRatiosTotal`. We will use this variable when we want to distribute the funds to the contract addresses.

```js
function getSplitRatiosTotal(uint256[] memory _splitRatios)
    private
    pure
    returns (uint256)
{
    uint256 total = 0;

    for (uint256 i = 0; i < _splitRatios.length; i++) {
        require(_splitRatios[i] > 0, "Split ratio can not be 0 or less");
        total += _splitRatios[i];
    }

    return total;
}
```

We have defined this function as `private` and `pure`. A **pure** function means that the operation inside the function doesn't rely on any external state. It would return the same output for the same inputs on every execution. Pure functions are highly desirable in programming since their behaviour is very predictable.

Having a pure function in Solidity is also great since they don't cost any gas when executed. As mentioned previously, **gas** is the computational and monetary cost of doing operations on the Ethereum blockchain.

## Receiving Payments

We can now store addresses and split ratios inside the smart contract. Let's update our contract so that it can also receive payments. Let's start by writing a failing test for this.

```js
it("can receive transactions in Ether", async () => {
  const Contract = await ethers.getContractFactory("Partnership");

  const [owner, person1] = await hre.ethers.getSigners();

  const addresses = [owner.address, person1.address];
  const splitRatios = [1, 1];

  const contract = await Contract.deploy(addresses, splitRatios);
  await contract.deployed();

  // send ether to contract
  await owner.sendTransaction({
    to: contract.address,
    value: ethers.utils.parseEther("1.0"),
  });
});
```

We are using the following function call to send Ether to the contract from the `owner` account.

```js
// send ether to contract
await owner.sendTransaction({
  to: contract.address,
  value: ethers.utils.parseEther("1.0"),
});
```

Our test currently fails because the contract can't receive funds. Making it to receive funds is super easy to do so. We just need to add a single line of code into our contract.

```sol
receive() external payable {}
```

Run the test again. It should pass. The contract can now receive funds!

We will also add a function called `getBalance` that will get the current balance of the contract. We can test this function by checking if the balance increased by the expected amount after receiving a transaction.

```js
it("has a balance that increases after receiving a transaction", async () => {
  const Contract = await ethers.getContractFactory("Partnership");

  const [owner, person1, person2] = await hre.ethers.getSigners();

  const addresses = [person1.address, person2.address];
  const splitRatios = [1, 1];

  const contract = await Contract.deploy(addresses, splitRatios);
  await contract.deployed();

  expect(await contract.getBalance()).to.equal(0);

  const value = ethers.utils.parseEther("1.0");

  // send ether to contract
  await owner.sendTransaction({
    to: contract.address,
    value,
  });

  expect(await contract.getBalance()).to.equal(value);
});
```

We first check to see if the contract balance is `0` to begin with.

```js
expect(await contract.getBalance()).to.equal(0);
```

We then use the `parseEther` method to convert a single Ether's value to its **Wei** equivalent.

```js
const value = ethers.utils.parseEther("1.0");
```

A **Wei** is the smallest unit of Ether. Think of it as what a penny is to a dollar. The similarity ends there, though, since a dollar is only 100 times more valuable than a penny, whereas an Ether is 10^18 times more valuable than a **Wei**!

We then execute a transaction with this Wei value and check to see if the balance has increased.

```js
expect(await contract.getBalance()).to.equal(value);
```

Let's implement the `getBalance` method inside our contract so that the test would pass. This is again straightforward to do so.

```js
function getBalance() public view returns (uint256) {
    return address(this).balance;
}
```

Note the usage of the `view` keyword in the function definition. A **view** function is a function that doesn't alter the state but merely reads it.

Our tests should start passing with the implementation of this function.

## Sending Payments

We will be implementing a function called `withdraw`. When called, this function will initiate the transfer of the funds to the recorded addresses according to the provided split ratio.

We can create a new `describe` block called `withdraw` inside the existing one. Using `describe` function allows us to categorize related tests together. We will include any withdrawal-related test inside this new block.

```js
describe("withdraw", () => {
  it("can be called if the contract balance is more than 0", async () => {
    const Contract = await ethers.getContractFactory("Partnership");

    const [owner, person1] = await hre.ethers.getSigners();

    const addresses = [owner.address, person1.address];
    const splitRatios = [1, 1];

    expect(addresses.length).to.equal(splitRatios.length);

    const contract = await Contract.deploy(addresses, splitRatios);
    await contract.deployed();

    // send ether to contract
    await owner.sendTransaction({
      to: contract.address,
      value: ethers.utils.parseEther("5.0"),
    });

    expect(await contract.getBalance()).to.not.equal(0);

    await contract.withdraw();
  });
});
```

We check to see if the contract balance is non-zero after the transfer.

```js
expect(await contract.getBalance()).to.not.equal(0);
```

Then we call the `withdraw` function. The test fails because the withdraw function doesn't exist. Let's fix that. We will add a `withdraw` function inside the contract.

```sol
function withdraw() public {
    uint256 addressesLength = addresses.length;

    for (uint256 i = 0; i < addressesLength; i++) {
        addresses[i].transfer(
            (balance / splitRatiosTotal) * splitRatios[i]
        );
    }
}
```

This function doesn't quite work yet. The reason for that is that we should define the addresses as `payable` for us to invoke the `transfer` method on them. We need to update the state variable for addresses and the argument inside the constructor with the `payable` keyword.

```sol
address payable[] public addresses;
```

And inside the `constructor`:

```sol
constructor(
    address payable[] memory _addresses,
    uint256[] memory _splitRatios
) {
    // constructor body
}
```

With this change, the `withdraw` function should start working!

We would like to have the `withdraw` function fail if the balance is less than 0. Let's write the test for that.

```js
it("can NOT be called if the contract balance is less than or equal to 0", async () => {
  const Contract = await ethers.getContractFactory("Partnership");

  const [owner, person1] = await hre.ethers.getSigners();

  const addresses = [owner.address, person1.address];
  const splitRatios = [1, 1];

  expect(addresses.length).to.equal(splitRatios.length);

  const contract = await Contract.deploy(addresses, splitRatios);
  await contract.deployed();

  expect(await contract.getBalance()).to.equal(0);

  await expect(contract.withdraw()).to.be.revertedWith("Insufficient balance");
});
```

We first ensure that the balance of the contract is `0`.

```js
expect(await contract.getBalance()).to.equal(0);
```

Then we try to call the `withdraw` function. We should make it fail with the `"Insufficient balance"` error.

```js
await expect(contract.withdraw()).to.be.revertedWith("Insufficient balance");
```

To implement this inside the contract, we should get the contract balance inside the `withdraw` function and ensure it is bigger than 0.

```sol
function withdraw() public {
    uint256 balance = getBalance();
    uint256 addressesLength = addresses.length;

    require(balance > 0, "Insufficient balance");

    for (uint256 i = 0; i < addressesLength; i++) {
        addresses[i].transfer(
            (balance / splitRatiosTotal) * splitRatios[i]
        );
    }
}
```

## Ensuring that Split Ratio Total is Smaller Than the Balance

We should ensure that the total split ratio is smaller than the balance. This way, we won't get any numerical errors when dividing the balance with the split ratio. Here is the test for that looks like.

```js
it("can NOT be called when the total split ratio is greater than the contract balance", async () => {
  const Contract = await ethers.getContractFactory("Partnership");

  const [owner, person1] = await hre.ethers.getSigners();

  const addresses = [owner.address, person1.address];
  const splitRatios = [10, 10];

  expect(addresses.length).to.equal(splitRatios.length);

  const contract = await Contract.deploy(addresses, splitRatios);
  await contract.deployed();

  expect(await contract.getBalance()).to.equal(0);

  // send ether to contract
  await owner.sendTransaction({
    to: contract.address,
    value: ethers.utils.parseEther("0.00000000000000001"),
  });

  expect(await contract.getBalance()).to.equal(10);

  await expect(contract.withdraw()).to.be.revertedWith(
    "Balance should be greater than the total split ratios"
  );
});
```

We are sending 10 Wei to the contract.

```js
// send ether to contract
await owner.sendTransaction({
  to: contract.address,
  value: ethers.utils.parseEther("0.00000000000000001"),
});

expect(await contract.getBalance()).to.equal(10);
```

The total of the split ratios in this test is greater than 10 (it is 20). This causes the contract to fail with the `"Balance should be greater than the total split ratios"` message. We can implement this inside the contract by adding a simple `require` statement.

```sol
require(balance >=
  splitRatiosTotal, "Balance should be greater than the total split ratios");
```

## Finalizing the Run Script

Now that we have finished the contract logic, let's update the `run` script to simulate how someone would interact with this contract.

```js
const hre = require("hardhat");

async function main() {
  await hre.run("compile");
  const [owner, person1, person2] = await hre.ethers.getSigners();
  const addresses = [person1.address, person2.address];
  const splitRatios = [4, 1];

  const Contract = await hre.ethers.getContractFactory("Partnership");
  const provider = hre.ethers.provider;
  const contract = await Contract.deploy(addresses, splitRatios);

  await contract.deployed();

  let balance1 = await provider.getBalance(person1.address);
  console.log(`The balance for the person1 is: ${balance1}`);

  let balance2 = await provider.getBalance(person2.address);
  console.log(`The balance for the person2 is: ${balance2}`);

  let contractBalance = await contract.getBalance();
  console.log(`The balance in the contract is: ${contractBalance}`);

  await owner.sendTransaction({
    to: contract.address,
    value: ethers.utils.parseEther("5.0"),
  });

  console.log(
    `The balance in the contract after receiving funds is: ${await contract.getBalance()}`
  );

  await contract.withdraw();

  console.log(
    `The balance in the contract after withdrawal is: ${await contract.getBalance()}`
  );

  balance1 = await provider.getBalance(person1.address);
  console.log(`The new balance for the person1 is:${balance1}`);

  balance2 = await provider.getBalance(person2.address);
  console.log(`The balance for the person2 is: ${balance2}`);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
```

Here, we simulate the contract deployment with two addresses and a 4 to 1 split ratio. We need to define a `provider` to get the balance from the Ethereum accounts.

```js
const provider = hre.ethers.provider;
```

We can then call the `getBalance` method on the provider to see the contract balance.

```js
let balance1 = await provider.getBalance(person1.address);
```

Hardhat initializes these accounts with a balance of 10000 ETH.

After the deployment, we get another user to send 5000 ETH into the contract. The balance of the contract changes accordingly.

```js
await owner.sendTransaction({
  to: contract.address,
  value: ethers.utils.parseEther("5000.0"),
});
```

We then call the `withdraw` function on the contract. This distributes the funds inside the contract in between the addresses. We then check to ensure the balance of the accounts has changed accordingly.

```js
await contract.withdraw();
```

It seems like things are working as expected!

Believe it or not, this is it! We now have a program that can permanently store data, securely receive transactions, store funds and transfer those funds to specified accounts (wallet addresses). And it took less than 100 lines to write it! Building something like this makes you truly appreciate the power of blockchains and smart contract platforms like Ethereum. The amount of code and complexity to achieve something similar using centralized solutions would have been substantial.

How can other people make use of this smart contract? We could upload the contract to a public repository like Github and have other people clone it from there. They would need to change the run script to handle the deployment of the contract on the Ethereum blockchain themselves. Obviously, this is not an ideal or feasible solution for those who don't have any familiarity with working with smart contracts.

We could do so much better than this! If you enjoyed this walkthrough, check out my [ebook](https://leanpub.com/develop-web3-apps-using-solidity-and-react/) for learning how to build the front end application for this contract using React, Next.js, and React.