Skip to content

Commit

Permalink
Inverse Projected 1155 contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
matejos committed Mar 25, 2024
1 parent f2f9e03 commit 5f00e11
Show file tree
Hide file tree
Showing 6 changed files with 369 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
// Forked from OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4906.sol)

pragma solidity ^0.8.13;

/// @title Agnostic Metadata Update Extension
interface IERC4906Agnostic {
/// @dev This event emits when the metadata of a token is changed.
/// So that the third-party platforms such as NFT market could
/// timely update the images and related attributes of the token.
event MetadataUpdate(uint256 _tokenId);

/// @dev This event emits when the metadata of a range of tokens is changed.
/// So that the third-party platforms such as NFT market could
/// timely update the images and related attributes of the tokens.
event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {IInverseProjected1155} from "./IInverseProjected1155.sol";

/// @dev A Paima Inverse Projection ERC1155 token where initialization is handled by the app-layer.
/// A standard ERC1155 that can be freely minted and stores an unique `<minter, userTokenId>` pair (used in `tokenURI`) when minted.
interface IInverseAppProjected1155 is IInverseProjected1155 {
/// @dev Emitted when `value` amount of globally-enforced `tokenId` in combination with an unique `<minter, userTokenId>` pair is minted.
event Minted(
uint256 indexed tokenId,
address indexed minter,
uint256 indexed userTokenId,
uint256 value
);

/// @dev Emitted when supply of globally-enforced `tokenId` in combination with an unique `<minter, userTokenId>` pair goes to zero.
event BurnedAll(uint256 indexed tokenId, address indexed minter, uint256 indexed userTokenId);

/// @dev Mints `value` of a new token to the transaction sender.
/// Increases the `currentTokenId`.
/// Reverts if transaction sender is a smart contract that does not implement IERC1155Receiver-onERC1155Received.
/// Emits the `Minted` event.
/// Returns the id of the minted token.
function mint(uint256 value, bytes memory data) external returns (uint256);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {IInverseProjected1155} from "./IInverseProjected1155.sol";

/// @dev A Paima Inverse Projection ERC1155 token where initialization is handled by the base-layer.
/// A standard ERC1155 that accepts calldata in the mint function for any initialization data needed in a Paima dApp.
interface IInverseBaseProjected1155 is IInverseProjected1155 {
/// @dev Emitted when `value` amount of globally-enforced `tokenId` is minted, with `initialData` provided in the `mint` function parameters.
event Minted(uint256 indexed tokenId, string initialData);

/// @dev Mints `value` of a new token to transaction sender, passing `initialData` to be emitted in the event.
/// Increases the `currentTokenId`.
/// Reverts if transaction sender is a smart contract that does not implement IERC1155Receiver-onERC1155Received.
/// Emits the `Minted` event.
/// Returns the id of the minted token.
function mint(
uint256 value,
bytes memory data,
string calldata initialData
) external returns (uint256);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {IERC1155MetadataURI} from "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import {IERC4906Agnostic} from "./IERC4906Agnostic.sol";

/// @dev A standard ERC1155 that can be burned and has a special uri function accepting a custom base URI.
interface IInverseProjected1155 is IERC1155MetadataURI, IERC4906Agnostic {
/// @dev Emitted when `baseExtension` is updated from `oldBaseExtension` to `newBaseExtension`.
event SetBaseExtension(string oldBaseExtension, string newBaseExtension);

/// @dev Emitted when `baseUri` is updated from `oldUri` to `newUri`.
event SetBaseURI(string oldUri, string newUri);

/// @dev Burns `value` amount of token of ID `id` from transaction sender.
/// Reverts if transaction sender's balance of `id` is less than `value`.
function burn(uint256 id, uint256 value) external;

/// @dev Sets `_URI` as the `baseURI`.
/// Callable only by the contract owner.
/// Emits the `SetBaseURI` event.
function setBaseURI(string memory _URI) external;

/// @dev Sets `_newBaseExtension` as the `baseExtension`.
/// Callable only by the contract owner.
function setBaseExtension(string memory _newBaseExtension) external;

/// @dev Returns the token URI of specified `id` using a custom base URI.
function uri(uint256 id, string memory customBaseUri) external view returns (string memory);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {ERC1155Supply} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
import {IERC1155MetadataURI} from "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol";
import {IInverseProjected1155} from "./IInverseProjected1155.sol";
import {IInverseAppProjected1155} from "./IInverseAppProjected1155.sol";
import {IERC4906Agnostic} from "./IERC4906Agnostic.sol";

struct MintEntry {
address minter;
uint256 userTokenId;
}

/// @dev A Paima Inverse Projection ERC1155 token where initialization is handled by the app-layer.
/// A standard ERC1155 that can be freely minted and stores an unique `<minter, userTokenId>` pair (used in `tokenURI`) when minted.
contract InverseAppProjected1155 is IInverseAppProjected1155, ERC1155Supply, Ownable {
using Strings for uint256;

string public name;
string public symbol;

mapping(uint256 id => MintEntry) public tokenToMint;
mapping(address minter => uint256) public mintCount;
mapping(uint256 id => uint256) private _initialSupply;

/// @dev The token ID that will be minted when calling the `mint` function.
uint256 public currentTokenId;
/// @dev Base URI that is used in the `uri` function to form the start of the token URI.
string public baseURI;
/// @dev Base extension that is used in the `uri` function to form the end of the token URI.
string public baseExtension;

/// @dev Sets the token's `_name`, `_symbol`, and transfers ownership to `_owner`.
/// Also sets `currentTokenId` to 1.
constructor(
string memory _name,
string memory _symbol,
address _owner
) ERC1155("") Ownable(_owner) {
name = _name;
symbol = _symbol;
currentTokenId = 1;
}

/// @dev Returns true if this contract implements the interface defined by `interfaceId`. See EIP165.
function supportsInterface(
bytes4 interfaceId
) public view virtual override(IERC165, ERC1155) returns (bool) {
return
interfaceId == type(IInverseProjected1155).interfaceId ||
interfaceId == type(IInverseAppProjected1155).interfaceId ||
super.supportsInterface(interfaceId);
}

/// @dev Returns the amount of token with ID `id` that had been initially minted.
function initialSupply(uint256 id) public view virtual returns (uint256) {
return _initialSupply[id];
}

/// @dev Mints `value` of a new token to transaction sender.
/// Increases the `currentTokenId`.
/// Reverts if transaction sender is a smart contract that does not implement IERC1155Receiver-onERC1155Received.
/// Emits the `Minted` event.
/// Returns the id of the minted token.
function mint(uint256 value, bytes memory data) external virtual returns (uint256) {
uint256 tokenId = currentTokenId;
_mint(msg.sender, tokenId, value, data);
mintCount[msg.sender] += 1;
uint256 userTokenId = mintCount[msg.sender];
tokenToMint[tokenId] = MintEntry(msg.sender, userTokenId);
_initialSupply[tokenId] = value;

currentTokenId++;

emit Minted(tokenId, msg.sender, userTokenId, value);
return tokenId;
}

/// @dev Burns `value` amount of token of ID `id` from transaction sender.
/// Reverts if transaction sender's balance of `id` is less than `value`.
function burn(uint256 id, uint256 value) external virtual {
_burn(msg.sender, id, value);
if (totalSupply(id) == 0) {
MintEntry memory entry = tokenToMint[id];
emit BurnedAll(id, entry.minter, entry.userTokenId);
}
}

/// @dev Returns the token URI of specified `id` using the default set base URI.
function uri(
uint256 id
) public view virtual override(ERC1155, IERC1155MetadataURI) returns (string memory) {
return uri(id, baseURI);
}

/// @dev Returns the token URI of specified `id` using a custom base URI.
function uri(
uint256 id,
string memory customBaseUri
) public view virtual returns (string memory) {
require(exists(id), "InverseAppProjected1155: URI query for nonexistent token");
MintEntry memory entry = tokenToMint[id];
string memory URI = bytes(customBaseUri).length > 0
? string.concat(
customBaseUri,
Strings.toHexString(uint160(entry.minter), 20),
"/",
entry.userTokenId.toString(),
"/",
initialSupply(id).toString()
)
: "";
return string(abi.encodePacked(URI, baseExtension));
}

/// @dev Sets `_URI` as the `baseURI` of the NFT.
/// Callable only by the contract owner.
/// Emits the `SetBaseURI` event.
function setBaseURI(string memory _URI) external virtual onlyOwner {
string memory oldURI = baseURI;
baseURI = _URI;
emit SetBaseURI(oldURI, _URI);
}

/// @dev Sets `_newBaseExtension` as the `baseExtension` of the NFT.
/// Callable only by the contract owner.
function setBaseExtension(string memory _newBaseExtension) public virtual onlyOwner {
string memory oldBaseExtension = baseExtension;
baseExtension = _newBaseExtension;
emit SetBaseURI(oldBaseExtension, _newBaseExtension);
}

/// @dev Function that emits an event to notify third-parties (e.g. NFT marketplaces) about
/// an update to consecutive range of tokens. Can be overriden in inheriting contract.
function updateMetadataBatch(uint256 _fromTokenId, uint256 _toTokenId) public virtual {
emit BatchMetadataUpdate(_fromTokenId, _toTokenId);
}

/// @dev Function that emits an event to notify third-parties (e.g. NFT marketplaces) about
/// an update to a single token. Can be overriden in inheriting contract.
function updateMetadata(uint256 _tokenId) public virtual {
emit MetadataUpdate(_tokenId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {ERC1155Supply} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
import {IERC1155MetadataURI} from "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol";
import {IInverseProjected1155} from "./IInverseProjected1155.sol";
import {IInverseBaseProjected1155} from "./IInverseBaseProjected1155.sol";
import {IERC4906Agnostic} from "./IERC4906Agnostic.sol";

/// @dev A Paima Inverse Projection ERC1155 token where initialization is handled by the base-layer.
/// A standard ERC1155 that accepts calldata in the mint function for any initialization data needed in a Paima dApp.
contract InverseBaseProjected1155 is IInverseBaseProjected1155, ERC1155Supply, Ownable {
using Strings for uint256;

string public name;
string public symbol;

/// @dev The token ID that will be minted when calling the `mint` function.
uint256 public currentTokenId;
/// @dev Base URI that is used in the `uri` function to form the start of the token URI.
string public baseURI;
/// @dev Base extension that is used in the `uri` function to form the end of the token URI.
string public baseExtension;

/// @dev Sets the NFT's `name`, `symbol`, and transfers ownership to `owner`.
/// Also sets `currentTokenId` to 1.
constructor(
string memory _name,
string memory _symbol,
address _owner
) ERC1155("") Ownable(_owner) {
name = _name;
symbol = _symbol;
currentTokenId = 1;
}

/// @dev Returns true if this contract implements the interface defined by `interfaceId`. See EIP165.
function supportsInterface(
bytes4 interfaceId
) public view virtual override(IERC165, ERC1155) returns (bool) {
return
interfaceId == type(IInverseProjected1155).interfaceId ||
interfaceId == type(IInverseBaseProjected1155).interfaceId ||
super.supportsInterface(interfaceId);
}

/// @dev Mints `value` of a new token to transaction sender, passing `initialData` to be emitted in the event.
/// Increases the `currentTokenId`.
/// Reverts if transaction sender is a smart contract that does not implement IERC1155Receiver-onERC1155Received.
/// Emits the `Minted` event.
/// Returns the id of the minted token.
function mint(
uint256 value,
bytes memory data,
string calldata initialData
) external virtual returns (uint256) {
uint256 tokenId = currentTokenId;
_mint(msg.sender, tokenId, value, data);

currentTokenId++;

emit Minted(tokenId, initialData);
return tokenId;
}

/// @dev Burns `value` amount of token of ID `id` from transaction sender.
/// Reverts if transaction sender's balance of `id` is less than `value`.
function burn(uint256 id, uint256 value) external virtual {
_burn(msg.sender, id, value);
}

/// @dev Returns the token URI of specified `id` using the default set base URI.
function uri(
uint256 id
) public view virtual override(ERC1155, IERC1155MetadataURI) returns (string memory) {
return uri(id, baseURI);
}

/// @dev Returns the token URI of specified `id` using a custom base URI.
function uri(
uint256 id,
string memory customBaseUri
) public view virtual returns (string memory) {
require(exists(id), "InverseBaseProjected1155: URI query for nonexistent token");
string memory URI = bytes(customBaseUri).length > 0
? string.concat(customBaseUri, id.toString())
: "";
return string(abi.encodePacked(URI, baseExtension));
}

/// @dev Sets `_URI` as the `baseURI` of the NFT.
/// Callable only by the contract owner.
/// Emits the `SetBaseURI` event.
function setBaseURI(string memory _URI) external virtual onlyOwner {
string memory oldURI = baseURI;
baseURI = _URI;
emit SetBaseURI(oldURI, _URI);
}

/// @dev Sets `_newBaseExtension` as the `baseExtension` of the NFT.
/// Callable only by the contract owner.
function setBaseExtension(string memory _newBaseExtension) public virtual onlyOwner {
string memory oldBaseExtension = baseExtension;
baseExtension = _newBaseExtension;
emit SetBaseURI(oldBaseExtension, _newBaseExtension);
}

/// @dev Function that emits an event to notify third-parties (e.g. NFT marketplaces) about
/// an update to consecutive range of tokens. Can be overriden in inheriting contract.
function updateMetadataBatch(uint256 _fromTokenId, uint256 _toTokenId) public virtual {
emit BatchMetadataUpdate(_fromTokenId, _toTokenId);
}

/// @dev Function that emits an event to notify third-parties (e.g. NFT marketplaces) about
/// an update to a single token. Can be overriden in inheriting contract.
function updateMetadata(uint256 _tokenId) public virtual {
emit MetadataUpdate(_tokenId);
}
}

0 comments on commit 5f00e11

Please sign in to comment.