π Project Overview
I've recently built a full-stack Decentralized Application (DApp) using:
- π PHP for backend logic and APIs
- π» JavaScript for the frontend UI
- π§ Solidity for smart contracts
- π Binance Smart Chain as the blockchain layer
- π³ Docker & Docker Compose for deployment
This project was an exciting mix of traditional backend skills and modern Web3 architecture. Here's how I did it π
π§± Tech Stack
Layer | Technology |
---|---|
Smart Contracts | Solidity on BSC |
Backend | PHP (vanilla or Laravel) |
Frontend | JavaScript (with Web3.js) |
Containerization | Docker & Docker Compose |
π Smart Contracts on Binance Smart Chain
The smart contracts were developed in Solidity, tested with Hardhat, and deployed to the Binance Smart Chain Testnet.
Example contract snippet:
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.6.12; library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } interface IBEP20 { function totalSupply() external view returns (uint256); function decimals() external view returns (uint8); function symbol() external view returns (string memory); function name() external view returns (string memory); function getOwner() external view returns (address); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function allowance(address _owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } contract FIZZYFY { using SafeMath for uint256; IBEP20 public busd; address public contractOwner; modifier onlyContractOwner() { require(msg.sender == contractOwner, "onlyOwner"); _; } uint256 private constant baseDivider = 10000; uint256 private constant feePercents = 200; uint256 private constant minDeposit = 50e18; uint256 private constant maxDeposit = 2000e18; uint256 private constant freezeIncomePercents = 3000; uint256 private constant timeStep = 1 days; uint256 private constant dayPerCycle = 10 days; uint256 private constant dayRewardPercents = 160; uint256 private constant maxAddFreeze = 45 days; uint256 private constant referDepth = 20; uint256 private constant directPercents = 500; uint256[4] private level4Percents = [100, 200, 300, 100]; uint256[15] private level5Percents = [200, 100, 100, 100, 100, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50]; uint256 private constant luckPoolPercents = 50; uint256 private constant starPoolPercents = 30; uint256 private constant topPoolPercents = 20; uint256[5] private balDown = [10e10, 30e10, 100e10, 500e10, 1000e10]; uint256[5] private balDownRate = [1000, 1500, 2000, 5000, 6000]; uint256[5] private balRecover = [15e18, 50e10, 150e10, 500e10, 1000e10]; mapping(uint256=>bool) public balStatus; // bal=>status address[2] public feeReceivers; address public defaultRefer; uint256 public startTime; uint256 public lastDistribute; uint256 public totalUser; uint256 public luckPool; uint256 public starPool; uint256 public topPool; mapping(uint256=>address[]) public dayLuckUsers; mapping(uint256=>uint256[]) public dayLuckUsersDeposit; mapping(uint256=>address[3]) public dayTopUsers; address[] public level4Users; struct OrderInfo { uint256 amount; uint256 start; uint256 unfreeze; bool isUnfreezed; } mapping(address => OrderInfo[]) public orderInfos; address[] public depositors; struct UserInfo { address referrer; uint256 start; uint256 level; uint256 maxDeposit; uint256 totalDeposit; uint256 teamNum; uint256 maxDirectDeposit; uint256 teamTotalDeposit; uint256 totalFreezed; uint256 totalRevenue; } mapping(address=>UserInfo) public userInfo; mapping(uint256 => mapping(address => uint256)) public userLayer1DayDeposit; mapping(address => mapping(uint256 => address[])) public teamUsers; struct RewardInfo{ uint256 capitals; uint256 statics; uint256 directs; uint256 level4Freezed; uint256 level4Released; uint256 level5Left; uint256 level5Freezed; uint256 level5Released; uint256 star; uint256 luck; uint256 top; uint256 split; uint256 splitDebt; } mapping(address=>RewardInfo) public rewardInfo; bool public isFreezeReward; event Register(address user, address referral); event Deposit(address user, uint256 amount); event DepositBySplit(address user, uint256 amount); event TransferBySplit(address user, address receiver, uint256 amount); event Withdraw(address user, uint256 withdrawable); constructor(IBEP20 _busdAddr, address _defaultRefer, address[2] memory _feeReceivers) public { busd = _busdAddr; feeReceivers = _feeReceivers; startTime = block.timestamp; lastDistribute = block.timestamp; defaultRefer = _defaultRefer; } function register(address _referral) external { require(userInfo[_referral].totalDeposit > 0 || _referral == defaultRefer, "invalid refer"); UserInfo storage user = userInfo[msg.sender]; require(user.referrer == address(0), "referrer bonded"); user.referrer = _referral; user.start = block.timestamp; _updateTeamNum(msg.sender); totalUser = totalUser.add(1); emit Register(msg.sender, _referral); } function deposit(uint256 _amount) external { busd.transferFrom(msg.sender, address(this), _amount); _deposit(msg.sender, _amount); emit Deposit(msg.sender, _amount); } function depositBySplit(uint256 _amount) external { require(_amount >= minDeposit && _amount.mod(minDeposit) == 0, "amount err"); require(userInfo[msg.sender].totalDeposit == 0, "actived"); uint256 splitLeft = getCurSplit(msg.sender); require(splitLeft >= _amount, "insufficient split"); rewardInfo[msg.sender].splitDebt = rewardInfo[msg.sender].splitDebt.add(_amount); _deposit(msg.sender, _amount); emit DepositBySplit(msg.sender, _amount); } function transferBySplit(address _receiver, uint256 _amount) external { require(_amount >= minDeposit && _amount.mod(minDeposit) == 0, "amount err"); uint256 splitLeft = getCurSplit(msg.sender); require(splitLeft >= _amount, "insufficient income"); rewardInfo[msg.sender].splitDebt = rewardInfo[msg.sender].splitDebt.add(_amount); rewardInfo[_receiver].split = rewardInfo[_receiver].split.add(_amount); emit TransferBySplit(msg.sender, _receiver, _amount); } function distributePoolRewards() public { if(block.timestamp > lastDistribute.add(timeStep)){ uint256 dayNow = getCurDay(); _distributeStarPool(); _distributeLuckPool(dayNow); _distributeTopPool(dayNow); lastDistribute = block.timestamp; } } function withdraw() external { distributePoolRewards(); (uint256 staticReward, uint256 staticSplit) = _calCurStaticRewards(msg.sender); uint256 splitAmt = staticSplit; uint256 withdrawable = staticReward; (uint256 dynamicReward, uint256 dynamicSplit) = _calCurDynamicRewards(msg.sender); withdrawable = withdrawable.add(dynamicReward); splitAmt = splitAmt.add(dynamicSplit); RewardInfo storage userRewards = rewardInfo[msg.sender]; userRewards.split = userRewards.split.add(splitAmt); userRewards.statics = 0; userRewards.directs = 0; userRewards.level4Released = 0; userRewards.level5Released = 0; userRewards.luck = 0; userRewards.star = 0; userRewards.top = 0; withdrawable = withdrawable.add(userRewards.capitals); userRewards.capitals = 0; busd.transfer(msg.sender, withdrawable); uint256 bal = busd.balanceOf(address(this)); _setFreezeReward(bal); emit Withdraw(msg.sender, withdrawable); } function getCurDay() public view returns(uint256) { return (block.timestamp.sub(startTime)).div(timeStep); } function getDayLuckLength(uint256 _day) external view returns(uint256) { return dayLuckUsers[_day].length; } function getTeamUsersLength(address _user, uint256 _layer) external view returns(uint256) { return teamUsers[_user][_layer].length; } function getOrderLength(address _user) external view returns(uint256) { return orderInfos[_user].length; } function getDepositorsLength() external view returns(uint256) { return depositors.length; } function getMaxFreezing(address _user) public view returns(uint256) { uint256 maxFreezing; for(uint256 i = orderInfos[_user].length; i > 0; i--){ OrderInfo storage order = orderInfos[_user][i - 1]; if(order.unfreeze > block.timestamp){ if(order.amount > maxFreezing){ maxFreezing = order.amount; } }else{ break; } } return maxFreezing; } function getTeamDeposit(address _user) public view returns(uint256, uint256, uint256){ uint256 totalTeam; uint256 maxTeam; uint256 otherTeam; for(uint256 i = 0; i < teamUsers[_user][0].length; i++){ uint256 userTotalTeam = userInfo[teamUsers[_user][0][i]].teamTotalDeposit.add(userInfo[teamUsers[_user][0][i]].totalDeposit); totalTeam = totalTeam.add(userTotalTeam); if(userTotalTeam > maxTeam){ maxTeam = userTotalTeam; } } otherTeam = totalTeam.sub(maxTeam); return(maxTeam, otherTeam, totalTeam); } function getCurSplit(address _user) public view returns(uint256){ (, uint256 staticSplit) = _calCurStaticRewards(_user); (, uint256 dynamicSplit) = _calCurDynamicRewards(_user); return rewardInfo[_user].split.add(staticSplit).add(dynamicSplit).sub(rewardInfo[_user].splitDebt); } function _calCurStaticRewards(address _user) private view returns(uint256, uint256) { RewardInfo storage userRewards = rewardInfo[_user]; uint256 totalRewards = userRewards.statics; uint256 splitAmt = totalRewards.mul(freezeIncomePercents).div(baseDivider); uint256 withdrawable = totalRewards.sub(splitAmt); return(withdrawable, splitAmt); } function _calCurDynamicRewards(address _user) private view returns(uint256, uint256) { RewardInfo storage userRewards = rewardInfo[_user]; uint256 totalRewards = userRewards.directs.add(userRewards.level4Released).add(userRewards.level5Released); totalRewards = totalRewards.add(userRewards.luck.add(userRewards.star).add(userRewards.top)); uint256 splitAmt = totalRewards.mul(freezeIncomePercents).div(baseDivider); uint256 withdrawable = totalRewards.sub(splitAmt); return(withdrawable, splitAmt); } function _updateTeamNum(address _user) private { UserInfo storage user = userInfo[_user]; address upline = user.referrer; for(uint256 i = 0; i < referDepth; i++){ if(upline != address(0)){ userInfo[upline].teamNum = userInfo[upline].teamNum.add(1); teamUsers[upline][i].push(_user); _updateLevel(upline); if(upline == defaultRefer) break; upline = userInfo[upline].referrer; }else{ break; } } } function _updateTopUser(address _user, uint256 _amount, uint256 _dayNow) private { userLayer1DayDeposit[_dayNow][_user] = userLayer1DayDeposit[_dayNow][_user].add(_amount); bool updated; for(uint256 i = 0; i < 3; i++){ address topUser = dayTopUsers[_dayNow][i]; if(topUser == _user){ _reOrderTop(_dayNow); updated = true; break; } } if(!updated){ address lastUser = dayTopUsers[_dayNow][2]; if(userLayer1DayDeposit[_dayNow][lastUser] < userLayer1DayDeposit[_dayNow][_user]){ dayTopUsers[_dayNow][2] = _user; _reOrderTop(_dayNow); } } } function _reOrderTop(uint256 _dayNow) private { for(uint256 i = 3; i > 1; i--){ address topUser1 = dayTopUsers[_dayNow][i - 1]; address topUser2 = dayTopUsers[_dayNow][i - 2]; uint256 amount1 = userLayer1DayDeposit[_dayNow][topUser1]; uint256 amount2 = userLayer1DayDeposit[_dayNow][topUser2]; if(amount1 > amount2){ dayTopUsers[_dayNow][i - 1] = topUser2; dayTopUsers[_dayNow][i - 2] = topUser1; } } } function _removeInvalidDeposit(address _user, uint256 _amount) private { UserInfo storage user = userInfo[_user]; address upline = user.referrer; for(uint256 i = 0; i < referDepth; i++){ if(upline != address(0)){ if(userInfo[upline].teamTotalDeposit > _amount){ userInfo[upline].teamTotalDeposit = userInfo[upline].teamTotalDeposit.sub(_amount); }else{ userInfo[upline].teamTotalDeposit = 0; } if(upline == defaultRefer) break; upline = userInfo[upline].referrer; }else{ break; } } } function _updateReferInfo(address _user, uint256 _amount) private { UserInfo storage user = userInfo[_user]; address upline = user.referrer; for(uint256 i = 0; i < referDepth; i++){ if(upline != address(0)){ userInfo[upline].teamTotalDeposit = userInfo[upline].teamTotalDeposit.add(_amount); _updateLevel(upline); if(upline == defaultRefer) break; upline = userInfo[upline].referrer; }else{ break; } } } function _updateLevel(address _user) private { UserInfo storage user = userInfo[_user]; uint256 levelNow = _calLevelNow(_user); if(levelNow > user.level){ user.level = levelNow; if(levelNow == 4){ level4Users.push(_user); } } } function _calLevelNow(address _user) private view returns(uint256) { UserInfo storage user = userInfo[_user]; uint256 total = user.totalDeposit; uint256 levelNow; if(total >= 1000e18){ (uint256 maxTeam, uint256 otherTeam, ) = getTeamDeposit(_user); if(total >= 2000e18 && user.teamNum >= 200 && maxTeam >= 50000e18 && otherTeam >= 50000e18){ levelNow = 5; }else if(user.teamNum >= 50 && maxTeam >= 10000e18 && otherTeam >= 10000e18){ levelNow = 4; }else{ levelNow = 3; } }else if(total >= 500e18){ levelNow = 2; }else if(total >= 50e18){ levelNow = 1; } return levelNow; } function _deposit(address _user, uint256 _amount) private { UserInfo storage user = userInfo[_user]; require(user.referrer != address(0), "register first"); require(_amount >= minDeposit, "less than min"); require(_amount.mod(minDeposit) == 0 && _amount >= minDeposit, "mod err"); require(user.maxDeposit == 0 || _amount >= user.maxDeposit, "less before"); if(user.maxDeposit == 0){ user.maxDeposit = _amount; }else if(user.maxDeposit < _amount){ user.maxDeposit = _amount; } _distributeDeposit(_amount); if(user.totalDeposit == 0){ uint256 dayNow = getCurDay(); dayLuckUsers[dayNow].push(_user); dayLuckUsersDeposit[dayNow].push(_amount); _updateTopUser(user.referrer, _amount, dayNow); } depositors.push(_user); user.totalDeposit = user.totalDeposit.add(_amount); user.totalFreezed = user.totalFreezed.add(_amount); _updateLevel(msg.sender); uint256 addFreeze = (orderInfos[_user].length.div(2)).mul(timeStep); if(addFreeze > maxAddFreeze){ addFreeze = maxAddFreeze; } uint256 unfreezeTime = block.timestamp.add(dayPerCycle).add(addFreeze); orderInfos[_user].push(OrderInfo( _amount, block.timestamp, unfreezeTime, false )); _unfreezeFundAndUpdateReward(msg.sender, _amount); distributePoolRewards(); _updateReferInfo(msg.sender, _amount); _updateReward(msg.sender, _amount); _releaseUpRewards(msg.sender, _amount); uint256 bal = busd.balanceOf(address(this)); _balActived(bal); if(isFreezeReward){ _setFreezeReward(bal); } } function _unfreezeFundAndUpdateReward(address _user, uint256 _amount) private { UserInfo storage user = userInfo[_user]; bool isUnfreezeCapital; for(uint256 i = 0; i < orderInfos[_user].length; i++){ OrderInfo storage order = orderInfos[_user][i]; if(block.timestamp > order.unfreeze && order.isUnfreezed == false && _amount >= order.amount){ order.isUnfreezed = true; isUnfreezeCapital = true; if(user.totalFreezed > order.amount){ user.totalFreezed = user.totalFreezed.sub(order.amount); }else{ user.totalFreezed = 0; } _removeInvalidDeposit(_user, order.amount); uint256 staticReward = order.amount.mul(dayRewardPercents).mul(dayPerCycle).div(timeStep).div(baseDivider); if(isFreezeReward){ if(user.totalFreezed > user.totalRevenue){ uint256 leftCapital = user.totalFreezed.sub(user.totalRevenue); if(staticReward > leftCapital){ staticReward = leftCapital; } }else{ staticReward = 0; } } rewardInfo[_user].capitals = rewardInfo[_user].capitals.add(order.amount); rewardInfo[_user].statics = rewardInfo[_user].statics.add(staticReward); user.totalRevenue = user.totalRevenue.add(staticReward); break; } } if(!isUnfreezeCapital){ RewardInfo storage userReward = rewardInfo[_user]; if(userReward.level5Freezed > 0){ uint256 release = _amount; if(_amount >= userReward.level5Freezed){ release = userReward.level5Freezed; } userReward.level5Freezed = userReward.level5Freezed.sub(release); userReward.level5Released = userReward.level5Released.add(release); user.totalRevenue = user.totalRevenue.add(release); } } } function _distributeStarPool() private { uint256 level4Count; for(uint256 i = 0; i < level4Users.length; i++){ if(userInfo[level4Users[i]].level == 4){ level4Count = level4Count.add(1); } } if(level4Count > 0){ uint256 reward = starPool.div(level4Count); uint256 totalReward; for(uint256 i = 0; i < level4Users.length; i++){ if(userInfo[level4Users[i]].level == 4){ rewardInfo[level4Users[i]].star = rewardInfo[level4Users[i]].star.add(reward); userInfo[level4Users[i]].totalRevenue = userInfo[level4Users[i]].totalRevenue.add(reward); totalReward = totalReward.add(reward); } } if(starPool > totalReward){ starPool = starPool.sub(totalReward); }else{ starPool = 0; } } } function _distributeLuckPool(uint256 _dayNow) private { uint256 dayDepositCount = dayLuckUsers[_dayNow - 1].length; if(dayDepositCount > 0){ uint256 checkCount = 10; if(dayDepositCount < 10){ checkCount = dayDepositCount; } uint256 totalDeposit; uint256 totalReward; for(uint256 i = dayDepositCount; i > dayDepositCount.sub(checkCount); i--){ totalDeposit = totalDeposit.add(dayLuckUsersDeposit[_dayNow - 1][i - 1]); } for(uint256 i = dayDepositCount; i > dayDepositCount.sub(checkCount); i--){ address userAddr = dayLuckUsers[_dayNow - 1][i - 1]; if(userAddr != address(0)){ uint256 reward = luckPool.mul(dayLuckUsersDeposit[_dayNow - 1][i - 1]).div(totalDeposit); totalReward = totalReward.add(reward); rewardInfo[userAddr].luck = rewardInfo[userAddr].luck.add(reward); userInfo[userAddr].totalRevenue = userInfo[userAddr].totalRevenue.add(reward); } } if(luckPool > totalReward){ luckPool = luckPool.sub(totalReward); }else{ luckPool = 0; } } } function _distributeTopPool(uint256 _dayNow) private { uint16[3] memory rates = [5000, 3000, 2000]; uint72[3] memory maxReward = [2000e18, 1000e18, 500e18]; uint256 totalReward; for(uint256 i = 0; i < 3; i++){ address userAddr = dayTopUsers[_dayNow - 1][i]; if(userAddr != address(0)){ uint256 reward = topPool.mul(rates[i]).div(baseDivider); if(reward > maxReward[i]){ reward = maxReward[i]; } rewardInfo[userAddr].top = rewardInfo[userAddr].top.add(reward); userInfo[userAddr].totalRevenue = userInfo[userAddr].totalRevenue.add(reward); totalReward = totalReward.add(reward); } } if(topPool > totalReward){ topPool = topPool.sub(totalReward); }else{ topPool = 0; } } function _distributeDeposit(uint256 _amount) private { uint256 fee = _amount.mul(feePercents).div(baseDivider); busd.transfer(feeReceivers[0], fee.div(2)); busd.transfer(feeReceivers[1], fee.div(2)); uint256 luck = _amount.mul(luckPoolPercents).div(baseDivider); luckPool = luckPool.add(luck); uint256 star = _amount.mul(starPoolPercents).div(baseDivider); starPool = starPool.add(star); uint256 top = _amount.mul(topPoolPercents).div(baseDivider); topPool = topPool.add(top); } function _updateReward(address _user, uint256 _amount) private { UserInfo storage user = userInfo[_user]; address upline = user.referrer; for(uint256 i = 0; i < referDepth; i++){ if(upline != address(0)){ uint256 newAmount = _amount; if(upline != defaultRefer){ uint256 maxFreezing = getMaxFreezing(upline); if(maxFreezing < _amount){ newAmount = maxFreezing; } } RewardInfo storage upRewards = rewardInfo[upline]; uint256 reward; if(i > 4){ if(userInfo[upline].level > 4){ reward = newAmount.mul(level5Percents[i - 5]).div(baseDivider); upRewards.level5Freezed = upRewards.level5Freezed.add(reward); } }else if(i > 0){ if( userInfo[upline].level > 3){ reward = newAmount.mul(level4Percents[i - 1]).div(baseDivider); upRewards.level4Freezed = upRewards.level4Freezed.add(reward); } }else{ reward = newAmount.mul(directPercents).div(baseDivider); upRewards.directs = upRewards.directs.add(reward); userInfo[upline].totalRevenue = userInfo[upline].totalRevenue.add(reward); } if(upline == defaultRefer) break; upline = userInfo[upline].referrer; }else{ break; } } } function _releaseUpRewards(address _user, uint256 _amount) private { UserInfo storage user = userInfo[_user]; address upline = user.referrer; for(uint256 i = 0; i < referDepth; i++){ if(upline != address(0)){ uint256 newAmount = _amount; if(upline != defaultRefer){ uint256 maxFreezing = getMaxFreezing(upline); if(maxFreezing < _amount){ newAmount = maxFreezing; } } RewardInfo storage upRewards = rewardInfo[upline]; if(i > 0 && i < 5 && userInfo[upline].level > 3){ if(upRewards.level4Freezed > 0){ uint256 level4Reward = newAmount.mul(level4Percents[i - 1]).div(baseDivider); if(level4Reward > upRewards.level4Freezed){ level4Reward = upRewards.level4Freezed; } upRewards.level4Freezed = upRewards.level4Freezed.sub(level4Reward); upRewards.level4Released = upRewards.level4Released.add(level4Reward); userInfo[upline].totalRevenue = userInfo[upline].totalRevenue.add(level4Reward); } } if(i >= 5 && userInfo[upline].level > 4){ if(upRewards.level5Left > 0){ uint256 level5Reward = newAmount.mul(level5Percents[i - 5]).div(baseDivider); if(level5Reward > upRewards.level5Left){ level5Reward = upRewards.level5Left; } upRewards.level5Left = upRewards.level5Left.sub(level5Reward); upRewards.level5Freezed = upRewards.level5Freezed.add(level5Reward); } } upline = userInfo[upline].referrer; }else{ break; } } } function _balActived(uint256 _bal) private { for(uint256 i = balDown.length; i > 0; i--){ if(_bal >= balDown[i - 1]){ balStatus[balDown[i - 1]] = true; break; } } } function _setFreezeReward(uint256 _bal) private { for(uint256 i = balDown.length; i > 0; i--){ if(balStatus[balDown[i - 1]]){ uint256 maxDown = balDown[i - 1].mul(balDownRate[i - 1]).div(baseDivider); if(_bal < balDown[i - 1].sub(maxDown)){ isFreezeReward = true; }else if(isFreezeReward && _bal >= balRecover[i - 1]){ isFreezeReward = false; } break; } } } }
After deployment, I interacted with this contract using Web3.js from the frontend.
βοΈ PHP Backend Integration
Even though Web3 is often associated with Node.js, I used PHP for its maturity and fast development cycle.
The backend handles:
Wallet signature verification
API endpoints for user interactions
Optional database logging
I used ext-curl to interact with blockchain nodes (like BSC RPC) and keccak256 libraries for signature verification.
π³ Dockerizing the App
β
Backend Dockerfile
FROM php:8.2-apache COPY . /var/www/html/ EXPOSE 80
β
Docker Compose Setup
version: '3.8' services: web: image: php:8.2-apache container_name: php-fizzyfy-container2 ports: - "8083:80" volumes: - ./:/var/www/html/
β
Docker Compose Setup (Local)
version: '3.8' services: php-backend: build: ./php-backend ports: - "8080:80" frontend: build: ./frontend ports: - "3000:3000" ganache: image: trufflesuite/ganache ports: - "8545:8545"
Run everything locally with:
docker-compose up -d
π Frontend Interaction with Web3.js
const web3 = new Web3(window.ethereum); async function connectWallet() { const accounts = await ethereum.request({ method: 'eth_requestAccounts' }); console.log('Connected account:', accounts[0]); }
The frontend connects to MetaMask and reads data from the deployed contract on BSC testnet.
π¦ Deployment Options
This app is fully containerized and can be deployed to:
- AWS EC2/ECS
- GCP Cloud Run
- DigitalOcean App Platform
- Or even Kubernetes using Helm charts
π οΈ Next Steps
- Integrate NFTs with ERC-721
- Add WalletConnect support
- CI/CD with GitHub Actions
π§ Lessons Learned
- PHP can still shine in Web3 projects when used wisely
- Docker ensures a consistent and portable development environment
- Binance Smart Chain is a fast, low-cost option for Ethereum-compatible dApps
π Letβs Connect
If you're building DApps and have a background in PHP or traditional stacks, Iβd love to connect and exchange ideas.
- πΌ LinkedIn: https://www.linkedin.com/in/surender-gupta/
- π» GitHub: https://github.com/surendergupta
Feel free to drop comments or suggestions below!
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.