Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions script/Config.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ pragma solidity ^0.8.20;

// Generated by scripts/sync-config.py
library Config {
address public constant ROUTER_MAINNET = 0x92e20164FC457a2aC35f53D06268168e6352b200;
address public constant ROUTER_SEPOLIA = 0xB587Bc36aaCf65962eCd6Ba59e2DA76f2f575408;
address public constant ROUTER_MAINNET = 0x4A73696ccF76E7381b044cB95127B3784369Ed63;
address public constant ROUTER_SEPOLIA = 0x24cD8b68aaC209217ff5a6ef1Bf55a59f2c8Ca6F;
address public constant TREASURY = 0x598bF63F5449876efafa7b36b77Deb2070621C0E;
address public constant USDC_MAINNET = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;
address public constant USDC_SEPOLIA = 0xeAC1f2C7099CdaFfB91Aa3b8Ffd653Ef16935798;
uint256 public constant MIN_PAYMENT_AMOUNT = 1000;
uint256 public constant FEE_BPS = 100;
}
3 changes: 2 additions & 1 deletion script/DeploySepolia.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ pragma solidity ^0.8.20;

import {Script, console} from "forge-std/Script.sol";
import {PayNodeRouter} from "../src/PayNodeRouter.sol";
import {Config} from "./Config.s.sol";

contract DeploySepolia is Script {
function run() external {
address treasury = 0x598bF63F5449876efafa7b36b77Deb2070621C0E;
address treasury = Config.TREASURY;

vm.startBroadcast();
PayNodeRouter router = new PayNodeRouter(treasury);
Expand Down
8 changes: 6 additions & 2 deletions src/PayNodeRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ contract PayNodeRouter is Ownable2Step, Pausable {
// Fixed protocol fee: 1% (100 basis points out of 10000)
uint256 public constant PROTOCOL_FEE_BPS = 100;
uint256 public constant MAX_BPS = 10000;
uint256 public constant MIN_PAYMENT_AMOUNT = 1000;

error InvalidAddress();
error AmountMustBeGreaterThanZero();
error AmountTooLow();
error UnauthorizedCaller();

// Redesigned event to match SDK requirements (indexed orderId, token verification, chainId)
event PaymentReceived(
Expand Down Expand Up @@ -98,6 +100,8 @@ contract PayNodeRouter is Ownable2Step, Pausable {
bytes32 r,
bytes32 s
) external whenNotPaused {
if (msg.sender != payer) revert UnauthorizedCaller();

// 1. Consume permit to grant allowance to this router
IERC20Permit(token).permit(payer, address(this), amount, deadline, v, r, s);

Expand All @@ -110,7 +114,7 @@ contract PayNodeRouter is Ownable2Step, Pausable {
*/
function _processPayment(address payer, address token, address merchant, uint256 amount, bytes32 orderId) internal {
if (merchant == address(0) || token == address(0)) revert InvalidAddress();
if (amount == 0) revert AmountMustBeGreaterThanZero();
if (amount < MIN_PAYMENT_AMOUNT) revert AmountTooLow();

// Calculate 1% fee
uint256 fee = (amount * PROTOCOL_FEE_BPS) / MAX_BPS;
Expand Down
5 changes: 2 additions & 3 deletions test/PayNodeRouter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ contract PayNodeRouterTest is Test {
vm.expectEmit(true, true, true, true);
emit PaymentReceived(orderId, merchant, payer, address(usdc), paymentAmount, expectedFee, block.chainid);

// We use an agent to send the transaction to test the Relayer functionality properly!
address agent = address(uint160(0x12345));
vm.prank(agent);
// Use the payer as the caller to prevent MiTM issues as per router security logic
vm.prank(payer);
router.payWithPermit(payer, address(usdc), merchant, paymentAmount, orderId, deadline, v, r, s);

assertEq(usdc.balanceOf(merchant), 99 * 10 ** 6);
Expand Down