ERC-8187 - Token Puller

Created 2026-02-27
Status Draft
Category ERC
Type Standards Track
Authors
Requires

Abstract

This ERC proposes a standardized interface for "Puller" contracts that enable approved spenders to initiate token transfers from an owner's account without requiring the owner to maintain liquid balances. The Puller handles custom logic for sourcing tokens (e.g., withdrawing or borrowing from lending protocols, liquidating positions, or other operations) and executes the transfer to a specified destination.

The interface supports:

This enables use cases such as recurring payments, subscriptions, automated settlements, guardian-managed limits, and credit-card-like spending controls in DeFi and payment applications, while improving security and yield optimization.

Motivation

Current token approval standards (ERC-20 approve/transferFrom, ERC-2612 permits) require owners to hold liquid balances and often involve multiple transactions or direct balance pulls. This creates friction and risks:

The Token Puller Interface addresses these by introducing an intermediary Puller contract that:

The core motivation behind this ERC is to cleanly decouple spending logic from asset management strategies. By introducing a Puller contract (or, in the smart-account case, the account itself), the act of sourcing tokens — whether from a lending position, a vault, a swap, or simply an internal balance — becomes an implementation detail hidden from the spender. The spender only requests a pull for a certain amount and token; it never needs to know or interact with how those tokens are actually obtained. This atomic sourcing + transfer pattern reduces complexity on the payment or spending side while letting users keep their funds invested until the moment they are needed.

Specification

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

Definitions

The following terms are used with these specific meanings in this specification:

Additional terms that appear frequently and benefit from clear definition:

Methods

approvePull

function approvePull(address token, address spender, uint256 limit) external

Sets or updates the pull allowance of spender for token from msg.sender (the owner).

pullFrom

function pullFrom(address token, address owner, address to, uint256 amount) external

Pulls amount of token from owner and transfers it to to, after executing the Puller's implementation-specific sourcing logic.

transferPullAllowance

function transferPullAllowance(address token, address owner, address toSpender, uint256 amount) external

Transfers amount of pull allowance from msg.sender (the current spender) to toSpender for the (token, owner) pair.

pullAllowance

function pullAllowance(address token, address owner, address spender) external view returns (uint256)

Returns the units of token that spender is allowed to pull from owner.

maxPullable

function maxPullable(address token, address owner, uint256 upTo) external view returns (uint256)

Returns the max amount that can be pulled of a given token from a given owner. The upTo parameter allows early termination if that amount is reached.

permitPull

function permitPull(
    address token,
    address owner,
    address spender,
    uint256 limit,
    uint256 deadline,
    bytes calldata signature
) external

Approves or updates a pull allowance using an off-chain EIP-712 signature.

pullFromWithPermit

function pullFromWithPermit(
    address token,
    address owner,
    address to,
    uint256 amount,
    uint256 deadline,
    bytes calldata signature
) external

Atomically applies a permit (with limit == amount) and executes a pull in a single transaction.

Other methods

Implementations SHOULD expose the domain via ERC-5267.

Implementations MUST expose nonces(owner) as described in ERC-2612.

Events

PullApproval

event PullApproval(address indexed token, address indexed owner, address indexed spender, uint256 limit)

Emitted when an owner approves or updates a spender's pull allowance for a given token.

TokensPulled

event TokensPulled(address indexed token, address indexed owner, address indexed spender, address to, uint256 amount)

Emitted when tokens are successfully pulled from an owner and transferred to the destination.

TransferPullAllowance

event TransferPullAllowance(address indexed token, address indexed owner, address indexed fromSpender, address toSpender, uint256 amount)

Emitted when a spender transfers part or all of their pull allowance to another spender (or renounces it by transferring to address(0)).

Interface

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;

interface IPuller {
    // Events
    event PullApproval(address indexed token, address indexed owner, address indexed spender, uint256 limit);
    event TokensPulled(address indexed token, address indexed owner, address indexed spender, address to, uint256 amount);
    event TransferPullAllowance(address indexed token, address indexed owner, address indexed fromSpender, address toSpender, uint256 amount);

    // Core functions
    function approvePull(address token, address spender, uint256 limit) external;
    function pullFrom(address token, address owner, address to, uint256 amount) external;
    function pullAllowance(address token, address owner, address spender) external view returns (uint256);
    function maxPullable(address token, address owner, uint256 upTo) external view returns (uint256);

    // Allowance delegation
    function transferPullAllowance(address token, address owner, address toSpender, uint256 amount) external;

    function permitPull(
        address token,
        address owner,
        address spender,
        uint256 limit,
        uint256 deadline,
        bytes calldata signature
    ) external;

    function pullFromWithPermit(
        address token,
        address owner,
        address to,
        uint256 amount,
        uint256 deadline,
        bytes calldata signature
    ) external;
}

Rationale

Gasless approvals via signed permits and the ability to transfer allowances between spenders were added specifically to support credit-card-like experiences and delegated spending flows. For example, a user might grant a large or infinite allowance to a trusted "guardian" service that enforces daily/monthly limits and automatically refills sub-allowances for individual spenders (e.g., a payment app or merchant processor). These features make recurring or delegated payments more practical without requiring the owner to sign every transaction or maintain liquid balances.

The interface deliberately mirrors familiar ERC-20 patterns (approve / allowance / transferFrom) and builds on established extensions like ERC-2612 (Permit) to minimize the learning curve and avoid unnecessary naming collisions. Where possible, function names, event structures, and parameter ordering stay close to precedents so developers and tools can adopt the standard quickly.

The maxPullable function provides a standardized way to query available pull capacity (similar to balanceOf for direct holdings or maxWithdraw in ERC-4626), independent of spender allowances. The upTo parameter allows efficient checks in cascaded sourcing implementations without forcing full strategy evaluation every time.

Finally, the design is intentionally compatible with both EOAs and smart accounts, while leaning into the current direction of account abstraction (ERC-4337 and others). A particularly powerful pattern is for a smart account to implement the IPuller interface directly on itself. In that case owner == address(this), the account already controls its own funds (and any pre-approved external positions), and there is no need to grant approvals or trust an external Puller contract. This reduces deployment overhead, eliminates an extra approval step, and allows the pull logic to participate in batched user operations — a natural fit for modular wallets that already expose custom execution and spending-limit interfaces.

Reference Implementation

A reference implementation is provided, with a commented interface and an educational example implementation of a Puller that pulls funds by withdrawing them from a vault.

This example has not been audited and should not be used in production environments.

See contracts

Security Considerations

Production implementations should be audited with special attention to the sourcing paths, signature validation, and allowance transfer logic.

Copyright

Copyright and related rights waived via CC0.