Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/contracts/contracts/NonFungibleTimeCollection.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import '@openzeppelin/contracts/utils/Strings.sol';
import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol';
import './utils/ERC721PermittableUpgradeable.sol';

/// @title Non-Fungible Time collection.
/// @author The Newt team.
/// @notice A primitive to mint time, our most valuable asset, on-chain.
/// @dev An ERC-721 contract with mint, buy, and transferFrom functions.
contract NonFungibleTimeCollection is IERC2981, ERC721Upgradeable, OwnableUpgradeable {
contract NonFungibleTimeCollection is IERC2981, ERC721PermittableUpgradeable, OwnableUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;

event TokenBought(uint256 indexed tokenId, address seller, address buyer);
Expand Down Expand Up @@ -94,7 +94,7 @@ contract NonFungibleTimeCollection is IERC2981, ERC721Upgradeable, OwnableUpgrad
address svgGeneratorContract,
address owner
) public initializer {
__ERC721_init(name, symbol);
__ERC721PermittableUpgradeable_init(name, symbol);
_transferOwnership(owner);
if (useNativeCurrency) {
isCurrencyAllowed[address(0)] = true;
Expand Down
16 changes: 16 additions & 0 deletions packages/contracts/contracts/interfaces/IERC1271.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.4;

/// @title Interface for verifying contract-based account signatures
/// @notice Interface that verifies provided signature for the data
/// @dev Interface defined by EIP-1271
interface IERC1271 {
/// @notice Returns whether the provided signature is valid for the provided data
/// @dev MUST return the bytes4 magic value 0x1626ba7e when function passes.
/// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5).
/// MUST allow external calls.
/// @param hash Hash of the data to be signed
/// @param signature Signature byte array associated with _data
/// @return magicValue The bytes4 magic value 0x1626ba7e
function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
}
127 changes: 127 additions & 0 deletions packages/contracts/contracts/utils/ERC721PermittableUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.4;

import '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol';
import '@openzeppelin/contracts/utils/Address.sol';
import '@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol';
import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
import './../interfaces/IERC1271.sol';

/// @title ERC721PermittableUpgradeable
/// @author The Newt team.
/// @notice An extension of `ERC721`.
/// The base is OpenZeppelin's `ERC721Upgradeable` which also includes the `Metadata` extension. This extension includes simpler transfers and gasless approvals.
contract ERC721PermittableUpgradeable is ERC721Upgradeable, EIP712Upgradeable {

/// @dev The nonces used in the permit signature verification.
/// tokenID => nonce
mapping(uint256 => uint256) private _nonces;

/// @dev Value is equal to keccak256('Permit(address spender,uint256 tokenID,uint256 nonce,uint256 deadline)');
// solhint-disable-next-line var-name-mixedcase
bytes32 private immutable _PERMIT_TYPEHASH = 0x137406564cdcf9b40b1700502a9241e87476728da7ae3d0edfcf0541e5b49b3e;


/// @notice Initializes the `ERC721Enhanced` contract.
/// @param name The name of the token.
/// @param symbol The symbol of the token.
// solhint-disable-next-line func-name-mixedcase
function __ERC721PermittableUpgradeable_init(string memory name, string memory symbol) internal onlyInitializing {
__ERC721_init(name, symbol);
__EIP712_init_unchained(name, '1');
}

// solhint-disable-next-line func-name-mixedcase
function __ERC721Enhanced_init_unchained() internal onlyInitializing { }

/***************************************
SIMPLER TRANSFERS
***************************************/


/// @notice Transfers `tokenID` from `msg.sender` to `to`.
/// @dev This was excluded from the official `ERC721` standard in favor of `transferFrom(address from, address to, uint256 tokenID)`. We elect to include it.
/// @param to The receipient of the token.
/// @param tokenID The token to transfer.
function transfer(address to, uint256 tokenID) public {
super.transferFrom(msg.sender, to, tokenID);
}

/// @notice Safely transfers `tokenID` from `msg.sender` to `to`.
/// @dev This was excluded from the official `ERC721` standard in favor of `safeTransferFrom(address from, address to, uint256 tokenID)`. We elect to include it.
/// @param to The receipient of the token.
/// @param tokenID The token to transfer.
function safeTransfer(address to, uint256 tokenID) public {
super.safeTransferFrom(msg.sender, to, tokenID, '');
}

/***************************************
GASLESS APPROVALS
***************************************/

/// @notice Approve of a specific `tokenID` for spending by `spender` via signature.
/// @param spender The account that is being approved.
/// @param tokenID The ID of the token that is being approved for spending.
/// @param deadline The deadline timestamp by which the call must be mined for the approve to work.
/// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s`.
/// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s`.
/// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v`.
function permit(
address spender,
uint256 tokenID,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(_exists(tokenID), 'query for nonexistent token');
// solhint-disable-next-line not-rely-on-time
require(block.timestamp <= deadline, 'permit expired');

uint256 nonce = _nonces[tokenID]++; // get then increment
bytes32 digest =
keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR(),
keccak256(abi.encode(_PERMIT_TYPEHASH, spender, tokenID, nonce, deadline))
)
);
address owner = ownerOf(tokenID);
require(spender != owner, 'cannot permit to self');

if (Address.isContract(owner)) {
require(IERC1271(owner).isValidSignature(digest, abi.encodePacked(r, s, v)) == 0x1626ba7e, 'unauthorized');
} else {
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0), 'invalid signature');
require(recoveredAddress == owner, 'unauthorized');
}

_approve(spender, tokenID);
}

/// @notice Returns the current nonce for `tokenID`. This value must be
/// included whenever a signature is generated for `permit`.
/// Every successful call to `permit` increases ``tokenID``'s nonce by one. This
/// prevents a signature from being used multiple times.
/// @param tokenID ID of the token to request nonce.
/// @return nonce Nonce of the token.
function nonces(uint256 tokenID) external view returns (uint256 nonce) {
return _nonces[tokenID];
}

/// @notice The permit typehash used in the `permit` signature.
/// @return typehash The typehash for the `permit`.
// solhint-disable-next-line func-name-mixedcase
function PERMIT_TYPEHASH() external pure returns (bytes32 typehash) {
return _PERMIT_TYPEHASH;
}

/// @notice The domain separator used in the encoding of the signature for `permit`, as defined by `EIP712`.
/// @return seperator The domain seperator for `permit`.
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() public view returns (bytes32 seperator) {
return _domainSeparatorV4();
}
}
111 changes: 111 additions & 0 deletions packages/contracts/docs/NonFungibleTimeCollection.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,40 @@ A primitive to mint time, our most valuable asset, on-chain.

## Methods

### DOMAIN_SEPARATOR

```solidity
function DOMAIN_SEPARATOR() external view returns (bytes32 seperator)
```

The domain separator used in the encoding of the signature for `permit`, as defined by `EIP712`.




#### Returns

| Name | Type | Description |
|---|---|---|
| seperator | bytes32 | The domain seperator for `permit`.

### PERMIT_TYPEHASH

```solidity
function PERMIT_TYPEHASH() external pure returns (bytes32 typehash)
```

The permit typehash used in the `permit` signature.




#### Returns

| Name | Type | Description |
|---|---|---|
| typehash | bytes32 | The typehash for the `permit`.

### approve

```solidity
Expand Down Expand Up @@ -250,6 +284,28 @@ function name() external view returns (string)
|---|---|---|
| _0 | string | undefined

### nonces

```solidity
function nonces(uint256 tokenID) external view returns (uint256 nonce)
```

Returns the current nonce for `tokenID`. This value must be included whenever a signature is generated for `permit`. Every successful call to `permit` increases ``tokenID``&#39;s nonce by one. This prevents a signature from being used multiple times.



#### Parameters

| Name | Type | Description |
|---|---|---|
| tokenID | uint256 | ID of the token to request nonce.

#### Returns

| Name | Type | Description |
|---|---|---|
| nonce | uint256 | Nonce of the token.

### owner

```solidity
Expand Down Expand Up @@ -289,6 +345,27 @@ function ownerOf(uint256 tokenId) external view returns (address)
|---|---|---|
| _0 | address | undefined

### permit

```solidity
function permit(address spender, uint256 tokenID, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external nonpayable
```

Approve of a specific `tokenID` for spending by `spender` via signature.



#### Parameters

| Name | Type | Description |
|---|---|---|
| spender | address | The account that is being approved.
| tokenID | uint256 | The ID of the token that is being approved for spending.
| deadline | uint256 | The deadline timestamp by which the call must be mined for the approve to work.
| v | uint8 | Must produce valid secp256k1 signature from the holder along with `r` and `s`.
| r | bytes32 | Must produce valid secp256k1 signature from the holder along with `v` and `s`.
| s | bytes32 | Must produce valid secp256k1 signature from the holder along with `r` and `v`.

### redeem

```solidity
Expand Down Expand Up @@ -340,6 +417,23 @@ function royaltyInfo(uint256 tokenId, uint256 salePrice) external view returns (
| _0 | address | The address who will receive the royalties and the royalty amount for the given price.
| _1 | uint256 | undefined

### safeTransfer

```solidity
function safeTransfer(address to, uint256 tokenID) external nonpayable
```

Safely transfers `tokenID` from `msg.sender` to `to`.

*This was excluded from the official `ERC721` standard in favor of `safeTransferFrom(address from, address to, uint256 tokenID)`. We elect to include it.*

#### Parameters

| Name | Type | Description |
|---|---|---|
| to | address | The receipient of the token.
| tokenID | uint256 | The token to transfer.

### safeTransferFrom

```solidity
Expand Down Expand Up @@ -555,6 +649,23 @@ function totalSupply() external view returns (uint256)
|---|---|---|
| _0 | uint256 | undefined

### transfer

```solidity
function transfer(address to, uint256 tokenID) external nonpayable
```

Transfers `tokenID` from `msg.sender` to `to`.

*This was excluded from the official `ERC721` standard in favor of `transferFrom(address from, address to, uint256 tokenID)`. We elect to include it.*

#### Parameters

| Name | Type | Description |
|---|---|---|
| to | address | The receipient of the token.
| tokenID | uint256 | The token to transfer.

### transferFrom

```solidity
Expand Down
Loading