Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ERC: AI Agent NFTs #348

Merged
merged 21 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions ERCS/erc-7662.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
eip: 7662
title: AI Agent NFTs
description: A specification for NFTs that represent AI Agents.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your description doesn't include any new information that wasn't in your title, and it has a bit of fluff ("A specification for".) You should use the description to elaborate on the ideas introduced in the title as succinctly as possible.

For example, what is an AI Agent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point - will add, thanks.

author: Greg Marlin (@marleymarl)
discussions-to: https://ethereum-magicians.org/t/erc-7662-ai-agent-nfts/19371
status: Draft
type: Standards Track
category: ERC
created: 2024-03-26
requires: 721
---

## Abstract

This proposal introduces a standard for AI agent NFTs. When AI Agents are created and traded as NFTs, it doesn't make sense to put the prompts in the token metadata, therefore it requires a standard custom struct. It also doesn't make sense to store the prompts directly onchain as they can be quite large, therefore this standard proposes they be stored as decentralized storage URLs. This standard also proposes two options on how this data should be made private to the owner of the NFT, with the favored implementation option being encrypting the data using custom contract parameters for decryption that decrypt only to the owner of the NFT.

## Motivation

The creation and trading of AI Agent NFTs are a natural fit and offer the potential for an entirely new onchain market. This requires some custom data to be embedded in the NFT through a custom struct and this needs to be standardized so that any marketplace or AI Agent management product, among others, know how to create and parse AI Agent NFTs. The goal of this standard is to provide a new utility for NFTs in the field of AI and also to provide new liquidity, through the NFT market, for AI Agents. If widely adopted by marketplaces, and infrastructure and no-code providers this should open up a new market and community for AI Agent creators in different fields, AI Agent consumers and NFT marketplaces.


## 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.


All ERC-XXXX compliant contracts MUST implement the standard [ERC-721](./eip-721.md) functionality for minting and transferring NFTs, and MUST additionally implement this standard's Agent interface

```solidity

interface IERC7662 is IERC721 {

function getAgentData(uint256 tokenId) external view returns (
string memory name,
string memory description,
string memory model,
string memory userPromptURI,
string memory systemPromptURI,
bool promptsEncrypted
);

event AgentUpdated(uint256 indexed tokenId);
}
```

and MUST implement the mapping between NFT Token ID and its Agent information.

It is RECOMMENDED that this mapping is public and that the URIs for User Prompt and System Prompt are made private through encryption with decryption logic set to the holder of the NFT via custom contract parameters set during encryption, and the method or platform used to provide this encryption SHOULD be retrievable as a data property of the NFT in order that platforms that should facilitate the use of these NFTs can set up a predictable way to handle this decryption, depending on the platform or method used.

It is conceivable to also create an implementation whereby this mapping was set to private and accessed through a custom function that restricted access to the holder of the NFT. This approach would explose the prompts through their urls though, therefore the RECOMMENDED approach is a public mapping and encryption on the URLs. This also has the benefit of publicly exposing the data in the Agent struct to verify name, description and model and that encyrpted URIs for the User Prompt and System Prompt exist.

All ERC-XXXX compliant contracts MUST implement a function to mint new Agent tokens. This function SHOULD:

- Accept parameters for all Agent properties (name, description, model, userPromptURI, systemPromptURI, etc.)
- Mint a new token to the specified recipient
- Associate the provided Agent properties with the newly minted token
- Emit an event signaling the creation of a new Agent token

It is RECOMMENDED that ERC-XXXX compliant contracts provide functionality to encrypt the user prompt and system prompt. This functionality SHOULD:

- Allow only the token owner to encrypt the prompts
- Update the userPromptURI and systemPromptURI with encrypted versions
- Set a flag indicating that the prompts are encrypted

It is RECOMMENDED to implement the following event:

```solidity
event AgentCreated(string name, string description, string model, address recipient, uint256 tokenId)

```

This event SHOULD be emitted when a new Agent token is minted, providing key information about the newly created Agent.

To enable dynamic variables being injected into the User Prompt before being run, any such variables MUST be surrounded with ${} e.g. ${dynamicVariableName} in order that they can be recognized and handled appropriately by programs and systems that will enabled the injection, e.g. web forms and automation systems.

It is RECOMMENDED to add a data to the [ERC-721](./eip-721.md) standard that makes it easy for e.g. NFT Marketplaces to display data about the AI Agent NFT, i.e. Model, which in turn reveals the platform that is used for the agent, e.g. OpenAI in the case of gpt-4-0125-preview or Anthropic in the case of claude-3-opus-20240229. The standard name and description can be used to display the Agent Name and Agent Description.

## Rationale

This standard provides a unified way to create and parse AI Agent NFTs.

This standard codifies the necessary parameters of Name, Description, Model, User Prompt, and System Prompt for creating and using AI Agent NFTs.

It doesn't make practical sense to store the user and system prompts in an existing [ERC-721](./eip-721.md) as the only place to put would be in the token metadata that is open for anyone to access the prompts without owning the NFT. By storing the prompts in a custom Agent struct and restricting access to the prompts to the holder of the NFT. One way to do this would be through restricing access to the struct info to the holder of the NFT through a custom function, however since that option still exposes the prompt URIs to the public and thus the data inside them, the recommended method is by encrypting the prompts onchain and tying the decryption of the URLs to the holder of the NFT, using onchain services that enable decryption to be tied to contract parameters such as ownerOf(tokenId).


## Backwards Compatibility

The AI Agents NFT standard introduces additional features and data to the standard [ERC-721](./eip-721.md) protocol, aimed at addressing the practical requirements of using NFTs to store, trade and use AI Agents. It is designed to be fully backward-compatible with the original [ERC-721](./eip-721.md) standard. All existing [ERC-721](./eip-721.md) functions (such as transferFrom, approve, and balanceOf) retain their original functionality and interfaces. Our extension does not modify these core behaviors, ensuring that any [ERC-721](./eip-721.md) compliant wallet or service can interact with these tokens without modifications.

### Reference Implementation

This is being currently implemented in a product for creating, managing and using AI Agents Onchain through a DApp interface. In this implementation, an encryption platform is being used to encrypt the prompts using custom EVMContractParameters that only decrypt for the holder of the NFT and using a decentralized storage network to store the URLs of this encrypted data. To facilitate that and make DApp handling easier, some parameters were added to Agent and the addEncryptedPrompts function is added that enables adding the encrypted prompt URIs after first minting the NFT (as the tokenId of the NFT is needed for setting the encryption/decryption conditions).

A reference smart contract is provided in the assets folder.



## Security Considerations

marleymarl marked this conversation as resolved.
Show resolved Hide resolved
<!-- TODO -->

Check warning on line 102 in ERCS/erc-7662.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

HTML comments are only allowed while `status` is one of: `Draft`, `Withdrawn`

warning[markdown-html-comments]: HTML comments are only allowed while `status` is one of: `Draft`, `Withdrawn` --> ERCS/erc-7662.md | 102 | <!-- TODO --> | = help: see https://ethereum.github.io/eipw/markdown-html-comments/

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
148 changes: 148 additions & 0 deletions assets/erc-7662/ERC7662.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/interfaces/IERC721.sol";
import "./lib/FactoryOperatorable.sol";

contract ERC7662 is FactoryOperatorable, ERC721URIStorage {

uint256 public tokenIds;

//NFT Base URI
string public baseURI;


struct Agent {
string name;
string description;
string model;
string userPromptURI;
string systemPromptURI;
string imageURI;
string category;
bool promptsEncrypted;
}

mapping(address => uint256[]) public collectionIds;
mapping(uint => Agent) public Agents;

event AgentCreated(string name, string description, string model, string category, address recipient, uint256 tokenId);

constructor(
string memory collectionBaseURI,
address admin,
address operator) ERC721("Agent NFTs", "AGENTS") FactoryOperatorable(admin, operator) {

baseURI = collectionBaseURI;

}

/**
* @dev Override supportInterface.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721) returns (bool) {
return super.supportsInterface(interfaceId);
}


/**
* @dev Mint an Agent NFT and attach its data to the token id
*
* @param _recipient address to receive NFT
* @param _name string Name of the Agent
* @param _description string Description of the Agent
* @param _model string AI Model of the Agent
* @param _userPromptURI string URI of the Agent's User Prompt
* @param _systemPromptURI string URI of the Agent's System Prompt
* @param _imageURI string URI of the NFT image
* @param _category string Category of Agent
* @param _tokenURI string URI of the NFT
*
* Emits an AgentCreated event.
*/
function mintAgent(address _recipient, string memory _name, string memory _description, string memory _model, string memory _userPromptURI, string memory _systemPromptURI, string memory _imageURI, string memory _category, string memory _tokenURI) public {
tokenIds++;
bool _promptsEncrypted = false;
Agents[tokenIds] = Agent(_name, _description, _model, _userPromptURI, _systemPromptURI, _imageURI, _category, _promptsEncrypted);

_mint(_recipient, tokenIds);
collectionIds[_recipient].push(tokenIds);
_setTokenURI(tokenIds, _tokenURI);
emit AgentCreated(_name, _description, _model, _category, _recipient, tokenIds);
}

/**
* @dev Update NFT with Encrypted Prompts as token id needed first for encryption params
*
* @param _tokenId uint256 Id of the NFT to update
* @param _encryptedUserPromptURI string Encrypted URI of the Agent's User Prompt
* @param _encryptedSystemPromptURI string Encrypted URI of the Agent's System Prompt
*/
function addEncryptedPrompts(uint256 _tokenId, string memory _encryptedUserPromptURI, string memory _encryptedSystemPromptURI) public {
require(ownerOf(_tokenId) == msg.sender, "Sender must be token owner");
Agent storage agent = Agents[_tokenId];
agent.userPromptURI = _encryptedUserPromptURI;
agent.systemPromptURI = _encryptedSystemPromptURI;
agent.promptsEncrypted = true;
}

/**
* @dev Return base URI
* Override {ERC721:_baseURI}
*/
function _baseURI() internal view override returns (string memory) {
return baseURI;
}

/**
* @dev Return all token ids owned by address
* @param _address address Address to check for
*/
function getCollectionIds(address _address) public view returns (uint256[] memory) {
return collectionIds[_address];
}


/**
* @dev Remove the given token from collectionIds.
*
* @param from address from
* @param tokenId tokenId to remove
*/
function _popId(address from, uint256 tokenId) internal {
uint256[] storage _collectionIds = collectionIds[from];
for (uint256 i = 0; i < _collectionIds.length; i++) {
if (_collectionIds[i] == tokenId) {
if (i != _collectionIds.length - 1) {
_collectionIds[i] = _collectionIds[_collectionIds.length - 1];
}
_collectionIds.pop();
break;
}
}
}

/**
* @dev Transfers `tokenId` from `from` to `to`.
*
* Requirements:
*
* - `tokenId` token must be owned by `from`.
*
* @param from address from
* @param to address to
* @param tokenId tokenId to transfer
*/
function _transfer(
address from,
address to,
uint256 tokenId
) internal override {
super._transfer(from, to, tokenId);
_popId(from, tokenId);
collectionIds[to].push(tokenId);
}


}
Loading