Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {NodeOwners} from "./NodeOwners.sol";
import {IContentGraph} from "../IContentGraph.sol";
contract TimeBased is NodeOwners {
mapping(bytes32 => Embargo) contentEmbargo;
mapping(bytes32 => mapping(address => bool)) purchased;
enum Time {
MINS,
DAYS,
WEEKS,
YEARS
}
enum PricingFunction {
STEP,
LINEAR,
EXPONENTIAL,
BINARY
}
struct Embargo {
uint256 embargoDate;
uint256 retailPrice;
uint256 premium;
Time timeDenom;
PricingFunction priceFunc;
}
event EmbargoSet(
bytes32 id, uint256 embargoDate, uint256 retailPrice, uint256 premium, Time timeDenom, PricingFunction priceFunc
);
event AccessPurchased(bytes32 id, address purchaser, uint256 price);
constructor(address _graph, address identity) NodeOwners(_graph, identity) {}
function price(bytes32 id, uint256 time) public view returns (uint256 accessPrice) {
Embargo memory embargo = contentEmbargo[id];
if (time >= embargo.embargoDate) {
accessPrice = embargo.retailPrice;
} else {
uint256 difference = embargo.embargoDate - time;
uint256 premium = embargo.premium;
if (embargo.timeDenom == Time.MINS) {
difference = difference / 60;
} else if (embargo.timeDenom == Time.DAYS) {
difference = difference / (60 * 60 * 24);
} else if (embargo.timeDenom == Time.WEEKS) {
difference = difference / (60 * 60 * 24 * 7);
} else if (embargo.timeDenom == Time.YEARS) {
difference = difference / (60 * 60 * 24 * 365);
}
if (embargo.priceFunc == PricingFunction.LINEAR) {
premium = premium * difference;
} else if (embargo.priceFunc == PricingFunction.EXPONENTIAL) {
for (uint256 i = 0; i < difference; i++) {
premium = premium * 2; // Assuming doubling the premium for each unit of difference
}
}
accessPrice = embargo.retailPrice + premium;
}
}
function setEmbargo(bytes32 id, Embargo calldata _embargo) public nodeOwner(id) {
contentEmbargo[id] = _embargo;
emit EmbargoSet(
id, _embargo.embargoDate, _embargo.retailPrice, _embargo.premium, _embargo.timeDenom, _embargo.priceFunc
);
}
function purchaseAccess(bytes32 id) public payable {
Embargo memory embargo = contentEmbargo[id];
if (embargo.priceFunc == PricingFunction.BINARY) {
require(block.timestamp >= embargo.embargoDate, "EMBARGO: Binary embargo date not passed.");
}
uint256 priceNow = price(id, block.timestamp);
require(msg.value >= priceNow, "EMBARGO: Value sent below price");
uint256 refund = msg.value - priceNow;
uint256 token = IContentGraph(contentGraph).getNode(id).token;
address owner = IContentGraph(contentGraph).ownerOf(token);
(bool sent,) = owner.call{value: priceNow}("");
require(sent, "EMBARGO: Failed to send Ether to owner");
if (refund > 0) {
(sent,) = msg.sender.call{value: refund}("");
require(sent, "EMBARGO: Failed to refund excess Ether");
}
purchased[id][msg.sender] = true;
emit AccessPurchased(id, msg.sender, priceNow);
}
function getEmbargo(bytes32 id) external view returns (Embargo memory embargo) {
embargo = contentEmbargo[id];
}
function auth(bytes32 id, address user) external view returns (bool isAuthorized) {
isAuthorized = purchased[id][user];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IContentGraph} from "../IContentGraph.sol";
import {IIdentityRegistry} from "../interfaces/IIdentityRegistry.sol";
/**
* @title Node Owners
* @author Blockchain Creative Labs
*/
contract NodeOwners {
address contentGraph;
address identityRegistry;
error NotAuthorized();
constructor(address _graph, address _identity) {
contentGraph = _graph;
identityRegistry = _identity;
}
modifier nodeOwner(bytes32 id) {
_checkOwner(id);
_;
}
function _checkOwner(bytes32 id) internal view virtual {
uint256 token = IContentGraph(contentGraph).getNode(id).token;
address owner = IContentGraph(contentGraph).ownerOf(token);
address actingAs = IIdentityRegistry(identityRegistry).whoIs(msg.sender);
if (actingAs != address(0)) {
require(owner == actingAs, "NodeOwners: Caller is not authorized");
} else {
revert NotAuthorized();
}
}
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;
import "./interfaces/IERC6150.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface IContentGraph is IERC6150, IERC721 {
event Moved(bytes32 _id, bytes32 indexed _from, bytes32 _to);
event AccessAuthUpdate(bytes32 _id, address _auth);
event ReferenceAuthUpdate(bytes32 _id, address _auth);
event URIUpdate(bytes32 _id, string _uri);
enum NodeType {
ORG,
REFERENCE,
ASSET
}
struct ContentNode {
bytes32 id;
NodeType nodeType;
bytes32 referenceOf;
string uri;
}
struct Node {
uint256 token;
NodeType nodeType;
bytes32 id;
bytes32 referenceOf;
string uri;
address accessAuth;
address referenceAuth;
}
/**
* @notice Publishes a new set content node (assets/references) to the passed parent id.
* @param parentId The id of an ORG node to publish the set of content nodes.
* @param content A list of content.
*/
function publishBulk(bytes32 parentId, ContentNode[] calldata content) external;
/**
* @notice Publishes a new asset node at a given parent in addition to setting the uri for the asset node.
* @param parentId The id of an ORG node to publish the set of content nodes.
* @param content A content node to publish.
*/
function publish(bytes32 parentId, ContentNode calldata content) external;
/**
* @notice Creates a node of a given type under the parent node provided.
* @param id The id of the node to create, must follow correct form based on type.
* @param parentId The id of a admin node to publish the node under
* @param nodeType The type of node to create, ADMIN, COLLECTION, or ASSET
*/
function createNode(bytes32 id, bytes32 parentId, NodeType nodeType) external;
/**
* @notice Creates a node of a given type under the parent node provided.
* @param id The id of the node to create, must follow the correct form based on type.
* @param parentId The id of a ORG node to publish the node under
* @param nodeType The type of node to create, ORG, REFERENCE, or ASSET
* @param referenceOf If the type is of REFERENCE the id of the node that is being referenced
*/
function createNode(bytes32 id, bytes32 parentId, NodeType nodeType, bytes32 referenceOf) external;
/**
* @notice Moves a node from current parent to a new parent.
* @param id The id of the node to move.
* @param newParentId The id of an existing admin node to move the node under.
*/
function move(bytes32 id, bytes32 newParentId) external;
/**
* @notice Sets the access auth module for a given node.
* @param id The id of the node whose auth modules should be set
* @param accessAuth The address to the auth module to be used access of node's content.
*/
function setAccessAuth(bytes32 id, address accessAuth) external;
/**
* @notice Sets the reference auth module for a given node.
* @param id The id of the node whose auth modules should be set
* @param referenceAuth The address to the auth module to be used for referencing a node in collection.
*/
function setReferenceAuth(bytes32 id, address referenceAuth) external;
/**
* @notice Sets the uri for a node.
* @param id The id of the node.
* @param uri The URI to the metadata to set for a node.
*/
function setURI(bytes32 id, string calldata uri) external;
/**
* @notice Validates if a given user may access the content at a given node.
* @param id The id of the node whose content is being accessed.
* @param user The address of the user who wishes to access the content.
*/
function auth(bytes32 id, address user) external view returns (bool);
/**
* @notice Validates if a given user may reference a given node in a collection.
* @param id The id of the node who is being referenced.
* @param user The address of the user who wishes to reference the node.
*/
function refAuth(bytes32 id, address user) external view returns (bool);
/**
* @notice retrieve node from node id
* @param id The id of the node to retrieve.
*/
function getNode(bytes32 id) external view returns (Node memory node);
/**
* @dev retrieve node from token id
* @param token The tokenId for the node to retrieve.
*/
function tokenToNode(uint256 token) external view returns (Node memory node);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
interface IIdentityRegistry {
function registerRoot(address root, string memory name) external;
function deregisterRoot(address root) external;
function registerIdentity(
bytes memory signature,
address root,
address identity,
uint256 expirary,
uint256 deadline
) external;
function deregisterIdentity(bytes memory signature, address root, address identity, uint256 deadline) external;
function whoIs(address identity) external view returns (address root);
function getSignature(address _root, address _identity)
external
view
returns (bytes memory signature, bytes32 digest, address root, address identity, uint256 expirary);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
// Note: the ERC-165 identifier for this interface is 0x897e2c73.
interface IERC6150 { /* is IERC721, IERC165 */
/**
* @notice Emitted when `tokenId` token under `parentId` is minted.
* @param minter The address of minter
* @param to The address received token
* @param parentId The id of parent token, if it's zero, it means minted `tokenId` is a root token.
* @param tokenId The id of minted token, required to be greater than zero
*/
event Minted(address indexed minter, address indexed to, uint256 parentId, uint256 tokenId);
/**
* @notice Get the parent token of `tokenId` token.
* @param tokenId The child token
* @return parentId The Parent token found
*/
function parentOf(uint256 tokenId) external view returns (uint256 parentId);
/**
* @notice Get the children tokens of `tokenId` token.
* @param tokenId The parent token
* @return childrenIds The array of children tokens
*/
function childrenOf(uint256 tokenId) external view returns (uint256[] memory childrenIds);
/**
* @notice Check the `tokenId` token if it is a root token.
* @param tokenId The token want to be checked
* @return Return `true` if it is a root token; if not, return `false`
*/
function isRoot(uint256 tokenId) external view returns (bool);
/**
* @notice Check the `tokenId` token if it is a leaf token.
* @param tokenId The token want to be checked
* @return Return `true` if it is a leaf token; if not, return `false`
*/
function isLeaf(uint256 tokenId) external view returns (bool);
}
// Note: the ERC-165 identifier for this interface is 0xba541a2e.
interface IERC6150Enumerable is IERC6150 /* IERC721Enumerable */ {
/**
* @notice Get total amount of children tokens under `parentId` token.
* @dev If `parentId` is zero, it means get total amount of root tokens.
* @return The total amount of children tokens under `parentId` token.
*/
function childrenCountOf(uint256 parentId) external view returns (uint256);
/**
* @notice Get the token at the specified index of all children tokens under `parentId` token.
* @dev If `parentId` is zero, it means get root token.
* @return The token ID at `index` of all chlidren tokens under `parentId` token.
*/
function childOfParentByIndex(uint256 parentId, uint256 index) external view returns (uint256);
/**
* @notice Get the index position of specified token in the children enumeration under specified parent token.
* @dev Throws if the `tokenId` is not found in the children enumeration.
* If `parentId` is zero, means get root token index.
* @param parentId The parent token
* @param tokenId The specified token to be found
* @return The index position of `tokenId` found in the children enumeration
*/
function indexInChildrenEnumeration(uint256 parentId, uint256 tokenId) external view returns (uint256);
}
// Note: the ERC-165 identifier for this interface is 0x4ac0aa46.
interface IERC6150Burnable is IERC6150 {
/**
* @notice Burn the `tokenId` token.
* @dev Throws if `tokenId` is not a leaf token.
* Throws if `tokenId` is not a valid NFT.
* Throws if `owner` is not the owner of `tokenId` token.
* Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this token.
* @param tokenId The token to be burnt
*/
function safeBurn(uint256 tokenId) external;
/**
* @notice Batch burn tokens.
* @dev Throws if one of `tokenIds` is not a leaf token.
* Throws if one of `tokenIds` is not a valid NFT.
* Throws if `owner` is not the owner of all `tokenIds` tokens.
* Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for all `tokenIds`.
* @param tokenIds The tokens to be burnt
*/
function safeBatchBurn(uint256[] memory tokenIds) external;
}
// Note: the ERC-165 identifier for this interface is 0xfa574808.
interface IERC6150ParentTransferable is IERC6150 {
/**
* @notice Emitted when the parent of `tokenId` token changed.
* @param tokenId The token changed
* @param oldParentId Previous parent token
* @param newParentId New parent token
*/
event ParentTransferred(uint256 tokenId, uint256 oldParentId, uint256 newParentId);
/**
* @notice Transfer parentship of `tokenId` token to a new parent token
* @param newParentId New parent token id
* @param tokenId The token to be changed
*/
function transferParent(uint256 newParentId, uint256 tokenId) external;
/**
* @notice Batch transfer parentship of `tokenIds` to a new parent token
* @param newParentId New parent token id
* @param tokenIds Array of token ids to be changed
*/
function batchTransferParent(uint256 newParentId, uint256[] memory tokenIds) external;
}
// Note: the ERC-165 identifier for this interface is 0x1d04f0b3.
interface IERC6150AccessControl is IERC6150 {
/**
* @notice Check the account whether a admin of `tokenId` token.
* @dev Each token can be set more than one admin. Admin have permission to do something to the token, like mint child token,
* or burn token, or transfer parentship.
* @param tokenId The specified token
* @param account The account to be checked
* @return If the account has admin permission, return true; otherwise, return false.
*/
function isAdminOf(uint256 tokenId, address account) external view returns (bool);
/**
* @notice Check whether the specified parent token and account can mint children tokens
* @dev If the `parentId` is zero, check whether account can mint root nodes
* @param parentId The specified parent token to be checked
* @param account The specified account to be checked
* @return If the token and account has mint permission, return true; otherwise, return false.
*/
function canMintChildren(uint256 parentId, address account) external view returns (bool);
/**
* @notice Check whether the specified token can be burnt by specified account
* @param tokenId The specified token to be checked
* @param account The specified account to be checked
* @return If the tokenId can be burnt by account, return true; otherwise, return false.
*/
function canBurnTokenByAccount(uint256 tokenId, address account) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC-721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @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`.
*
* 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 approved 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;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC-721 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 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) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* 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;
/**
* @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;
/**
* @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 address zero.
*
* 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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}