---
eip: 5827
title: Auto-renewable allowance extension
description: Extension to enable automatic renewals on allowance approvals
author: zlace (@zlace0x), zhongfu (@zhongfu), edison0xyz (@edison0xyz)
discussions-to: https://ethereum-magicians.org/t/eip-5827-auto-renewable-allowance-extension/10392
status: Draft
type: Standards Track
category: ERC
created: 2022-10-22
requires: 20, 165
---

## Abstract

This extension adds a renewable allowance mechanism to [ERC-20](./eip-20.md) allowances, in which a `recoveryRate` defines the amount of token per second that the allowance regains towards the initial maximum approval `amount`.

## Motivation

Currently, ERC-20 tokens support allowances, with which token owners can allow a spender to spend a certain amount of tokens on their behalf. However, this is not ideal in circumstances involving recurring payments (e.g. subscriptions, salaries, recurring direct-cost-averaging purchases).

Many existing DApps circumvent this limitation by requesting that users grant a large or unlimited allowance. This presents a security risk as malicious DApps can drain users' accounts up to the allowance granted, and users may not be aware of the implications of granting allowances.

An auto-renewable allowance enables many traditional financial concepts like credit and debit limits. An account owner can specify a spending limit, and limit the amount charged to the account based on an allowance that recovers over time.


## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

```solidity
pragma solidity ^0.8.0;

interface IERC5827 /* is ERC20, ERC165 */ {
    /*
     * Note: the ERC-165 identifier for this interface is 0x93cd7af6.
     * 0x93cd7af6 ===
     *   bytes4(keccak256('approveRenewable(address,uint256,uint256)')) ^
     *   bytes4(keccak256('renewableAllowance(address,address)')) ^
     *   bytes4(keccak256('approve(address,uint256)') ^
     *   bytes4(keccak256('transferFrom(address,address,uint256)') ^
     *   bytes4(keccak256('allowance(address,address)') ^
     */

    /**
     * @notice  Thrown when the available allowance is less than the transfer amount.
     * @param   available       allowance available; 0 if unset
     */
    error InsufficientRenewableAllowance(uint256 available);

    /**
     * @notice  Emitted when any allowance is set.
     * @dev     MUST be emitted even if a non-renewable allowance is set; if so, the
     * @dev     `_recoveryRate` MUST be 0.
     * @param   _owner          owner of token
     * @param   _spender        allowed spender of token
     * @param   _value          initial and maximum allowance granted to spender
     * @param   _recoveryRate   recovery amount per second
     */
    event RenewableApproval(
        address indexed _owner,
        address indexed _spender,
        uint256 _value,
        uint256 _recoveryRate
    );

    /**
     * @notice  Grants an allowance of `_value` to `_spender` initially, which recovers over time 
     * @notice  at a rate of `_recoveryRate` up to a limit of `_value`.
     * @dev     SHOULD cause `allowance(address _owner, address _spender)` to return `_value`, 
     * @dev     SHOULD throw when `_recoveryRate` is larger than `_value`, and MUST emit a 
     * @dev     `RenewableApproval` event.
     * @param   _spender        allowed spender of token
     * @param   _value          initial and maximum allowance granted to spender
     * @param   _recoveryRate   recovery amount per second
     */
    function approveRenewable(
        address _spender,
        uint256 _value,
        uint256 _recoveryRate
    ) external returns (bool success);

    /**
     * @notice  Returns approved max amount and recovery rate of allowance granted to `_spender` 
     * @notice  by `_owner`.
     * @dev     `amount` MUST also be the initial approval amount when a non-renewable allowance 
     * @dev     has been granted, e.g. with `approve(address _spender, uint256 _value)`.
     * @param    _owner         owner of token
     * @param   _spender        allowed spender of token
     * @return  amount initial and maximum allowance granted to spender
     * @return  recoveryRate recovery amount per second
     */
    function renewableAllowance(address _owner, address _spender)
        external
        view
        returns (uint256 amount, uint256 recoveryRate);

    /// Overridden ERC-20 functions

    /**
     * @notice  Grants a (non-increasing) allowance of _value to _spender and clears any existing 
     * @notice  renewable allowance.
     * @dev     MUST clear set `_recoveryRate` to 0 on the corresponding renewable allowance, if 
     * @dev     any.
     * @param   _spender        allowed spender of token
     * @param   _value          allowance granted to spender
     */
    function approve(address _spender, uint256 _value)
        external
        returns (bool success);

    /**
    * @notice   Moves `amount` tokens from `from` to `to` using the caller's allowance.
    * @dev      When deducting `amount` from the caller's allowance, the allowance amount used 
    * @dev      SHOULD include the amount recovered since the last transfer, but MUST NOT exceed 
    * @dev      the maximum allowed amount returned by `renewableAllowance(address _owner, address 
    * @dev      _spender)`. 
    * @dev      SHOULD also throw `InsufficientRenewableAllowance` when the allowance is 
    * @dev      insufficient.
    * @param    from            token owner address
    * @param    to              token recipient
    * @param    amount          amount of token to transfer
    */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);

    /**
     * @notice  Returns amount currently spendable by `_spender`.
     * @dev     The amount returned MUST be as of `block.timestamp`, if a renewable allowance 
     * @dev     for the `_owner` and `_spender` is present.
     * @param   _owner         owner of token
     * @param   _spender       allowed spender of token
     * @return  remaining allowance at the current point in time
     */
    function allowance(address _owner, address _spender)
        external
        view
        returns (uint256 remaining);
}
```

Base method `approve(address _spender, uint256 _value)` MUST set `recoveryRate` to 0.

Both `allowance()` and `transferFrom()` MUST be updated to include allowance recovery logic.

`approveRenewable(address _spender, uint256 _value, uint256 _recoveryRate)` MUST set both the initial allowance amount and the maximum allowance limit (to which the allowance can recover) to `_value`.

`supportsInterface(0x93cd7af6)` MUST return `true`.

### Additional interfaces

**Token Proxy**

Existing ERC-20 tokens can delegate allowance enforcement to a proxy contract that implements this specification. An additional query function exists to get the underlying ERC-20 token.

```solidity
interface IERC5827Proxy /* is IERC5827 */ {

    /*
     * Note: the ERC-165 identifier for this interface is 0xc55dae63.
     * 0xc55dae63 ===
     *   bytes4(keccak256('baseToken()')
     */

    /**
     * @notice   Get the underlying base token being proxied.
     * @return   baseToken address of the base token
     */
    function baseToken() external view returns (address);
}
```

The `transfer()` function on the proxy MUST NOT emit the `Transfer` event (as the underlying token already does so).

**Automatic Expiration**

```solidity
interface IERC5827Expirable /* is IERC5827 */ {
    /*
     * Note: the ERC-165 identifier for this interface is 0x46c5b619.
     * 0x46c5b619 ===
     *   bytes4(keccak256('approveRenewable(address,uint256,uint256,uint64)')) ^
     *   bytes4(keccak256('renewableAllowance(address,address)')) ^
     */

    /**
     * @notice  Grants an allowance of `_value` to `_spender` initially, which recovers over time 
     * @notice  at a rate of `_recoveryRate` up to a limit of `_value` and expires at 
     * @notice  `_expiration`.
     * @dev     SHOULD throw when `_recoveryRate` is larger than `_value`, and MUST emit 
     * @dev     `RenewableApproval` event.
     * @param   _spender        allowed spender of token
     * @param   _value          initial allowance granted to spender
     * @param   _recoveryRate   recovery amount per second
     * @param   _expiration     Unix time (in seconds) at which the allowance expires
     */
    function approveRenewable(
        address _spender,
        uint256 _value,
        uint256 _recoveryRate,
        uint64 _expiration
    ) external returns (bool success);

    /**
     * @notice  Returns approved max amount, recovery rate, and expiration timestamp.
     * @return  amount initial and maximum allowance granted to spender
     * @return  recoveryRate recovery amount per second
     * @return  expiration Unix time (in seconds) at which the allowance expires
     */
    function renewableAllowance(address _owner, address _spender)
        external
        view
        returns (uint256 amount, uint256 recoveryRate, uint64 expiration);
}
```

## Rationale

Renewable allowances can be implemented with discrete resets per time cycle. However, a continuous `recoveryRate` allows for more flexible use cases not bound by reset cycles and can be implemented with simpler logic.

## Backwards Compatibility

Existing ERC-20 token contracts can delegate allowance enforcement to a proxy contract that implements this specification.

## Reference Implementation

An minimal implementation is included [here](../assets/eip-5827/ERC5827.sol)

An audited, open source implemention of this standard as a `IERC5827Proxy` can be found at `https://github.com/suberra/funnel-contracts`

## Security Considerations

This EIP introduces a stricter set of constraints compared to ERC-20 with unlimited allowances. However, when `_recoveryRate` is set to a large value, large amounts can still be transferred over multiple transactions.

Applications that are not [ERC-5827](./eip-5827.md)-aware may erroneously infer that the value returned by `allowance(address _owner, address _spender)` or included in `Approval` events is the maximum amount of tokens that `_spender` can spend from `_owner`. This may not be the case, such as when a renewable allowance is granted to `_spender` by `_owner`.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
