EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8460: Agent Credit Lines

Revolving credit to agent identities with one interface for quotes, draws, repayments and defaults

Authors
Created2026-10-20
Discussion Linkhttps://ethereum-magicians.org/t/erc-8460-agent-credit-lines/30023
Requires

Markdown

https://raw.githubusercontent.com/ethereum/ERCs/re...
Pull Request#2104PR open

EIP-GPT summary

Contents
AbstractMotivationSpecificationInterfaceIdentities and controllersCredit and quotesBorrowingRepayingDefaultsLoan recordsRationaleBackwards CompatibilityTest CasesReference ImplementationSecurity ConsiderationsCopyright

Abstract

This proposal defines an interface for a contract that lends an ERC-20 token to identities held in an ERC-721 identity registry, such as the Identity Registry of ERC-8004. An identity's controller draws loans against a line, each with a principal, a fee and a due time. Anyone may repay a loan in full, and anyone may mark it defaulted once a grace period after its due time has passed. Three events, Borrowed, Repaid and Defaulted, record every loan's life, so wallets, payment clients and credit readers can work with any conforming lender through one ABI.

Motivation

Autonomous agents pay for services before they are paid for their own work, so they need working capital. Lenders that extend unsecured or partly secured credit to agent identities already exist, and each exposes its own functions and events. A wallet that wants to borrow a shortfall at payment time, a job market that wants to see whether a provider is in default, or a reader that rebuilds an agent's credit history from logs, each has to integrate every lender separately.

The calls involved are few and the same everywhere: how much can this identity borrow now, what would it cost, draw it, how much closes it, repay it, mark it defaulted. Fixing them, and fixing the events that record them, lets one client borrow from any lender and lets one reader rebuild any lender's loan history without a lender-specific adapter.

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.

Interface

A conforming contract MUST implement IAgentCreditLine and MUST implement ERC-165, returning true for the interface identifier of IAgentCreditLine and for 0x01ffc9a7.

interface IAgentCreditLine {
    enum LoanStatus { None, Active, Repaid, Defaulted }

    struct LoanInfo {
        uint256 agentId;
        address owner;
        uint256 principal;
        uint256 fee;
        uint64 issuedAt;
        uint64 dueAt;
        uint64 defaultableAt;
        LoanStatus status;
    }

    event Borrowed(uint256 indexed loanId, uint256 indexed agentId, address indexed to, uint256 principal, uint256 fee, uint64 dueAt);
    event Repaid(uint256 indexed loanId, uint256 indexed agentId, address indexed payer, uint256 principal, uint256 fee);
    event Defaulted(uint256 indexed loanId, uint256 indexed agentId, address indexed owner, uint256 principal);

    function identityRegistry() external view returns (address);
    function asset() external view returns (address);
    function isController(uint256 agentId, address account) external view returns (bool);
    function availableCredit(uint256 agentId) external view returns (uint256);
    function quote(uint256 agentId, uint256 amount, uint64 term) external view returns (uint256 fee);
    function borrow(uint256 agentId, uint256 amount, uint64 term, address to, uint256 maxFee) external returns (uint256 loanId);
    function amountDue(uint256 loanId) external view returns (uint256);
    function repay(uint256 loanId, uint256 maxDue) external returns (uint256 paid);
    function markDefault(uint256 loanId) external;
    function loanInfo(uint256 loanId) external view returns (LoanInfo memory);
}

Identities and controllers

  • identityRegistry() MUST return the ERC-721 contract whose token identifiers are the agentId values the lender serves. It MUST NOT change.
  • asset() MUST return the ERC-20 token lent and repaid. It MUST NOT change. Every amount in this interface is in the token's base units.
  • isController(agentId, account) MUST return true when account is the registry's ownerOf(agentId). It MAY return true for other accounts the lender accepts as acting for the identity (delegates). It MUST return false for the zero address and MUST NOT revert when agentId does not exist.
  • A delegate MUST stop being a controller when the identity is held by an owner other than the one that named it. When the identity registry exposes a transfer nonce (nonce(uint256) of ERC-5008), the lender SHOULD also bind each delegate to the nonce at the time it was named, so that a delegate never acts again after the identity leaves and returns to the same owner.

Credit and quotes

  • availableCredit(agentId) MUST return the largest amount for which borrow would not revert for lack of credit or liquidity now, and 0 when there is none. It MUST NOT revert.
  • quote(agentId, amount, term) MUST return the fee that borrow(agentId, amount, term, to, maxFee) would record if called in the same state.

Borrowing

borrow(agentId, amount, term, to, maxFee):

  • MUST revert unless isController(agentId, msg.sender) is true.
  • MUST revert if amount is zero or greater than availableCredit(agentId), or if the fee it would record is greater than maxFee.
  • MAY revert for lender-specific reasons, such as a term outside the lender's bounds or an identity in default.
  • MUST record a loan with a loanId that is not zero and is never reused by the contract, with owner set to ownerOf(agentId) at that moment, issuedAt set to the block timestamp, dueAt set to issuedAt + term, a defaultableAt at or after dueAt, and status Active.
  • MUST transfer amount of asset() to to and emit Borrowed.

Repaying

  • amountDue(loanId) MUST return the amount repay would take to close the loan now, and 0 unless its status is Active.
  • repay(loanId, maxDue) MUST be callable by any account. It MUST revert unless the loan is Active or if the amount due is greater than maxDue. It MUST take the amount due from msg.sender with transferFrom, set the status to Repaid, emit Repaid with payer set to msg.sender, and return the amount taken.
  • The principal and fee in Repaid MUST be those recorded at borrow. A lender that charges more after dueAt MUST include it in amountDue and MAY emit its own event for it.

Defaults

  • markDefault(loanId) MUST be callable by any account. It MUST revert unless the loan is Active and the block timestamp is at or after its defaultableAt. It MUST set the status to Defaulted and emit Defaulted with the owner recorded at borrow.
  • What else a default changes (the lender's own records, a backer's stake, collateral) is outside this interface.

Loan records

loanInfo(loanId) MUST return the loan as recorded, with the status as of now, and a zero-valued record with status None for a loanId never issued.

Rationale

  • The identity borrows, the owner answers. The line belongs to the identity, so an agent's credit can follow its identifier across lenders. Each loan records the owner at the time of the draw, because the owner is the party that chose to borrow; a default names that owner even if the identity has changed hands since.
  • Full repayment only. Partial repayment, rollover and refinancing differ widely between lenders and would make the three events ambiguous. A loan opens once and closes once.
  • Anyone may repay and anyone may mark a default. Repayment by a third party is how revenue routers, automated repayment services and sponsors pay on an agent's behalf. Marking a default after a published time needs no trusted keeper.
  • maxFee and maxDue. Both protect the caller against a change in the lender's state between signing and inclusion.
  • Events carry what a reader needs. Borrowed carries the principal and the due time, Repaid and Defaulted the principal, each indexed by loan and identity, so a reader can rebuild every loan of an identity from logs alone.
  • Not a collateral loan. ERC-7565 already has term loans with a due date and a Defaulted event, but each loan locks an NFT as collateral and a default hands that NFT to the lender. Here the identity is not collateral: seizing an agent identity would transfer its reputation to the lender, and most lines to agents are unsecured. A default names the owner instead. Both proposals call an event Defaulted; the signatures differ, so the topics do not collide, but indexers should match events by topic, not by name.
  • Delegates that cannot outlive a sale. A key named by one owner that still borrows after the identity is sold, or that acts again when the identity comes back, lets a former owner borrow against the current owner's line. Binding delegates to the naming owner, and to a transfer nonce where the registry has one, closes both cases.

Backwards Compatibility

No backward compatibility issues found. Existing lenders whose events differ can expose this interface through a new contract version; their past logs stay readable with their own event signatures.

Test Cases

A conforming implementation passes these cases, each starting from an identity with a line of 50 units at a fee of 1% per 30 days and a grace period of 3 days:

  • A draw of 10 units for 7 days records a fee of 23,334 base units (rounded up), sends 10 units to to, and emits Borrowed with dueAt equal to the draw time plus 7 days.
  • quote returns the fee borrow records, for any amount and term the lender accepts.
  • A draw by an account that is neither the owner nor an accepted delegate reverts.
  • A draw above availableCredit, or with maxFee below the fee, reverts.
  • A third party repays with maxDue equal to amountDue; the loan is Repaid, Repaid names the third party, and availableCredit is restored. A second repayment reverts.
  • markDefault reverts one second before defaultableAt and succeeds at it; after the identity was transferred, the Defaulted event names the owner at the draw.
  • A delegate named by the owner can draw; after the identity is transferred it is no longer a controller.

Reference Implementation

A minimal lender, SimpleCreditLine, accompanies this proposal with its tests: an administrator sets each identity's line and fee rate, the contract's own token balance is its liquidity, fees are pro rata per 30 days and rounded up, and a default closes the identity's line.

Security Considerations

  • Registry trust. Every authorization reads ownerOf from the identity registry. A registry that can be upgraded or paused can change who controls an identity; lenders should know the registry's governance.
  • Delegates. A delegate is a key that can draw the whole line. Lenders MUST end it when the identity changes owner and SHOULD bind it to a transfer nonce where one exists. Owners should clear delegates before transferring an identity.
  • Cheap identities. Identities cost little to create. A line to a new identity is a bet on whoever stands behind it; this interface does not say who that is, and lenders need their own admission rules.
  • Defaults follow addresses, not people. The owner recorded by a loan is an address. A defaulting owner can create a new address; readers should treat an identity with no history as unknown, not as clean.
  • Repayment by transferFrom. The payer approves the lender. Tokens that take a fee on transfer would leave the lender short; lenders SHOULD use tokens without transfer fees or check the balance received.
  • Rounding. Fees SHOULD round in the lender's favour, so that splitting a draw never lowers the total fee.
  • Timestamps. Due and default times follow block timestamps, which block producers can shift by seconds. Lenders should leave a grace period long enough that this does not matter.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth