tip: 2612
title: Permit Extension for TIP-20 Signed Approvals
description: TIP-20 approvals via TIP-712 secp256k1 signatures
author: yanghang8612@gmail.com
status: Draft
type: Standards Track
category: TRC
created: 2026-03-24
requires: 20, 712
Abstract
Arguably one of the main reasons for the success of TIP-20 tokens lies in the interplay between approve and transferFrom, which allows for tokens to not only be transferred between externally owned accounts (EOA), but to be used in other contracts under application specific conditions by abstracting away msg.sender as the defining mechanism for token access control.
However, a limiting factor in this design stems from the fact that the TIP-20 approve function itself is defined in terms of msg.sender. This means that user's initial action involving TIP-20 tokens must be performed by an EOA (but see Note below). If the user needs to interact with a smart contract, then they need to make 2 transactions (approve and the smart contract call which will internally call transferFrom). Even in the simple use case of paying another person, they need to hold ETH to pay for transaction gas costs.
This TRC extends the TIP-20 standard with a new function permit, which allows users to modify the allowance mapping using a signed message, instead of through msg.sender.
For an improved user experience, the signed data is structured following TIP-712, which already has wide spread adoption in major RPC providers.
Note: TIP-20 must be performed by an EOA unless the address owning the token is actually a contract wallet. Although contract wallets solves many of the same problems that motivates this TIP, they are currently only scarcely adopted in the ecosystem. Contract wallets suffer from a UX problem -- since they separate the EOA owner of the contract wallet from the contract wallet itself (which is meant to carry out actions on the owners behalf and holds all of their funds), user interfaces need to be specifically designed to support them. The permit pattern reaps many of the same benefits while requiring little to no change in user interfaces.
Motivation
While TIP-20 tokens have become ubiquitous in the Ethereum ecosystem, their status remains that of second class tokens from the perspective of the protocol. The ability for users to interact with Ethereum without holding any ETH has been a long outstanding goal and the subject of many TIPs.
So far, many of these proposals have seen very little adoption, and the ones that have been adopted (such as TIP-777), introduce a lot of additional functionality, causing unexpected behavior in mainstream contracts.
This TRC proposes an alternative solution which is designed to be as minimal as possible and to only address one problem: the lack of abstraction in the TIP-20 approve method.
While it may be tempting to introduce *_by_signature counterparts for every TIP-20 function, they are intentionally left out of this TIP-20 for two reasons:
- the desired specifics of such functions, such as decision regarding fees for
transfer_by_signature, possible batching algorithms, varies depending on the use case, and,
- they can be implemented using a combination of
permit and additional helper contracts without loss of generality.
Specification
Compliant contracts must implement 3 new functions in addition to TIP-20:
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external
function nonces(address owner) external view returns (uint)
function DOMAIN_SEPARATOR() external view returns (bytes32)
The semantics of which are as follows:
For all addresses owner, spender, uint256s value, deadline and nonce, uint8 v, bytes32 r and s,
a call to permit(owner, spender, value, deadline, v, r, s) will set
allowance[owner][spender] to value,
increment nonces[owner] by 1,
and emit a corresponding Approval event,
if and only if the following conditions are met:
- The current blocktime is less than or equal to
deadline.
owner is not the zero address.
nonces[owner] (before the state update) is equal to nonce.
r, s and v is a valid secp256k1 signature from owner of the message:
If any of these conditions are not met, the permit call must revert.
keccak256(abi.encodePacked(
hex"1901",
DOMAIN_SEPARATOR,
keccak256(abi.encode(
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
owner,
spender,
value,
nonce,
deadline))
))
where DOMAIN_SEPARATOR is defined according to TIP-712. The DOMAIN_SEPARATOR should be unique to the contract and chain to prevent replay attacks from other domains,
and satisfy the requirements of TIP-712, but is otherwise unconstrained.
A common choice for DOMAIN_SEPARATOR is:
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes(version)),
chainid,
address(this)
));
In other words, the message is the TIP-712 typed structure:
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Permit": [
{
"name": "owner",
"type": "address"
},
{
"name": "spender",
"type": "address"
},
{
"name": "value",
"type": "uint256"
},
{
"name": "nonce",
"type": "uint256"
},
{
"name": "deadline",
"type": "uint256"
}
],
},
"primaryType": "Permit",
"domain": {
"name": trc20name,
"version": version,
"chainId": chainid,
"verifyingContract": tokenAddress
},
"message": {
"owner": owner,
"spender": spender,
"value": value,
"nonce": nonce,
"deadline": deadline
}
}
Note that nowhere in this definition we refer to msg.sender. The caller of the permit function can be any address.
Rationale
The permit function is sufficient for enabling any operation involving TIP-20 tokens to be paid for using the token itself, rather than using ETH.
The nonces mapping is given for replay protection.
A common use case of permit has a relayer submit a Permit on behalf of the owner. In this scenario, the relaying party is essentially given a free option to submit or withhold the Permit. If this is a cause of concern, the owner can limit the time a Permit is valid for by setting deadline to a value in the near future. The deadline argument can be set to uint(-1) to create Permits that effectively never expire.
TIP-712 typed messages are included because of its wide spread adoption in many wallet providers.
Backwards Compatibility
There are already a couple of permit functions in token contracts implemented in contracts in the wild, most notably the one introduced in the dai.sol.
Its implementation differs slightly from the presentation here in that:
- instead of taking a
value argument, it takes a bool allowed, setting approval to 0 or uint(-1).
- the
deadline argument is instead called expiry. This is not just a syntactic change, as it effects the contents of the signed message.
There is also an implementation in the token Stake (Ethereum address 0x0Ae055097C6d159879521C384F1D2123D1f195e6) with the same ABI as dai but with different semantics: it lets users issue "expiring approvals", that only allow transferFrom to occur while expiry >= block.timestamp.
The specification presented here is in line with the implementation in Uniswap V2.
The requirement to revert if the permit is invalid was added when the TIP was already widely deployed, but at the moment it was consistent with all found implementations.
Security Considerations
Though the signer of a Permit may have a certain party in mind to submit their transaction, another party can always front run this transaction and call permit before the intended party. The end result is the same for the Permit signer, however.
Since the ecrecover precompile fails silently and just returns the zero address as signer when given malformed messages, it is important to ensure owner != address(0) to avoid permit from creating an approval to spend "zombie funds" belong to the zero address.
Signed Permit messages are censorable. The relaying party can always choose to not submit the Permit after having received it, withholding the option to submit it. The deadline parameter is one mitigation to this. If the signing party holds ETH they can also just submit the Permit themselves, which can render previously signed Permits invalid.
The standard TIP-20 race condition for approvals (SWC-114) applies to permit as well.
If the DOMAIN_SEPARATOR contains the chainId and is defined at contract deployment instead of reconstructed for every signature, there is a risk of possible replay attacks between chains in the event of a future chain split.
Future Extensions
Permit2 for Existing Tokens
TRC-2612 requires token contracts to natively implement the permit function, which means only newly deployed tokens can benefit directly. For existing high-TVL TRC-20 tokens that are already deployed without permit support (e.g., USDT-TRC20), a Permit2-style universal approval contract can serve as a complementary solution.
The Permit2 pattern, originally introduced by Uniswap on Ethereum, works by deploying a standalone contract that:
- Users approve once via the standard TRC-20
approve to the Permit2 contract.
- Subsequent approvals to specific spenders are managed via off-chain signatures through the Permit2 contract, eliminating repeated on-chain approve transactions.
This approach brings signature-based approval benefits to any TRC-20 token regardless of whether the token itself implements TRC-2612. A TRON-native Permit2 specification is outside the scope of this TIP but is a natural next step for the ecosystem.
Copyright
Copyright and related rights waived via CC0.
Abstract
Arguably one of the main reasons for the success of TIP-20 tokens lies in the interplay between
approveandtransferFrom, which allows for tokens to not only be transferred between externally owned accounts (EOA), but to be used in other contracts under application specific conditions by abstracting awaymsg.senderas the defining mechanism for token access control.However, a limiting factor in this design stems from the fact that the TIP-20
approvefunction itself is defined in terms ofmsg.sender. This means that user's initial action involving TIP-20 tokens must be performed by an EOA (but see Note below). If the user needs to interact with a smart contract, then they need to make 2 transactions (approveand the smart contract call which will internally calltransferFrom). Even in the simple use case of paying another person, they need to hold ETH to pay for transaction gas costs.This TRC extends the TIP-20 standard with a new function
permit, which allows users to modify theallowancemapping using a signed message, instead of throughmsg.sender.For an improved user experience, the signed data is structured following TIP-712, which already has wide spread adoption in major RPC providers.
Note: TIP-20 must be performed by an EOA unless the address owning the token is actually a contract wallet. Although contract wallets solves many of the same problems that motivates this TIP, they are currently only scarcely adopted in the ecosystem. Contract wallets suffer from a UX problem -- since they separate the EOA
ownerof the contract wallet from the contract wallet itself (which is meant to carry out actions on theowners behalf and holds all of their funds), user interfaces need to be specifically designed to support them. Thepermitpattern reaps many of the same benefits while requiring little to no change in user interfaces.Motivation
While TIP-20 tokens have become ubiquitous in the Ethereum ecosystem, their status remains that of second class tokens from the perspective of the protocol. The ability for users to interact with Ethereum without holding any ETH has been a long outstanding goal and the subject of many TIPs.
So far, many of these proposals have seen very little adoption, and the ones that have been adopted (such as TIP-777), introduce a lot of additional functionality, causing unexpected behavior in mainstream contracts.
This TRC proposes an alternative solution which is designed to be as minimal as possible and to only address one problem: the lack of abstraction in the TIP-20
approvemethod.While it may be tempting to introduce
*_by_signaturecounterparts for every TIP-20 function, they are intentionally left out of this TIP-20 for two reasons:transfer_by_signature, possible batching algorithms, varies depending on the use case, and,permitand additional helper contracts without loss of generality.Specification
Compliant contracts must implement 3 new functions in addition to TIP-20:
The semantics of which are as follows:
For all addresses
owner,spender, uint256svalue,deadlineandnonce, uint8v, bytes32rands,a call to
permit(owner, spender, value, deadline, v, r, s)will setallowance[owner][spender]tovalue,increment
nonces[owner]by 1,and emit a corresponding
Approvalevent,if and only if the following conditions are met:
deadline.owneris not the zero address.nonces[owner](before the state update) is equal tononce.r,sandvis a validsecp256k1signature fromownerof the message:If any of these conditions are not met, the
permitcall must revert.where
DOMAIN_SEPARATORis defined according to TIP-712. TheDOMAIN_SEPARATORshould be unique to the contract and chain to prevent replay attacks from other domains,and satisfy the requirements of TIP-712, but is otherwise unconstrained.
A common choice for
DOMAIN_SEPARATORis:In other words, the message is the TIP-712 typed structure:
Note that nowhere in this definition we refer to
msg.sender. The caller of thepermitfunction can be any address.Rationale
The
permitfunction is sufficient for enabling any operation involving TIP-20 tokens to be paid for using the token itself, rather than using ETH.The
noncesmapping is given for replay protection.A common use case of
permithas a relayer submit aPermiton behalf of theowner. In this scenario, the relaying party is essentially given a free option to submit or withhold thePermit. If this is a cause of concern, theownercan limit the time aPermitis valid for by settingdeadlineto a value in the near future. Thedeadlineargument can be set touint(-1)to createPermits that effectively never expire.TIP-712 typed messages are included because of its wide spread adoption in many wallet providers.
Backwards Compatibility
There are already a couple of
permitfunctions in token contracts implemented in contracts in the wild, most notably the one introduced in thedai.sol.Its implementation differs slightly from the presentation here in that:
valueargument, it takes a boolallowed, setting approval to 0 oruint(-1).deadlineargument is instead calledexpiry. This is not just a syntactic change, as it effects the contents of the signed message.There is also an implementation in the token
Stake(Ethereum address0x0Ae055097C6d159879521C384F1D2123D1f195e6) with the same ABI asdaibut with different semantics: it lets users issue "expiring approvals", that only allowtransferFromto occur whileexpiry >= block.timestamp.The specification presented here is in line with the implementation in Uniswap V2.
The requirement to revert if the permit is invalid was added when the TIP was already widely deployed, but at the moment it was consistent with all found implementations.
Security Considerations
Though the signer of a
Permitmay have a certain party in mind to submit their transaction, another party can always front run this transaction and callpermitbefore the intended party. The end result is the same for thePermitsigner, however.Since the ecrecover precompile fails silently and just returns the zero address as
signerwhen given malformed messages, it is important to ensureowner != address(0)to avoidpermitfrom creating an approval to spend "zombie funds" belong to the zero address.Signed
Permitmessages are censorable. The relaying party can always choose to not submit thePermitafter having received it, withholding the option to submit it. Thedeadlineparameter is one mitigation to this. If the signing party holds ETH they can also just submit thePermitthemselves, which can render previously signedPermits invalid.The standard TIP-20 race condition for approvals (SWC-114) applies to
permitas well.If the
DOMAIN_SEPARATORcontains thechainIdand is defined at contract deployment instead of reconstructed for every signature, there is a risk of possible replay attacks between chains in the event of a future chain split.Future Extensions
Permit2 for Existing Tokens
TRC-2612 requires token contracts to natively implement the
permitfunction, which means only newly deployed tokens can benefit directly. For existing high-TVL TRC-20 tokens that are already deployed without permit support (e.g., USDT-TRC20), a Permit2-style universal approval contract can serve as a complementary solution.The Permit2 pattern, originally introduced by Uniswap on Ethereum, works by deploying a standalone contract that:
approveto the Permit2 contract.This approach brings signature-based approval benefits to any TRC-20 token regardless of whether the token itself implements TRC-2612. A TRON-native Permit2 specification is outside the scope of this TIP but is a natural next step for the ecosystem.
Copyright
Copyright and related rights waived via CC0.