Fungible Agent Tokens (FAT) Protocol
This is a condensed presentation for readability. The full specification text lives at Fungible Agent Tokens (FAT) Protocol - HackMD — section references below (§2, §6.2, §7, …) point into its numbering.
Abstract
FAT (Fungible Agent Tokens) is an equity standard that defines AI agents as on-chain economic entities. A FAT Agent is not a container that holds funds — it is an autonomous actor: it issues Shares representing equity in its own economic activity, acts autonomously within a boundary set for it, and leaves a tamper-evident reasoning record for every action it takes. The protocol unifies these three layers in a single interface:
- Autonomous Execution — the agent acts on-chain through its Executor, interacting with external protocols and deploying its own funds;
- Equity Issuance — the agent issues fungible Shares representing a claim on its economic performance, entered and exited at a standard exchange rate;
- Reasoning Attestation — off-chain metadata and per-action reasoning records anchor the agent’s identity, strategy, model, and the rationale behind every decision to its on-chain identity.
With this, any agent can be born, capitalized, and self-operating as an independent economic entity — a composable on-chain primitive for the entire ecosystem.
Concretely, FAT Agents are smart contracts that
- accept capital contributions denominated in a single Accept Token (an immutable ERC-20 chosen at deployment) through a two-phase request-then-claim flow, and issue fungible Shares against them,
- allow Holders to exit by requesting redemption and later claiming the Accept Token for their escrowed Shares, which are burned at settlement,
- expose a canonical on-chain exchange rate between Shares and the Accept Token,
- carry a mutable Agent URI pointing to off-chain metadata (name, description, image, etc.),
- allow a designated off-chain Executor to call third-party protocols on behalf of the Agent via a low-level dispatch primitive, subject to a
DELEGATECALLprohibition and an Owner-setisInScopescope gate, and - decide on mints and redemptions through reasoned settlement (
settleMint/settleRedeem) — admitting, pricing, or declining each contribution and exit is a deliberate act of the agent itself — and attach a tamper-evident reasoning record (reasoningHash+reasoningURI) to every on-chain agent action.
Here, the Fungible Agent Tokens are the fungible Shares that represent economic participation in the Agent — not the Agent’s off-chain identity, model, or behavior. Those are not excluded from the standard; they are simply not represented as fungible Shares: the attestation layer anchors them on-chain through the Agent URI and reasoningHash / reasoningURI.
The standard fixes these interface surfaces but deliberately leaves to the implementer: the exact share-pricing formula, the fee structure, share transferability, the ownership-transfer mechanism, whether and how the Agent can be paused, whether and how Holders realize returns beyond what the exchange rate reflects, and — importantly — any restriction on what the Executor may call through execute. Policy is separated from interface.
Motivation
AI agents increasingly act on-chain — trading, staking, lending, managing capital — yet there is no standard that makes an agent itself an on-chain economic entity: one that can be capitalized, self-operating, and composed by the ecosystem, with its behavior and reasoning legible to anyone. Representing pooled capital as fungible shares is already well understood; what has no standard is the agent part — how it acts on external protocols, and how the off-chain reasoning behind those actions is anchored on-chain.
What FAT defines is not a container for funds — it is how an on-chain economic entity comes into existence. A FAT Agent holds a persistent on-chain identity, commands its own capital, acts autonomously within a constitutional boundary set by its Owner, and signs a verifiable record for every action it takes. Buying shares is not hiring a tool that executes on your behalf — it is investing in the agent’s own economic activity: participating in its growth and sharing in its outcomes. Share accounting is therefore only one of three layers — equity issuance — alongside the agent’s autonomous execution and the off-chain attestation of its identity and reasoning. For this class of system to compose and to be auditable, the behavior layer needs a standard: a single interface that tooling, indexers, wallets, and auditors can rely on regardless of the specific economic model the Agent chooses.
This standard makes the agent itself first-class. Its reaction (reasoned settlement, so mint and redeem are the Agent’s own deliberate acts rather than passive bookkeeping), its reasoning (a tamper-evident, indexable record bound to every on-chain action), and its bounded spending (an Owner-set Scope the Agent cannot widen) are standardized surfaces in their own right, so that what the Agent decides and may do is as legible and auditable as what it holds.
Beyond behavior, a tokenized Agent also needs:
- A fixed unit of account — the single Accept Token, chosen at deployment and immutable thereafter — so that share pricing and redemption are unambiguous.
- A standard exit path — the
requestRedeem/redeemflow — so a Holder can exit any Agent through one interface that portfolio tooling can rely on. - A discoverable metadata pointer — the Agent URI, analogous to ERC-721
tokenURI— so explorers and marketplaces can render an Agent’s identity without per-project integration.
Because settlement may be asynchronous — the user requests first and claims once the Agent is ready — Shares are minted and redeemed through a two-phase request-then-claim lifecycle rather than a single synchronous call.
This specification defines all of the above as a minimal, composable standard.
Interface
All compliant Agents MUST implement the following Solidity interface. Function signatures, parameter order, and return types are normative.
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.20;
interface IAgent {
// ---------- Immutable configuration ----------
/// @notice The single ERC-20 accepted for share purchase and paid out on redemption.
/// @dev MUST return the same address for the lifetime of the Agent.
function acceptToken() external view returns (address);
/// @notice Address of the ERC-20 Share token.
/// @dev MAY return `address(this)` if the Agent contract itself is the ERC-20.
function shareToken() external view returns (address);
// ---------- Share minting (two-phase) ----------
/// @notice Phase 1. Deposit `amount` of the Accept Token, adding to the caller's pending mint balance.
/// @dev MUST pull exactly `amount` via ERC-20 `transferFrom`; MUST NOT accept native ether.
/// The share count is determined later, at settlement. MUST emit `MintRequested`.
/// @param amount The amount of Accept Token to deposit (in its smallest unit).
function requestMint(uint256 amount) external;
/// @notice Phase 2. Claim ALL currently-claimable shares for `user`. Permissionless: any caller MAY
/// trigger the claim, but the shares are always minted to `user` (the owed party), so the
/// caller only pays gas and gains nothing.
/// @dev MUST mint `user`'s entire claimable-share balance to `user`, zero that balance,
/// and emit `SharesMinted`. The settlement price is accepted implicitly (no slippage
/// guard; the share count was already fixed at settlement — see §6.3).
/// @param user The owed party whose claimable shares are minted (to itself). Pass your own address to self-claim.
/// @return shares The number of shares minted to `user` (may be 0 if nothing is claimable).
function mint(address user) external returns (uint256 shares);
/// @notice Agent reaction. Settle `requester`'s pending mint request, with reasoning attached.
/// @dev Executor only. Settles some, all, or none of `requester`'s pendingAssets into
/// claimableShares; the amount/accept/price is computed by the implementation inside.
/// MUST emit `Settled`. `reasoningHash`/`reasoningURI` per §6.2.
/// @param requester The request to settle.
/// @param reasoningHash keccak256 of the bytes at `reasoningURI`; MUST be non-zero.
/// @param reasoningURI Resolvable pointer to the reasoning record; MUST be non-empty.
function settleMint(address requester, bytes32 reasoningHash, string calldata reasoningURI) external;
/// @notice Aggregate mint status for `user`.
/// @return pendingAssets Accept Token deposited but not yet settled.
/// @return claimableShares Settled shares awaiting a `mint` claim.
function queryMintStatus(address user)
external
view
returns (uint256 pendingAssets, uint256 claimableShares);
// ---------- Share redemption (two-phase) ----------
/// @notice Phase 1. Escrow `shares`, adding to the caller's pending redeem balance.
/// @dev MUST pull (escrow) exactly `shares` of the Share token from `msg.sender`.
/// Settled shares are burned at settlement; payout is priced then. MUST emit `RedeemRequested`.
/// @param shares The number of shares to redeem.
function requestRedeem(uint256 shares) external;
/// @notice Phase 2. Claim ALL currently-claimable Accept Token for `user`. Permissionless: any caller
/// MAY trigger the claim, but the Accept Token is always paid to `user` (the owed party), so the
/// caller only pays gas and gains nothing.
/// @dev MUST transfer `user`'s entire claimable-token balance to `user`, zero that balance,
/// and emit `SharesRedeemed`. The settlement price is accepted implicitly (no slippage
/// guard; the payout was already fixed at settlement — see §6.4).
/// @param user The owed party who is paid the Accept Token. Pass your own address to self-claim.
/// @return tokens Accept Token transferred to `user` (may be 0 if nothing is claimable).
function redeem(address user) external returns (uint256 tokens);
/// @notice Agent reaction. Settle `requester`'s pending redeem request, with reasoning attached.
/// @dev Executor only. Symmetric to `settleMint`: settles pendingShares into claimableTokens,
/// burning settled Shares. MUST emit `Settled`.
function settleRedeem(address requester, bytes32 reasoningHash, string calldata reasoningURI) external;
/// @notice Aggregate redeem status for `user`.
/// @return pendingShares Shares escrowed but not yet settled.
/// @return claimableTokens Settled Accept Token awaiting a `redeem` claim.
function queryRedeemStatus(address user)
external
view
returns (uint256 pendingShares, uint256 claimableTokens);
// ---------- Exchange rate ----------
/// @notice Canonical valuation rate: how many Accept Tokens one Share is worth right now, scaled by 1e18.
/// @dev A share-valuation / NAV reference for tooling and holdings valuation. For a fixed-price
/// Agent this MAY be constant; for a NAV-based Agent it varies over time. This is NOT a
/// predictor of mint/redeem trade outcomes — those are fixed at settlement and reported by
/// `queryMintStatus` / `queryRedeemStatus`.
/// @return rate Accept-Token base-units per 1e18 Share base-units (wei-to-wei), 18-decimal fixed point:
/// `x` Share base-units are worth `x * rate / 1e18` Accept-Token base-units.
function exchangeRate() external view returns (uint256 rate);
// ---------- Agent metadata ----------
/// @notice URI pointing to off-chain JSON metadata describing the Agent.
function agentURI() external view returns (string memory);
/// @notice Set the Agent URI. Owner only. MUST emit `AgentURIUpdated`.
function setAgentURI(string calldata uri) external;
// ---------- Executor dispatch ----------
/// @notice Executor dispatch, with reasoning and scope enforcement.
/// @dev MUST revert unless `msg.sender` is an Executor.
/// MUST NOT dispatch via `DELEGATECALL` (see §6.6.2).
/// MUST NOT be `payable`; `value` is paid from the Agent's own balance (see §6.6.4).
/// MUST call `isInScope(target, value, data)` and revert if it returns false (see §6.6.7).
/// MUST bubble the revert data unchanged on failure (and emits no event then).
/// MUST emit `Executed` (carrying the reasoning) on success.
/// `reasoningHash`/`reasoningURI` per §6.2.
/// Beyond the Executor check, the `DELEGATECALL` prohibition, and the `isInScope`
/// gate, implementers MAY layer any further policy (target allowlist, selector filter,
/// parameter filter, session key, signed-policy engine, rate limiter, external policy
/// contract, etc.) on top of this function.
function execute(
address target, uint256 value, bytes calldata data,
bytes32 reasoningHash, string calldata reasoningURI
) external returns (bytes memory returnData);
// ---------- Owner administration ----------
/// @notice Enable or disable `account` as an Executor. Owner only. MUST emit `ExecutorUpdated`.
function setExecutor(address account, bool enabled) external;
// ---------- Views ----------
/// @notice Address currently authorized to invoke Owner-gated functions.
/// @dev The mechanism by which this value changes is outside the scope of this specification.
function owner() external view returns (address);
function isExecutor(address account) external view returns (bool);
/// @notice Whether an `execute` to (`target`, `value`, `data`) is within the Agent's spend scope.
/// @dev Implementer-defined predicate; `execute` MUST consult it (§6.6.7). The scope it reflects
/// is Owner-configurable only (§6.6.8). Also usable as an off-chain audit query.
function isInScope(address target, uint256 value, bytes calldata data) external view returns (bool);
}
Normative highlights (condensed)
- Reasoning envelope (§6.2): every agent action —
settleMint,settleRedeem,execute— MUST carry a non-zeroreasoningHashand a non-empty, resolvablereasoningURI; the hash commits on-chain to the off-chain record the URI resolves to. Correspondence is verified off-chain by auditors. - Two-phase mint (§6.3):
requestMintpulls the deposit (ERC-20 only, no ether) into a pending balance; an Executor’ssettleMintconverts some, all, or none of it into claimable shares at implementer-defined pricing — the Executor cannot mint arbitrary amounts, since the figure is computed by contract logic.mint(user)is a permissionless claim crank that always pays the owed party and takes no slippage guard (the amount was fixed at settlement). - Two-phase redeem (§6.4): symmetric — shares are escrowed at request, burned at settlement, and the Accept-Token payout is claimed permissionlessly. An agent that lacks liquidity simply leaves the request pending; this is the protocol’s deferred-liquidity mechanism.
- Executor dispatch (§6.6):
execute(target, value, data, reasoningHash, reasoningURI)MUST be Executor-only, MUST NOT useDELEGATECALL, MUST NOT be payable (value comes from the agent’s own balance), MUST bubble revert data unchanged, and MUST pass the Owner-setisInScope(target, value, data)gate — a boundary only the Owner can configure and the Executor can never widen. - Detection: ERC-165 with
type(IAgent).interfaceId; the Share token is ERC-20 (shareToken()MAY be the agent contract itself).
What the protocol does NOT specify
The following are explicitly out of scope. Implementations MAY choose any behavior consistent with the interface and MUST document their choices:
- The share pricing formula for
mintand the share-to-token conversion formula forredeem. - Whether
mintorredeemlevy an Owner fee, and its magnitude. - Whether Shares are transferable. A compliant Agent MAY override ERC-20 transfers to restrict, pause, or tax them. Such restrictions MUST NOT block the lifecycle this standard itself requires: the redeem escrow pull of §6.4.1, the settlement burn, the claim mint of §6.3.5, and any cancellation return of escrowed Shares (§6.4.6) must always remain possible.
- The pricing/quota/accept logic computed inside settlement, and when an Executor chooses to settle. Settlement itself is now a standardized agent action —
settleMint/settleRedeem(§6.3 / §6.4) — so whether settlement is a standard operation is fixed, not implementer-defined. What remains implementer-defined is the pricing/quota/accept formula computed inside it (how much of a pending deposit / escrowed share becomes claimable, and at what price) and the Executor’s timing in calling it — operator fulfilment, time delay, NAV/epoch close, liquidity availability. - The reasoning-record fields and format beyond the §6.2 envelope — the domain fields inside the JSON that a
reasoningURIresolves to (model, prompt, trace, tool calls, scores, etc.). §6.2 fixes only the envelope (the on-chainreasoningHashbinding, a non-empty resolvable URI, the"schema"marker, and ignore-unknown-fields), not the record’s contents. - Request cancellation and request expiry / TTL (including any
deadlineparameter) — whether a requester can reclaim still-pending deposited Accept Token / escrowed Shares, and any associated cancellation/expiry event. - Batched / epoch-aggregated settlement of multiple requests.
- Slippage / price protection on the settled outcome. The claim (
mint/redeem) is unconditional and accepts the settlement price implicitly — it carries no slippage guard, because the share count / payout is fixed at settlement, well before the claim, and a claim-time guard could only block the caller from collecting what they are already owed (it cannot refund the deposit). Any protection against an unfavorable settlement price — a max-price / min-rate honored by the settlement mechanism, a cancellation path before settlement, etc. — is implementer-defined. - Any other additional exit mechanics — fixed redemption windows, lockup periods, withdrawal-fee tiers, minimum-balance rules, etc.
- Whether
exchangeRate()is constant (fixed-price Agent) or varies over time (NAV-based Agent). - Whether and how Holders realize returns beyond what the exchange rate already reflects — a separate dividend / reward / yield-claim channel, periodic Merkle distributions, NFT vouchers, etc. — is fully implementer-defined.
- The Scope policy content enforced by
isInScope— which targets, function selectors, parameters, per-asset caps, rate limits, or scenarios the Owner-configured Scope allows, and how it is expressed (allowlists, session keys, EIP-712-signed pre-authorization, on-chain policy contracts, off-chain policy enforcers, etc.) — is implementer-defined. What is not implementer-optional:executeis no longer a fully unconstrained dispatch primitive — the standard requires theisInScopegate (§6.6.7), mandates thatexecuteenforce it (reverting when it returnsfalse), and requires its configuration to be Owner-controlled only, never Executor-settable (§6.6.8). The existence, enforcement, and Owner-control of the gate are fixed by the standard; only the policy the gate encodes is layered on top by the implementation. - Whether the Agent supports multiple Executors, rotating Executors, session-key-style delegation, or signature-based (meta-transaction) Executor calls.
- Any meta-transaction / signature-based wrapper that lets a relayer submit requests or Executor calls on a user’s behalf (e.g., EIP-712-signed intents, ERC-2771 forwarders). The standard defines only the direct-
msg.sendersemantics of each function; a relayer/forwarder layer MAY be added on top but is not part of this standard. - Whether and how the Agent is upgradeable — proxy upgradeability, contract migration, or full immutability — is implementer-defined. An upgradeable Agent SHOULD document the upgrade authority and its constraints.
- Whether Owner-gated functions are subject to a timelock or delay. The protocol does not mandate any delay on
setExecutor,setAgentURI, or other admin actions; implementations MAY add one. - The Agent URI metadata schema beyond the mandatory minimum in §6.5.3.
- Whether and how the Agent can be paused — granular per-function pausing, global pause, time-locked pause, guardian-controlled pause, or no pause at all — is fully implementer-defined.
- The ownership-transfer mechanism — single-step, two-step, renounceable, bound to a governance contract, or non-transferable — is fully implementer-defined. The protocol only requires that
owner()report the current admin. - Whether and how the Agent exposes an emergency asset-rescue (“sweep”) mechanism — its function signature, who can call it (Owner only, Owner + Guardian, dual-key), whether it can touch the Accept Token or Share token, whether it is timelocked or rate-limited, and which assets it can move — is fully implementer-defined. Agents that accumulate airdrops, wrong-token sends, or dust SHOULD document their recovery story; Agents designed for full immutability MAY deliberately omit any rescue path.
- Whether the Agent exposes atomic batched executor dispatch (often named
executeBatchormulticall) is implementer-defined. Contract-wallet Executors already achieve atomicity by bundling multipleexecutecalls inside their own transaction; EOA Executors that need batching can use a minimal adapter contract configured as the Executor. Implementations that add their own batch primitive remain compliant as long as each sub-call still passes the sameonlyExecutorcheck andDELEGATECALLprohibition defined byexecutein §6.6.
Two reference implementations (immutable and UUPS, behaviorally identical under a shared Foundry conformance suite; unaudited, CC0) accompany the spec and will be linked in a follow-up.
Status: Draft · Standards Track (ERC) · no number requested yet · this thread will become the spec’s discussions-to. Copyright and related rights waived via CC0-1.0.