Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
TomiOhl committed Jun 28, 2023
0 parents commit 56c2ff0
Show file tree
Hide file tree
Showing 39 changed files with 11,976 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Your Etherscan API key for contract source code verification.
ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
# Your infura.io project ID for deploying to Ethereum networks.
INFURA_ID=73157d26f55d413eb06614f4ead1de461
# The private key of your address for deploying contracts on public networks.
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
5 changes: 5 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
artifacts
cache
coverage
*.js
25 changes: 25 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module.exports = {
env: {
browser: false,
es2021: true,
mocha: true,
node: true
},
plugins: ["@typescript-eslint"],
extends: ["airbnb-base", "prettier", "plugin:node/recommended"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 12
},
rules: {
"func-names": "off",
"no-console": "off",
"import/no-extraneous-dependencies": ["error", { devDependencies: true }],
"node/no-unpublished-import": "off",
"node/no-unsupported-features/es-syntax": ["error", { ignores: ["modules"] }],
"no-shadow": "off",
"@typescript-eslint/no-shadow": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
};
65 changes: 65 additions & 0 deletions .github/workflows/contract-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Code checks

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup NodeJS
uses: actions/setup-node@v3
with:
node-version: "18"
cache: "npm"

- name: Cache node modules
uses: actions/cache@v3
id: cache
with:
path: "**/node_modules"
key: npm-v1-${{ hashFiles('**/package-lock.json') }}
restore-keys: npm-v1-

- name: Install dependencies
run: npm ci
if: steps.cache.outputs.cache-hit != 'true'

- name: Lint contracts
run: npm run lint-contracts

- name: Lint scripts
run: npm run lint-ts

tests:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup NodeJS
uses: actions/setup-node@v3
with:
node-version: "18"
cache: "npm"

- name: Cache node modules
uses: actions/cache@v3
id: cache
with:
path: "**/node_modules"
key: npm-v1-${{ hashFiles('**/package-lock.json') }}
restore-keys: npm-v1-

- name: Install dependencies
run: npm ci
if: steps.cache.outputs.cache-hit != 'true'

- name: Run tests
run: npm test
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
.env
coverage
coverage.json
typechain

#Hardhat files
cache
artifacts
6 changes: 6 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
artifacts
cache
coverage*
gasReporterOutput.json
docs/templates
16 changes: 16 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"useTabs": false,
"tabWidth": 2,
"singleQuote": false,
"trailingComma": "none",
"printWidth": 120,
"overrides": [
{
"files": "*.sol",
"options": {
"tabWidth": 4,
"bracketSpacing": true
}
}
]
}
10 changes: 10 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "solhint:recommended",
"rules": {
"compiler-version": "off",
"func-visibility": ["warn", { "ignoreConstructors": true }],
"max-line-length": ["warn", 123],
"no-inline-assembly": "off",
"not-rely-on-time": "off"
}
}
1 change: 1 addition & 0 deletions .solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 guild.xyz

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
94 changes: 94 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# NFT reward factory

NFT contracts used for [Guild.xyz](https://guild.xyz)'s `CONTRACT_CALL` reward.

## Setup

To run the project you need [Node.js](https://nodejs.org) development environment.

Pull the repository from GitHub, then install its dependencies by executing this command:

```bash
npm install
```

Certain actions, like deploying to a public network or verifying source code on block explorers, need environment variables in a file named `.env`. See _[.env.example](.env.example)_ for more info.

### Some additional steps before deployment

Open the script you wish to use, depending on if you want to deploy the contracts for the first time or upgrade an existing deployment. Notice the constants at the top and edit them according to your needs.

## Contract deployment

To deploy the smart contracts to a network, replace _[networkName]_ with the name of the network and _[scriptName]_ with the name of the script you wish to run in this command:

```bash
npx hardhat run scripts/[scriptName] --network [networkName]
```

Networks can be configured in _[hardhat.config.ts](hardhat.config.ts)_. We've preconfigured the following:

- `hardhat` (for local testing, default)
- `ethereum` (Ethereum Mainnet)
- `goerli` (Görli Ethereum Testnet)
- `sepolia` (Sepolia Ethereum Testnet)
- `bsc` (BNB Smart Chain)
- `bsctest` (BNB Smart Chain Testnet)
- `polygon` (Polygon Mainnet (formerly Matic))
- `mumbai` (Matic Mumbai Testnet)
- `gnosis` (Gnosis Chain (formerly xDai Chain))
- `arbitrum` (Arbitrum One (Mainnet))

## Verification

For source code verification on block explorers, you can use the Etherscan plugin:

```bash
npx hardhat verify [contractAddress] [constructorArguments] --network [networkName]
```

For more detailed instructions, check out it's documentation [here](https://hardhat.org/plugins/nomiclabs-hardhat-etherscan#usage).

## Linting

The project uses [Solhint](https://github.com/protofire/solhint) for Solidity smart contracts and [ESLint](https://eslint.org) for TypeScript files. To lint all files, simply execute:

```bash
npm run lint
```

To lint only the Solidity files:

```bash
npm run lint-contracts
```

To lint only the TypeScript files:

```bash
npm run lint-ts
```

## Tests

To run the unit tests written for this project, execute this command in a terminal:

```bash
npm test
```

To run the unit tests only in a specific file, just append the path to the command. For example, to run tests just for ContractName:

```bash
npm test test/ContractName.spec.ts
```

## Documentation

The documentation for the contracts is generated via the [solidity-docgen](https://github.com/OpenZeppelin/solidity-docgen) package. Run the tool via the following command:

```bash
npm run docgen
```

The output can be found in the _[docs/contracts](docs/contracts)_ folder.
110 changes: 110 additions & 0 deletions contracts/GuildRewardNFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import { IGuildRewardNFT } from "./interfaces/IGuildRewardNFT.sol";
import { LibTransfer } from "./lib/LibTransfer.sol";
import { SoulboundERC721 } from "./token/SoulboundERC721.sol";
import { TreasuryManager } from "./utils/TreasuryManager.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { ECDSAUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";

/// @title An NFT distributed as a reward for Guild.xyz users.
contract GuildRewardNFT is
IGuildRewardNFT,
Initializable,
OwnableUpgradeable,
UUPSUpgradeable,
SoulboundERC721,
TreasuryManager
{
using ECDSAUpgradeable for bytes32;
using LibTransfer for address;
using LibTransfer for address payable;

address public validSigner;

/// @notice The cid for tokenURI.
string internal cid;

/// @notice Empty space reserved for future updates.
uint256[48] private __gap;

/// @notice Sets metadata and the associated addresses.
/// @param name The name of the token.
/// @param symbol The symbol of the token.
/// @param treasury The address where the collected fees will be sent.
/// @param _validSigner The address that should sign the parameters for certain functions.
/// @param _cid The cid used to construct the tokenURI for the token to be minted.
function initialize(
string memory name,
string memory symbol,
address payable treasury,
address payable _validSigner,
string calldata _cid
) public initializer {
validSigner = _validSigner;
cid = _cid;
__Ownable_init();
__UUPSUpgradeable_init();
__SoulboundERC721_init(name, symbol);
__TreasuryManager_init(treasury);
}

function claim(address payToken, address receiver, bytes calldata signature) external payable {
if (balanceOf(receiver) > 0) revert AlreadyClaimed();
if (!isValidSignature(receiver, signature)) revert IncorrectSignature();

uint256 tokenId = totalSupply();

uint256 fee = fee[payToken];
if (fee == 0) revert IncorrectPayToken(payToken);

// Fee collection
// When there is no msg.value, try transferring ERC20
// When there is msg.value, ensure it's the correct amount
if (msg.value == 0) treasury.sendTokenFrom(msg.sender, payToken, fee);
else if (msg.value != fee) revert IncorrectFee(msg.value, fee);
else treasury.sendEther(fee);

_safeMint(receiver, tokenId);

emit Claimed(receiver, tokenId);
}

function burn(uint256 tokenId) external {
if (msg.sender != ownerOf(tokenId)) revert IncorrectSender();
_burn(tokenId);
}

function setValidSigner(address newValidSigner) external onlyOwner {
validSigner = newValidSigner;
emit ValidSignerChanged(newValidSigner);
}

function updateTokenURI(string calldata newCid) external onlyOwner {
cid = newCid;
emit MetadataUpdate();
}

function hasClaimed(address account) external view returns (bool claimed) {
return balanceOf(account) > 0;
}

function tokenURI(uint256 tokenId) public view override returns (string memory) {
if (!_exists(tokenId)) revert NonExistentToken(tokenId);

return string.concat("ipfs://", cid);
}

// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address) internal override onlyOwner {}

/// @notice Checks the validity of the signature for the given params.
function isValidSignature(address receiver, bytes calldata signature) internal view returns (bool) {
if (signature.length != 65) revert IncorrectSignature();
bytes32 message = keccak256(abi.encode(receiver, block.chainid, address(this))).toEthSignedMessageHash();
return message.recover(signature) == validSigner;
}
}
Loading

0 comments on commit 56c2ff0

Please sign in to comment.