Skip to content
2 changes: 1 addition & 1 deletion contracts/evm/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ cbor_metadata = false
bytecode_hash = "none"
ffi = false
fs_permissions = [{ access = "read", path = "./" }]
gas_reports = ["x402ExactPermit2Proxy", "x402UptoPermit2Proxy"]
gas_reports = ["x402ExactPermit2Proxy", "x402UptoPermit2Proxy", "x402BatchSettlement"]

[profile.default.fuzz]
runs = 256
Expand Down
46 changes: 45 additions & 1 deletion contracts/evm/script/ComputeAddress.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Script, console2} from "forge-std/Script.sol";

import {x402ExactPermit2Proxy} from "../src/x402ExactPermit2Proxy.sol";
import {x402UptoPermit2Proxy} from "../src/x402UptoPermit2Proxy.sol";
import {x402BatchSettlement} from "../src/x402BatchSettlement.sol";

/**
* @title ComputeAddress
Expand All @@ -19,8 +20,11 @@ import {x402UptoPermit2Proxy} from "../src/x402UptoPermit2Proxy.sol";
* @dev Run with default salts:
* forge script script/ComputeAddress.s.sol
*
* @dev Run with custom salts:
* @dev Run with custom salts (exact + upto):
* forge script script/ComputeAddress.s.sol --sig "computeAddresses(bytes32,bytes32)" <EXACT_SALT> <UPTO_SALT>
*
* @dev Run BatchSettlement only with custom salt:
* forge script script/ComputeAddress.s.sol --sig "computeBatchAddress(bytes32)" <BATCH_SALT>
*/
contract ComputeAddress is Script {
/// @notice Arachnid's deterministic CREATE2 deployer
Expand All @@ -37,6 +41,10 @@ contract ComputeAddress is Script {
/// @dev Vanity mined for address 0x4020a4f3b7b90cca423b9fabcc0ce57c6c240002
bytes32 constant DEFAULT_UPTO_SALT = 0x000000000000000000000000000000000000000000000000b000000001db633d;

/// @notice Default salt for x402BatchSettlement
/// @dev PLACEHOLDER — vanity mine for address 0x4020...0003 after contract is finalized
bytes32 constant DEFAULT_BATCH_SALT = bytes32(0);

/// @notice Expected initCodeHash for x402ExactPermit2Proxy (pre-built, includes CBOR metadata)
bytes32 constant EXACT_INIT_CODE_HASH = 0xe774d1d5a07218946ab54efe010b300481478b86861bb17d69c98a57f68a604c;

Expand Down Expand Up @@ -111,6 +119,42 @@ contract ComputeAddress is Script {
}
}

/**
* @notice Computes the CREATE2 address for x402BatchSettlement
* @param batchSalt The salt to use for x402BatchSettlement
*/
function computeBatchAddress(bytes32 batchSalt) public view {
console2.log("");
console2.log("============================================================");
console2.log(" x402BatchSettlement Address Computation");
console2.log("============================================================");
console2.log("");

console2.log("Configuration:");
console2.log(" CREATE2 Deployer: ", CREATE2_DEPLOYER);
console2.log(" Permit2 (ctor arg): ", CANONICAL_PERMIT2);
console2.log("");

bytes memory initCode =
abi.encodePacked(type(x402BatchSettlement).creationCode, abi.encode(CANONICAL_PERMIT2));
bytes32 initCodeHash = keccak256(initCode);
address expectedAddress = _computeCreate2Addr(batchSalt, initCodeHash, CREATE2_DEPLOYER);

console2.log("------------------------------------------------------------");
console2.log(" x402BatchSettlement (deterministic build)");
console2.log("------------------------------------------------------------");
console2.log(" Salt: ", vm.toString(batchSalt));
console2.log(" Init Code Hash: ", vm.toString(initCodeHash));
console2.log(" Address: ", expectedAddress);

if (block.chainid != 0 && expectedAddress.code.length > 0) {
console2.log(" Status: DEPLOYED");
} else {
console2.log(" Status: NOT DEPLOYED");
}
console2.log("");
}

function _computeCreate2Addr(
bytes32 salt,
bytes32 initCodeHash,
Expand Down
107 changes: 107 additions & 0 deletions contracts/evm/script/DeployBatchSettlement.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script, console2} from "forge-std/Script.sol";
import {x402BatchSettlement} from "../src/x402BatchSettlement.sol";

/// @title DeployBatchSettlement
/// @notice Deployment script for x402BatchSettlement using CREATE2
/// @dev Run with: forge script script/DeployBatchSettlement.s.sol --rpc-url $RPC_URL --broadcast --verify
///
/// Uses deterministic bytecode (cbor_metadata = false in foundry.toml) so
/// any machine compiling at the same git commit produces the same initCode
/// and therefore the same CREATE2 address.
contract DeployBatchSettlement is Script {
/// @notice Canonical Permit2 address (Uniswap's official deployment)
address constant CANONICAL_PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

/// @notice Arachnid's deterministic CREATE2 deployer (same on all EVM chains)
address constant CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C;

// TODO: Re-mine vanity salt after contract rewrite. Initcode has changed.
bytes32 constant BATCH_SALT = bytes32(0);

function run() public {
address permit2 = vm.envOr("PERMIT2_ADDRESS", CANONICAL_PERMIT2);

console2.log("");
console2.log("============================================================");
console2.log(" x402BatchSettlement Deterministic Deployment (CREATE2)");
console2.log(" Model: Stateless Channel-Config");
console2.log("============================================================");
console2.log("");

console2.log("Network: chainId", block.chainid);
console2.log("Permit2:", permit2);
console2.log("CREATE2 Deployer:", CREATE2_DEPLOYER);
console2.log("");

if (block.chainid != 31_337 && block.chainid != 1337) {
require(permit2.code.length > 0, "Permit2 not found on this network");
console2.log("Permit2 verified");

require(CREATE2_DEPLOYER.code.length > 0, "CREATE2 deployer not found on this network");
console2.log("CREATE2 deployer verified");
}

_deploy(permit2);

console2.log("");
console2.log("Deployment complete!");
console2.log("");
}

function _deploy(address permit2) internal {
console2.log("");
console2.log("------------------------------------------------------------");
console2.log(" Deploying x402BatchSettlement");
console2.log("------------------------------------------------------------");

bytes memory initCode = abi.encodePacked(type(x402BatchSettlement).creationCode, abi.encode(permit2));
bytes32 initCodeHash = keccak256(initCode);
address expectedAddress = _computeCreate2Addr(BATCH_SALT, initCodeHash, CREATE2_DEPLOYER);

console2.log("Salt:", vm.toString(BATCH_SALT));
console2.log("Expected address:", expectedAddress);
console2.log("Init code hash:", vm.toString(initCodeHash));

x402BatchSettlement bs;

if (expectedAddress.code.length > 0) {
console2.log("Contract already deployed at", expectedAddress);
bs = x402BatchSettlement(expectedAddress);
console2.log("PERMIT2:", address(bs.PERMIT2()));
return;
}

vm.startBroadcast();

address deployedAddress;
if (block.chainid == 31_337 || block.chainid == 1337) {
console2.log("(Using regular deployment for local network)");
bs = new x402BatchSettlement(permit2);
deployedAddress = address(bs);
} else {
bytes memory deploymentData = abi.encodePacked(BATCH_SALT, initCode);
(bool success,) = CREATE2_DEPLOYER.call(deploymentData);
require(success, "CREATE2 deployment failed for BatchSettlement");
deployedAddress = expectedAddress;
require(deployedAddress.code.length > 0, "No bytecode at expected address");
bs = x402BatchSettlement(deployedAddress);
}

vm.stopBroadcast();

console2.log("Deployed to:", deployedAddress);
console2.log("Verification - PERMIT2:", address(bs.PERMIT2()));
require(address(bs.PERMIT2()) == permit2, "PERMIT2 mismatch");
}

function _computeCreate2Addr(
bytes32 salt,
bytes32 initCodeHash,
address deployer
) internal pure returns (address) {
return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initCodeHash)))));
}
}
20 changes: 20 additions & 0 deletions contracts/evm/src/interfaces/IERC3009.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
* @title IERC3009
* @notice Minimal interface for EIP-3009 transferWithAuthorization / receiveWithAuthorization
* @dev Used by tokens like USDC that support gasless transfers via signed authorizations.
* See https://eips.ethereum.org/EIPS/eip-3009
*/
interface IERC3009 {
function receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
bytes memory signature
) external;
}
Loading
Loading