From 9b43cc3cba584109488b44a76e853b87c3f18249 Mon Sep 17 00:00:00 2001 From: daveroga Date: Thu, 18 Dec 2025 17:44:23 +0100 Subject: [PATCH 1/5] add withdrawer role to allow withdraw --- contracts/payment/MCPayment.sol | 53 +++++++++++++++++++++---- test/payment/mc-payment.test.ts | 70 ++++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 10 deletions(-) diff --git a/contracts/payment/MCPayment.sol b/contracts/payment/MCPayment.sol index 680baf54..6bc31ba9 100644 --- a/contracts/payment/MCPayment.sol +++ b/contracts/payment/MCPayment.sol @@ -8,11 +8,17 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; /** * @dev MCPayment multi-chain payment contract */ -contract MCPayment is Ownable2StepUpgradeable, EIP712Upgradeable, ReentrancyGuardUpgradeable { +contract MCPayment is + Ownable2StepUpgradeable, + EIP712Upgradeable, + ReentrancyGuardUpgradeable, + AccessControlUpgradeable +{ using ECDSA for bytes32; using SafeERC20 for IERC20; /** @@ -43,6 +49,11 @@ contract MCPayment is Ownable2StepUpgradeable, EIP712Upgradeable, ReentrancyGuar "Iden3PaymentRailsERC20RequestV1(address tokenAddress,address recipient,uint256 amount,uint256 expirationDate,uint256 nonce,bytes metadata)" ); + /** + * @dev Withdrawer role to withdraw contract amount + */ + bytes32 public constant WITHDRAWER_ROLE = keccak256("WITHDRAWER_ROLE"); + struct Iden3PaymentRailsRequestV1 { address recipient; uint256 amount; @@ -117,6 +128,19 @@ contract MCPayment is Ownable2StepUpgradeable, EIP712Upgradeable, ReentrancyGuar _; } + /** + * @dev Modifier to make a function callable only by a certain role. In + * addition to checking the sender's role, `address(0)` 's role is also + * considered. Granting a role to `address(0)` is equivalent to enabling + * this role for everyone. + */ + modifier onlyWithdrawerRoleOrOwner() { + if (_msgSender() != owner()) { + _checkRole(WITHDRAWER_ROLE, _msgSender()); + } + _; + } + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); @@ -138,6 +162,15 @@ contract MCPayment is Ownable2StepUpgradeable, EIP712Upgradeable, ReentrancyGuar __ReentrancyGuard_init(); } + /** + * @dev Set admin role to an account + * @param admin Address to be granted admin role + */ + function setAdminRole(address admin) external onlyOwner { + // Grant admin role. Admin role can grant and revoke other roles like WITHDRAWER_ROLE + _grantRole(DEFAULT_ADMIN_ROLE, admin); + } + /** * @dev Get the owner percentage value * @return ownerPercentage @@ -375,7 +408,7 @@ contract MCPayment is Ownable2StepUpgradeable, EIP712Upgradeable, ReentrancyGuar * @dev Get owner balance * @return balance of owner */ - function getOwnerBalance() public view onlyOwner returns (uint256) { + function getOwnerBalance() public view onlyWithdrawerRoleOrOwner returns (uint256) { MCPaymentStorage storage $ = _getMCPaymentStorage(); return $.ownerBalance; } @@ -384,7 +417,9 @@ contract MCPayment is Ownable2StepUpgradeable, EIP712Upgradeable, ReentrancyGuar * @dev Get owner ERC-20 balance * @return balance of owner */ - function getOwnerERC20Balance(address token) public view onlyOwner returns (uint256) { + function getOwnerERC20Balance( + address token + ) public view onlyWithdrawerRoleOrOwner returns (uint256) { return IERC20(token).balanceOf(address(this)); } @@ -398,26 +433,26 @@ contract MCPayment is Ownable2StepUpgradeable, EIP712Upgradeable, ReentrancyGuar /** * @dev Withdraw balance to owner */ - function ownerWithdraw() public onlyOwner nonReentrant { + function ownerWithdraw() public onlyWithdrawerRoleOrOwner nonReentrant { MCPaymentStorage storage $ = _getMCPaymentStorage(); if ($.ownerBalance == 0) { revert WithdrawErrorNoBalance(); } uint256 amount = $.ownerBalance; $.ownerBalance = 0; - _withdraw(amount, owner()); + _withdraw(amount, _msgSender()); } /** * @dev Withdraw ERC-20 balance to owner */ - function ownerERC20Withdraw(address token) public onlyOwner nonReentrant { + function ownerERC20Withdraw(address token) public onlyWithdrawerRoleOrOwner nonReentrant { uint256 amount = IERC20(token).balanceOf(address(this)); if (amount == 0) { revert WithdrawErrorNoBalance(); } - IERC20(token).transfer(owner(), amount); + IERC20(token).transfer(_msgSender(), amount); } function _recoverERC20PaymentSignature( @@ -455,7 +490,9 @@ contract MCPayment is Ownable2StepUpgradeable, EIP712Upgradeable, ReentrancyGuar uint8 ownerPercentage = getIssuerOwnerPercentage(paymentData.recipient); uint256 ownerPart = (paymentData.amount * ownerPercentage) / 100; uint256 issuerPart = paymentData.amount - ownerPart; - token.transfer(paymentData.recipient, issuerPart); + if (issuerPart > 0) { + token.transfer(paymentData.recipient, issuerPart); + } emit Payment(signer, paymentData.nonce); bytes32 paymentId = keccak256(abi.encode(signer, paymentData.nonce)); $.isPaid[paymentId] = true; diff --git a/test/payment/mc-payment.test.ts b/test/payment/mc-payment.test.ts index 52214b2e..33a42223 100644 --- a/test/payment/mc-payment.test.ts +++ b/test/payment/mc-payment.test.ts @@ -177,6 +177,72 @@ describe("MC Payment Contract", () => { ); }); + it("Check payment with WITHDRAWER_ROLE account", async () => { + const paymentData = { + recipient: issuer1Signer.address, + amount: 100, + expirationDate: Math.round(new Date().getTime() / 1000) + 60 * 60, // 1 hour + nonce: 25, + metadata: "0x", + }; + const signature = await issuer1Signer.signTypedData(domainData, types, paymentData); + + await expect( + payment.connect(userSigner).pay(paymentData, signature, { + value: 100, + }), + ).to.changeEtherBalances([userSigner, payment], [-100, 100]); + + const isPaymentDone = await payment.isPaymentDone(issuer1Signer.address, 25); + expect(isPaymentDone).to.be.true; + + // issuer withdraw + const issuer1BalanceInContract = await payment.getBalance(issuer1Signer.address); + expect(issuer1BalanceInContract).to.be.eq(90); + + await expect(payment.connect(issuer1Signer).issuerWithdraw()).to.changeEtherBalance( + issuer1Signer, + 90, + ); + + // second issuer withdraw + await expect(payment.connect(issuer1Signer).issuerWithdraw()).to.be.revertedWithCustomError( + payment, + "WithdrawErrorNoBalance", + ); + + const issuer1BalanceAfterWithdraw = await payment.getBalance(issuer1Signer.address); + expect(issuer1BalanceAfterWithdraw).to.be.eq(0); + + // owner withdraw + const ownerBalanceInContract = await payment.connect(owner).getOwnerBalance(); + expect(ownerBalanceInContract).to.be.eq(10); + + await expect(payment.connect(userSigner).ownerWithdraw()).to.be.revertedWithCustomError( + payment, + "AccessControlUnauthorizedAccount", + ); + + // grant admin role to owner + await payment.connect(owner).setAdminRole(owner.address); + // grant WITHDRAWER_ROLE to userSigner + await payment + .connect(owner) + .grantRole(await payment.WITHDRAWER_ROLE(), userSigner.getAddress()); + + // now userSigner can withdraw owner balance + await expect(payment.connect(userSigner).ownerWithdraw()).to.changeEtherBalance(userSigner, 10); + // owner balance should be 0 + const ownerBalanceAfterWithdraw = await payment.connect(owner).getOwnerBalance(); + expect(ownerBalanceAfterWithdraw).to.be.eq(0); + + // second owner withdraw + await expect(payment.connect(userSigner).ownerWithdraw()).to.be.revertedWithCustomError( + payment, + "WithdrawErrorNoBalance", + ); + }); + it("Update owner percentage:", async () => { expect(await payment.getOwnerPercentage()).to.be.eq(10); await payment.connect(owner).updateOwnerPercentage(20); @@ -208,10 +274,10 @@ describe("MC Payment Contract", () => { ).to.be.revertedWithCustomError(payment, "OwnableUnauthorizedAccount"); }); - it("Owner withdraw not owner account:", async () => { + it("Owner withdraw not owner or WITHDRAWER_ROLE account:", async () => { await expect(payment.connect(issuer1Signer).ownerWithdraw()).to.be.revertedWithCustomError( payment, - "OwnableUnauthorizedAccount", + "AccessControlUnauthorizedAccount", ); }); From b599e2386121edcba8da3db89565157aec4ff87f Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 19 Dec 2025 17:44:39 +0100 Subject: [PATCH 2/5] add integration test --- contracts/payment/MCPayment.sol | 17 ++-- helpers/constants.ts | 2 +- scripts/integration/mcpayment-payment.ts | 76 +++++++++++++++ test/payment/mc-payment.test.ts | 117 ++++++++++++++++++++++- 4 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 scripts/integration/mcpayment-payment.ts diff --git a/contracts/payment/MCPayment.sol b/contracts/payment/MCPayment.sol index 6bc31ba9..8959fdba 100644 --- a/contracts/payment/MCPayment.sol +++ b/contracts/payment/MCPayment.sol @@ -24,7 +24,7 @@ contract MCPayment is /** * @dev Version of contract */ - string public constant VERSION = "1.0.5"; + string public constant VERSION = "1.0.6"; /** * @dev Version of EIP 712 domain @@ -129,10 +129,8 @@ contract MCPayment is } /** - * @dev Modifier to make a function callable only by a certain role. In - * addition to checking the sender's role, `address(0)` 's role is also - * considered. Granting a role to `address(0)` is equivalent to enabling - * this role for everyone. + * @dev Modifier to make a function callable only by a Withdrawer role + * or the owner. */ modifier onlyWithdrawerRoleOrOwner() { if (_msgSender() != owner()) { @@ -160,6 +158,7 @@ contract MCPayment is __Ownable_init(owner); __EIP712_init("MCPayment", DOMAIN_VERSION); __ReentrancyGuard_init(); + __AccessControl_init(); } /** @@ -405,7 +404,7 @@ contract MCPayment is } /** - * @dev Get owner balance + * @dev Get owner balance from owner or withdrawer role * @return balance of owner */ function getOwnerBalance() public view onlyWithdrawerRoleOrOwner returns (uint256) { @@ -414,7 +413,7 @@ contract MCPayment is } /** - * @dev Get owner ERC-20 balance + * @dev Get owner ERC-20 balance from owner or withdrawer role * @return balance of owner */ function getOwnerERC20Balance( @@ -431,7 +430,7 @@ contract MCPayment is } /** - * @dev Withdraw balance to owner + * @dev Withdraw balance to owner or withdrawer role */ function ownerWithdraw() public onlyWithdrawerRoleOrOwner nonReentrant { MCPaymentStorage storage $ = _getMCPaymentStorage(); @@ -444,7 +443,7 @@ contract MCPayment is } /** - * @dev Withdraw ERC-20 balance to owner + * @dev Withdraw ERC-20 balance to owner or withdrawer role */ function ownerERC20Withdraw(address token) public onlyWithdrawerRoleOrOwner nonReentrant { uint256 amount = IERC20(token).balanceOf(address(this)); diff --git a/helpers/constants.ts b/helpers/constants.ts index 39f79d2c..52aba92a 100644 --- a/helpers/constants.ts +++ b/helpers/constants.ts @@ -312,7 +312,7 @@ export const contractsInfo = Object.freeze({ }, MC_PAYMENT: { name: "MCPayment", - version: "1.0.5", + version: "1.0.6", unifiedAddress: "0xe317A4f1450116b2fD381446DEaB41c882D6136D", create2Calldata: ethers.hexlify(ethers.toUtf8Bytes("iden3.create2.MCPayment")), verificationOpts: { diff --git a/scripts/integration/mcpayment-payment.ts b/scripts/integration/mcpayment-payment.ts new file mode 100644 index 00000000..2703cd2b --- /dev/null +++ b/scripts/integration/mcpayment-payment.ts @@ -0,0 +1,76 @@ +import { ethers } from "hardhat"; + +async function main() { + const mcPaymentAddress = "0xYourMCPaymentContractAddressHere"; // replace with actual deployed contract address + const tokenAddress = "0xYourTokenAddressHere"; // replace with actual token address + const issuerPrivateKey = "0xYourIssuerPrivateKeyHere"; // replace with actual issuer private key + const amount = 1000; // amount to be paid + + const issuerWallet = new ethers.Wallet(issuerPrivateKey); + + const [signer] = await ethers.getSigners(); + const token = await ethers.getContractAt("ERC20Token", tokenAddress); + const signerBalance = await token.balanceOf(signer.address); + console.log( + `Balance ${signer.address}: ${ethers.formatEther(signerBalance)} ${await token.symbol()}`, + ); + + const payment = await ethers.getContractAt("MCPayment", mcPaymentAddress); + + const domainData = { + name: "MCPayment", + version: "1.0.0", + chainId: 31337, + verifyingContract: await payment.getAddress(), + }; + + const erc20types = { + Iden3PaymentRailsERC20RequestV1: [ + { name: "tokenAddress", type: "address" }, + { name: "recipient", type: "address" }, + { name: "amount", type: "uint256" }, + { name: "expirationDate", type: "uint256" }, + { name: "nonce", type: "uint256" }, + { name: "metadata", type: "bytes" }, + ], + }; + + const paymentData = { + tokenAddress: tokenAddress, + recipient: issuerWallet.address, + amount: amount, + expirationDate: Math.round(new Date().getTime() / 1000) + 60 * 60, // 1 hour + nonce: 35, + metadata: "0x", + }; + const signature = await issuerWallet.signTypedData(domainData, erc20types, paymentData); + + // approve MCPayment contract to spend tokens + const txApprove = await token.connect(signer).approve(await payment.getAddress(), amount); + await txApprove.wait(); + console.log( + `${signer.address} approved ${amount} ${await token.symbol()} for MCPayment contract at ${mcPaymentAddress}`, + ); + + const erc20PaymentGas = await payment + .connect(signer) + .payERC20.estimateGas(paymentData, signature); + console.log("ERC-20 Payment Gas: " + erc20PaymentGas); + + let ownerBalance = await payment.getOwnerERC20Balance(tokenAddress); + console.log(`Owner ERC-20 Balance before payment: ${ownerBalance}`); + + const tx = await payment.connect(signer).payERC20(paymentData, signature); + console.log("ERC-20 Payment Tx Hash: " + tx.hash); + await tx.wait(); + + ownerBalance = await payment.getOwnerERC20Balance(tokenAddress); + console.log(`Owner ERC-20 Balance after payment: ${ownerBalance}`); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/test/payment/mc-payment.test.ts b/test/payment/mc-payment.test.ts index 33a42223..fa457bd0 100644 --- a/test/payment/mc-payment.test.ts +++ b/test/payment/mc-payment.test.ts @@ -228,7 +228,7 @@ describe("MC Payment Contract", () => { // grant WITHDRAWER_ROLE to userSigner await payment .connect(owner) - .grantRole(await payment.WITHDRAWER_ROLE(), userSigner.getAddress()); + .grantRole(await payment.WITHDRAWER_ROLE(), await userSigner.getAddress()); // now userSigner can withdraw owner balance await expect(payment.connect(userSigner).ownerWithdraw()).to.changeEtherBalance(userSigner, 10); @@ -241,6 +241,24 @@ describe("MC Payment Contract", () => { payment, "WithdrawErrorNoBalance", ); + await expect(payment.connect(owner).ownerWithdraw()).to.be.revertedWithCustomError( + payment, + "WithdrawErrorNoBalance", + ); + }); + + it("Calling setAdminRole by owner:", async () => { + expect(await payment.hasRole(await payment.DEFAULT_ADMIN_ROLE(), owner.address)).to.be.false; + await payment.connect(owner).setAdminRole(owner.address); + expect(await payment.hasRole(await payment.DEFAULT_ADMIN_ROLE(), owner.address)).to.be.true; + await payment.connect(owner).revokeRole(await payment.DEFAULT_ADMIN_ROLE(), owner.address); + expect(await payment.hasRole(await payment.DEFAULT_ADMIN_ROLE(), owner.address)).to.be.false; + }); + + it("Calling setAdminRole by non-owner:", async () => { + await expect( + payment.connect(userSigner).setAdminRole(owner.address), + ).to.be.revertedWithCustomError(payment, "OwnableUnauthorizedAccount"); }); it("Update owner percentage:", async () => { @@ -391,6 +409,62 @@ describe("MC Payment Contract", () => { expect(await payment.getOwnerERC20Balance(tokenAddress)).to.be.eq(0); }); + it("ERC-20 payment with withdrawer role:", async () => { + const tokenFactory = await ethers.getContractFactory("ERC20Token", owner); + const token = await tokenFactory.deploy(1_000); + await token.connect(owner).transfer(await userSigner.getAddress(), 100); + expect(await token.balanceOf(await userSigner.getAddress())).to.be.eq(100); + + await token.connect(userSigner).approve(await payment.getAddress(), 10); + + const paymentData = { + tokenAddress: await token.getAddress(), + recipient: issuer1Signer.address, + amount: 10, + expirationDate: Math.round(new Date().getTime() / 1000) + 60 * 60, // 1 hour + nonce: 35, + metadata: "0x", + }; + + const signature = await issuer1Signer.signTypedData(domainData, erc20types, paymentData); + const erc20PaymentGas = await payment + .connect(userSigner) + .payERC20.estimateGas(paymentData, signature); + console.log("ERC-20 Payment Gas: " + erc20PaymentGas); + + await expect( + payment.connect(userSigner).payERC20(paymentData, signature), + ).to.changeTokenBalances(token, [userSigner, issuer1Signer, payment], [-10, 9, 1]); + expect(await payment.isPaymentDone(issuer1Signer.address, 35)).to.be.true; + // owner ERC-20 withdraw + const tokenAddress = await token.getAddress(); + const ownerBalance = await payment.getOwnerERC20Balance(tokenAddress); + expect(ownerBalance).to.be.eq(1); + + await expect( + payment.connect(userSigner).ownerERC20Withdraw(tokenAddress), + ).to.be.revertedWithCustomError(payment, "AccessControlUnauthorizedAccount"); + + // grant admin role to owner + await payment.connect(owner).setAdminRole(owner.address); + // grant WITHDRAWER_ROLE to userSigner + await payment.grantRole(await payment.WITHDRAWER_ROLE(), await userSigner.getAddress()); + + await expect( + payment.connect(userSigner).ownerERC20Withdraw(tokenAddress), + ).to.changeTokenBalances(token, [userSigner, payment], [1, -1]); + + // second owner or withdrawer withdraw + await expect( + payment.connect(userSigner).ownerERC20Withdraw(tokenAddress), + ).to.be.revertedWithCustomError(payment, "WithdrawErrorNoBalance"); + + await expect( + payment.connect(owner).ownerERC20Withdraw(tokenAddress), + ).to.be.revertedWithCustomError(payment, "WithdrawErrorNoBalance"); + expect(await payment.getOwnerERC20Balance(tokenAddress)).to.be.eq(0); + }); + it("ERC-20 payment with different issuer owner percentage:", async () => { const tokenFactory = await ethers.getContractFactory("ERC20Token", owner); const token = await tokenFactory.deploy(1_000); @@ -432,6 +506,47 @@ describe("MC Payment Contract", () => { expect(await payment.getOwnerERC20Balance(tokenAddress)).to.be.eq(0); }); + it("ERC-20 payment with different issuer owner percentage 100%:", async () => { + const tokenFactory = await ethers.getContractFactory("ERC20Token", owner); + const token = await tokenFactory.deploy(1_000); + await token.connect(owner).transfer(await userSigner.getAddress(), 100); + expect(await token.balanceOf(await userSigner.getAddress())).to.be.eq(100); + + await token.connect(userSigner).approve(await payment.getAddress(), 10); + + await payment.connect(owner).updateIssuerOwnerPercentage(issuer1Signer.address, 100); + + const paymentData = { + tokenAddress: await token.getAddress(), + recipient: issuer1Signer.address, + amount: 10, + expirationDate: Math.round(new Date().getTime() / 1000) + 60 * 60, // 1 hour + nonce: 35, + metadata: "0x", + }; + + const signature = await issuer1Signer.signTypedData(domainData, erc20types, paymentData); + const erc20PaymentGas = await payment + .connect(userSigner) + .payERC20.estimateGas(paymentData, signature); + console.log("ERC-20 Payment Gas: " + erc20PaymentGas); + + await expect( + payment.connect(userSigner).payERC20(paymentData, signature), + ).to.changeTokenBalances(token, [userSigner, issuer1Signer, payment], [-10, 0, 10]); + expect(await payment.isPaymentDone(issuer1Signer.address, 35)).to.be.true; + // owner ERC-20 withdraw + const tokenAddress = await token.getAddress(); + const ownerBalance = await payment.getOwnerERC20Balance(tokenAddress); + expect(ownerBalance).to.be.eq(10); + await expect(payment.connect(owner).ownerERC20Withdraw(tokenAddress)).to.changeTokenBalances( + token, + [owner, payment], + [10, -10], + ); + expect(await payment.getOwnerERC20Balance(tokenAddress)).to.be.eq(0); + }); + it("ERC-20 payment - expired:", async () => { const tokenFactory = await ethers.getContractFactory("ERC20Token", owner); const token = await tokenFactory.deploy(1_000); From 459d8ccc785d6cfe51e2e05b69dc0692db928dba Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 19 Dec 2025 18:21:28 +0100 Subject: [PATCH 3/5] small updates --- contracts/payment/MCPayment.sol | 4 ++-- test/payment/mc-payment.test.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/payment/MCPayment.sol b/contracts/payment/MCPayment.sol index 8959fdba..058f297a 100644 --- a/contracts/payment/MCPayment.sol +++ b/contracts/payment/MCPayment.sol @@ -404,7 +404,7 @@ contract MCPayment is } /** - * @dev Get owner balance from owner or withdrawer role + * @dev Get owner balance, callable by owner or withdrawer role * @return balance of owner */ function getOwnerBalance() public view onlyWithdrawerRoleOrOwner returns (uint256) { @@ -413,7 +413,7 @@ contract MCPayment is } /** - * @dev Get owner ERC-20 balance from owner or withdrawer role + * @dev Get owner ERC-20 balance, callable by owner or withdrawer role * @return balance of owner */ function getOwnerERC20Balance( diff --git a/test/payment/mc-payment.test.ts b/test/payment/mc-payment.test.ts index fa457bd0..e0beea05 100644 --- a/test/payment/mc-payment.test.ts +++ b/test/payment/mc-payment.test.ts @@ -448,7 +448,9 @@ describe("MC Payment Contract", () => { // grant admin role to owner await payment.connect(owner).setAdminRole(owner.address); // grant WITHDRAWER_ROLE to userSigner - await payment.grantRole(await payment.WITHDRAWER_ROLE(), await userSigner.getAddress()); + await payment + .connect(owner) + .grantRole(await payment.WITHDRAWER_ROLE(), await userSigner.getAddress()); await expect( payment.connect(userSigner).ownerERC20Withdraw(tokenAddress), From bc48b47b48812961fd2229c2158f3ac733ebe99c Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 19 Dec 2025 19:09:15 +0100 Subject: [PATCH 4/5] merge from master --- contracts/payment/MCPayment.sol | 6 +++--- helpers/constants.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/payment/MCPayment.sol b/contracts/payment/MCPayment.sol index 058f297a..6f1b7714 100644 --- a/contracts/payment/MCPayment.sol +++ b/contracts/payment/MCPayment.sol @@ -24,7 +24,7 @@ contract MCPayment is /** * @dev Version of contract */ - string public constant VERSION = "1.0.6"; + string public constant VERSION = "1.0.8"; /** * @dev Version of EIP 712 domain @@ -451,7 +451,7 @@ contract MCPayment is revert WithdrawErrorNoBalance(); } - IERC20(token).transfer(_msgSender(), amount); + IERC20(token).safeTransfer(owner(), amount); } function _recoverERC20PaymentSignature( @@ -490,7 +490,7 @@ contract MCPayment is uint256 ownerPart = (paymentData.amount * ownerPercentage) / 100; uint256 issuerPart = paymentData.amount - ownerPart; if (issuerPart > 0) { - token.transfer(paymentData.recipient, issuerPart); + token.safeTransfer(paymentData.recipient, issuerPart); } emit Payment(signer, paymentData.nonce); bytes32 paymentId = keccak256(abi.encode(signer, paymentData.nonce)); diff --git a/helpers/constants.ts b/helpers/constants.ts index 52aba92a..27b2a1ef 100644 --- a/helpers/constants.ts +++ b/helpers/constants.ts @@ -312,7 +312,7 @@ export const contractsInfo = Object.freeze({ }, MC_PAYMENT: { name: "MCPayment", - version: "1.0.6", + version: "1.0.8", unifiedAddress: "0xe317A4f1450116b2fD381446DEaB41c882D6136D", create2Calldata: ethers.hexlify(ethers.toUtf8Bytes("iden3.create2.MCPayment")), verificationOpts: { From e6abc8d8ac1b6dada3f61c79dfde76c2525850f3 Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 19 Dec 2025 23:40:11 +0100 Subject: [PATCH 5/5] fix safetransfer msgsender --- contracts/payment/MCPayment.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/payment/MCPayment.sol b/contracts/payment/MCPayment.sol index 6f1b7714..bfd65050 100644 --- a/contracts/payment/MCPayment.sol +++ b/contracts/payment/MCPayment.sol @@ -451,7 +451,7 @@ contract MCPayment is revert WithdrawErrorNoBalance(); } - IERC20(token).safeTransfer(owner(), amount); + IERC20(token).safeTransfer(_msgSender(), amount); } function _recoverERC20PaymentSignature(