2 years of Permit2
January 08, 2025
Summary: Two years ago, Uniswap introduced Permit2, a single “proxy” contract designed to reduce gas costs, improve usability, and increase flexibility for users during token approvals. However, this also (i) expanded the attack surface for hackers and more importantly highlights a deeper issue: high gas fees force applications to create work-arounds which ultimately introduces centralization risks.
Background on EIP-20, EIP-2612 (Permit), and Permit2:
EIP-20: Under the EIP-20 standard, users must first call approve() on their token contract (e.g., USDC) to grant a third-party contract permission to transfer tokens via transferFrom(). This two-step process increases transaction costs and introduces security risks, as approvals are often indefinite and commonly set to the maximum token amount. If the third-party contract or address with the approval is compromised or behaves maliciously, it can result in the unauthorized use of the approved tokens.
// Step 1: Alice approves the dApp to spend her tokens.
token.approve(
dAppContractAddress, // Spender
1000 // Amount of tokens allowed
);
// Step 2: The dApp transfers tokens from Alice's balance to its own address.
token.transferFrom(
aliceAddress, // From Alice
dAppContractAddress, // To dApp
500 // Transfer 500 tokens now
);
// Step 1: Alice approves the dApp to spend her tokens.
token.approve(
dAppContractAddress, // Spender
1000 // Amount of tokens allowed
);
// Step 2: The dApp transfers tokens from Alice's balance to its own address.
token.transferFrom(
aliceAddress, // From Alice
dAppContractAddress, // To dApp
500 // Transfer 500 tokens now
);
Reference: https://eips.ethereum.org/EIPS/eip-20
EIP-2612 (Permit): To address the UX and security challenges of EIP-20, Martin Lundfall introduced Permit in 2020 as an extension to EIP-20. Permit enables users to grant token approvals through off-chain signatures (PermitData from EIP-712), eliminating the need for a separate approve() transaction. DApps then submit this signature to the permit function in the Permit contract, which verifies the signature, checks the expiration deadline, and updates the allowance in a single on-chain call. Permit reduces gas costs, and streamlines the token approval process. However, it’s limited to tokens that implement the EIP-2612 standard while introducing less intrusive ways for hackers to Phish users’ tokens.
// Step 1: Alice signs an off-chain permit message and sends it to the dApp
AliceSigniture = {
owner: aliceAddress,
spender: dAppContractAddress,
value: 1000,
deadline: block.timestamp + 3600
}
// Step 2: The dApp submits the signed permit to the token contract in the same transaction as the transfer.
token.permit(
aliceAddress,
dAppContractAddress,
1000,
block.timestamp + 3600,
AliceSigniture // Alice Signiture
);
token.transferFrom(
aliceAddress,
dAppContractAddress,
500
);
// Step 1: Alice signs an off-chain permit message and sends it to the dApp
AliceSigniture = {
owner: aliceAddress,
spender: dAppContractAddress,
value: 1000,
deadline: block.timestamp + 3600
}
// Step 2: The dApp submits the signed permit to the token contract in the same transaction as the transfer.
token.permit(
aliceAddress,
dAppContractAddress,
1000,
block.timestamp + 3600,
AliceSigniture // Alice Signiture
);
token.transferFrom(
aliceAddress,
dAppContractAddress,
500
);
Reference: https://eips.ethereum.org/EIPS/eip-2612, https://eips.ethereum.org/EIPS/eip-712
Permit2: In 2022, Uniswap introduced Permit2 to extend the Permit concept to all ERC-20 tokens using a pair of “proxy” contracts. Users first approve Permit2 contract to manage their tokens, and instead of sending off-chain signatures directly to the token contract, Permit2 contract serves as an intermediary access control layer. This enables off-chain approval benefits similar to Permit for any token, while adding features like batch operations, multi-contract allowances, and one-time signature-based transfers. These further enhancements streamline user experience and lower transaction costs.
// Step 1: Alice approves Permit2 to spend her tokens.
token.approve(
permit2ContractAddress, // Spender
1000 // Spend Allowance
);
// Alice signs off-chain signature for Permit2
{
owner: aliceAddress,
spender: dAppContractAddress,
token: 0xTokenAddress,
value: 1000,
deadline: block.timestamp + 3600
}
// Step 2: dApp calls Permit2's permitTransferFrom to execute the transfer.
permit2.permitTransferFrom(
PermitTransferFrom({
permitted: TokenPermissions({
token: 0xTokenAddress,
amount: 1000
}),
nonce: 1,
deadline: block.timestamp + 3600
}),
SignatureTransferDetails({
to: dAppContractAddress,
requestedAmount: 500
}),
aliceAddress,
AliceSignature // Off-chain signature from Alice
);
// Step 1: Alice approves Permit2 to spend her tokens.
token.approve(
permit2ContractAddress, // Spender
1000 // Spend Allowance
);
// Alice signs off-chain signature for Permit2
{
owner: aliceAddress,
spender: dAppContractAddress,
token: 0xTokenAddress,
value: 1000,
deadline: block.timestamp + 3600
}
// Step 2: dApp calls Permit2's permitTransferFrom to execute the transfer.
permit2.permitTransferFrom(
PermitTransferFrom({
permitted: TokenPermissions({
token: 0xTokenAddress,
amount: 1000
}),
nonce: 1,
deadline: block.timestamp + 3600
}),
SignatureTransferDetails({
to: dAppContractAddress,
requestedAmount: 500
}),
aliceAddress,
AliceSignature // Off-chain signature from Alice
);
This is how Permit 2 works with Uniswap’s routers and other projects.
Reference: https://blog.uniswap.org/permit2-and-universal-router
Challenges and issues:
Even with its advantages, Permit2 and Permit introduce new vulnerabilities that amplify the risks of phishing attacks. Previously, phishing attempts required convincing a user to send an on-chain transaction, which involved gas fees and often prompted users to think twice before proceeding. With Permit, attackers can exploit off-chain signatures, making it easier to deceive users into granting permissions without realizing the consequences.
What makes this even more concerning is Permit2's ability to handle bulk actions across multiple tokens and protocols. A single compromised signature can result in sweeping approvals, enabling attackers to drain assets from multiple tokens or interact maliciously with several protocols simultaneously. This creates a broader attack surface compared to traditional approval mechanisms or the original Permit standard. We have already seen this with recent hacks where users have lost over 32 million USD in tokens.
Addressing these risks requires increased user awareness, stricter wallet UX to validate signature requests, and proactive measures from dApps to clearly communicate transaction intent and permissions. More importantly, the added convenience of Permit2 must be balanced with robust security practices to mitigate its inherent vulnerabilities. To mitigate some of these issues, organizations can implement additional layers of scrutiny and verification that go beyond cryptographic solutions alone.
For example, one way is to use Cobo’s WaaS 2.0 to proactively monitor token allowances and signature submissions in real-time, front-running suspicious approvals and requiring secondary confirmations or identity checks before finalizing high-value Permit2 transactions. There are additional intelligent off-chain workflows that flag anomalies, enforce user re-verification, and set granular spending thresholds, which not only maintains the user experience gains offered by Permit2 but also ensures that user-experience do not come at the cost of security.
Next Article:
However, these vulnerabilities are not the fundamental issue. The core challenge lies in blockchain inefficiencies, particularly high gas costs, which limit compute cycles and reduce overall TPS. I will explore this deeper in the next article.