diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..3ad5d59 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,33 @@ +PayrollFactory deployed to: 0xfe44aB0B966E57F126130BE6401546c7351484ad --mumbai +TokenTransferor deployed to: 0x21d762ab159676d3bd05e12A95699C1d0b043A48 --mumbai +Payroll deployed to: 0xDBAbe75848c608bDA3382f0D68219542032B3fEa --mumbai +All deployed from 0x9768818565ED5968fAACC6F66ca02CBf2785dB84 + +https://api-testnet.polygonscan.com/IEWN9BX92EIKVXR22UKQQ9A4UWEF5J9IRG + +There is no log out / wallet modal on Register Deploy +Need a rundown of how to log in with wallet and connect to mumbai chain without AA + +I shouldnt need to change the web3auth client id correct? + +deploy contract in register flow is stuck at getting account address... + +## 12/7 +Funkornaut@gmail -- 0xBB65877a1207572321dE0ad64A2e196545904a09 +trevfost503@gmail -- 0xfd330177602f092b72a3b65893602067Ab69cE2c +sign in with funkornaut through gmail -- 0x1aD394b0c57dbC57f16A5A54b4ccee436b678287 + +deployed new payroll 0xAB34603b0A8c54f9F9Fe9207b98da8ac354dB68e +- added function isOwner returns bool, owner() func on ownable works but since we then needed to look for bool on front end thought that would simplify from end logic / routing +- think that the LogIn flow is a little better + +Login.tsx +Web3 modal pops up when you hit login button - should only pop up if there is a contract address in the form + +Now logOut if funky +Cant log in with Metamask + +DeployForm +- just using useContractWrite was able to click button and make action got this error in browser console + next-dev.js:20 ConnectorNotFoundError: Connector not found at writeContract (chunk-TSH6VVF4.js:2348:1) + diff --git a/package.json b/package.json index c4d4ea4..7b46570 100644 --- a/package.json +++ b/package.json @@ -36,5 +36,8 @@ }, "resolutions": { "usehooks-ts@^2.7.2": "patch:usehooks-ts@npm:^2.7.2#./.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch" + }, + "dependencies": { + "@web3auth/web3auth-wagmi-connector": "^5.0.1" } } diff --git a/packages/hardhat/contracts/Payroll.sol b/packages/hardhat/contracts/Payroll.sol index a09a1f8..b4df6d1 100644 --- a/packages/hardhat/contracts/Payroll.sol +++ b/packages/hardhat/contracts/Payroll.sol @@ -527,8 +527,8 @@ ITokenTransferor public ccip; } } - function getOwner() public view returns (address){ - return owner(); + function isOwner(address _address) public view returns (bool){ + return _address == owner(); } function getEmployeePaymentSplits(address _employeeAddress) public view returns (PaymentSplit memory){ diff --git a/packages/hardhat/contracts/PayrollFactory.sol b/packages/hardhat/contracts/PayrollFactory.sol index 991e528..a738a9b 100644 --- a/packages/hardhat/contracts/PayrollFactory.sol +++ b/packages/hardhat/contracts/PayrollFactory.sol @@ -1,98 +1,121 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {TokenTransferor} from "./TokenTransferor.sol"; -import {Payroll} from "./Payroll.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; -import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; -import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; -import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/token/ERC20/IERC20.sol"; +import { TokenTransferor } from "./TokenTransferor.sol"; +import { Payroll } from "./Payroll.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; +import { OwnerIsCreator } from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; +import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; +import { IERC20 } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; contract PayrollFactory is Ownable { - // Set the deployment fee - uint256 public deployment_fee = 1 wei; // Example fee - - IRouterClient public router; - IERC20 public linkToken; - IERC20 public paymentToken; - - error InvalidAddress(); - error InsufficientFunds(uint256 amount); - - event TokenTransferorDeployed(address indexed deployer, address contractAddress); - event PayrollDeployed(address indexed deployer, address contractAddress, address tokenTransferorAddress); - - ///@param _router The address of the Chainlink Router for local chain - ///@param _linkToken The address of the LINK token for local chain - ///@param _paymentToken The address of the payment token for Payroll Contract on native chain - constructor(address _router, address _linkToken, address _paymentToken) { - router = IRouterClient(_router); - linkToken = IERC20(_linkToken); - paymentToken = IERC20(_paymentToken); - } - - ///@dev Deploys TokenTransferor and Payroll contracts - function deployPayrollAndTokenTransferor() external payable returns (address payrollAddress, address tokenTransferorAddress) { - if(msg.value < deployment_fee){ - revert InsufficientFunds(msg.value); - } - - tokenTransferorAddress = deployTokenTransferor(); - payrollAddress = deployPayroll(tokenTransferorAddress); - return (payrollAddress, tokenTransferorAddress); - } - - // Function to deploy TokenTransferor - ///@dev transfers ownership of TokenTransferor to caller of deployPayrollAndTokenTransferor - function deployTokenTransferor() internal returns (address) { - //require(msg.value >= deployment_fee, "Insufficient funds sent for deployment"); - - TokenTransferor newTokenTransferor = new TokenTransferor(address(router), address(linkToken)); - newTokenTransferor.transferOwnership(msg.sender); - emit TokenTransferorDeployed(msg.sender, address(newTokenTransferor)); - return address(newTokenTransferor); - } - - /// Function to deploy Payroll - ///@dev transfers ownership of Payroll to caller of deployPayrollAndTokenTransferor - ///@param tokenTransferorAddress The address of the newly deployed TokenTransferor contract - function deployPayroll(address tokenTransferorAddress) internal returns (address) { - //require(msg.value >= deployment_fee, "Insufficient funds sent for deployment"); - - Payroll newPayroll = new Payroll(tokenTransferorAddress, address(paymentToken)); - newPayroll.transferOwnership(msg.sender); // Transfer ownership - emit PayrollDeployed(msg.sender, address(newPayroll), tokenTransferorAddress); - return address(newPayroll); - } - - /// Functions to update deployment params so that they can be changed in the future or for deployment on other chains - function updateDeploymentFee(uint256 newFee) external onlyOwner { - deployment_fee = newFee; - } - - function changeRouter(address newRouter) external onlyOwner { - router = IRouterClient(newRouter); - } - - function changeLinkToken(address newLinkToken) external onlyOwner { - linkToken = IERC20(newLinkToken); - } - - function changePaymentToken(address newPaymentToken) external onlyOwner { - paymentToken = IERC20(newPaymentToken); - } - - // Fallback function to receive ETH - receive() external payable {} - - // Function to withdraw collected fees - function withdrawFees(address payable _beneficiary) public onlyOwner { - // Only contract owner should be able to withdraw fees - // Implement ownership and access control - if (_beneficiary == address(0)) { - revert InvalidAddress(); - } - _beneficiary.transfer(address(this).balance); - } + // Set the deployment fee + uint256 public deployment_fee = 1 wei; // Example fee + + IRouterClient public router; + IERC20 public linkToken; + IERC20 public paymentToken; + + error InvalidAddress(); + error InsufficientFunds(uint256 amount); + + event TokenTransferorDeployed( + address indexed deployer, + address contractAddress + ); + event PayrollDeployed( + address indexed deployer, + address contractAddress, + address tokenTransferorAddress + ); + + ///@param _router The address of the Chainlink Router for local chain + ///@param _linkToken The address of the LINK token for local chain + ///@param _paymentToken The address of the payment token for Payroll Contract on native chain + constructor(address _router, address _linkToken, address _paymentToken) { + router = IRouterClient(_router); + linkToken = IERC20(_linkToken); + paymentToken = IERC20(_paymentToken); + } + + ///@dev Deploys TokenTransferor and Payroll contracts + function deployPayrollAndTokenTransferor() + external + payable + returns (address payrollAddress, address tokenTransferorAddress) + { + if (msg.value < deployment_fee) { + revert InsufficientFunds(msg.value); + } + + tokenTransferorAddress = deployTokenTransferor(); + payrollAddress = deployPayroll(tokenTransferorAddress); + return (payrollAddress, tokenTransferorAddress); + } + + // Function to deploy TokenTransferor + ///@dev transfers ownership of TokenTransferor to caller of deployPayrollAndTokenTransferor + function deployTokenTransferor() internal returns (address) { + //require(msg.value >= deployment_fee, "Insufficient funds sent for deployment"); + + TokenTransferor newTokenTransferor = new TokenTransferor( + address(router), + address(linkToken) + ); + newTokenTransferor.transferOwnership(msg.sender); + emit TokenTransferorDeployed(msg.sender, address(newTokenTransferor)); + return address(newTokenTransferor); + } + + /// Function to deploy Payroll + ///@dev transfers ownership of Payroll to caller of deployPayrollAndTokenTransferor + ///@param tokenTransferorAddress The address of the newly deployed TokenTransferor contract + function deployPayroll( + address tokenTransferorAddress + ) internal returns (address) { + //require(msg.value >= deployment_fee, "Insufficient funds sent for deployment"); + + Payroll newPayroll = new Payroll( + tokenTransferorAddress, + address(paymentToken) + ); + newPayroll.transferOwnership(msg.sender); // Transfer ownership + emit PayrollDeployed( + msg.sender, + address(newPayroll), + tokenTransferorAddress + ); + return address(newPayroll); + } + + /// Functions to update deployment params so that they can be changed in the future or for deployment on other chains + function updateDeploymentFee(uint256 newFee) external onlyOwner { + deployment_fee = newFee; + } + + function changeRouter(address newRouter) external onlyOwner { + router = IRouterClient(newRouter); + } + + function changeLinkToken(address newLinkToken) external onlyOwner { + linkToken = IERC20(newLinkToken); + } + + function changePaymentToken(address newPaymentToken) external onlyOwner { + paymentToken = IERC20(newPaymentToken); + } + + // Fallback function to receive ETH + receive() external payable {} + + // Function to withdraw collected fees + function withdrawFees(address payable _beneficiary) public onlyOwner { + // Only contract owner should be able to withdraw fees + // Implement ownership and access control + if (_beneficiary == address(0)) { + revert InvalidAddress(); + } + _beneficiary.transfer(address(this).balance); + } } diff --git a/packages/hardhat/contracts/TokenTransferor.sol b/packages/hardhat/contracts/TokenTransferor.sol index 8b4c70b..5a2738d 100644 --- a/packages/hardhat/contracts/TokenTransferor.sol +++ b/packages/hardhat/contracts/TokenTransferor.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; -import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; -import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/token/ERC20/IERC20.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; +import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; +import { IERC20 } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; /** * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. @@ -14,236 +14,234 @@ import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-sol /// @title - A simple contract for transferring tokens across chains. contract TokenTransferor is Ownable { - // Custom errors to provide more descriptive revert messages. - error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees. - error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw. - error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails. - error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner. - error CallerNotWhiteListed(address caller); // Used when the caller is not whitelisted by the contract owner. - - // Event emitted when the tokens are transferred to an account on another chain. - event TokensTransferred( - bytes32 indexed messageId, // The unique ID of the message. - uint64 indexed destinationChainSelector, // The chain selector of the destination chain. - address receiver, // The address of the receiver on the destination chain. - address token, // The token address that was transferred. - uint256 tokenAmount, // The token amount that was transferred. - address feeToken, // the token address used to pay CCIP fees. - uint256 fees // The fees paid for sending the message. - ); - - // Mapping to keep track of allowlisted destination chains. - mapping(uint64 => bool) public allowlistedChains; - - // Mapping to keep track of whitelisted addressed that can call functions on this contract - mapping(address => bool) public whitelistedAddresses; - - // Mutable extra args - struct ExtraArgs { - uint256 gasLimit; - bool strict; - } - - ExtraArgs public currentExtraArgs; - - IRouterClient private s_router; - - IERC20 private s_linkToken; - - // @notice Constructor initializes the contract with the router address. - // @param _router The address of the router contract. - // @param _link The address of the link contract. - // mumbai router 0x70499c328e1e2a3c41108bd3730f6670a44595d1 - // mumbai LINK 0x326C977E6efc84E512bB9C30f76E30c160eD06FB - constructor(address _router, address _link) { - s_router = IRouterClient(_router); - s_linkToken = IERC20(_link); - - // Initialixe default values for ExtraArgs - currentExtraArgs = ExtraArgs ({ - gasLimit: 0, - strict: false - }); - } - - /// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted. - /// @param _destinationChainSelector The selector of the destination chain. - modifier onlyAllowlistedChain(uint64 _destinationChainSelector) { - if (!allowlistedChains[_destinationChainSelector]) - revert DestinationChainNotAllowlisted(_destinationChainSelector); - _; - } - - /// @dev Modifier that checks if the caller is allowlisted. - modifier onlyAllowlistedAddress() { - if (!whitelistedAddresses[msg.sender]) revert CallerNotWhiteListed(msg.sender); - _; - } - - /// @dev Updates the allowlist status of a destination chain for transactions. - /// @notice This function can only be called by the owner. - /// @param _destinationChainSelector The selector of the destination chain to be updated. - /// @param allowed The allowlist status to be set for the destination chain. - function allowlistDestinationChain( - uint64 _destinationChainSelector, - bool allowed - ) external onlyOwner { - allowlistedChains[_destinationChainSelector] = allowed; - } - - /// @notice Transfer tokens to receiver on the destination chain. - /// @notice pay in LINK. - /// @notice the token must be in the list of supported tokens. - /// @notice This function can only be called by allowlisted addresses. - /// @dev Assumes your contract has sufficient LINK tokens to pay for the fees. - /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain. - /// @param _receiver The address of the recipient on the destination blockchain. - /// @param _token token address. - /// @param _amount token amount. - /// @return messageId The ID of the message that was sent. - function transferTokensPayLINK( - uint64 _destinationChainSelector, - address _receiver, - address _token, - uint256 _amount - ) - external - onlyAllowlistedAddress - onlyAllowlistedChain(_destinationChainSelector) - returns (bytes32 messageId) - { - // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message - // address(linkToken) means fees are paid in LINK - Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage( - _receiver, - _token, - _amount, - address(s_linkToken) - ); - - // Get the fee required to send the message - uint256 fees = s_router.getFee( - _destinationChainSelector, - evm2AnyMessage - ); - - if (fees > s_linkToken.balanceOf(address(this))) - revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); - - // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK - s_linkToken.approve(address(s_router), fees); - - // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token - IERC20(_token).approve(address(s_router), _amount); - - // Send the message through the router and store the returned message ID - messageId = s_router.ccipSend( - _destinationChainSelector, - evm2AnyMessage - ); - - // Emit an event with message details - emit TokensTransferred( - messageId, - _destinationChainSelector, - _receiver, - _token, - _amount, - address(s_linkToken), - fees - ); - - // Return the message ID - return messageId; - } - - /// @notice Transfer tokens to receiver on the destination chain. - /// @notice Pay in native gas such as ETH on Ethereum or MATIC on Polgon. - /// @notice the token must be in the list of supported tokens. - /// @notice This function can only be called by allowlisted addresses. - /// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or MATIC on Polygon. - /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain. - /// @param _receiver The address of the recipient on the destination blockchain. - /// @param _token token address. - /// @param _amount token amount. - /// @return messageId The ID of the message that was sent. - function transferTokensPayNative( - uint64 _destinationChainSelector, - address _receiver, - address _token, - uint256 _amount - ) - external - onlyAllowlistedAddress - onlyAllowlistedChain(_destinationChainSelector) - returns (bytes32 messageId) - { - // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message - // address(0) means fees are paid in native gas - Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage( - _receiver, - _token, - _amount, - address(0) - ); - - // Get the fee required to send the message - uint256 fees = s_router.getFee( - _destinationChainSelector, - evm2AnyMessage - ); - - if (fees > address(this).balance) - revert NotEnoughBalance(address(this).balance, fees); - - // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token - IERC20(_token).approve(address(s_router), _amount); - - // Send the message through the router and store the returned message ID - messageId = s_router.ccipSend{value: fees}( - _destinationChainSelector, - evm2AnyMessage - ); - - // Emit an event with message details - emit TokensTransferred( - messageId, - _destinationChainSelector, - _receiver, - _token, - _amount, - address(0), - fees - ); - - // Return the message ID - return messageId; - } - - /// @notice Construct a CCIP message. - /// @dev This function will create an EVM2AnyMessage struct with all the necessary information for tokens transfer. - /// @param _receiver The address of the receiver. - /// @param _token The token to be transferred. - /// @param _amount The amount of the token to be transferred. - /// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas. - /// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message. - function _buildCCIPMessage( - address _receiver, - address _token, - uint256 _amount, - address _feeTokenAddress - ) internal view returns (Client.EVM2AnyMessage memory) { - // Set the token amounts - Client.EVMTokenAmount[] - memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({ - token: _token, - amount: _amount - }); - - // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message - - /** + // Custom errors to provide more descriptive revert messages. + error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees. + error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw. + error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails. + error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner. + error CallerNotWhiteListed(address caller); // Used when the caller is not whitelisted by the contract owner. + + // Event emitted when the tokens are transferred to an account on another chain. + event TokensTransferred( + bytes32 indexed messageId, // The unique ID of the message. + uint64 indexed destinationChainSelector, // The chain selector of the destination chain. + address receiver, // The address of the receiver on the destination chain. + address token, // The token address that was transferred. + uint256 tokenAmount, // The token amount that was transferred. + address feeToken, // the token address used to pay CCIP fees. + uint256 fees // The fees paid for sending the message. + ); + + // Mapping to keep track of allowlisted destination chains. + mapping(uint64 => bool) public allowlistedChains; + + // Mapping to keep track of whitelisted addressed that can call functions on this contract + mapping(address => bool) public whitelistedAddresses; + + // Mutable extra args + struct ExtraArgs { + uint256 gasLimit; + bool strict; + } + + ExtraArgs public currentExtraArgs; + + IRouterClient private s_router; + + IERC20 private s_linkToken; + + // @notice Constructor initializes the contract with the router address. + // @param _router The address of the router contract. + // @param _link The address of the link contract. + // mumbai router 0x70499c328e1e2a3c41108bd3730f6670a44595d1 + // mumbai LINK 0x326C977E6efc84E512bB9C30f76E30c160eD06FB + constructor(address _router, address _link) { + s_router = IRouterClient(_router); + s_linkToken = IERC20(_link); + + // Initialixe default values for ExtraArgs + currentExtraArgs = ExtraArgs({ gasLimit: 0, strict: false }); + } + + /// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted. + /// @param _destinationChainSelector The selector of the destination chain. + modifier onlyAllowlistedChain(uint64 _destinationChainSelector) { + if (!allowlistedChains[_destinationChainSelector]) + revert DestinationChainNotAllowlisted(_destinationChainSelector); + _; + } + + /// @dev Modifier that checks if the caller is allowlisted. + modifier onlyAllowlistedAddress() { + if (!whitelistedAddresses[msg.sender]) + revert CallerNotWhiteListed(msg.sender); + _; + } + + /// @dev Updates the allowlist status of a destination chain for transactions. + /// @notice This function can only be called by the owner. + /// @param _destinationChainSelector The selector of the destination chain to be updated. + /// @param allowed The allowlist status to be set for the destination chain. + function allowlistDestinationChain( + uint64 _destinationChainSelector, + bool allowed + ) external onlyOwner { + allowlistedChains[_destinationChainSelector] = allowed; + } + + /// @notice Transfer tokens to receiver on the destination chain. + /// @notice pay in LINK. + /// @notice the token must be in the list of supported tokens. + /// @notice This function can only be called by allowlisted addresses. + /// @dev Assumes your contract has sufficient LINK tokens to pay for the fees. + /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain. + /// @param _receiver The address of the recipient on the destination blockchain. + /// @param _token token address. + /// @param _amount token amount. + /// @return messageId The ID of the message that was sent. + function transferTokensPayLINK( + uint64 _destinationChainSelector, + address _receiver, + address _token, + uint256 _amount + ) + external + onlyAllowlistedAddress + onlyAllowlistedChain(_destinationChainSelector) + returns (bytes32 messageId) + { + // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message + // address(linkToken) means fees are paid in LINK + Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage( + _receiver, + _token, + _amount, + address(s_linkToken) + ); + + // Get the fee required to send the message + uint256 fees = s_router.getFee( + _destinationChainSelector, + evm2AnyMessage + ); + + if (fees > s_linkToken.balanceOf(address(this))) + revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); + + // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK + s_linkToken.approve(address(s_router), fees); + + // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token + IERC20(_token).approve(address(s_router), _amount); + + // Send the message through the router and store the returned message ID + messageId = s_router.ccipSend( + _destinationChainSelector, + evm2AnyMessage + ); + + // Emit an event with message details + emit TokensTransferred( + messageId, + _destinationChainSelector, + _receiver, + _token, + _amount, + address(s_linkToken), + fees + ); + + // Return the message ID + return messageId; + } + + /// @notice Transfer tokens to receiver on the destination chain. + /// @notice Pay in native gas such as ETH on Ethereum or MATIC on Polgon. + /// @notice the token must be in the list of supported tokens. + /// @notice This function can only be called by allowlisted addresses. + /// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or MATIC on Polygon. + /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain. + /// @param _receiver The address of the recipient on the destination blockchain. + /// @param _token token address. + /// @param _amount token amount. + /// @return messageId The ID of the message that was sent. + function transferTokensPayNative( + uint64 _destinationChainSelector, + address _receiver, + address _token, + uint256 _amount + ) + external + onlyAllowlistedAddress + onlyAllowlistedChain(_destinationChainSelector) + returns (bytes32 messageId) + { + // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message + // address(0) means fees are paid in native gas + Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage( + _receiver, + _token, + _amount, + address(0) + ); + + // Get the fee required to send the message + uint256 fees = s_router.getFee( + _destinationChainSelector, + evm2AnyMessage + ); + + if (fees > address(this).balance) + revert NotEnoughBalance(address(this).balance, fees); + + // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token + IERC20(_token).approve(address(s_router), _amount); + + // Send the message through the router and store the returned message ID + messageId = s_router.ccipSend{ value: fees }( + _destinationChainSelector, + evm2AnyMessage + ); + + // Emit an event with message details + emit TokensTransferred( + messageId, + _destinationChainSelector, + _receiver, + _token, + _amount, + address(0), + fees + ); + + // Return the message ID + return messageId; + } + + /// @notice Construct a CCIP message. + /// @dev This function will create an EVM2AnyMessage struct with all the necessary information for tokens transfer. + /// @param _receiver The address of the receiver. + /// @param _token The token to be transferred. + /// @param _amount The amount of the token to be transferred. + /// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas. + /// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message. + function _buildCCIPMessage( + address _receiver, + address _token, + uint256 _amount, + address _feeTokenAddress + ) internal view returns (Client.EVM2AnyMessage memory) { + // Set the token amounts + Client.EVMTokenAmount[] + memory tokenAmounts = new Client.EVMTokenAmount[](1); + tokenAmounts[0] = Client.EVMTokenAmount({ + token: _token, + amount: _amount + }); + + // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message + + /** * @todo * DO NOT HARDCODE EXTRAARGS @@ -251,90 +249,96 @@ contract TokenTransferor is Ownable { This allows you to build it off-chain and pass it in a call to a function or store it in a variable that you can update on-demand. This makes extraArgs compatible with future CCIP upgrades. */ - return - Client.EVM2AnyMessage({ - receiver: abi.encode(_receiver), // ABI-encoded receiver address - data: "", // No data - tokenAmounts: tokenAmounts, // The amount and type of token being transferred - extraArgs: Client._argsToBytes( - // Additional arguments, setting gas limit to 0 as we are not sending any data and non-strict sequencing mode - Client.EVMExtraArgsV1({ - gasLimit: currentExtraArgs.gasLimit, - strict: currentExtraArgs.strict - }) - ), - // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees - feeToken: _feeTokenAddress - }); - } - - /// @notice Fallback function to allow the contract to receive Ether. - /// @dev This function has no function body, making it a default function for receiving Ether. - /// It is automatically called when Ether is transferred to the contract without any data. - receive() external payable {} - - /// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract. - /// @dev This function reverts if there are no funds to withdraw or if the transfer fails. - /// It should only be callable by the owner of the contract. - /// @param _beneficiary The address to which the Ether should be transferred. - function withdraw(address _beneficiary) public onlyOwner { - // Retrieve the balance of this contract - uint256 amount = address(this).balance; - - // Revert if there is nothing to withdraw - if (amount == 0) revert NothingToWithdraw(); - - // Attempt to send the funds, capturing the success status and discarding any return data - (bool sent, ) = _beneficiary.call{value: amount}(""); - - // Revert if the send failed, with information about the attempted transfer - if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount); - } - - /// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token. - /// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw. - /// @param _beneficiary The address to which the tokens will be sent. - /// @param _token The contract address of the ERC20 token to be withdrawn. - function withdrawToken( - address _beneficiary, - address _token - ) public onlyOwner { - // Retrieve the balance of this contract - uint256 amount = IERC20(_token).balanceOf(address(this)); - - // Revert if there is nothing to withdraw - if (amount == 0) revert NothingToWithdraw(); - - IERC20(_token).transfer(_beneficiary, amount); - } - - function updateExtraArgs(uint256 _gasLimit, bool _strict) public onlyOwner { - currentExtraArgs.gasLimit = _gasLimit; - currentExtraArgs.strict = _strict; - } - - function getExtraArgs() public view returns (uint256, bool) { - return (currentExtraArgs.gasLimit, currentExtraArgs.strict); - } - - function addToWhitelist(address _address) public onlyOwner { - whitelistedAddresses[_address] = true; - } - - function removeFromWhitelist(address _address) public onlyOwner { - whitelistedAddresses[_address] = false; - } - - function isWhitelisted(address _address) public view returns (bool) { - return whitelistedAddresses[_address]; - } - - function isChainIdAllowed(uint64 _chainId) public view returns (bool) { - return allowlistedChains[_chainId]; - } - - function transferOwnership(address newOwner) public override onlyOwner { - require(newOwner != address(0), "Ownable: new owner is the zero address"); - _transferOwnership(newOwner); - } + return + Client.EVM2AnyMessage({ + receiver: abi.encode(_receiver), // ABI-encoded receiver address + data: "", // No data + tokenAmounts: tokenAmounts, // The amount and type of token being transferred + extraArgs: Client._argsToBytes( + // Additional arguments, setting gas limit to 0 as we are not sending any data and non-strict sequencing mode + Client.EVMExtraArgsV1({ + gasLimit: currentExtraArgs.gasLimit + }) + // Client.EVMExtraArgsV1({ + // gasLimit: currentExtraArgs.gasLimit, + // strict: currentExtraArgs.strict + // }) + ), + // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees + feeToken: _feeTokenAddress + }); + } + + /// @notice Fallback function to allow the contract to receive Ether. + /// @dev This function has no function body, making it a default function for receiving Ether. + /// It is automatically called when Ether is transferred to the contract without any data. + receive() external payable {} + + /// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract. + /// @dev This function reverts if there are no funds to withdraw or if the transfer fails. + /// It should only be callable by the owner of the contract. + /// @param _beneficiary The address to which the Ether should be transferred. + function withdraw(address _beneficiary) public onlyOwner { + // Retrieve the balance of this contract + uint256 amount = address(this).balance; + + // Revert if there is nothing to withdraw + if (amount == 0) revert NothingToWithdraw(); + + // Attempt to send the funds, capturing the success status and discarding any return data + (bool sent, ) = _beneficiary.call{ value: amount }(""); + + // Revert if the send failed, with information about the attempted transfer + if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount); + } + + /// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token. + /// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw. + /// @param _beneficiary The address to which the tokens will be sent. + /// @param _token The contract address of the ERC20 token to be withdrawn. + function withdrawToken( + address _beneficiary, + address _token + ) public onlyOwner { + // Retrieve the balance of this contract + uint256 amount = IERC20(_token).balanceOf(address(this)); + + // Revert if there is nothing to withdraw + if (amount == 0) revert NothingToWithdraw(); + + IERC20(_token).transfer(_beneficiary, amount); + } + + function updateExtraArgs(uint256 _gasLimit, bool _strict) public onlyOwner { + currentExtraArgs.gasLimit = _gasLimit; + currentExtraArgs.strict = _strict; + } + + function getExtraArgs() public view returns (uint256, bool) { + return (currentExtraArgs.gasLimit, currentExtraArgs.strict); + } + + function addToWhitelist(address _address) public onlyOwner { + whitelistedAddresses[_address] = true; + } + + function removeFromWhitelist(address _address) public onlyOwner { + whitelistedAddresses[_address] = false; + } + + function isWhitelisted(address _address) public view returns (bool) { + return whitelistedAddresses[_address]; + } + + function isChainIdAllowed(uint64 _chainId) public view returns (bool) { + return allowlistedChains[_chainId]; + } + + function transferOwnership(address newOwner) public override onlyOwner { + require( + newOwner != address(0), + "Ownable: new owner is the zero address" + ); + _transferOwnership(newOwner); + } } diff --git a/packages/hardhat/deploy/03_deploy_payroll.ts b/packages/hardhat/deploy/03_deploy_payroll.ts index a0980ad..ca1c6a2 100644 --- a/packages/hardhat/deploy/03_deploy_payroll.ts +++ b/packages/hardhat/deploy/03_deploy_payroll.ts @@ -6,7 +6,7 @@ async function main() { console.log("Deploying contracts with the account:", deployer.address); // Add the addresses of the dependencies of your Payroll contract - const ccipTokenTransferorAddress = "0xFFc15127eB60C4Ab1a2e5e0319615ca0982952f2"; // deployed via script + const ccipTokenTransferorAddress = "0x21d762ab159676d3bd05e12A95699C1d0b043A48"; // deployed via script const bnmTokenAddress = "0xf1E3A5842EeEF51F2967b3F05D45DD4f4205FF40"; //BnM Mumbai // Deploy the Payroll contract diff --git a/packages/hardhat/package.json b/packages/hardhat/package.json index d630720..3b72ce7 100644 --- a/packages/hardhat/package.json +++ b/packages/hardhat/package.json @@ -46,6 +46,8 @@ "typescript": "^5.1.6" }, "dependencies": { + "@chainlink/contracts": "^0.8.0", + "@chainlink/contracts-ccip": "^1.2.1", "@openzeppelin/contracts": "^4.8.1", "dotenv": "^16.0.3", "envfile": "^6.18.0", diff --git a/packages/nextjs/.env.example b/packages/nextjs/.env.example index 6769588..47c95bd 100644 --- a/packages/nextjs/.env.example +++ b/packages/nextjs/.env.example @@ -12,7 +12,8 @@ NEXT_PUBLIC_ALCHEMY_API_KEY= NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID= -NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS="" # Polygon Mumbai -NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS="" # Polygon Mumbai +NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS= # Polygon Mumbai +NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS= # Polygon Mumbai +NEXT_PUBLIC_TOKEN_TRANSFEROR_CONTRACT_ADDRESS= # Polygon Mumbai NEXT_PUBLIC_LOCAL_CHAIN_ID=0 # REPLACE WITH LOCAL DEV CHAIN NEXT_PUBLIC_TESTNET_CHAIN_ID=80001 # Polygon Mumbai diff --git a/packages/nextjs/.gitignore b/packages/nextjs/.gitignore index 6985d55..16dad0e 100644 --- a/packages/nextjs/.gitignore +++ b/packages/nextjs/.gitignore @@ -26,6 +26,7 @@ yarn-error.log* .pnpm-debug.log* # local env files +.env .env.local .env.development.local .env.test.local diff --git a/packages/nextjs/auth/web3auth.ts b/packages/nextjs/auth/web3auth.ts index f6da7db..b6713f3 100644 --- a/packages/nextjs/auth/web3auth.ts +++ b/packages/nextjs/auth/web3auth.ts @@ -1,11 +1,28 @@ // import { useEffect, useState } from "react"; import { Web3Auth } from "@web3auth/modal"; +//import { Web3AuthConnector } from "@web3auth/web3auth-wagmi-connector"; + // import { createWalletClient, custom } from "viem"; // import { polygonMumbai } from "viem/chains"; // import { AuthProvider, setAuthProvider, setIsConnected } from "~~/auth/authSlice"; // import { MyState, useMyDispatch, useMySelector } from "~~/components/dash-wind/app/store"; +// set up your web3auth instance with all the features you want +// const web3AuthInstance = new Web3Auth({ +// clientId: "BM0SLNkhMCfIygw0Xi79dG6qbWGMN0o0mEeDjRT0dxlP3BEok9pnu5aqxCNfj2TZ9XT7sQaXm0ltuWbCQ1tsRNI", +// chainConfig: { +// chainNamespace: "eip155", +// chainId: "0x13881", // Mumbai +// rpcTarget: "https://rpc.ankr.com/polygon_mumbai", +// displayName: "Mumbai Testnet", +// blockExplorer: "https://mumbai.polygonscan.com/", +// ticker: "MATIC", +// tickerName: "Polygon", +// }, +// web3AuthNetwork: "sapphire_mainnet", +// }); + export const web3auth = new Web3Auth({ clientId: "BM0SLNkhMCfIygw0Xi79dG6qbWGMN0o0mEeDjRT0dxlP3BEok9pnu5aqxCNfj2TZ9XT7sQaXm0ltuWbCQ1tsRNI", // Get your Client ID from the Web3Auth Dashboard web3AuthNetwork: "sapphire_devnet", // Web3Auth Network diff --git a/packages/nextjs/components/dash-wind/containers/Header.tsx b/packages/nextjs/components/dash-wind/containers/Header.tsx index 20d32ac..8ea2c1d 100644 --- a/packages/nextjs/components/dash-wind/containers/Header.tsx +++ b/packages/nextjs/components/dash-wind/containers/Header.tsx @@ -5,6 +5,8 @@ import { useRouter } from "next/router"; import { openRightDrawer } from "../features/common/rightDrawerSlice"; import { RIGHT_DRAWER_TYPES } from "../utils/globalConstantUtil"; import { themeChange } from "theme-change"; +import { Address, createWalletClient, custom } from "viem"; +import { polygonMumbai } from "viem/chains"; import Bars3Icon from "@heroicons/react/24/outline/Bars3Icon"; import BellIcon from "@heroicons/react/24/outline/BellIcon"; import MoonIcon from "@heroicons/react/24/outline/MoonIcon"; @@ -13,7 +15,7 @@ import { setIsAdmin, setIsConnected } from "~~/auth/authSlice"; import { web3auth } from "~~/auth/web3auth"; // import UserIcon from "@heroicons/react/24/outline/UserIcon"; import { MyState, useMyDispatch, useMySelector } from "~~/components/dash-wind/app/store"; -import { Address } from "~~/components/web-3-crew/Address"; +import { Address as AddressDisplay } from "~~/components/web-3-crew/Address"; function Header() { const dispatch = useMyDispatch(); @@ -22,8 +24,19 @@ function Header() { const [currentTheme, setCurrentTheme] = useState( typeof window !== "undefined" ? localStorage.getItem("theme") : null, ); + const [address, setAddress] = useState
(null); const router = useRouter(); + useEffect(() => { + async function getAddress() { + const address = await getAccounts(); + if (address) { + setAddress(address); + } + } + getAddress(); + }, []); + useEffect(() => { themeChange(false); if (currentTheme === null) { @@ -36,6 +49,23 @@ function Header() { // 👆 false parameter is required for react project }, [currentTheme]); + async function getAccounts() { + if (!web3auth.provider) { + console.log("from login - getAccounts: provider not defined"); + return; + } + const client = createWalletClient({ + // account: privateKeyToAccount('0x...'); // from viem + chain: polygonMumbai, + transport: custom(web3auth.provider), + }); + + // Get user's public address + const [address] = await client.getAddresses(); + //console.log("user address: ", address); + return address as Address; + } + // Opening right sidebar for notification const openNotification = () => { dispatch(openRightDrawer({ header: "Notifications", bodyType: RIGHT_DRAWER_TYPES.NOTIFICATION })); @@ -107,12 +137,7 @@ function Header() {