Hackernoon logoHow I Built A Smart Contract That Pays People Automatically by@grantbartel

How I Built A Smart Contract That Pays People Automatically

AllowanceWallet is an open-source smart contract that pays your people algorithmically. All you have to do is fund the smart contract, add their wallet addresses, specify how much they get per pay period (e.g., every 7 days), and they can withdraw some or all of their money whenever they want. The amount they're owed accrues without me doing anything, and the amount they don't need it for a few weeks. It shows how much you can do with so little if you know how to build smart contracts with Solidity.
image
Grant Bartel Hacker Noon profile picture

Grant Bartel

Freelance quant, dev, and writer with a background in AI, crypto, and quant finance.

Who do you pay money to on a regular basis? Your kids, employees, landlord, whomever? And each time you send them money, do you have to send the same amount on a regular basis? That's a huge waste of time, an unnecessary amount of fees, and a drain on your mental bandwidth.

What if you could just deposit some money into an account and have your people just withdraw the money they're owed, when they're owed?

Thanks to smart contracts, we can easily do that!

Pay Your People Algorithmically

I built an Ethereum smart contract called AllowanceWallet that pays my people algorithmically. That means all I have to do is fund my smart contract, add their Ethereum wallet addresses, specify how much they get per pay period (e.g., every 7 days), and they can withdraw some or all of their money whenever they want.

I can also remove people from the list of who gets paid so they no longer accrue money. But that's only possible if I transfer what they're owed directly to their wallet. Seemed fair!

On top of that, the amount they're owed accrues without me doing anything. So if they don't need their money for a few weeks and they get paid every week, the smart contract keeps track of the total amount they're owed.

While the idea and functionality are all pretty simple, it shows how much you can do with so little if you know how to build smart contracts. It also sets the stage for improvements later on!

Why traditional banks haven't implemented this, I have NO idea. Maybe they have and I don't know about it. But thanks to blockchain technology, we can build decentralized, trustless systems that traditional banks can't compete with. Even relatively simple ones like this.

How AllowanceWallet Works (Code and All!)

Before I dive into the ins and outs of how AllowanceWallet works, I need to give a shoutout to the creators of the Udemy course Ethereum Blockchain Developer Bootcamp With Solidity. What I built here is an extension of a similar project I did in the course.

OK let's get into it!

To start out, let's take a look at the tools I used to build AllowanceWallet:

  • Solidity - the most popular programming language to build Ethereum smart contracts
  • Remix - a browser-based IDE for programming in Solidity
  • OpenZeppelin - an open-source Solidity library for secure smart contract development

Now when I set out to create AllowanceWallet, I wanted to make sure anyone could fund the smart contract, but only one person (i.e., the owner of the smart contract) could withdraw.

receive () external payable {
    emit MoneyReceived(msg.sender, msg.value);
}

This code allows anyone who interacts with the smart contract to add money.

Payable
used at the end of the function gives the function
receive()
permission to accept money. Without this, an exception will be raised.

function withdrawFromWalletBalance(address payable addr, uint amount) public onlyOwner {
    require(address(this).balance >= amount, "Wallet balance too low to fund withdraw");
    addr.transfer(amount);
    
    emit MoneySent(msg.sender, amount);
}

function withdrawAllFromWalletBalance(address payable addr) public onlyOwner {
    withdrawFromWalletBalance(addr, address(this).balance);
}

This code gives the owner the ability to withdraw money saved in the smart contract. You'll notice

onlyOwner
appended to the end of the functions, which is functionality extracted from the OpenZeppelin smart contract
Ownable.sol
.

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol";

As you can see, importing smart contracts from GitHub is very easy!

So now we have a smart contract that supports deposits and withdrawals, but that's not enough. We still need to give the owner of the smart contract the ability to add and remove addresses, which will ultimately be the addresses owned by the people who'll be getting paid from the smart contract.

But before we get into that, let's take a look at how we'll store this information in the first place.

struct Allowance {
    uint allowanceAmount;
    uint allowancePeriodInDays;
    uint whenLastAllowance;
    uint unspentAllowance;
}

mapping(address => Allowance) allowances;

In Solidity,

structs
and
mappings
are common-place. Here you'll see that for every stored address (or allowance recipient), we have an
Allowance
struct. In there we store the amount of money they get paid, their payment frequency in days, their most recent allowance date, and their total unspent allowance.

With this data, the smart contract will be able to keep track of how much each address is owed at any time.

Now let's see how allowances are added to the smart contract:

function addAllowance(address addr, uint allowanceAmount, uint allowancePeriodInDays) public onlyOwner {
    require(allowances[addr].allowanceAmount == 0, "Allowance already exists");
    require(address(this).balance >= allowanceAmount, "Wallet balance too low to add allowance");
    
    // Initialize new allowance
    Allowance memory allowance;
    allowance.allowanceAmount = allowanceAmount;
    allowance.allowancePeriodInDays = allowancePeriodInDays.mul(1 days);
    allowance.whenLastAllowance = block.timestamp;
    allowance.unspentAllowance = allowanceAmount;
    
    allowances[addr] = allowance;
    emit AllowanceCreated(addr, allowance);
}

First off, you'll notice two

require
statements right away. This is how we ensure the input coming from the user of the smart contract isn't going to break anything. If these functions evaluate to
false
, the transaction fails.

Then we allocate an

Allowance
struct into memory, initialize it, and store it for later use.

You might've noticed the

mul()
function trailing variable
allowancePeriodInDays
. This is functionality derived from OpenZeppelin's
SafeMath.sol
smart contract, which we import the same way we did before with
Ownable.sol
.

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol";

Alright, but what if we want to remove someone from receiving an allowance after having added them?

function removeAllowance(address payable addr) public onlyOwner {
    require(allowances[addr].allowanceAmount != 0, "Allowance already doesn't exist");
    
    // Payout unspent allowance
    if(allowances[addr].unspentAllowance > 0){
        require(address(this).balance >= allowances[addr].unspentAllowance, "Wallet balance too low to payout unspent allowance");
        addr.transfer(allowances[addr].unspentAllowance);
    }
    
    delete allowances[addr];
    
    emit MoneySent(addr, allowances[addr].unspentAllowance);
    emit AllowanceDeleted(addr);
}

At first, I just removed the allowance from the smart contract, but then I thought:

"What if they still have some leftover money they didn't spend?"

The second part of the function fixes this so the owner of the smart contract MUST pay out the remaining money owed to the assigned address before removing them. Otherwise, the recipient will keep racking up a balance that'll need to be paid eventually.

Now, that pretty much covers the main functionality of AllowanceWallet except for one part: How do the allowance recipients get paid?!

Let's get into that now.

function getPaidAllowance(uint amount) public {
    require(allowances[msg.sender].allowanceAmount > 0, "You're not a recipient of an allowance");
    require(address(this).balance >= amount, "Wallet balance too low to pay allowance");
    
    // Calculate and update unspent allowance
    uint numAllowances = block.timestamp.sub(allowances[msg.sender].whenLastAllowance).div(allowances[msg.sender].allowancePeriodInDays);
    allowances[msg.sender].unspentAllowance = allowances[msg.sender].allowanceAmount.mul(numAllowances).add(allowances[msg.sender].unspentAllowance);
    allowances[msg.sender].whenLastAllowance = numAllowances.mul(1 days).add(allowances[msg.sender].whenLastAllowance);
    
    // Pay allowance
    require(allowances[msg.sender].unspentAllowance >= amount, "You asked for more allowance than you're owed'");
    payable(msg.sender).transfer(amount);
    allowances[msg.sender].unspentAllowance = allowances[msg.sender].unspentAllowance.sub(amount);
    
    emit MoneySent(msg.sender, amount);
    emit AllowanceChanged(msg.sender, allowances[msg.sender]);
}

Right away you'll see that only those who've been added to the smart contract by the owner can get paid and they obviously need to have money to withdraw. That's covered in the initial

require
statements.

The next part of the code keeps track of how much the recipient is owed. For example, if you had a weekly allowance and you didn't withdraw money for 4 weeks, you'd accrue 4 weeks' worth of allowance automatically.

Then once the allowance balance is updated, the requested money is transferred to the recipient as long as they don't ask for more than they're owed.

And that's it! If you want to take a look at how this all fits together, you can find the code on my GitHub.

What Next?

I took a simple idea and expressed it by creating a simple smart contract. But there's a whole lot more that could be done to make AllowanceWallet a whole lot better.

Here are a few ideas of how AllowanceWallet can be improved:

  1. Create a simple web interface using something like React or Vue.
  2. Redesign it to support multiple groups of owners and allowance recipients.
  3. Integrate it into a layer-2 ecosystem (like zkSync) for cheaper transactions.
  4. Add functionality for streaming (i.e., continuously accruing) allowance.
  5. Allow deposited, unspent money to accrue interest by using a DeFi protocol like yearn.
  6. Reimburse withdrawal fees paid by allowance recipients through a native token (AllowanceCoin?).

Not sure how many of these I'll actually implement, but it's fun to imagine the possibilities!

Final Thoughts

If you're paying your people a constant amount of money at regular intervals, you shouldn't have to think much about it. It's predictable and therefore it should be as automated as possible. And as more people start accepting crypto as a form of payment, using smart contracts to handle these types of transactions is a no-brainer.

That's EXACTLY why I built AllowanceWallet.

Hope you enjoyed my post! If you want to reach out to me directly, feel free to hit me up on Twitter or LinkedIn 🔥

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.