// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs
pragma solidity ^0.8.4;
/**
* @dev Interface of ERC721A.
*/
interface IERC721A {
/**
* The caller must own the token or be an approved operator.
*/
error ApprovalCallerNotOwnerNorApproved();
/**
* The token does not exist.
*/
error ApprovalQueryForNonexistentToken();
/**
* Cannot query the balance for the zero address.
*/
error BalanceQueryForZeroAddress();
/**
* Cannot mint to the zero address.
*/
error MintToZeroAddress();
/**
* The quantity of tokens minted must be more than zero.
*/
error MintZeroQuantity();
/**
* The token does not exist.
*/
error OwnerQueryForNonexistentToken();
/**
* The caller must own the token or be an approved operator.
*/
error TransferCallerNotOwnerNorApproved();
/**
* The token must be owned by `from`.
*/
error TransferFromIncorrectOwner();
/**
* Cannot safely transfer to a contract that does not implement the
* ERC721Receiver interface.
*/
error TransferToNonERC721ReceiverImplementer();
/**
* Cannot transfer to the zero address.
*/
error TransferToZeroAddress();
/**
* The token does not exist.
*/
error URIQueryForNonexistentToken();
/**
* The `quantity` minted with ERC2309 exceeds the safety limit.
*/
error MintERC2309QuantityExceedsLimit();
/**
* The `extraData` cannot be set on an unintialized ownership slot.
*/
error OwnershipNotInitializedForExtraData();
// =============================================================
// STRUCTS
// =============================================================
struct TokenOwnership {
// The address of the owner.
address addr;
// Stores the start time of ownership with minimal overhead for tokenomics.
uint64 startTimestamp;
// Whether the token has been burned.
bool burned;
// Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
uint24 extraData;
}
// =============================================================
// TOKEN COUNTERS
// =============================================================
/**
* @dev Returns the total number of tokens in existence.
* Burned tokens will reduce the count.
* To get the total number of tokens minted, please see {_totalMinted}.
*/
function totalSupply() external view returns (uint256);
// =============================================================
// IERC165
// =============================================================
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
* to learn more about how these ids are created.
*
* This function call must use less than 30000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
// =============================================================
// IERC721
// =============================================================
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables
* (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in `owner`'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`,
* checking first that contract recipients are aware of the ERC721 protocol
* to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be have been allowed to move
* this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement
* {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external payable;
/**
* @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external payable;
/**
* @dev Transfers `tokenId` from `from` to `to`.
*
* WARNING: Usage of this method is discouraged, use {safeTransferFrom}
* whenever possible.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token
* by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external payable;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the
* zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external payable;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom}
* for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool _approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}.
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
// =============================================================
// IERC721Metadata
// =============================================================
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
// =============================================================
// IERC2309
// =============================================================
/**
* @dev Emitted when tokens in `fromTokenId` to `toTokenId`
* (inclusive) is transferred from `from` to `to`, as defined in the
* [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
*
* See {_mintERC2309} for more details.
*/
event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.16;
import "ERC721A/IERC721A.sol";
interface INonon is IERC721A {
function exists(uint256 _tokenId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
/// @title nonon swap
pragma solidity 0.8.16;
import "./interfaces/INonon.sol";
// errors
error Unauthorized();
error OfferForNonexistentToken();
error NoActiveOffer();
error NotRequestedToken();
error TokenHasExistingOffer();
// state
struct TokenOffer {
address owner;
uint16 ownedId;
uint16 wantedId; // unset (zero) is considered to be open
}
contract NononSwap {
uint256 public constant nononMaxSupply = 5000;
address public immutable nononAddress;
// events
event OfferCreated(address indexed owner, uint256 indexed ownedId, uint256 indexed wantedId);
event OfferCancelled(address indexed owner, uint256 indexed ownedId);
event SwapCompleted(uint256 indexed firstTokenId, uint256 indexed secondTokenId);
/**
* @dev Mapping (implemented as an array for gas efficiency) between token
* ids and token offers. Thus, `offers[0]` should never be defined.
*/
TokenOffer[nononMaxSupply + 1] public offers;
constructor(address _nononAddress) {
nononAddress = _nononAddress;
}
/**
* @dev Create a token swap offer for the owned token `_ownedId` and the
* wanted token `_wantedId`. It can also be used to update already set
* offers so the owner doenst need to call `removeOffer` every time they
* want to change their offer.
* @param _ownedId Token that `msg.sender` owns and wants to swap for
* `_wantedId`
* @param _wantedId Token that `msg.sender` wants, 0 if they dont care
* and just want to farm friendship points.
*/
function createTokenOffer(uint16 _ownedId, uint16 _wantedId) external {
INonon nonon = INonon(nononAddress);
if (!nononExists(_ownedId) || (_wantedId != 0 && !nononExists(_wantedId))) {
revert OfferForNonexistentToken();
}
if (nonon.ownerOf(_ownedId) != msg.sender) {
revert Unauthorized();
}
offers[_ownedId] = TokenOffer({owner: msg.sender, ownedId: _ownedId, wantedId: _wantedId});
emit OfferCreated(msg.sender, _ownedId, _wantedId);
}
function completeTokenOffer(uint16 _offerTokenId, uint16 _swapId) external {
INonon nonon = INonon(nononAddress);
TokenOffer memory offer = offers[_offerTokenId];
if (offer.owner == address(0) || !nononExists(_offerTokenId)) {
revert NoActiveOffer();
}
if (offer.wantedId != 0 && _swapId != offer.wantedId) {
revert NotRequestedToken();
}
if (!nononExists(_swapId)) {
revert OfferForNonexistentToken();
}
emit SwapCompleted(_offerTokenId, _swapId);
assembly {
sstore(add(offers.slot, _offerTokenId), 0)
}
// transfer tokens
nonon.transferFrom(msg.sender, offer.owner, _swapId);
nonon.transferFrom(offer.owner, msg.sender, offer.ownedId);
}
function removeOffer(uint16 _tokenId) external {
INonon nonon = INonon(nononAddress);
TokenOffer memory offer = offers[_tokenId];
// allow removal (aka: dont revert) in one of these cases:
// - the user is the creator of the offer OR
// - the offer creator no longer owns the offer token OR
// - the swap contract does not have approval to transfer nonons on behalf of owner OR
// - msg.sender is the owner of offer.wantedId
// if none of these conditions are met (inverses are all true), revert
if (
offer.owner != msg.sender // User is not the creator of the offer
&& nonon.ownerOf(_tokenId) == offer.owner // Offer creator still owns the token
&& nonon.isApprovedForAll(offer.owner, address(this)) // Contract is approved
&& (offer.wantedId == 0 || nonon.ownerOf(offer.wantedId) != msg.sender) // No wantedId or user does not own the wantedId
) {
revert Unauthorized();
}
assembly {
sstore(add(offers.slot, _tokenId), 0)
}
emit OfferCancelled(msg.sender, _tokenId);
}
function nononExists(uint16 tokenId) internal view returns (bool success) {
(success,) = nononAddress.staticcall(abi.encodeWithSignature("ownerOf(uint256)", tokenId));
}
function getAllOffers() external view returns (TokenOffer[nononMaxSupply + 1] memory) {
return offers;
}
}
{
"compilationTarget": {
"src/NononSwap.sol": "NononSwap"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":ERC721A/=lib/ERC721A/contracts/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":solady/=lib/solady/src/",
":solmate/=lib/solady/lib/solmate/src/"
]
}
[{"inputs":[{"internalType":"address","name":"_nononAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"NoActiveOffer","type":"error"},{"inputs":[],"name":"NotRequestedToken","type":"error"},{"inputs":[],"name":"OfferForNonexistentToken","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"ownedId","type":"uint256"}],"name":"OfferCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"ownedId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"wantedId","type":"uint256"}],"name":"OfferCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"firstTokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"secondTokenId","type":"uint256"}],"name":"SwapCompleted","type":"event"},{"inputs":[{"internalType":"uint16","name":"_offerTokenId","type":"uint16"},{"internalType":"uint16","name":"_swapId","type":"uint16"}],"name":"completeTokenOffer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"_ownedId","type":"uint16"},{"internalType":"uint16","name":"_wantedId","type":"uint16"}],"name":"createTokenOffer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAllOffers","outputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint16","name":"ownedId","type":"uint16"},{"internalType":"uint16","name":"wantedId","type":"uint16"}],"internalType":"struct TokenOffer[5001]","name":"","type":"tuple[5001]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nononAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nononMaxSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"offers","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint16","name":"ownedId","type":"uint16"},{"internalType":"uint16","name":"wantedId","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"_tokenId","type":"uint16"}],"name":"removeOffer","outputs":[],"stateMutability":"nonpayable","type":"function"}]