@aixyz/erc-8004 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -0
- package/contracts/IdentityRegistryUpgradeable.sol +214 -0
- package/contracts/ReputationRegistryUpgradeable.sol +386 -0
- package/contracts/ValidationRegistryUpgradeable.sol +198 -0
- package/dist/abis/IdentityRegistry_V1.d.ts +802 -0
- package/dist/abis/IdentityRegistry_V1.js +1047 -0
- package/dist/abis/IdentityRegistry_V2.d.ts +802 -0
- package/dist/abis/IdentityRegistry_V2.js +1047 -0
- package/dist/abis/ReputationRegistry_V1.d.ts +214 -0
- package/dist/abis/ReputationRegistry_V1.js +275 -0
- package/dist/abis/ReputationRegistry_V2.d.ts +567 -0
- package/dist/abis/ReputationRegistry_V2.js +730 -0
- package/dist/abis/ReputationRegistry_V3.d.ts +567 -0
- package/dist/abis/ReputationRegistry_V3.js +730 -0
- package/dist/abis/ValidationRegistry_V1.d.ts +391 -0
- package/dist/abis/ValidationRegistry_V1.js +508 -0
- package/dist/index.js +4379 -0
- package/dist/src/index.d.ts +101 -0
- package/dist/src/index.js +173 -0
- package/dist/src/schemas/feedback.d.ts +190 -0
- package/dist/src/schemas/feedback.js +146 -0
- package/dist/src/schemas/registration.d.ts +219 -0
- package/dist/src/schemas/registration.js +125 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# @aixyz/erc-8004
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004), the decentralized agent registry standard. Provides contract ABIs, deployed addresses, and Zod schemas for agent registration and feedback files.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @aixyz/erc-8004
|
|
9
|
+
# or
|
|
10
|
+
npm install @aixyz/erc-8004
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Contract Interaction
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { IdentityRegistryAbi, ADDRESSES, CHAIN_ID } from "@aixyz/erc-8004";
|
|
19
|
+
import { createPublicClient, http } from "viem";
|
|
20
|
+
import { sepolia } from "viem/chains";
|
|
21
|
+
|
|
22
|
+
const client = createPublicClient({
|
|
23
|
+
chain: sepolia,
|
|
24
|
+
transport: http(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const address = ADDRESSES[CHAIN_ID.SEPOLIA].identityRegistry;
|
|
28
|
+
|
|
29
|
+
const tokenURI = await client.readContract({
|
|
30
|
+
address,
|
|
31
|
+
abi: IdentityRegistryAbi,
|
|
32
|
+
functionName: "tokenURI",
|
|
33
|
+
args: [1n],
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Parsing Registration Files
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { parseRawRegistrationFile, getServices, hasX402Support } from "@aixyz/erc-8004";
|
|
41
|
+
|
|
42
|
+
// Parse an existing file fetched from an agent's tokenURI
|
|
43
|
+
const result = parseRawRegistrationFile(fetchedData);
|
|
44
|
+
if (result.success) {
|
|
45
|
+
const services = getServices(result.data);
|
|
46
|
+
const supportsPayment = hasX402Support(result.data);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Creating Registration Files
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { validateRegistrationFile } from "@aixyz/erc-8004";
|
|
54
|
+
|
|
55
|
+
// Strict validation before on-chain submission — requires type literal and at least one service
|
|
56
|
+
const result = validateRegistrationFile(newFile);
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
console.error(result.error.issues);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Parsing Feedback Files
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { parseRawFeedbackFile } from "@aixyz/erc-8004";
|
|
66
|
+
|
|
67
|
+
const result = parseRawFeedbackFile(fetchedData);
|
|
68
|
+
if (result.success) {
|
|
69
|
+
const { agentId, value, valueDecimals, clientAddress } = result.data;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Solidity (Foundry)
|
|
74
|
+
|
|
75
|
+
```toml
|
|
76
|
+
# foundry.toml
|
|
77
|
+
remappings = ["@aixyz/erc-8004/=node_modules/@aixyz/erc-8004/"]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```solidity
|
|
81
|
+
import { IdentityRegistryUpgradeable } from "@aixyz/erc-8004/contracts/IdentityRegistryUpgradeable.sol";
|
|
82
|
+
import { ReputationRegistryUpgradeable } from "@aixyz/erc-8004/contracts/ReputationRegistryUpgradeable.sol";
|
|
83
|
+
import { ValidationRegistryUpgradeable } from "@aixyz/erc-8004/contracts/ValidationRegistryUpgradeable.sol";
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Supported Chains
|
|
87
|
+
|
|
88
|
+
**Mainnets:**
|
|
89
|
+
|
|
90
|
+
| Chain | Chain ID | Constant |
|
|
91
|
+
| ------- | -------- | ------------------ |
|
|
92
|
+
| Mainnet | `1` | `CHAIN_ID.MAINNET` |
|
|
93
|
+
| Base | `8453` | `CHAIN_ID.BASE` |
|
|
94
|
+
| Polygon | `137` | `CHAIN_ID.POLYGON` |
|
|
95
|
+
| Scroll | `534352` | `CHAIN_ID.SCROLL` |
|
|
96
|
+
| Monad | `143` | `CHAIN_ID.MONAD` |
|
|
97
|
+
| BSC | `56` | `CHAIN_ID.BSC` |
|
|
98
|
+
| Gnosis | `100` | `CHAIN_ID.GNOSIS` |
|
|
99
|
+
|
|
100
|
+
**Testnets:**
|
|
101
|
+
|
|
102
|
+
| Chain | Chain ID | Constant |
|
|
103
|
+
| -------------- | ---------- | ------------------------- |
|
|
104
|
+
| Sepolia | `11155111` | `CHAIN_ID.SEPOLIA` |
|
|
105
|
+
| Base Sepolia | `84532` | `CHAIN_ID.BASE_SEPOLIA` |
|
|
106
|
+
| Polygon Amoy | `80002` | `CHAIN_ID.POLYGON_AMOY` |
|
|
107
|
+
| Scroll Sepolia | `534351` | `CHAIN_ID.SCROLL_SEPOLIA` |
|
|
108
|
+
| Monad Testnet | `10143` | `CHAIN_ID.MONAD_TESTNET` |
|
|
109
|
+
| BSC Testnet | `97` | `CHAIN_ID.BSC_TESTNET` |
|
|
110
|
+
|
|
111
|
+
## ABIs
|
|
112
|
+
|
|
113
|
+
| Export | Description |
|
|
114
|
+
| ----------------------- | ------------------------------------- |
|
|
115
|
+
| `IdentityRegistryAbi` | ERC-721 based agent identity registry |
|
|
116
|
+
| `ReputationRegistryAbi` | Agent reputation/feedback registry |
|
|
117
|
+
| `ValidationRegistryAbi` | Third-party agent validation registry |
|
|
118
|
+
|
|
119
|
+
Versioned ABIs (e.g. `IdentityRegistryAbi_V1`, `ReputationRegistryAbi_V3`) are available for pinning. See [CHANGELOG.md](./CHANGELOG.md) for details.
|
|
120
|
+
|
|
121
|
+
## Contract Addresses
|
|
122
|
+
|
|
123
|
+
All contracts use UUPS proxies. Addresses are consistent across all chains within each environment.
|
|
124
|
+
|
|
125
|
+
**Mainnets** (Ethereum, Base, Polygon, Scroll, Monad, BSC, Gnosis):
|
|
126
|
+
|
|
127
|
+
| Contract | Address |
|
|
128
|
+
| -------------------- | -------------------------------------------- |
|
|
129
|
+
| `identityRegistry` | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` |
|
|
130
|
+
| `reputationRegistry` | `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` |
|
|
131
|
+
| `validationRegistry` | `0x8004Cc8439f36fd5F9F049D9fF86523Df6dAAB58` |
|
|
132
|
+
|
|
133
|
+
**Testnets** (Sepolia, Base Sepolia, Polygon Amoy, Scroll Sepolia, Monad Testnet, BSC Testnet):
|
|
134
|
+
|
|
135
|
+
| Contract | Address |
|
|
136
|
+
| -------------------- | -------------------------------------------- |
|
|
137
|
+
| `identityRegistry` | `0x8004A818BFB912233c491871b3d84c89A494BD9e` |
|
|
138
|
+
| `reputationRegistry` | `0x8004B663056A597Dffe9eCcC1965A193B7388713` |
|
|
139
|
+
| `validationRegistry` | `0x8004Cb1BF31DAf7788923b405b754f57acEB4272` |
|
|
140
|
+
|
|
141
|
+
## References
|
|
142
|
+
|
|
143
|
+
- [ERC-8004 Specification](https://eips.ethereum.org/EIPS/eip-8004)
|
|
144
|
+
- [erc-8004-contracts](https://github.com/erc-8004/erc-8004-contracts)
|
|
145
|
+
- [CHANGELOG.md](./CHANGELOG.md) - ABI version history and breaking changes
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// As copied from https://github.com/erc-8004/erc-8004-contracts/blob/093d7b91eb9c22048d411896ed397d695742a5f8/contracts/IdentityRegistryUpgradeable.sol
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
pragma solidity ^0.8.20;
|
|
4
|
+
|
|
5
|
+
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
|
|
6
|
+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
7
|
+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
8
|
+
import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
|
|
9
|
+
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
10
|
+
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
|
|
11
|
+
|
|
12
|
+
contract IdentityRegistryUpgradeable is
|
|
13
|
+
ERC721URIStorageUpgradeable,
|
|
14
|
+
OwnableUpgradeable,
|
|
15
|
+
UUPSUpgradeable,
|
|
16
|
+
EIP712Upgradeable
|
|
17
|
+
{
|
|
18
|
+
struct MetadataEntry {
|
|
19
|
+
string metadataKey;
|
|
20
|
+
bytes metadataValue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// @custom:storage-location erc7201:erc8004.identity.registry
|
|
24
|
+
struct IdentityRegistryStorage {
|
|
25
|
+
uint256 _lastId;
|
|
26
|
+
// agentId => metadataKey => metadataValue (includes "agentWallet")
|
|
27
|
+
mapping(uint256 => mapping(string => bytes)) _metadata;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// keccak256(abi.encode(uint256(keccak256("erc8004.identity.registry")) - 1)) & ~bytes32(uint256(0xff))
|
|
31
|
+
bytes32 private constant IDENTITY_REGISTRY_STORAGE_LOCATION =
|
|
32
|
+
0xa040f782729de4970518741823ec1276cbcd41a0c7493f62d173341566a04e00;
|
|
33
|
+
|
|
34
|
+
function _getIdentityRegistryStorage() private pure returns (IdentityRegistryStorage storage $) {
|
|
35
|
+
assembly {
|
|
36
|
+
$.slot := IDENTITY_REGISTRY_STORAGE_LOCATION
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
event Registered(uint256 indexed agentId, string agentURI, address indexed owner);
|
|
41
|
+
event MetadataSet(uint256 indexed agentId, string indexed indexedMetadataKey, string metadataKey, bytes metadataValue);
|
|
42
|
+
event URIUpdated(uint256 indexed agentId, string newURI, address indexed updatedBy);
|
|
43
|
+
|
|
44
|
+
bytes32 private constant AGENT_WALLET_SET_TYPEHASH =
|
|
45
|
+
keccak256("AgentWalletSet(uint256 agentId,address newWallet,address owner,uint256 deadline)");
|
|
46
|
+
bytes4 private constant ERC1271_MAGICVALUE = 0x1626ba7e;
|
|
47
|
+
uint256 private constant MAX_DEADLINE_DELAY = 5 minutes;
|
|
48
|
+
bytes32 private constant RESERVED_AGENT_WALLET_KEY_HASH = keccak256("agentWallet");
|
|
49
|
+
|
|
50
|
+
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
51
|
+
constructor() {
|
|
52
|
+
_disableInitializers();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function initialize() public reinitializer(2) onlyOwner {
|
|
56
|
+
__ERC721_init("AgentIdentity", "AGENT");
|
|
57
|
+
__ERC721URIStorage_init();
|
|
58
|
+
__EIP712_init("ERC8004IdentityRegistry", "1");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function register() external returns (uint256 agentId) {
|
|
62
|
+
IdentityRegistryStorage storage $ = _getIdentityRegistryStorage();
|
|
63
|
+
agentId = $._lastId++;
|
|
64
|
+
$._metadata[agentId]["agentWallet"] = abi.encodePacked(msg.sender);
|
|
65
|
+
_safeMint(msg.sender, agentId);
|
|
66
|
+
emit Registered(agentId, "", msg.sender);
|
|
67
|
+
emit MetadataSet(agentId, "agentWallet", "agentWallet", abi.encodePacked(msg.sender));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function register(string memory agentURI) external returns (uint256 agentId) {
|
|
71
|
+
IdentityRegistryStorage storage $ = _getIdentityRegistryStorage();
|
|
72
|
+
agentId = $._lastId++;
|
|
73
|
+
$._metadata[agentId]["agentWallet"] = abi.encodePacked(msg.sender);
|
|
74
|
+
_safeMint(msg.sender, agentId);
|
|
75
|
+
_setTokenURI(agentId, agentURI);
|
|
76
|
+
emit Registered(agentId, agentURI, msg.sender);
|
|
77
|
+
emit MetadataSet(agentId, "agentWallet", "agentWallet", abi.encodePacked(msg.sender));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function register(string memory agentURI, MetadataEntry[] memory metadata) external returns (uint256 agentId) {
|
|
81
|
+
IdentityRegistryStorage storage $ = _getIdentityRegistryStorage();
|
|
82
|
+
agentId = $._lastId++;
|
|
83
|
+
$._metadata[agentId]["agentWallet"] = abi.encodePacked(msg.sender);
|
|
84
|
+
_safeMint(msg.sender, agentId);
|
|
85
|
+
_setTokenURI(agentId, agentURI);
|
|
86
|
+
emit Registered(agentId, agentURI, msg.sender);
|
|
87
|
+
emit MetadataSet(agentId, "agentWallet", "agentWallet", abi.encodePacked(msg.sender));
|
|
88
|
+
|
|
89
|
+
for (uint256 i; i < metadata.length; i++) {
|
|
90
|
+
require(keccak256(bytes(metadata[i].metadataKey)) != RESERVED_AGENT_WALLET_KEY_HASH, "reserved key");
|
|
91
|
+
$._metadata[agentId][metadata[i].metadataKey] = metadata[i].metadataValue;
|
|
92
|
+
emit MetadataSet(agentId, metadata[i].metadataKey, metadata[i].metadataKey, metadata[i].metadataValue);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getMetadata(uint256 agentId, string memory metadataKey) external view returns (bytes memory) {
|
|
97
|
+
IdentityRegistryStorage storage $ = _getIdentityRegistryStorage();
|
|
98
|
+
return $._metadata[agentId][metadataKey];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function setMetadata(uint256 agentId, string memory metadataKey, bytes memory metadataValue) external {
|
|
102
|
+
address agentOwner = _ownerOf(agentId);
|
|
103
|
+
require(
|
|
104
|
+
msg.sender == agentOwner ||
|
|
105
|
+
isApprovedForAll(agentOwner, msg.sender) ||
|
|
106
|
+
msg.sender == getApproved(agentId),
|
|
107
|
+
"Not authorized"
|
|
108
|
+
);
|
|
109
|
+
require(keccak256(bytes(metadataKey)) != RESERVED_AGENT_WALLET_KEY_HASH, "reserved key");
|
|
110
|
+
IdentityRegistryStorage storage $ = _getIdentityRegistryStorage();
|
|
111
|
+
$._metadata[agentId][metadataKey] = metadataValue;
|
|
112
|
+
emit MetadataSet(agentId, metadataKey, metadataKey, metadataValue);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function setAgentURI(uint256 agentId, string calldata newURI) external {
|
|
116
|
+
address owner = ownerOf(agentId);
|
|
117
|
+
require(
|
|
118
|
+
msg.sender == owner ||
|
|
119
|
+
isApprovedForAll(owner, msg.sender) ||
|
|
120
|
+
msg.sender == getApproved(agentId),
|
|
121
|
+
"Not authorized"
|
|
122
|
+
);
|
|
123
|
+
_setTokenURI(agentId, newURI);
|
|
124
|
+
emit URIUpdated(agentId, newURI, msg.sender);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getAgentWallet(uint256 agentId) external view returns (address) {
|
|
128
|
+
IdentityRegistryStorage storage $ = _getIdentityRegistryStorage();
|
|
129
|
+
bytes memory walletData = $._metadata[agentId]["agentWallet"];
|
|
130
|
+
return address(bytes20(walletData));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function setAgentWallet(
|
|
134
|
+
uint256 agentId,
|
|
135
|
+
address newWallet,
|
|
136
|
+
uint256 deadline,
|
|
137
|
+
bytes calldata signature
|
|
138
|
+
) external {
|
|
139
|
+
address owner = ownerOf(agentId);
|
|
140
|
+
require(
|
|
141
|
+
msg.sender == owner ||
|
|
142
|
+
isApprovedForAll(owner, msg.sender) ||
|
|
143
|
+
msg.sender == getApproved(agentId),
|
|
144
|
+
"Not authorized"
|
|
145
|
+
);
|
|
146
|
+
require(newWallet != address(0), "bad wallet");
|
|
147
|
+
require(block.timestamp <= deadline, "expired");
|
|
148
|
+
require(deadline <= block.timestamp + MAX_DEADLINE_DELAY, "deadline too far");
|
|
149
|
+
|
|
150
|
+
bytes32 structHash = keccak256(abi.encode(AGENT_WALLET_SET_TYPEHASH, agentId, newWallet, owner, deadline));
|
|
151
|
+
bytes32 digest = _hashTypedDataV4(structHash);
|
|
152
|
+
|
|
153
|
+
// Try ECDSA first (EOAs + EIP-7702 delegated EOAs)
|
|
154
|
+
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(digest, signature);
|
|
155
|
+
if (err != ECDSA.RecoverError.NoError || recovered != newWallet) {
|
|
156
|
+
// ECDSA failed, try ERC1271 (smart contract wallets)
|
|
157
|
+
(bool ok, bytes memory res) = newWallet.staticcall(
|
|
158
|
+
abi.encodeCall(IERC1271.isValidSignature, (digest, signature))
|
|
159
|
+
);
|
|
160
|
+
require(ok && res.length >= 32 && abi.decode(res, (bytes4)) == ERC1271_MAGICVALUE, "invalid wallet sig");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
IdentityRegistryStorage storage $ = _getIdentityRegistryStorage();
|
|
164
|
+
$._metadata[agentId]["agentWallet"] = abi.encodePacked(newWallet);
|
|
165
|
+
emit MetadataSet(agentId, "agentWallet", "agentWallet", abi.encodePacked(newWallet));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function unsetAgentWallet(uint256 agentId) external {
|
|
169
|
+
address owner = ownerOf(agentId);
|
|
170
|
+
require(
|
|
171
|
+
msg.sender == owner ||
|
|
172
|
+
isApprovedForAll(owner, msg.sender) ||
|
|
173
|
+
msg.sender == getApproved(agentId),
|
|
174
|
+
"Not authorized"
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
IdentityRegistryStorage storage $ = _getIdentityRegistryStorage();
|
|
178
|
+
$._metadata[agentId]["agentWallet"] = "";
|
|
179
|
+
emit MetadataSet(agentId, "agentWallet", "agentWallet", "");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @dev Override _update to clear agentWallet on transfer.
|
|
186
|
+
* This ensures the verified wallet doesn't persist to new owners.
|
|
187
|
+
* Clear BEFORE super._update() to follow Checks-Effects-Interactions pattern.
|
|
188
|
+
*/
|
|
189
|
+
function _update(address to, uint256 tokenId, address auth) internal override returns (address) {
|
|
190
|
+
address from = _ownerOf(tokenId);
|
|
191
|
+
|
|
192
|
+
// If this is a transfer (not mint), clear agentWallet BEFORE external call
|
|
193
|
+
if (from != address(0) && to != address(0)) {
|
|
194
|
+
IdentityRegistryStorage storage $ = _getIdentityRegistryStorage();
|
|
195
|
+
$._metadata[tokenId]["agentWallet"] = "";
|
|
196
|
+
emit MetadataSet(tokenId, "agentWallet", "agentWallet", "");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return super._update(to, tokenId, auth);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @notice Checks if spender is owner or approved for the agent
|
|
204
|
+
* @dev Reverts with ERC721NonexistentToken if agent doesn't exist
|
|
205
|
+
*/
|
|
206
|
+
function isAuthorizedOrOwner(address spender, uint256 agentId) external view returns (bool) {
|
|
207
|
+
address owner = ownerOf(agentId);
|
|
208
|
+
return _isAuthorized(owner, spender, agentId);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getVersion() external pure returns (string memory) {
|
|
212
|
+
return "2.0.0";
|
|
213
|
+
}
|
|
214
|
+
}
|