├── .gitignore ├── README.md ├── contracts ├── ERC4610.sol ├── Wrapper.sol ├── example │ ├── AToken.sol │ └── ATokenWrapper.sol └── interfaces │ ├── IERC4610.sol │ └── IWrapper.sol ├── docs ├── deposit.png ├── read.png └── withdraw.png ├── migrations └── 1_initial_migration.js ├── package.json ├── test └── .gitkeep └── truffle-config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # local 2 | *.DS_Store 3 | data.json 4 | package-lock.json 5 | 6 | # folder 7 | node_modules/ 8 | build/ 9 | log/ 10 | script/ 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Abstract 2 | 3 | Wrapper is a contract where you can wrap or transform an existing ERC-721 token into a new wrapped token with ERC-4610 standard. 4 | 5 | ## Design 6 | 7 | User deposit ERC721 token to the wrapper contract and get the new wrapped token based on ERC4610 standard. The tokenId of wrapped token is the same as the tokenId of the deposited token. 8 | 9 | Notice that, a `mint` is performed for each `deposit`, and a `burn` is performed for each `withdraw`. 10 | 11 | When we need to view the attribute value of the token, the `get method` of the original token will eventually be called. 12 | 13 | ### Deposit 14 | 15 | ![deposit](https://github.com/AFKDAO/wrapper/blob/main/docs/deposit.png) 16 | 17 | ### Withdraw 18 | 19 | ![withdraw](https://github.com/AFKDAO/wrapper/blob/main/docs/withdraw.png) 20 | 21 | ### TokenURI 22 | 23 | ![tokenuri](https://github.com/AFKDAO/wrapper/blob/main/docs/read.png) 24 | 25 | ## Implement 26 | 27 | Logically, the Wrapper contract consists of two parts. In the first part, the transmission and reading of ERC721 basic parameters, like `tokenURI`, have been implemented. 28 | 29 | Developers need to develop the second part according to their specific needs of wrapping tokens. When the ERC721 token has properties beyond the ERC721 standard, such as rarity, then the developer needs to develop the interface to obtain these properties separately in the second part. 30 | 31 | You can see the example in contracts/example. 32 | 33 | -------------------------------------------------------------------------------- /contracts/ERC4610.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./interfaces/IERC4610.sol"; 6 | import "@openzeppelin/contracts/utils/Address.sol"; 7 | import "@openzeppelin/contracts/utils/Context.sol"; 8 | import "@openzeppelin/contracts/utils/Strings.sol"; 9 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 10 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 11 | import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; 12 | 13 | 14 | /** 15 | * @dev ERC4610 is an extension of ERC721 16 | */ 17 | contract ERC4610 is Context, ERC165, IERC4610, IERC721Metadata { 18 | using Address for address; 19 | using Strings for uint256; 20 | 21 | // Token name 22 | string private _name; 23 | 24 | // Token symbol 25 | string private _symbol; 26 | 27 | // Mapping from token ID to delegator address 28 | mapping(uint256 => address) private _delegators; 29 | 30 | // Mapping from token ID to owner address 31 | mapping(uint256 => address) private _owners; 32 | 33 | // Mapping owner address to token count 34 | mapping(address => uint256) private _balances; 35 | 36 | // Mapping from token ID to approved address 37 | mapping(uint256 => address) private _tokenApprovals; 38 | 39 | // Mapping from owner to operator approvals 40 | mapping(address => mapping(address => bool)) private _operatorApprovals; 41 | 42 | /** 43 | * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. 44 | */ 45 | constructor(string memory name_, string memory symbol_) { 46 | _name = name_; 47 | _symbol = symbol_; 48 | } 49 | 50 | /** 51 | * @dev See {IERC165-supportsInterface}. 52 | */ 53 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 54 | return 55 | interfaceId == type(IERC4610).interfaceId || 56 | interfaceId == type(IERC721).interfaceId || 57 | interfaceId == type(IERC721Metadata).interfaceId || 58 | super.supportsInterface(interfaceId); 59 | } 60 | 61 | /** 62 | * @dev See {IERC721-balanceOf}. 63 | */ 64 | function balanceOf(address owner) public view virtual override returns (uint256) { 65 | require(owner != address(0), "ERC721: balance query for the zero address"); 66 | return _balances[owner]; 67 | } 68 | 69 | /** 70 | * @dev See {IERC721-ownerOf}. 71 | */ 72 | function ownerOf(uint256 tokenId) public view virtual override returns (address) { 73 | address owner = _owners[tokenId]; 74 | require(owner != address(0), "ERC721: owner query for nonexistent token"); 75 | return owner; 76 | } 77 | 78 | /** 79 | * @dev See {IERC721Metadata-name}. 80 | */ 81 | function name() public view virtual override returns (string memory) { 82 | return _name; 83 | } 84 | 85 | /** 86 | * @dev See {IERC721Metadata-symbol}. 87 | */ 88 | function symbol() public view virtual override returns (string memory) { 89 | return _symbol; 90 | } 91 | 92 | /** 93 | * @dev See {IERC721Metadata-tokenURI}. 94 | */ 95 | function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { 96 | require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); 97 | 98 | string memory baseURI = _baseURI(); 99 | return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; 100 | } 101 | 102 | /** 103 | * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each 104 | * token will be the concatenation of the `baseURI` and the `tokenId`. Empty 105 | * by default, can be overriden in child contracts. 106 | */ 107 | function _baseURI() internal view virtual returns (string memory) { 108 | return ""; 109 | } 110 | 111 | /** 112 | * @dev See {IERC721-approve}. 113 | */ 114 | function approve(address to, uint256 tokenId) public virtual override { 115 | address owner = ownerOf(tokenId); 116 | require(to != owner, "ERC721: approval to current owner"); 117 | 118 | require( 119 | _msgSender() == owner || isApprovedForAll(owner, _msgSender()), 120 | "ERC721: approve caller is not owner nor approved for all" 121 | ); 122 | 123 | _approve(to, tokenId); 124 | } 125 | 126 | /** 127 | * @dev See {IERC721-getApproved}. 128 | */ 129 | function getApproved(uint256 tokenId) public view virtual override returns (address) { 130 | require(_exists(tokenId), "ERC721: approved query for nonexistent token"); 131 | 132 | return _tokenApprovals[tokenId]; 133 | } 134 | 135 | /** 136 | * @dev See {IERC721-setApprovalForAll}. 137 | */ 138 | function setApprovalForAll(address operator, bool approved) public virtual override { 139 | require(operator != _msgSender(), "ERC721: approve to caller"); 140 | 141 | _operatorApprovals[_msgSender()][operator] = approved; 142 | emit ApprovalForAll(_msgSender(), operator, approved); 143 | } 144 | 145 | /** 146 | * @dev See {IERC721-isApprovedForAll}. 147 | */ 148 | function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { 149 | return _operatorApprovals[owner][operator]; 150 | } 151 | 152 | /** 153 | * @dev See {IERC4610-setDelegator}. 154 | */ 155 | function setDelegator(address delegator, uint256 tokenId) public virtual override { 156 | address owner = ownerOf(tokenId); 157 | // require(delegator != owner, "ERC4610: setDelegator to current owner"); 158 | 159 | require( 160 | _msgSender() == owner || isApprovedForAll(owner, _msgSender()), 161 | "ERC4610: setDelegator caller is not owner nor approved for all" 162 | ); 163 | 164 | _setDelegator(delegator, tokenId); 165 | } 166 | 167 | /** 168 | * @dev See {IERC4610-delegatorOf}. 169 | */ 170 | function delegatorOf(uint256 tokenId) public view virtual override returns (address) { 171 | require(_exists(tokenId), "ERC4610: delegated query for nonexistent token"); 172 | address delegator = _delegators[tokenId]; 173 | return delegator; 174 | } 175 | 176 | /** 177 | * @dev See {IERC721-transferFrom}. 178 | */ 179 | function transferFrom( 180 | address from, 181 | address to, 182 | uint256 tokenId 183 | ) public virtual override { 184 | //solhint-disable-next-line max-line-length 185 | require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); 186 | 187 | _transfer(from, to, tokenId); 188 | } 189 | 190 | /** 191 | * @dev See {IERC721-safeTransferFrom}. 192 | */ 193 | function safeTransferFrom( 194 | address from, 195 | address to, 196 | uint256 tokenId 197 | ) public virtual override { 198 | safeTransferFrom(from, to, tokenId, ""); 199 | } 200 | 201 | /** 202 | * @dev See {IERC4610-safeTransferFrom}. 203 | */ 204 | function safeTransferFrom( 205 | address from, 206 | address to, 207 | uint256 tokenId, 208 | bool reserved 209 | ) public virtual override { 210 | address delegator = delegatorOf(tokenId); 211 | safeTransferFrom(from, to, tokenId, ""); 212 | if (reserved) _setDelegator(delegator, tokenId); 213 | } 214 | 215 | /** 216 | * @dev See {IERC721-safeTransferFrom}. 217 | */ 218 | function safeTransferFrom( 219 | address from, 220 | address to, 221 | uint256 tokenId, 222 | bytes memory _data 223 | ) public virtual override { 224 | require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); 225 | _safeTransfer(from, to, tokenId, _data); 226 | } 227 | 228 | /** 229 | * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients 230 | * are aware of the ERC721 protocol to prevent tokens from being forever locked. 231 | * 232 | * `_data` is additional data, it has no specified format and it is sent in call to `to`. 233 | * 234 | * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. 235 | * implement alternative mechanisms to perform token transfer, such as signature-based. 236 | * 237 | * Requirements: 238 | * 239 | * - `from` cannot be the zero address. 240 | * - `to` cannot be the zero address. 241 | * - `tokenId` token must exist and be owned by `from`. 242 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 243 | * 244 | * Emits a {Transfer} event. 245 | */ 246 | function _safeTransfer( 247 | address from, 248 | address to, 249 | uint256 tokenId, 250 | bytes memory _data 251 | ) internal virtual { 252 | _transfer(from, to, tokenId); 253 | require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); 254 | } 255 | 256 | /** 257 | * @dev Returns whether `tokenId` exists. 258 | * 259 | * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. 260 | * 261 | * Tokens start existing when they are minted (`_mint`), 262 | * and stop existing when they are burned (`_burn`). 263 | */ 264 | function _exists(uint256 tokenId) internal view virtual returns (bool) { 265 | return _owners[tokenId] != address(0); 266 | } 267 | 268 | /** 269 | * @dev Set `delegator` as the delegator of `tokenId`. 270 | * 271 | * Emits a {SetDelegator} event. 272 | */ 273 | function _setDelegator(address delegator, uint256 tokenId) internal virtual { 274 | _delegators[tokenId] = delegator; 275 | emit SetDelegator(_msgSender(), delegator, tokenId); 276 | } 277 | 278 | /** 279 | * @dev Returns whether `spender` is allowed to manage `tokenId`. 280 | * 281 | * Requirements: 282 | * 283 | * - `tokenId` must exist. 284 | */ 285 | function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { 286 | require(_exists(tokenId), "ERC721: operator query for nonexistent token"); 287 | address owner = ownerOf(tokenId); 288 | return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); 289 | } 290 | 291 | /** 292 | * @dev Safely mints `tokenId` and transfers it to `to`. 293 | * 294 | * Requirements: 295 | * 296 | * - `tokenId` must not exist. 297 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 298 | * 299 | * Emits a {Transfer} event. 300 | */ 301 | function _safeMint(address to, uint256 tokenId) internal virtual { 302 | _safeMint(to, tokenId, ""); 303 | } 304 | 305 | /** 306 | * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is 307 | * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. 308 | */ 309 | function _safeMint( 310 | address to, 311 | uint256 tokenId, 312 | bytes memory _data 313 | ) internal virtual { 314 | _mint(to, tokenId); 315 | require( 316 | _checkOnERC721Received(address(0), to, tokenId, _data), 317 | "ERC721: transfer to non ERC721Receiver implementer" 318 | ); 319 | } 320 | 321 | /** 322 | * @dev Mints `tokenId` and transfers it to `to`. 323 | * 324 | * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible 325 | * 326 | * Requirements: 327 | * 328 | * - `tokenId` must not exist. 329 | * - `to` cannot be the zero address. 330 | * 331 | * Emits a {Transfer} event. 332 | */ 333 | function _mint(address to, uint256 tokenId) internal virtual { 334 | require(to != address(0), "ERC721: mint to the zero address"); 335 | require(!_exists(tokenId), "ERC721: token already minted"); 336 | 337 | _beforeTokenTransfer(address(0), to, tokenId); 338 | 339 | _balances[to] += 1; 340 | _owners[tokenId] = to; 341 | 342 | emit Transfer(address(0), to, tokenId); 343 | } 344 | 345 | /** 346 | * @dev Destroys `tokenId`. 347 | * The approval is cleared when the token is burned. 348 | * 349 | * Requirements: 350 | * 351 | * - `tokenId` must exist. 352 | * 353 | * Emits a {Transfer} event. 354 | */ 355 | function _burn(uint256 tokenId) internal virtual { 356 | address owner = ownerOf(tokenId); 357 | 358 | _beforeTokenTransfer(owner, address(0), tokenId); 359 | 360 | // Clear approvals 361 | _approve(address(0), tokenId); 362 | 363 | // Clear delegators from the previous owner 364 | _setDelegator(address(0), tokenId); 365 | 366 | _balances[owner] -= 1; 367 | delete _owners[tokenId]; 368 | 369 | emit Transfer(owner, address(0), tokenId); 370 | } 371 | 372 | /** 373 | * @dev Transfers `tokenId` from `from` to `to`. 374 | * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. 375 | * 376 | * Requirements: 377 | * 378 | * - `to` cannot be the zero address. 379 | * - `tokenId` token must be owned by `from`. 380 | * 381 | * Emits a {Transfer} event. 382 | */ 383 | function _transfer( 384 | address from, 385 | address to, 386 | uint256 tokenId 387 | ) internal virtual { 388 | require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); 389 | require(to != address(0), "ERC721: transfer to the zero address"); 390 | 391 | _beforeTokenTransfer(from, to, tokenId); 392 | 393 | // Clear approvals from the previous owner 394 | _approve(address(0), tokenId); 395 | 396 | // Clear delegators from the previous owner 397 | _setDelegator(address(0), tokenId); 398 | 399 | _balances[from] -= 1; 400 | _balances[to] += 1; 401 | _owners[tokenId] = to; 402 | 403 | emit Transfer(from, to, tokenId); 404 | } 405 | 406 | /** 407 | * @dev Approve `to` to operate on `tokenId` 408 | * 409 | * Emits a {Approval} event. 410 | */ 411 | function _approve(address to, uint256 tokenId) internal virtual { 412 | _tokenApprovals[tokenId] = to; 413 | emit Approval(ownerOf(tokenId), to, tokenId); 414 | } 415 | 416 | /** 417 | * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. 418 | * The call is not executed if the target address is not a contract. 419 | * 420 | * @param from address representing the previous owner of the given token ID 421 | * @param to target address that will receive the tokens 422 | * @param tokenId uint256 ID of the token to be transferred 423 | * @param _data bytes optional data to send along with the call 424 | * @return bool whether the call correctly returned the expected magic value 425 | */ 426 | function _checkOnERC721Received( 427 | address from, 428 | address to, 429 | uint256 tokenId, 430 | bytes memory _data 431 | ) private returns (bool) { 432 | if (to.isContract()) { 433 | try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { 434 | return retval == IERC721Receiver.onERC721Received.selector; 435 | } catch (bytes memory reason) { 436 | if (reason.length == 0) { 437 | revert("ERC721: transfer to non ERC721Receiver implementer"); 438 | } else { 439 | assembly { 440 | revert(add(32, reason), mload(reason)) 441 | } 442 | } 443 | } 444 | } else { 445 | return true; 446 | } 447 | } 448 | 449 | /** 450 | * @dev Hook that is called before any token transfer. This includes minting 451 | * and burning. 452 | * 453 | * Calling conditions: 454 | * 455 | * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be 456 | * transferred to `to`. 457 | * - When `from` is zero, `tokenId` will be minted for `to`. 458 | * - When `to` is zero, ``from``'s `tokenId` will be burned. 459 | * - `from` and `to` are never both zero. 460 | * 461 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 462 | */ 463 | function _beforeTokenTransfer( 464 | address from, 465 | address to, 466 | uint256 tokenId 467 | ) internal virtual {} 468 | } -------------------------------------------------------------------------------- /contracts/Wrapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./ERC4610.sol"; 6 | import "./interfaces/IWrapper.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 9 | 10 | contract Wrapper is ERC4610, IWrapper, IERC721Receiver { 11 | 12 | address internal _underlyingToken; 13 | 14 | constructor(address underlyingToken_, string memory name_, string memory symbol_) ERC4610(name_,symbol_) { 15 | _underlyingToken = underlyingToken_; 16 | } 17 | 18 | function onERC721Received( 19 | address, 20 | address, 21 | uint256, 22 | bytes memory 23 | ) public virtual override returns (bytes4) { 24 | return this.onERC721Received.selector; 25 | } 26 | 27 | function underlyingToken() public view returns (address) { 28 | return _underlyingToken; 29 | } 30 | 31 | function deposit(uint256 tokenId) public override { 32 | address owner = IERC721(_underlyingToken).ownerOf(tokenId); 33 | require(_msgSender() == owner, "only owner can call"); 34 | 35 | ERC721(_underlyingToken).safeTransferFrom(_msgSender(), address(this), tokenId); 36 | _mint(_msgSender(), tokenId); 37 | 38 | emit Deposit(_msgSender(), tokenId); 39 | } 40 | 41 | function withdraw(uint256 tokenId) public override { 42 | address owner = IERC721(_underlyingToken).ownerOf(tokenId); 43 | require(_msgSender() == ownerOf(tokenId), "only owner can call"); 44 | require(address(this) == owner, "invalid tokenId"); 45 | 46 | _burn(tokenId); 47 | ERC721(_underlyingToken).safeTransferFrom(address(this), _msgSender(), tokenId); 48 | 49 | emit Withdraw(_msgSender(), tokenId); 50 | } 51 | 52 | function tokenURI(uint256 tokenId) public view override returns (string memory) { 53 | return ERC721(_underlyingToken).tokenURI(tokenId); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /contracts/example/AToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | 7 | /** 8 | * @dev AToken is the token that needs to be wrapped 9 | */ 10 | contract AToken is ERC721 { 11 | 12 | struct Data { 13 | string name; 14 | uint256 generation; 15 | } 16 | 17 | mapping(uint256 => Data) private _data; 18 | uint256 private _rarity; 19 | 20 | constructor(string memory name_, string memory symbol_) ERC721(name_,symbol_) { 21 | } 22 | 23 | function mint(string memory name) external { 24 | uint256 tokenId = uint256(keccak256(abi.encodePacked(block.timestamp, block.number))); 25 | Data memory data = Data(name,block.number); 26 | _data[tokenId] = data; 27 | _mint(_msgSender(), tokenId); 28 | } 29 | 30 | function setRarity(uint256 rarity_) external { 31 | _rarity = rarity_; 32 | } 33 | 34 | // read ratity property value 35 | function rarity() external view returns (uint256) { 36 | return _rarity; 37 | } 38 | 39 | // read data property value 40 | function getData(uint256 tokenId) external view returns (string memory, uint256) { 41 | return (_data[tokenId].name, _data[tokenId].generation); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /contracts/example/ATokenWrapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./AToken.sol"; 6 | import "../Wrapper.sol"; 7 | import "../interfaces/IERC4610.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 9 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 10 | 11 | /** 12 | * @dev ATokenWrapper is the AToken after wrappering 13 | */ 14 | contract ATokenWrapper is Wrapper { 15 | 16 | constructor(address underlyingToken_, string memory name_, string memory symbol_) Wrapper(underlyingToken_,name_,symbol_) { 17 | } 18 | 19 | // add READ function to get ratity property value from AToken 20 | function rarity() external view returns (uint256) { 21 | return AToken(_underlyingToken).rarity(); 22 | } 23 | 24 | // add READ function to get data property value from AToken 25 | function getData(uint256 tokenId) external view returns (string memory, uint256) { 26 | return AToken(_underlyingToken).getData(tokenId); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /contracts/interfaces/IERC4610.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | interface IERC4610 is IERC721 { 8 | 9 | /** 10 | * @dev Emitted when `owner` or `approved account` enables `setDelegator` to manage the `tokenId` token. 11 | */ 12 | event SetDelegator(address indexed caller, address indexed delegator, uint256 indexed tokenId); 13 | 14 | /** 15 | * @dev Set or remove `delegator` for the owner. 16 | * The delegator has no direct permission, just an additional attribute. 17 | * 18 | * Requirements: 19 | * 20 | * - The `delegator` cannot be the caller. 21 | * - `tokenId` must exist. 22 | * 23 | * Emits an {SetDelegator} event. 24 | */ 25 | function setDelegator(address delegator, uint256 tokenId) external; 26 | 27 | /** 28 | * @dev Returns the delegator of the `tokenId` token. 29 | * 30 | * Requirements: 31 | * 32 | * - `tokenId` must exist. 33 | */ 34 | function delegatorOf(uint256 tokenId) external view returns (address); 35 | 36 | /** 37 | * @dev Safely transfers `tokenId` token from `from` to `to` 38 | * `delegator` won't be cleared if `reserved` is true. 39 | * 40 | * Requirements: 41 | * 42 | * - `from` cannot be the zero address. 43 | * - `to` cannot be the zero address. 44 | * - `tokenId` token must exist and be owned by `from`. 45 | * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. 46 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 47 | * - If `reserved` is true, it won't clear the `delegator`. 48 | * 49 | * Emits a {Transfer} event. 50 | */ 51 | function safeTransferFrom( 52 | address from, 53 | address to, 54 | uint256 tokenId, 55 | bool reserved 56 | ) external; 57 | 58 | } -------------------------------------------------------------------------------- /contracts/interfaces/IWrapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IWrapper { 6 | 7 | event Deposit(address indexed caller, uint256 indexed tokenId); 8 | 9 | event Withdraw(address indexed caller, uint256 indexed tokenId); 10 | 11 | function deposit(uint256 tokenId) external; 12 | 13 | function withdraw(uint256 tokenId) external; 14 | 15 | } -------------------------------------------------------------------------------- /docs/deposit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFKDAO/wrapper/f4f11b8ceae91c3a1ce21b4fd3485064749a0f44/docs/deposit.png -------------------------------------------------------------------------------- /docs/read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFKDAO/wrapper/f4f11b8ceae91c3a1ce21b4fd3485064749a0f44/docs/read.png -------------------------------------------------------------------------------- /docs/withdraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFKDAO/wrapper/f4f11b8ceae91c3a1ce21b4fd3485064749a0f44/docs/withdraw.png -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wrapper", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle-config.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@openzeppelin/contracts": "^4.4.1", 16 | "@truffle/hdwallet-provider": "^2.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AFKDAO/wrapper/f4f11b8ceae91c3a1ce21b4fd3485064749a0f44/test/.gitkeep -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * trufflesuite.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 22 | // const infuraKey = "fj4jll3k....."; 23 | // 24 | // const fs = require('fs'); 25 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 26 | 27 | module.exports = { 28 | /** 29 | * Networks define how you connect to your ethereum client and let you set the 30 | * defaults web3 uses to send transactions. If you don't specify one truffle 31 | * will spin up a development blockchain for you on port 9545 when you 32 | * run `develop` or `test`. You can ask a truffle command to use a specific 33 | * network from the command line, e.g 34 | * 35 | * $ truffle test --network 36 | */ 37 | 38 | networks: { 39 | // Useful for testing. The `development` name is special - truffle uses it by default 40 | // if it's defined here and no other network is specified at the command line. 41 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 42 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 43 | // options below to some value. 44 | // 45 | // development: { 46 | // host: "127.0.0.1", // Localhost (default: none) 47 | // port: 8545, // Standard Ethereum port (default: none) 48 | // network_id: "*", // Any network (default: none) 49 | // }, 50 | // Another network with more advanced options... 51 | // advanced: { 52 | // port: 8777, // Custom port 53 | // network_id: 1342, // Custom network 54 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 55 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 56 | // from:
, // Account to send txs from (default: accounts[0]) 57 | // websocket: true // Enable EventEmitter interface for web3 (default: false) 58 | // }, 59 | // Useful for deploying to a public network. 60 | // NB: It's important to wrap the provider as a function. 61 | // ropsten: { 62 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 63 | // network_id: 3, // Ropsten's id 64 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 65 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 66 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 67 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 68 | // }, 69 | // Useful for private networks 70 | // private: { 71 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 72 | // network_id: 2111, // This network is yours, in the cloud. 73 | // production: true // Treats this network as if it was a public net. (default: false) 74 | // } 75 | }, 76 | 77 | // Set default mocha options here, use special reporters etc. 78 | mocha: { 79 | // timeout: 100000 80 | }, 81 | 82 | // Configure your compilers 83 | compilers: { 84 | solc: { 85 | version: "0.8.0", // Fetch exact version from solc-bin (default: truffle's version) 86 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 87 | // settings: { // See the solidity docs for advice about optimization and evmVersion 88 | // optimizer: { 89 | // enabled: false, 90 | // runs: 200 91 | // }, 92 | // evmVersion: "byzantium" 93 | // } 94 | } 95 | }, 96 | 97 | // Truffle DB is currently disabled by default; to enable it, change enabled: false to enabled: true 98 | // 99 | // Note: if you migrated your contracts prior to enabling this field in your Truffle project and want 100 | // those previously migrated contracts available in the .db directory, you will need to run the following: 101 | // $ truffle migrate --reset --compile-all 102 | 103 | db: { 104 | enabled: false 105 | } 106 | }; 107 | --------------------------------------------------------------------------------