visit
OK let's get into it!
To start out, let's take a look at the tools I used to build AllowanceWallet: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 "//github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol";
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.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 "//github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol";
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);
}
"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 .I’m Grant and I’m a freelance fintech writer! If you’re looking for engaging, informative fintech content that speaks to your audience and can grow your brand awareness and online reach, I can help.
Learn more on how I can help with your