A standard interface for NFTs specifically designed for AI agents, where the metadata represents agent capabilities and requires privacy protection. Unlike traditional NFT standards that focus on static metadata, this standard introduces mechanisms for verifiable data ownership and secure transfer. By defining a unified interface for different verification methods (e.g., Trusted Execution Environment (TEE), Zero-Knowledge Proof (ZKP)), it enables secure management of valuable agent metadata such as models, memory, and character definitions, while maintaining confidentiality and verifiability.
With the increasing intelligence of AI models, agents have become powerful tools for automating meaningful daily tasks. The integration of agents with blockchain technology has been recognized as a major narrative in the crypto industry, with many projects enabling agent creation for their users. However, a crucial missing piece is the decentralized management of agent ownership.
AI agents possess inherent non-fungible properties that make them natural candidates for NFT representation:
However, current NFT standards like ERC-721 are insufficient for representing AI agents as digital assets. While NFTs can establish ownership of digital items, using them to represent AI agents introduces unique challenges. The key issue lies in the metadata transfer mechanism. Unlike traditional NFTs where metadata is typically static and publicly accessible, an AI agent's metadata (which constitutes the agent itself):
For example, when transferring an agent NFT, we need to ensure:
This EIP introduces a standard for NFTs with private metadata that addresses these requirements through privacy-preserving verification mechanisms, enabling secure ownership and transfer of valuable agent data while maintaining confidentiality and verifiability. This standard will serve as a foundation for the emerging agent ecosystem, allowing platforms to provide verifiable agent ownership and secure metadata management in a decentralized manner.
The EIP defines three key interfaces: the main NFT interface, the metadata interface, and the data verification interface.
The verification system consists of two core components that work together to ensure secure data operations:
Can be implemented using different verification mechanisms (TEE/ZKP)
Off-chain Prover
The system supports two types of proofs:
Verified on-chain through verifyOwnership()
Transfer Validity Proof
verifyTransferValidity()
The ownership verification is optional because when the minted token is transferred or cloned, the ownership verification is checked again inside the availability verification. It's better to be safe than sorry, so we recommend doing ownership verification for minting and updates.
Different verification mechanisms have distinct capabilities:
Verifier checks TEE attestations
ZKP-based Implementation
/// @notice The type of the oracle
/// There are two types of oracles: TEE and ZKP
enum OracleType {
TEE,
ZKP
}
/// @notice The access proof which is a signature signed by the receiver (the receiver may delegate the signing privilege to the access assistant)
/// @param oldDataHash The hash of the old data
/// @param newDataHash The hash of the new data
/// @param nonce The nonce of the access proof
/// @param encryptedPubKey The encrypted public key, the receiver's public key which used to encrypt the new data key. `encryptedPubKey` can be empty in `accessProof`, and means that use the receiver's ethereum public key to encrypt the new data key
/// @param proof The proof
struct AccessProof {
bytes32 oldDataHash;
bytes32 newDataHash;
bytes nonce;
bytes encryptedPubKey;
bytes proof;
}
/// @notice The ownership proof which is a signature signed by the receiver (the receiver may delegate the signing privilege to the access assistant)
/// @param proofType The type of the proof
/// @param oldDataHash The hash of the old data
/// @param newDataHash The hash of the new data
/// @param sealedKey The sealed key of the new data key
/// @param encryptedPubKey The encrypted public key, the receiver's public key which used to encrypt the new data key
/// @param nonce The nonce
struct OwnershipProof {
OracleType oracleType; // The type of the oracle
bytes32 oldDataHash; // The hash of the old data
bytes32 newDataHash; // The hash of the new data
bytes sealedKey; // The sealed key of the new data key
bytes encryptedPubKey; // The encrypted public key, the receiver's public key which used to encrypt the new data key
bytes nonce; // The nonce
bytes proof; // The proof
}
struct TransferValidityProof {
AccessProof accessProof;
OwnershipProof ownershipProof;
}
struct TransferValidityProofOutput {
bytes32 oldDataHash;
bytes32 newDataHash;
bytes sealedKey;
bytes encryptedPubKey;
bytes wantedKey;
address accessAssistant;
bytes accessProofNonce;
bytes ownershipProofNonce;
}
interface IERC7857DataVerifier {
/// @notice Verify data transfer validity, the _proofs prove:
/// 1. The pre-image of oldDataHashes
/// 2. The oldKey (old data key) can decrypt the pre-image and the new key re-encrypt the plaintexts to new ciphertexts
/// 3. The newKey (new data key) is encrypted using the encryptedPubKey
/// 4. The hashes of new ciphertexts is newDataHashes
/// 5. The newDataHashes identified ciphertexts are available in the storage: need the signature from the receiver or the access assistant signing oldDataHashes, newDataHashes, and encryptedPubKey
/// @param _proofs Proof generated by TEE/ZKP
function verifyTransferValidity(
TransferValidityProof[] calldata _proofs
) external returns (TransferValidityProofOutput[] memory);
}
struct IntelligentData {
string dataDescription;
bytes32 dataHash;
}
interface IERC7857Metadata {
/// @notice Get the name of the NFT collection
function name() external view returns (string memory);
/// @notice Get the symbol of the NFT collection
function symbol() external view returns (string memory);
/// @notice Get the data hash of a token
/// @param _tokenId The token identifier
/// @return The current data hash of the token
function intelligentDataOf(uint256 _tokenId) external view returns (IntelligentData[] memory);
}
interface IERC7857 {
/// @notice The event emitted when an address is approved to transfer a token
/// @param _from The address that is approving
/// @param _to The address that is being approved
/// @param _tokenId The token identifier
event Approval(
address indexed _from,
address indexed _to,
uint256 indexed _tokenId
);
/// @notice The event emitted when an address is approved for all
/// @param _owner The owner
/// @param _operator The operator
/// @param _approved The approval
event ApprovalForAll(
address indexed _owner,
address indexed _operator,
bool _approved
);
/// @notice The event emitted when an address is authorized to use a token
/// @param _from The address that is authorizing
/// @param _to The address that is being authorized
/// @param _tokenId The token identifier
event Authorization(
address indexed _from,
address indexed _to,
uint256 indexed _tokenId
);
/// @notice The event emitted when an address is revoked from using a token
/// @param _from The address that is revoking
/// @param _to The address that is being revoked
/// @param _tokenId The token identifier
event AuthorizationRevoked(
address indexed _from,
address indexed _to,
uint256 indexed _tokenId
);
/// @notice The event emitted when a token is transferred
/// @param _tokenId The token identifier
/// @param _from The address that is transferring
/// @param _to The address that is receiving
event Transferred(
uint256 _tokenId,
address indexed _from,
address indexed _to
);
/// @notice The event emitted when a token is cloned
/// @param _tokenId The token identifier
/// @param _newTokenId The new token identifier
/// @param _from The address that is cloning
/// @param _to The address that is receiving
event Cloned(
uint256 indexed _tokenId,
uint256 indexed _newTokenId,
address _from,
address _to
);
/// @notice The event emitted when a sealed key is published
/// @param _to The address that is receiving
/// @param _tokenId The token identifier
/// @param _sealedKeys The sealed keys
event PublishedSealedKey(
address indexed _to,
uint256 indexed _tokenId,
bytes[] _sealedKeys
);
/// @notice The event emitted when a user is delegated to an assistant
/// @param _user The user
/// @param _assistant The assistant
event DelegateAccess(address indexed _user, address indexed _assistant);
/// @notice The verifier interface that this NFT uses
/// @return The address of the verifier contract
function verifier() external view returns (IERC7857DataVerifier);
/// @notice Transfer data with ownership
/// @param _to Address to transfer data to
/// @param _tokenId The token to transfer data for
/// @param _proofs Proofs of data available for _to
function iTransfer(
address _to,
uint256 _tokenId,
TransferValidityProof[] calldata _proofs
) external;
/// @notice Clone data
/// @param _to Address to clone data to
/// @param _tokenId The token to clone data for
/// @param _proofs Proofs of data available for _to
/// @return _newTokenId The ID of the newly cloned token
function iClone(
address _to,
uint256 _tokenId,
TransferValidityProof[] calldata _proofs
) external returns (uint256 _newTokenId);
/// @notice Add authorized user to group
/// @param _tokenId The token to add to group
function authorizeUsage(uint256 _tokenId, address _user) external;
/// @notice Revoke authorization from a user
/// @param _tokenId The token to revoke authorization from
/// @param _user The user to revoke authorization from
function revokeAuthorization(uint256 _tokenId, address _user) external;
/// @notice Approve an address to transfer a token
/// @param _to The address to approve
/// @param _tokenId The token identifier
function approve(address _to, uint256 _tokenId) external;
/// @notice Set approval for all
/// @param _operator The operator
/// @param _approved The approval
function setApprovalForAll(address _operator, bool _approved) external;
/// @notice Delegate access check to an assistant
/// @param _assistant The assistant
function delegateAccess(address _assistant) external;
/// @notice Get token owner
/// @param _tokenId The token identifier
/// @return The current owner of the token
function ownerOf(uint256 _tokenId) external view returns (address);
/// @notice Get the authorized users of a token
/// @param _tokenId The token identifier
/// @return The current authorized users of the token
function authorizedUsersOf(
uint256 _tokenId
) external view returns (address[] memory);
/// @notice Get the approved address for a token
/// @param _tokenId The token identifier
/// @return The approved address
function getApproved(uint256 _tokenId) external view returns (address);
/// @notice Check if an address is approved for all
/// @param _owner The owner
/// @param _operator The operator
/// @return The approval
function isApprovedForAll(
address _owner,
address _operator
) external view returns (bool);
/// @notice Get the delegate access for a user
/// @param _user The user
/// @return The delegate access
function getDelegateAccess(address _user) external view returns (address);
}
The design choices in this standard are motivated by several key requirements:
IDataVerifier
), allowing different verification mechanisms (TEE, ZKP) to be implemented and used interchangeably. The verifier should support two types of proof:oldDataHashes
and newDataHashes
Data Protection: The standard uses data hashes and encrypted keys to ensure that valuable NFT data remains protected while still being integrity and availability verifiable
Flexible Data Management: Three distinct data operations are supported:
Sealed Executor: Although the Sealed Executor is not defined and out of the scope of this standard, it is a crucial component for the standard to work. The Sealed Executor is an environment that can authenticate the user and process the request from the authorized user secretly. The Sealed Executor should get authorized group by tokenId, and the verify the signature of the user using the public keys in the authorized group. If the verification is successful, the executor will process the request and return the result to the user, and the sealed executor could be implemented by a trusted party (where permitted), TEE or Fully Homomorphic Encryption (FHE)
This EIP does not inherit from existing NFT standards to maintain its focus on functional data management. However, implementations can choose to additionally implement ERC-721 if traditional NFT compatibility is desired.
abstract contract BaseVerifier is IERC7857DataVerifier {
// prevent replay attack
mapping(bytes32 => bool) internal usedProofs;
// prevent replay attack
mapping(bytes32 => uint256) internal proofTimestamps;
function _checkAndMarkProof(bytes32 proofNonce) internal {
require(!usedProofs[proofNonce], "Proof already used");
usedProofs[proofNonce] = true;
proofTimestamps[proofNonce] = block.timestamp;
}
// clean expired proof records (save gas)
function cleanExpiredProofs(bytes32[] calldata proofNonces) external {
for (uint256 i = 0; i < proofNonces.length; i++) {
bytes32 nonce = proofNonces[i];
if (usedProofs[nonce] &&
block.timestamp > proofTimestamps[nonce] + 7 days) {
delete usedProofs[nonce];
delete proofTimestamps[nonce];
}
}
}
uint256[50] private __gap;
}
struct AttestationConfig {
OracleType oracleType;
address contractAddress;
}
contract Verifier is
BaseVerifier,
Initializable,
AccessControlUpgradeable,
PausableUpgradeable
{
using ECDSA for bytes32;
using MessageHashUtils for bytes32;
event AttestationContractUpdated(AttestationConfig[] attestationConfigs);
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
mapping(OracleType => address) public attestationContract;
uint256 public maxProofAge;
string public constant VERSION = "2.0.0";
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
AttestationConfig[] calldata _attestationConfigs,
address _admin
) external initializer {
__AccessControl_init();
__Pausable_init();
for (uint256 i = 0; i < _attestationConfigs.length; i++) {
attestationContract[
_attestationConfigs[i].oracleType
] = _attestationConfigs[i].contractAddress;
}
maxProofAge = 7 days;
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
_grantRole(ADMIN_ROLE, _admin);
_grantRole(PAUSER_ROLE, _admin);
emit AttestationContractUpdated(_attestationConfigs);
}
function updateAttestationContract(
AttestationConfig[] calldata _attestationConfigs
) external onlyRole(ADMIN_ROLE) {
for (uint256 i = 0; i < _attestationConfigs.length; i++) {
attestationContract[
_attestationConfigs[i].oracleType
] = _attestationConfigs[i].contractAddress;
}
emit AttestationContractUpdated(_attestationConfigs);
}
function updateMaxProofAge(
uint256 _maxProofAge
) external onlyRole(ADMIN_ROLE) {
maxProofAge = _maxProofAge;
}
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}
function hashNonce(bytes memory nonce) private pure returns (bytes32) {
return keccak256(nonce);
}
function teeOracleVerify(
bytes32 messageHash,
bytes memory signature
) internal view returns (bool) {
return
TEEVerifier(attestationContract[OracleType.TEE]).verifyTEESignature(
messageHash,
signature
);
}
/// @notice Extract and verify signature from the access proof
/// @param accessProof The access proof
/// @return The recovered access assistant address
function verifyAccessibility(
AccessProof memory accessProof
) private pure returns (address) {
bytes32 messageHash = keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n66",
Strings.toHexString(
uint256(
keccak256(
abi.encodePacked(
accessProof.oldDataHash,
accessProof.newDataHash,
accessProof.encryptedPubKey,
accessProof.nonce
)
)
),
32
)
)
);
address accessAssistant = messageHash.recover(accessProof.proof);
require(accessAssistant != address(0), "Invalid access assistant");
return accessAssistant;
}
function verfifyOwnershipProof(
OwnershipProof memory ownershipProof
) private view returns (bool) {
if (ownershipProof.oracleType == OracleType.TEE) {
bytes32 messageHash = keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n66",
Strings.toHexString(
uint256(
keccak256(
abi.encodePacked(
ownershipProof.oldDataHash,
ownershipProof.newDataHash,
ownershipProof.sealedKey,
ownershipProof.encryptedPubKey,
ownershipProof.nonce
)
)
),
32
)
)
);
return teeOracleVerify(messageHash, ownershipProof.proof);
}
// TODO: add ZKP verification
else {
return false;
}
}
/// @notice Process a single transfer validity proof
/// @param proof The proof data
/// @return output The processed proof data as a struct
function processTransferProof(
TransferValidityProof calldata proof
) private view returns (TransferValidityProofOutput memory output) {
// compare the proof data in access proof and ownership proof
require(
proof.accessProof.oldDataHash == proof.ownershipProof.oldDataHash,
"Invalid oldDataHashes"
);
output.oldDataHash = proof.accessProof.oldDataHash;
require(
proof.accessProof.newDataHash == proof.ownershipProof.newDataHash,
"Invalid newDataHashes"
);
output.newDataHash = proof.accessProof.newDataHash;
output.wantedKey = proof.accessProof.encryptedPubKey;
output.accessProofNonce = proof.accessProof.nonce;
output.encryptedPubKey = proof.ownershipProof.encryptedPubKey;
output.sealedKey = proof.ownershipProof.sealedKey;
output.ownershipProofNonce = proof.ownershipProof.nonce;
// verify the access assistant
output.accessAssistant = verifyAccessibility(proof.accessProof);
bool isOwn = verfifyOwnershipProof(proof.ownershipProof);
require(isOwn, "Invalid ownership proof");
return output;
}
/// @notice Verify data transfer validity, the _proof prove:
/// 1. The pre-image of oldDataHashes
/// 2. The oldKey can decrypt the pre-image and the new key re-encrypt the plaintexts to new ciphertexts
/// 3. The newKey is encrypted with the receiver's pubKey to get the sealedKey
/// 4. The hashes of new ciphertexts is newDataHashes (key to note: TEE could support a private key of the receiver)
/// 5. The newDataHashes identified ciphertexts are available in the storage: need the signature from the receiver signing oldDataHashes and newDataHashes
/// @param proofs Proof generated by TEE/ZKP oracle
function verifyTransferValidity(
TransferValidityProof[] calldata proofs
)
public
virtual
override
whenNotPaused
returns (TransferValidityProofOutput[] memory)
{
TransferValidityProofOutput[]
memory outputs = new TransferValidityProofOutput[](proofs.length);
for (uint256 i = 0; i < proofs.length; i++) {
TransferValidityProofOutput memory output = processTransferProof(
proofs[i]
);
outputs[i] = output;
bytes32 accessProofNonce = hashNonce(output.accessProofNonce);
_checkAndMarkProof(accessProofNonce);
bytes32 ownershipProofNonce = hashNonce(output.ownershipProofNonce);
_checkAndMarkProof(ownershipProofNonce);
}
return outputs;
}
uint256[50] private __gap;
}
contract AgentNFT is
AccessControlEnumerableUpgradeable,
IERC7857,
IERC7857Metadata
{
event Updated(
uint256 indexed _tokenId,
IntelligentData[] _oldDatas,
IntelligentData[] _newDatas
);
event Minted(
uint256 indexed _tokenId,
address indexed _creator,
address indexed _owner
);
struct TokenData {
address owner;
address[] authorizedUsers;
address approvedUser;
IntelligentData[] iDatas;
}
/// @custom:storage-location erc7201:agent.storage.AgentNFT
struct AgentNFTStorage {
// Token data
mapping(uint256 => TokenData) tokens;
mapping(address owner => mapping(address operator => bool)) operatorApprovals;
mapping(address user => address accessAssistant) accessAssistants;
uint256 nextTokenId;
// Contract metadata
string name;
string symbol;
string storageInfo;
// Core components
IERC7857DataVerifier verifier;
}
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
string public constant VERSION = "2.0.0";
// keccak256(abi.encode(uint(keccak256("agent.storage.AgentNFT")) - 1)) & ~bytes32(uint(0xff))
bytes32 private constant AGENT_NFT_STORAGE_LOCATION =
0x4aa80aaafbe0e5fe3fe1aa97f3c1f8c65d61f96ef1aab2b448154f4e07594600;
function _getAgentStorage()
private
pure
returns (AgentNFTStorage storage $)
{
assembly {
$.slot := AGENT_NFT_STORAGE_LOCATION
}
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
string memory name_,
string memory symbol_,
string memory storageInfo_,
address verifierAddr,
address admin_
) public virtual initializer {
require(verifierAddr != address(0), "Zero address");
__AccessControlEnumerable_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin_);
_grantRole(ADMIN_ROLE, admin_);
_grantRole(PAUSER_ROLE, admin_);
AgentNFTStorage storage $ = _getAgentStorage();
$.name = name_;
$.symbol = symbol_;
$.storageInfo = storageInfo_;
$.verifier = IERC7857DataVerifier(verifierAddr);
}
// Basic getters
function name() public view virtual returns (string memory) {
return _getAgentStorage().name;
}
function symbol() public view virtual returns (string memory) {
return _getAgentStorage().symbol;
}
function verifier() public view virtual returns (IERC7857DataVerifier) {
return _getAgentStorage().verifier;
}
// Admin functions
function updateVerifier(
address newVerifier
) public virtual onlyRole(ADMIN_ROLE) {
require(newVerifier != address(0), "Zero address");
_getAgentStorage().verifier = IERC7857DataVerifier(newVerifier);
}
function update(
uint256 tokenId,
IntelligentData[] calldata newDatas
) public virtual {
AgentNFTStorage storage $ = _getAgentStorage();
TokenData storage token = $.tokens[tokenId];
require(token.owner == msg.sender, "Not owner");
require(newDatas.length > 0, "Empty data array");
IntelligentData[] memory oldDatas = new IntelligentData[](
token.iDatas.length
);
for (uint i = 0; i < token.iDatas.length; i++) {
oldDatas[i] = token.iDatas[i];
}
delete token.iDatas;
for (uint i = 0; i < newDatas.length; i++) {
token.iDatas.push(newDatas[i]);
}
emit Updated(tokenId, oldDatas, newDatas);
}
function mint(
IntelligentData[] calldata iDatas,
address to
) public payable virtual returns (uint256 tokenId) {
require(to != address(0), "Zero address");
require(iDatas.length > 0, "Empty data array");
AgentNFTStorage storage $ = _getAgentStorage();
tokenId = $.nextTokenId++;
TokenData storage newToken = $.tokens[tokenId];
newToken.owner = to;
newToken.approvedUser = address(0);
for (uint i = 0; i < iDatas.length; i++) {
newToken.iDatas.push(iDatas[i]);
}
emit Minted(tokenId, msg.sender, to);
}
function _proofCheck(
address from,
address to,
uint256 tokenId,
TransferValidityProof[] calldata proofs
)
internal
returns (bytes[] memory sealedKeys, IntelligentData[] memory newDatas)
{
AgentNFTStorage storage $ = _getAgentStorage();
require(to != address(0), "Zero address");
require($.tokens[tokenId].owner == from, "Not owner");
require(proofs.length > 0, "Empty proofs array");
TransferValidityProofOutput[] memory proofOutput = $
.verifier
.verifyTransferValidity(proofs);
require(
proofOutput.length == $.tokens[tokenId].iDatas.length,
"Proof count mismatch"
);
sealedKeys = new bytes[](proofOutput.length);
newDatas = new IntelligentData[](proofOutput.length);
for (uint i = 0; i < proofOutput.length; i++) {
// require the initial data hash is the same as the old data hash
require(
proofOutput[i].oldDataHash ==
$.tokens[tokenId].iDatas[i].dataHash,
"Old data hash mismatch"
);
// only the receiver itself or the access assistant can sign the access proof
require(
proofOutput[i].accessAssistant == $.accessAssistants[to] ||
proofOutput[i].accessAssistant == to,
"Access assistant mismatch"
);
bytes memory wantedKey = proofOutput[i].wantedKey;
bytes memory encryptedPubKey = proofOutput[i].encryptedPubKey;
if (wantedKey.length == 0) {
// if the wanted key is empty, the default wanted receiver is receiver itself
address defaultWantedReceiver = Utils.pubKeyToAddress(
encryptedPubKey
);
require(
defaultWantedReceiver == to,
"Default wanted receiver mismatch"
);
} else {
// if the wanted key is not empty, the data is private
require(
Utils.bytesEqual(encryptedPubKey, wantedKey),
"encryptedPubKey mismatch"
);
}
sealedKeys[i] = proofOutput[i].sealedKey;
newDatas[i] = IntelligentData({
dataDescription: $.tokens[tokenId].iDatas[i].dataDescription,
dataHash: proofOutput[i].newDataHash
});
}
return (sealedKeys, newDatas);
}
function _transfer(
address from,
address to,
uint256 tokenId,
TransferValidityProof[] calldata proofs
) internal {
AgentNFTStorage storage $ = _getAgentStorage();
(
bytes[] memory sealedKeys,
IntelligentData[] memory newDatas
) = _proofCheck(from, to, tokenId, proofs);
TokenData storage token = $.tokens[tokenId];
token.owner = to;
token.approvedUser = address(0);
delete token.iDatas;
for (uint i = 0; i < newDatas.length; i++) {
token.iDatas.push(newDatas[i]);
}
emit Transferred(tokenId, from, to);
emit PublishedSealedKey(to, tokenId, sealedKeys);
}
function iTransfer(
address to,
uint256 tokenId,
TransferValidityProof[] calldata proofs
) public virtual {
require(_isApprovedOrOwner(msg.sender, tokenId), "Not authorized");
_transfer(ownerOf(tokenId), to, tokenId, proofs);
}
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual {
TokenData storage token = _getAgentStorage().tokens[tokenId];
require(_isApprovedOrOwner(msg.sender, tokenId), "Not authorized");
require(to != address(0), "Zero address");
require(token.owner == from, "Not owner");
token.owner = to;
token.approvedUser = address(0);
emit Transferred(tokenId, from, to);
}
function iTransferFrom(
address from,
address to,
uint256 tokenId,
TransferValidityProof[] calldata proofs
) public virtual {
require(_isApprovedOrOwner(msg.sender, tokenId), "Not authorized");
_transfer(from, to, tokenId, proofs);
}
function _clone(
address from,
address to,
uint256 tokenId,
TransferValidityProof[] calldata proofs
) internal returns (uint256) {
AgentNFTStorage storage $ = _getAgentStorage();
(
bytes[] memory sealedKeys,
IntelligentData[] memory newDatas
) = _proofCheck(from, to, tokenId, proofs);
uint256 newTokenId = $.nextTokenId++;
TokenData storage newToken = $.tokens[newTokenId];
newToken.owner = to;
newToken.approvedUser = address(0);
for (uint i = 0; i < newDatas.length; i++) {
newToken.iDatas.push(newDatas[i]);
}
emit Cloned(tokenId, newTokenId, from, to);
emit PublishedSealedKey(to, newTokenId, sealedKeys);
return newTokenId;
}
function iClone(
address to,
uint256 tokenId,
TransferValidityProof[] calldata proofs
) public virtual returns (uint256) {
require(_isApprovedOrOwner(msg.sender, tokenId), "Not authorized");
return _clone(ownerOf(tokenId), to, tokenId, proofs);
}
function iCloneFrom(
address from,
address to,
uint256 tokenId,
TransferValidityProof[] calldata proofs
) public virtual returns (uint256) {
require(_isApprovedOrOwner(msg.sender, tokenId), "Not authorized");
return _clone(from, to, tokenId, proofs);
}
function authorizeUsage(uint256 tokenId, address to) public virtual {
require(to != address(0), "Zero address");
AgentNFTStorage storage $ = _getAgentStorage();
require($.tokens[tokenId].owner == msg.sender, "Not owner");
address[] storage authorizedUsers = $.tokens[tokenId].authorizedUsers;
for (uint i = 0; i < authorizedUsers.length; i++) {
require(authorizedUsers[i] != to, "Already authorized");
}
authorizedUsers.push(to);
emit Authorization(msg.sender, to, tokenId);
}
function ownerOf(uint256 tokenId) public view virtual returns (address) {
AgentNFTStorage storage $ = _getAgentStorage();
address owner = $.tokens[tokenId].owner;
require(owner != address(0), "Token does not exist");
return owner;
}
function authorizedUsersOf(
uint256 tokenId
) public view virtual returns (address[] memory) {
AgentNFTStorage storage $ = _getAgentStorage();
require(_exists(tokenId), "Token does not exist");
return $.tokens[tokenId].authorizedUsers;
}
function storageInfo(
uint256 tokenId
) public view virtual returns (string memory) {
require(_exists(tokenId), "Token does not exist");
return _getAgentStorage().storageInfo;
}
function _exists(uint256 tokenId) internal view returns (bool) {
return _getAgentStorage().tokens[tokenId].owner != address(0);
}
function intelligentDataOf(
uint256 tokenId
) public view virtual returns (IntelligentData[] memory) {
AgentNFTStorage storage $ = _getAgentStorage();
require(_exists(tokenId), "Token does not exist");
return $.tokens[tokenId].iDatas;
}
function approve(address to, uint256 tokenId) public virtual {
address owner = ownerOf(tokenId);
require(to != owner, "Approval to current owner");
require(
msg.sender == owner || isApprovedForAll(owner, msg.sender),
"Not authorized"
);
_getAgentStorage().tokens[tokenId].approvedUser = to;
emit Approval(owner, to, tokenId);
}
function setApprovalForAll(address operator, bool approved) public virtual {
require(operator != msg.sender, "Approve to caller");
_getAgentStorage().operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function getApproved(
uint256 tokenId
) public view virtual returns (address) {
require(_exists(tokenId), "Token does not exist");
return _getAgentStorage().tokens[tokenId].approvedUser;
}
function isApprovedForAll(
address owner,
address operator
) public view virtual returns (bool) {
return _getAgentStorage().operatorApprovals[owner][operator];
}
function delegateAccess(address assistant) public virtual {
require(assistant != address(0), "Zero address");
_getAgentStorage().accessAssistants[msg.sender] = assistant;
emit DelegateAccess(msg.sender, assistant);
}
function getDelegateAccess(
address user
) public view virtual returns (address) {
return _getAgentStorage().accessAssistants[user];
}
function _isApprovedOrOwner(
address spender,
uint256 tokenId
) internal view returns (bool) {
require(_exists(tokenId), "Token does not exist");
address owner = ownerOf(tokenId);
return (spender == owner ||
getApproved(tokenId) == spender ||
isApprovedForAll(owner, spender));
}
function batchAuthorizeUsage(
uint256 tokenId,
address[] calldata users
) public virtual {
require(users.length > 0, "Empty users array");
AgentNFTStorage storage $ = _getAgentStorage();
require($.tokens[tokenId].owner == msg.sender, "Not owner");
for (uint i = 0; i < users.length; i++) {
require(users[i] != address(0), "Zero address in users");
$.tokens[tokenId].authorizedUsers.push(users[i]);
emit Authorization(msg.sender, users[i], tokenId);
}
}
function revokeAuthorization(uint256 tokenId, address user) public virtual {
AgentNFTStorage storage $ = _getAgentStorage();
require($.tokens[tokenId].owner == msg.sender, "Not owner");
require(user != address(0), "Zero address");
address[] storage authorizedUsers = $.tokens[tokenId].authorizedUsers;
bool found = false;
for (uint i = 0; i < authorizedUsers.length; i++) {
if (authorizedUsers[i] == user) {
authorizedUsers[i] = authorizedUsers[
authorizedUsers.length - 1
];
authorizedUsers.pop();
found = true;
break;
}
}
require(found, "User not authorized");
emit AuthorizationRevoked(msg.sender, user, tokenId);
}
}
Copyright and related rights waived via CC0.