Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2f08226
Create native-swap.md
galekseev Sep 26, 2025
8e2835e
Merge pull request #395 from 1inch/deploy/native-order
galekseev Sep 30, 2025
1e88b2e
Merge pull request #384 from 1inch/feature/new-eth-orders
SevenSwen Oct 1, 2025
a6148e1
[PT1-347] Feature/permit2 without witness (#400)
ifelsedeveloper Feb 3, 2026
f0c8be5
Enhance Permit2Proxy contract to accept dynamic Permit2 address and u…
ifelsedeveloper Feb 6, 2026
5629946
Implement deployment script for Permit2Proxy
ifelsedeveloper Feb 6, 2026
d6cc85f
Update Permit2Proxy documentation to reflect constructor changes
ifelsedeveloper Feb 6, 2026
d3f3805
Update Permit2Proxy contract and documentation to correct deployment …
ifelsedeveloper Feb 6, 2026
e37777a
Update Permit2Proxy documentation to include important usage note reg…
ifelsedeveloper Feb 6, 2026
9ee6ca7
Enhance documentation for Permit2Proxy and IPermit2TransferFrom inter…
ifelsedeveloper Feb 6, 2026
20cc259
Refine documentation in Permit2Proxy and IPermit2TransferFrom; remove…
ifelsedeveloper Feb 12, 2026
035ff73
Update docs/extensions/Permit2Proxy.md
ifelsedeveloper Feb 12, 2026
ae7a1d9
Update test/Permit2Proxy.js
ifelsedeveloper Feb 12, 2026
7021651
Merge pull request #413 from 1inch/feature/permit2-proxy-zk-partial
SevenSwen Feb 12, 2026
bc2fa74
Merge branch 'master' into feature/deployment-automation
Sunnesoft Feb 12, 2026
af3c577
added cronos support
Sunnesoft Feb 12, 2026
dbfaa2e
fix: quote values in jq upsert-constant to handle addresses and strings
galekseev Feb 16, 2026
5c3b186
fix: respect OPS_SKIP_VERIFY in deploy scripts
galekseev Feb 16, 2026
e2422cf
updated solidity-utils; fixed OPS_SKIP_VERIFY
Sunnesoft Feb 19, 2026
7b171e8
updated hardhat.config.js
Sunnesoft Feb 19, 2026
6d8f281
permit2proxy
galekseev Feb 25, 2026
6d65c8b
fix permit2 makefile
galekseev Feb 25, 2026
1031d4c
fix makefile
galekseev Feb 25, 2026
bf9279a
fix permit2 deploy script
galekseev Feb 25, 2026
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
52 changes: 43 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,34 @@ export
OPS_NETWORK := $(subst ",,$(OPS_NETWORK))
OPS_CHAIN_ID := $(subst ",,$(OPS_CHAIN_ID))
OPS_DEPLOYMENT_METHOD := $(subst ",,$(OPS_DEPLOYMENT_METHOD))
OPS_SKIP_VERIFY := $(subst ",,$(OPS_SKIP_VERIFY))

CURRENT_DIR:=$(shell pwd)

FILE_DEPLOY_HELPERS:=$(CURRENT_DIR)/deploy/deploy-helpers.js
FILE_DEPLOY_FEE_TAKER:=$(CURRENT_DIR)/deploy/deploy-fee-taker.js
FILE_DEPLOY_LOP:=$(CURRENT_DIR)/deploy/deploy.js
FILE_DEPLOY_NATIVE_ORDER_FACTORY:=$(CURRENT_DIR)/deploy/deploy-native-order-factory.js
FILE_DEPLOY_PERMIT2_PROXY:=$(CURRENT_DIR)/deploy/deploy-Permit2Proxy.js

FILE_CONSTANTS_JSON:=$(CURRENT_DIR)/config/constants.json

IS_ZKSYNC := $(findstring zksync,$(OPS_NETWORK))

deploy-helpers:
@$(MAKE) OPS_CURRENT_DEP_FILE=$(FILE_DEPLOY_HELPERS) OPS_DEPLOYMENT_METHOD=$(if $(OPS_DEPLOYMENT_METHOD),$(OPS_DEPLOYMENT_METHOD),create3) validate-helpers deploy-skip-all deploy-noskip deploy-impl deploy-skip
@$(MAKE) OPS_SKIP_VERIFY=$(OPS_SKIP_VERIFY) OPS_CURRENT_DEP_FILE=$(FILE_DEPLOY_HELPERS) OPS_DEPLOYMENT_METHOD=$(if $(OPS_DEPLOYMENT_METHOD),$(OPS_DEPLOYMENT_METHOD),create3) validate-helpers deploy-skip-all deploy-noskip deploy-impl deploy-skip

deploy-lop:
@$(MAKE) OPS_CURRENT_DEP_FILE=$(FILE_DEPLOY_LOP) validate-lop deploy-skip-all deploy-noskip deploy-impl deploy-skip
@$(MAKE) OPS_SKIP_VERIFY=$(OPS_SKIP_VERIFY) OPS_CURRENT_DEP_FILE=$(FILE_DEPLOY_LOP) validate-lop deploy-skip-all deploy-noskip deploy-impl deploy-skip

deploy-fee-taker:
@$(MAKE) OPS_CURRENT_DEP_FILE=$(FILE_DEPLOY_FEE_TAKER) validate-fee-taker deploy-skip-all deploy-noskip deploy-impl deploy-skip
@$(MAKE) OPS_SKIP_VERIFY=$(OPS_SKIP_VERIFY) OPS_CURRENT_DEP_FILE=$(FILE_DEPLOY_FEE_TAKER) validate-fee-taker deploy-skip-all deploy-noskip deploy-impl deploy-skip

deploy-native-order-factory:
@$(MAKE) OPS_CURRENT_DEP_FILE=$(FILE_DEPLOY_NATIVE_ORDER_FACTORY) validate-native-order-factory deploy-skip-all deploy-noskip deploy-impl deploy-skip
@$(MAKE) OPS_SKIP_VERIFY=$(OPS_SKIP_VERIFY) OPS_CURRENT_DEP_FILE=$(FILE_DEPLOY_NATIVE_ORDER_FACTORY) validate-native-order-factory deploy-skip-all deploy-noskip deploy-impl deploy-skip

deploy-permit2-proxy:
@$(MAKE) OPS_SKIP_VERIFY=$(OPS_SKIP_VERIFY) OPS_CURRENT_DEP_FILE=$(FILE_DEPLOY_PERMIT2_PROXY) validate-permit2-proxy deploy-skip-all deploy-noskip deploy-impl deploy-skip

deploy-impl:
@{ \
Expand All @@ -50,6 +55,15 @@ validate-common:
$(MAKE) process-weth || exit 1; \
}

validate-basic:
@{ \
$(MAKE) ID=OPS_NETWORK validate || exit 1; \
$(MAKE) ID=OPS_CHAIN_ID validate || exit 1; \
if [ "$(OPS_NETWORK)" = "hardhat" ]; then \
$(MAKE) ID=MAINNET_RPC_URL validate || exit 1; \
fi; \
}

validate-helpers:
@{ \
$(MAKE) validate-common || exit 1; \
Expand Down Expand Up @@ -87,6 +101,17 @@ validate-native-order-factory:
$(MAKE) process-router-v6 process-access-token process-native-order-factory-salt process-create3-deployer || exit 1; \
}

validate-permit2-proxy:
@{ \
$(MAKE) validate-basic || exit 1; \
$(MAKE) ID=OPS_AGGREGATION_ROUTER_V6_ADDRESS validate || exit 1; \
if [ "$(IS_ZKSYNC)" = "" ]; then \
$(MAKE) ID=OPS_CREATE3_DEPLOYER_ADDRESS validate || exit 1; \
$(MAKE) ID=OPS_PERMIT2_PROXY_SALT validate || exit 1; \
fi; \
$(MAKE) process-router-v6 process-permit2-proxy-salt process-create3-deployer || exit 1; \
}

validate-lop:
@$(MAKE) validate-common || exit 1

Expand Down Expand Up @@ -115,13 +140,20 @@ process-native-order-factory-salt:
process-permit2-witness-proxy-salt:
@if [ -n "$$OPS_PERMIT2_WITNESS_PROXY_SALT" ]; then $(MAKE) OPS_GEN_VAL='$(OPS_PERMIT2_WITNESS_PROXY_SALT)' OPS_GEN_KEY='permit2WitnessProxySalt' upsert-constant; fi

process-permit2-proxy-salt:
@if [ -n "$$OPS_PERMIT2_PROXY_SALT" ]; then $(MAKE) OPS_GEN_VAL='$(OPS_PERMIT2_PROXY_SALT)' OPS_GEN_KEY='permit2ProxySalt' upsert-constant; fi

upsert-constant:
@{ \
$(MAKE) ID=OPS_GEN_VAL validate || exit 1; \
$(MAKE) ID=OPS_GEN_KEY validate || exit 1; \
$(MAKE) ID=OPS_CHAIN_ID validate || exit 1; \
tmpfile=$$(mktemp); \
jq '.$(OPS_GEN_KEY)."$(OPS_CHAIN_ID)" = $(OPS_GEN_VAL)' $(FILE_CONSTANTS_JSON) > $$tmpfile && mv $$tmpfile $(FILE_CONSTANTS_JSON); \
if echo '$(OPS_GEN_VAL)' | jq type >/dev/null 2>&1; then \
jq --argjson val '$(OPS_GEN_VAL)' '.$(OPS_GEN_KEY)."$(OPS_CHAIN_ID)" = $$val' $(FILE_CONSTANTS_JSON) > $$tmpfile; \
else \
jq --arg val '$(OPS_GEN_VAL)' '.$(OPS_GEN_KEY)."$(OPS_CHAIN_ID)" = $$val' $(FILE_CONSTANTS_JSON) > $$tmpfile; \
fi && mv $$tmpfile $(FILE_CONSTANTS_JSON); \
echo "Updated $(OPS_GEN_KEY)[$(OPS_CHAIN_ID)] = $(OPS_GEN_VAL)"; \
}

Expand Down Expand Up @@ -174,6 +206,7 @@ get:
"OPS_PRIORITY_FEE_LIMITER_ADDRESS") CONTRACT_FILE="PriorityFeeLimiter.json" ;; \
"OPS_CALLS_SIMULATOR_ADDRESS") CONTRACT_FILE="CallsSimulator.json" ;; \
"OPS_NATIVE_ORDER_FACTORY_ADDRESS") CONTRACT_FILE="NativeOrderFactory.json" ;; \
"OPS_PERMIT2_PROXY_ADDRESS") CONTRACT_FILE="Permit2Proxy.json" ;; \
*) echo "Error: Unknown parameter $(PARAMETER)"; exit 1 ;; \
esac; \
DEPLOYMENT_FILE="$(CURRENT_DIR)/deployments/$(OPS_NETWORK)/$$CONTRACT_FILE"; \
Expand Down Expand Up @@ -204,6 +237,7 @@ help:
@echo " deploy-lop Deploy LimitOrderProtocol contract"
@echo " deploy-fee-taker Deploy FeeTaker contract"
@echo " deploy-native-order-factory Deploy NativeOrderFactory contract"
@echo " deploy-permit2-proxy Deploy Permit2Proxy contract"
@echo " deploy-impl Run deployment script for current file"
@echo " deploy-skip Set skip=true in deployment file"
@echo " deploy-noskip Set skip=false in deployment file"
Expand Down Expand Up @@ -231,9 +265,9 @@ help:
.PHONY: \
install install-utils install-dependencies clean \
deploy-helpers deploy-lop deploy-fee-taker deploy-impl \
deploy-skip deploy-noskip deploy-skip-all deploy-native-order-factory \
deploy-skip deploy-noskip deploy-skip-all deploy-native-order-factory deploy-permit2-proxy \
get help \
validate-helpers validate-fee-taker validate-lop \
validate-helpers validate-fee-taker validate-permit2-proxy validate-lop \
process-create3-deployer process-weth process-router-v6 process-order-registrator process-access-token \
process-fee-taker-salt process-permit2-witness-proxy-salt process-native-order-factory-salt \
upsert-constant validate validate-common launch-hh-node
process-fee-taker-salt process-permit2-witness-proxy-salt process-native-order-factory-salt process-permit2-proxy-salt \
upsert-constant validate validate-common validate-basic launch-hh-node
1 change: 1 addition & 0 deletions config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
CREATE3_DEPLOYER: constants.create3Deployer || {},
ORDER_REGISTRATOR: constants.orderRegistrator || {},
FEE_TAKER_SALT: constants.feeTakerSalt || {},
PERMIT2_PROXY_SALT: constants.permit2ProxySalt || {},
PERMIT2_WITNESS_PROXY_SALT: constants.permit2WitnessProxySalt || {},
NATIVE_ORDER_SALT: constants.nativeOrderSalt || {},
};
63 changes: 63 additions & 0 deletions contracts/extensions/Permit2Proxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "../interfaces/IPermit2TransferFrom.sol";
import "./ImmutableOwner.sol";

/* solhint-disable func-name-mixedcase */

/// @title Permit2Proxy
/// @notice A proxy contract that enables using Uniswap's Permit2 `permitTransferFrom` within the limit order protocol.
/// @dev Permit2 nonces are single-use

contract Permit2Proxy is ImmutableOwner {

/// @notice The Permit2 contract address.
/// @dev Use `0x000000000022D473030F116dDEE9F6B43aC78BA3` for EVM chains
/// or `0x0000000000225e31d15943971f47ad3022f714fa` for zkSync Era.
/// See https://docs.uniswap.org/contracts/v3/reference/deployments
IPermit2TransferFrom private immutable _PERMIT2;

/// @notice Thrown when `func_nZHTch` selector does not match `IERC20.transferFrom` selector.
error Permit2ProxyBadSelector();

/// @notice Initializes the proxy with the immutable owner and the Permit2 contract address.
/// @param _immutableOwner The address of the limit order protocol contract.
/// @param _permit2 The Permit2 contract address for the target chain.
constructor(address _immutableOwner, address _permit2) ImmutableOwner(_immutableOwner) {
if (Permit2Proxy.func_nZHTch.selector != IERC20.transferFrom.selector) revert Permit2ProxyBadSelector();
_PERMIT2 = IPermit2TransferFrom(_permit2);
}

/// @notice Proxy transfer method for `Permit2.permitTransferFrom`. Selector must match `IERC20.transferFrom`.
/// @dev The function name `func_nZHTch` is chosen so that its selector equals `0x23b872dd`
/// (same as `IERC20.transferFrom`), allowing it to be used as a maker asset in limit orders.
/// keccak256("func_nZHTch(address,address,uint256,((address,uint256),uint256,uint256),bytes)") == 0x23b872dd
/// @param from The token owner whose tokens are being transferred.
/// @param to The recipient of the tokens.
/// @param amount The amount of tokens to transfer.
/// @param permit The Permit2 permit data containing token permissions, nonce, and deadline.
/// @param sig The signature authorizing the transfer, signed by `from`.
function func_nZHTch(
address from,
address to,
uint256 amount,
IPermit2TransferFrom.PermitTransferFrom calldata permit,
bytes calldata sig
) external onlyImmutableOwner {
_PERMIT2.permitTransferFrom(
permit,
IPermit2TransferFrom.SignatureTransferDetails({
to: to,
requestedAmount: amount
}),
from,
sig
);
}
}

/* solhint-enable func-name-mixedcase */
42 changes: 42 additions & 0 deletions contracts/interfaces/IPermit2TransferFrom.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @title IPermit2TransferFrom
/// @notice Interface for Uniswap's Permit2 SignatureTransfer `permitTransferFrom` functionality.
/// @custom:security-contact security@1inch.io
interface IPermit2TransferFrom {
struct TokenPermissions {
// ERC20 token address
address token;
// the maximum amount that can be spent
uint256 amount;
}

struct PermitTransferFrom {
TokenPermissions permitted;
// a unique value for every token owner's signature to prevent signature replays
uint256 nonce;
// deadline on the permit signature
uint256 deadline;
}

struct SignatureTransferDetails {
// recipient address
address to;
// spender requested amount
uint256 requestedAmount;
}

/// @notice Transfers tokens using a signed permit.
/// @param permit The permit data containing token permissions, nonce, and deadline.
/// @param transferDetails The transfer recipient and requested amount.
/// @param owner The token owner who signed the permit.
/// @param signature The signature authorizing the transfer.
function permitTransferFrom(
PermitTransferFrom calldata permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes calldata signature
) external;
}
52 changes: 52 additions & 0 deletions deploy/deploy-Permit2Proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { deployAndGetContractWithCreate3, deployAndGetContract } = require('@1inch/solidity-utils');

const hre = require('hardhat');
const { ethers, getChainId } = hre;
const { permit2Address } = require('@uniswap/permit2-sdk');
const constants = require('../config/constants');

module.exports = async ({ deployments }) => {
const networkName = hre.network.name;
console.log(`running ${networkName} deploy script`);
const chainId = await getChainId();
console.log('network id ', chainId);

if (
networkName in hre.config.networks[networkName] &&
chainId !== hre.config.networks[networkName].chainId.toString()
) {
console.log(`network chain id: ${hre.config.networks[networkName].chainId}, your chain id ${chainId}`);
console.log('skipping wrong chain id deployment');
return;
}

const PERMIT2_ADDRESS = permit2Address(Number(chainId));
const { deployer } = await getNamedAccounts();

Check failure on line 24 in deploy/deploy-Permit2Proxy.js

View workflow job for this annotation

GitHub Actions / lint

'getNamedAccounts' is not defined

if (networkName.indexOf('zksync') !== -1) { // zksync
await deployAndGetContract({
contractName: 'Permit2Proxy',
constructorArgs: [constants.ROUTER_V6[chainId], PERMIT2_ADDRESS],
deployments,
deployer,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
} else {
const salt = constants.PERMIT2_PROXY_SALT[chainId].startsWith('0x')
? constants.PERMIT2_PROXY_SALT[chainId]
: ethers.keccak256(ethers.toUtf8Bytes(constants.PERMIT2_PROXY_SALT[chainId]));

console.log(`Using salt: ${salt}`);

await deployAndGetContractWithCreate3({
contractName: 'Permit2Proxy',
constructorArgs: [constants.ROUTER_V6[chainId], PERMIT2_ADDRESS],
create3Deployer: constants.CREATE3_DEPLOYER[chainId],
salt,
deployments,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
}
};

module.exports.skip = async () => true;
2 changes: 2 additions & 0 deletions deploy/deploy-Permit2WitnessProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = async ({ deployments, getNamedAccounts }) => {
constructorArgs: [constants.ROUTER_V6[chainId]],
deployments,
deployer,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
} else {
const salt = constants.PERMIT2_WITNESS_PROXY_SALT[chainId].startsWith('0x')
Expand All @@ -42,6 +43,7 @@ module.exports = async ({ deployments, getNamedAccounts }) => {
create3Deployer: constants.CREATE3_DEPLOYER[chainId],
salt,
deployments,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
}
};
Expand Down
2 changes: 2 additions & 0 deletions deploy/deploy-fee-taker.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = async ({ deployments, getNamedAccounts }) => {
constructorArgs: [constants.ROUTER_V6[chainId], constants.ACCESS_TOKEN[chainId], constants.WETH[chainId], deployer],
deployments,
deployer,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
} else {
const salt = constants.FEE_TAKER_SALT[chainId].startsWith('0x')
Expand All @@ -40,6 +41,7 @@ module.exports = async ({ deployments, getNamedAccounts }) => {
create3Deployer: constants.CREATE3_DEPLOYER[chainId],
salt,
deployments,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
}
};
Expand Down
2 changes: 2 additions & 0 deletions deploy/deploy-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ module.exports = async ({ getNamedAccounts, deployments, config }) => {
create3Deployer: constants.CREATE3_DEPLOYER[chainId],
salt,
deployments,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
} else {
const { deployer } = await getNamedAccounts();
Expand All @@ -79,6 +80,7 @@ module.exports = async ({ getNamedAccounts, deployments, config }) => {
constructorArgs: args,
deployments,
deployer,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
}

Expand Down
4 changes: 3 additions & 1 deletion deploy/deploy-native-order-factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module.exports = async ({ getNamedAccounts, deployments }) => {
constructorArgs: [constants.WETH[chainId], constants.ROUTER_V6[chainId], constants.ACCESS_TOKEN[chainId], 60, '1inch Aggregation Router', '6'],
deployments,
deployer,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
} else {
const salt = constants.NATIVE_ORDER_SALT[chainId].startsWith('0x')
Expand All @@ -43,10 +44,11 @@ module.exports = async ({ getNamedAccounts, deployments }) => {
create3Deployer: constants.CREATE3_DEPLOYER[chainId],
salt,
deployments,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
}

if (chainId !== '31337') {
if (chainId !== '31337' && process.env.OPS_SKIP_VERIFY !== 'true') {
const implementationAddress = await nativeOrderFactory.IMPLEMENTATION();
console.log(`NativeOrderImpl deployed to: ${implementationAddress}`);

Expand Down
1 change: 1 addition & 0 deletions deploy/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = async ({ getNamedAccounts, deployments }) => {
constructorArgs: [constants.WETH[chainId]],
deployments,
deployer,
skipVerify: process.env.OPS_SKIP_VERIFY === 'true',
});
};

Expand Down
21 changes: 21 additions & 0 deletions dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Development Guide

## Selector Bruteforce Tool

When developing proxy contracts that need specific function selectors (e.g., matching `IERC20.transferFrom` selector `0x23b872dd`), use the selector bruteforce tool from:

**https://github.com/1inch/smart-contract-helper-utils/src**

### Usage Example

To find a function name with custom suffix that produces the `transferFrom` selector:

```bash
python selector_bruteforce.py \
--target 0x23b872dd \
--params "address,address,uint256,((address,uint256),uint256,uint256),bytes" \
--prefix "func_" \
--fast
```

This is used for contracts like `Permit2Proxy` and `Permit2WitnessProxy` where the proxy function must have the same selector as `transferFrom` to work with the limit order protocol's extension mechanism.
Loading
Loading