├── .circleci
└── config.yml
├── .eslintignore
├── .eslintrc.yaml
├── .gitattributes
├── .gitignore
├── .gitmodules
├── .mocharc.json
├── .npmpackagejsonlintrc.json
├── .prettierignore
├── .prettierrc
├── .solcover.js
├── .solhint.json
├── .solhintignore
├── CHANGELOG.md
├── README.md
├── addresses.js
├── contracts
├── FETH.sol
├── FNDMiddleware.sol
├── FoundationTreasury.sol
├── NFTCollection.sol
├── NFTCollectionFactory.sol
├── NFTDropCollection.sol
├── NFTDropMarket.sol
├── NFTMarket.sol
├── PercentSplitETH.sol
├── interfaces
│ ├── dependencies
│ │ ├── ens
│ │ │ ├── IENS.sol
│ │ │ ├── IFIFSRegistrar.sol
│ │ │ ├── IPublicResolver.sol
│ │ │ └── IReverseRegistrar.sol
│ │ └── tokens
│ │ │ ├── IERC20Approve.sol
│ │ │ ├── IERC20IncreaseAllowance.sol
│ │ │ └── IWETH9.sol
│ ├── internal
│ │ ├── IFethMarket.sol
│ │ ├── INFTCollectionInitializer.sol
│ │ ├── INFTDropCollectionInitializer.sol
│ │ ├── INFTDropCollectionMint.sol
│ │ └── roles
│ │ │ ├── IAdminRole.sol
│ │ │ ├── IHasRolesContract.sol
│ │ │ ├── IOperatorRole.sol
│ │ │ └── IRoles.sol
│ ├── standards
│ │ └── royalties
│ │ │ ├── IGetFees.sol
│ │ │ ├── IGetRoyalties.sol
│ │ │ ├── IOwnable.sol
│ │ │ ├── IRoyaltyInfo.sol
│ │ │ └── ITokenCreator.sol
│ └── subgraph
│ │ └── IMarketDeprecatedAPIs.sol
├── libraries
│ ├── AddressLibrary.sol
│ ├── ArrayLibrary.sol
│ ├── BytesLibrary.sol
│ ├── LockedBalance.sol
│ └── OZERC165Checker.sol
└── mixins
│ ├── collections
│ ├── CollectionRoyalties.sol
│ └── SequentialMintCollection.sol
│ ├── nftDropMarket
│ ├── NFTDropMarketCore.sol
│ └── NFTDropMarketFixedPriceSale.sol
│ ├── nftMarket
│ ├── NFTMarketAuction.sol
│ ├── NFTMarketBuyPrice.sol
│ ├── NFTMarketCore.sol
│ ├── NFTMarketOffer.sol
│ ├── NFTMarketPrivateSaleGap.sol
│ └── NFTMarketReserveAuction.sol
│ ├── roles
│ ├── AdminRole.sol
│ └── MinterRole.sol
│ ├── shared
│ ├── Constants.sol
│ ├── ContractFactory.sol
│ ├── FETHNode.sol
│ ├── FoundationTreasuryNode.sol
│ ├── Gap10000.sol
│ ├── MarketFees.sol
│ ├── MarketSharedCore.sol
│ └── SendValueWithFallbackWithdraw.sol
│ └── treasury
│ ├── AdminRoleEnumerable.sol
│ ├── CollateralManagement.sol
│ ├── OZAccessControlUpgradeable.sol
│ └── OperatorRoleEnumerable.sol
├── docs
├── FETH.md
├── FNDMiddleware.md
├── FoundationTreasury.md
├── NFTCollection.md
├── NFTCollectionFactory.md
├── NFTDropCollection.md
├── NFTDropMarket.md
├── NFTMarket.md
└── PercentSplitETH.md
├── docs_template.sqrl
├── hardhat.config.ts
├── package.json
├── subgraph
├── codegen.yml
├── scalars.graphql
├── schema.graphql
├── src
│ ├── mappings
│ │ ├── collections.ts
│ │ ├── feth.ts
│ │ ├── nft.ts
│ │ ├── nftDropMarket.ts
│ │ ├── nftMarket.ts
│ │ └── percentSplit.ts
│ └── shared
│ │ ├── accounts.ts
│ │ ├── buyNow.ts
│ │ ├── constants.ts
│ │ ├── conversions.ts
│ │ ├── creators.ts
│ │ ├── events.ts
│ │ ├── feth.ts
│ │ ├── ids.ts
│ │ ├── offers.ts
│ │ └── revenue.ts
└── subgraph.template.yaml
├── tsconfig.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | orbs:
3 | wait-for: cobli/wait-for@0.0.2
4 | jobs:
5 | build:
6 | docker:
7 | - image: circleci/node:16
8 | working_directory: ~/repo
9 | steps:
10 | - checkout
11 |
12 | # NPM auth from https://discuss.circleci.com/t/yarn-with-private-packages/10157
13 | - run: echo "//registry.npmjs.org/:_authToken=${npm_TOKEN}" > ~/.npmrc
14 | - run: yarn
15 | - run: yarn build
16 |
17 | - persist_to_workspace:
18 | root: ~/repo
19 | paths: .
20 | lint:
21 | docker:
22 | - image: circleci/node:16
23 | working_directory: ~/repo
24 | steps:
25 | - attach_workspace:
26 | at: ~/repo
27 |
28 | - run: yarn lint
29 | publish:
30 | docker:
31 | - image: circleci/node:16
32 | working_directory: ~/repo
33 | steps:
34 | - attach_workspace:
35 | at: ~/repo
36 | - run:
37 | name: Authenticate with registry
38 | command: echo "//registry.npmjs.org/:_authToken=$npm_TOKEN" > ~/repo/.npmrc
39 | - run:
40 | name: Publish package
41 | command: npm publish
42 |
43 | workflows:
44 | build:
45 | jobs:
46 | - build:
47 | filters:
48 | tags:
49 | only: /.*/
50 | branches:
51 | ignore:
52 | - gh-pages
53 | - artifacts
54 | - lint:
55 | requires:
56 | - build
57 | - publish:
58 | requires:
59 | - build
60 | filters:
61 | tags:
62 | only: /^v.*/
63 | branches:
64 | ignore: /.*/
65 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # folders
2 | artifacts/
3 | build/
4 | cache/
5 | coverage/
6 | dist/
7 | docs/
8 | lib/
9 | node_modules/
10 | src/typechain/
11 | subgraph/config/
12 | subgraph/build/
13 | subgraph/generated/
14 | subgraph/graph-node/
15 | versions/
16 | deployments/
17 |
18 | # files
19 | .solcover.js
20 | coverage.json
21 |
--------------------------------------------------------------------------------
/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 | extends:
2 | - plugin:@typescript-eslint/eslint-recommended
3 | parser: "@typescript-eslint/parser"
4 | parserOptions:
5 | project: tsconfig.json
6 | plugins:
7 | - "@typescript-eslint"
8 | - "no-only-tests"
9 | - "simple-import-sort"
10 | - "unused-imports"
11 | root: true
12 | rules:
13 | "@typescript-eslint/no-floating-promises": "error"
14 | "@typescript-eslint/no-unused-vars": "off" # Replaced by the unused-imports plugin
15 | "unused-imports/no-unused-imports": "warn"
16 | "unused-imports/no-unused-vars": "warn"
17 | "no-only-tests/no-only-tests": "warn"
18 | "simple-import-sort/imports": "warn"
19 | "simple-import-sort/exports": "warn"
20 | overrides:
21 | - files: subgraph/**/*.ts
22 | rules:
23 | prefer-const: 0
24 | "@typescript-eslint/ban-types": 0
25 | "unused-imports/no-unused-vars": 0
26 |
27 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | coverage
4 | coverage.json
5 | typechain-types
6 | typechain
7 | contracts-exposed
8 |
9 | #Hardhat files
10 | cache
11 | artifacts
12 |
13 | yarn-error.log
14 |
15 | .openzeppelin/
16 | subgraph/build
17 | subgraph/generated
18 | subgraph/subgraph.yaml
19 |
20 | .DS_Store
21 |
22 | subgraph/graph-node/
23 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/forge-std"]
2 | path = lib/forge-std
3 | url = https://github.com/foundry-rs/forge-std
4 |
--------------------------------------------------------------------------------
/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extension": ["ts"],
3 | "recursive": "test",
4 | "require": "ts-node/register/files",
5 | "timeout": 60000
6 | }
7 |
--------------------------------------------------------------------------------
/.npmpackagejsonlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "npm-package-json-lint-config-default",
3 | "rules": {
4 | "require-name": "error",
5 | "require-version": "off",
6 | "prefer-absolute-version-dependencies": "error",
7 | "prefer-alphabetical-peerDependencies": "error",
8 | "prefer-alphabetical-optionalDependencies": "error",
9 | "prefer-alphabetical-bundledDependencies": "error",
10 | "prefer-alphabetical-devDependencies": "error",
11 | "prefer-alphabetical-dependencies": "error",
12 | "version-format": "warning",
13 | "name-format": "error"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # folders
2 | artifacts/
3 | build/
4 | cache/
5 | coverage/
6 | dist/
7 | docs/
8 | lib/
9 | node_modules/
10 | src/typechain/
11 | subgraph/build/
12 | subgraph/generated/
13 | subgraph/graph-node/
14 | versions/
15 | .vscode/
16 | deployments/
17 | contracts-exposed/
18 |
19 | # files
20 | coverage.json
21 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "endOfLine":"auto",
5 | "printWidth": 120,
6 | "singleQuote": false,
7 | "tabWidth": 2,
8 | "trailingComma": "all",
9 | "overrides": [
10 | {
11 | "files": "*.sol",
12 | "options": {
13 | "tabWidth": 2
14 | }
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.solcover.js:
--------------------------------------------------------------------------------
1 | const shell = require("shelljs");
2 |
3 | // The environment variables are loaded in hardhat.config.ts
4 | const mnemonic = process.env.MNEMONIC;
5 | if (!mnemonic) {
6 | throw new Error("Please set your MNEMONIC in a .env file");
7 | }
8 |
9 | module.exports = {
10 | istanbulReporter: ["html"],
11 | onCompileComplete: async function (_config) {
12 | await run("typechain");
13 | },
14 | onIstanbulComplete: async function (_config) {
15 | // We need to do this because solcover generates bespoke artifacts.
16 | shell.rm("-rf", "./artifacts");
17 | shell.rm("-rf", "./src/typechain");
18 | },
19 | providerOptions: {
20 | mnemonic,
21 | },
22 | skipFiles: ["mocks", "test", "FNDMiddleware", "archive"],
23 | istanbulReporter: ["lcov", "html", "text-summary"],
24 | mocha: {
25 | fgrep: "[skip-on-coverage]",
26 | invert: true,
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/.solhint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "solhint:recommended",
3 | "plugins": ["prettier"],
4 | "rules": {
5 | "code-complexity": ["error", 7],
6 | "compiler-version": ["error", "^0.8.12"],
7 | "const-name-snakecase": "off",
8 | "constructor-syntax": "error",
9 | "func-visibility": ["error", { "ignoreConstructors": true }],
10 | "max-line-length": ["error", 120],
11 | "not-rely-on-time": "off",
12 | "prettier/prettier": [
13 | "error",
14 | {
15 | "endOfLine": "auto"
16 | }
17 | ],
18 | "reason-string": ["warn", { "maxLength": 64 }],
19 | "var-name-mixedcase": "off",
20 | "no-inline-assembly": "off"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.solhintignore:
--------------------------------------------------------------------------------
1 | # folders
2 | .yarn/
3 | build/
4 | dist/
5 | node_modules/
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 2.3.0
4 |
5 | - Add Drops
6 | - [Remove admin cancel](https://github.com/f8n/fnd-contracts/pull/1973) functions from the market
7 | - Remove `makeOffer` (was deprecated in favor of `makeOfferV2`).
8 | - Introduce the `NFTDropMarket`.
9 | - Add `getSellerOf` to the markets and `getSellerOrOwnerOf` to the middleware.
10 | - Upgrade to [solc 0.8.16](https://github.com/ethereum/solidity/releases/tag/v0.8.16)
11 | - SplitsV3: Gas-optimization to reduce costs of creating splits
12 |
13 | ## 2.2.5
14 |
15 | - Upgrade to [solc 0.8.15](https://github.com/ethereum/solidity/releases/tag/v0.8.15)
16 |
17 | ## 2.2.4
18 |
19 | - Middleware [fix div by 0](https://github.com/f8n/fnd-contracts/pull/1888) when 0 royalties are requested
20 |
21 | ## 2.2.3
22 |
23 | - [Remove Private Sales](https://github.com/f8n/fnd-contracts/pull/1864)
24 | - [Try/catch `tokenCreator`](https://github.com/f8n/fnd-contracts/pull/1867) so that other royalty APIs are checked for contracts with a fallback function.
25 | - [Ignore `owner` when address(0)](https://github.com/f8n/fnd-contracts/pull/1868)
26 |
27 | ## 2.2.2
28 |
29 | - Middleware: [Fix `probeNFT`](https://github.com/f8n/fnd-contracts/pull/1865) for fallback function in the royalty recipient.
30 | - Upgrade to [solc 0.8.14](https://github.com/ethereum/solidity/releases/tag/v0.8.14)
31 |
32 | ## 2.2.1
33 |
34 | ### Market
35 |
36 | - [Try try catch](https://github.com/f8n/fnd-contracts/pull/1838) so that contracts with a fallback function or unsupported return types do not cause the NFT to get stuck in escrow.
37 |
38 | ## 2.2.0
39 |
40 | ### Market
41 |
42 | - [Bid Referrals](https://github.com/f8n/fnd-contracts/pull/1782): adds `placeBidV2` with referral incentives.
43 | - [Offer Referrals](https://github.com/f8n/fnd-contracts/pull/1790): adds `makeOfferV2` with referral incentives.
44 | - Auction gas savings: don't store duration/extension. https://github.com/f8n/fnd-contracts/pull/1793
45 |
46 | ## 2.1.1
47 |
48 | ### Middleware
49 |
50 | - Catch reverts in `getFees` and return `keccak256("Failed to getFees")`.
51 |
52 | ## 2.1.0
53 |
54 | ### General
55 |
56 | - Upgrade to [solc 0.8.13](https://github.com/ethereum/solidity/releases/tag/v0.8.13)
57 |
58 | ### Market
59 |
60 | - [Buy referrals](https://github.com/f8n/fnd-contracts/pull/1726): adds `buyV2` with referral incentives.
61 | - Royalties: ignore `royaltyInfo` from the NFT contract when the amount is 0 (does not impact the royalty override)
62 | - On placeBidOf, leverage FETH from an outstanding offer.
63 | - Remove withdraw from escrow (leaning on fallback to FETH instead).
64 | - Revert when setting the same buy or reserve price.
65 | - Don't check royalty APIs for non-address(0) recipient (still checks length > 0).
66 | - Use OZ ECDSA for private sale signature verification.
67 | - Gas optimizations & style improvements.
68 |
69 | ### Middleware
70 |
71 | - Switch `probeNFT` to return bytes32 instead of throwing errors.
72 |
73 | ## 2.0.3
74 |
75 | ### Middleware
76 |
77 | - `probeNFT` https://github.com/f8n/fnd-contracts/pull/1645
78 |
79 | ## 2.0.1
80 |
81 | ### Percent Split Factory
82 |
83 | - Block proxy to `increaseAllowance`
84 | - Optimize storage to save gas
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Documentation
4 |
5 | See the [documentation](https://docs.foundation.app/docs/protocol).
6 |
7 | # Bounty Program
8 |
9 | If you find a security issue impacting contracts deployed to mainnet, please report them via our [Immunefi bounty program](https://immunefi.com/bounty/foundation/).
10 |
11 | Other issues can be reported via [GitHub Issues](https://github.com/f8n/fnd-protocol/issues). Questions? Ask in [Discussions](https://github.com/f8n/fnd-protocol/discussions).
12 |
13 | # Testing
14 |
15 | ```
16 | yarn
17 | yarn build
18 | yarn test
19 | ```
20 |
21 | Note that our full test suite is not included here at this time. The tests are meant to demonstrate how to work with our contracts and a few of the core features.
22 |
23 | # Code conventions
24 |
25 | ## Data sizes
26 |
27 | Where possible we should be consistent on the size of fields used. Even if it offers no immediate benefit, it will leave room for packing new fields in the future.
28 |
29 | - When uints are used as a mapping key, there is no known benefit to compressing so uint256 is preferred.
30 | - External APIs and events should always use uint256. Benefits of compressing are small and 256 is industry standard so integration should be easier.
31 | - Math in Solidity always operates in 256 bit form, so best to cast to the smaller size only at the time of storage.
32 | - Solidity does not check for overflows when down casting, explicit checks should be added when assumptions are made about user inputs.
33 |
34 | Recommendations:
35 |
36 | - ETH: uint96
37 | - Circulating supply is currently 119,440,269 ETH (119440269000000000000000000 wei / 1.2 \* 10^26).
38 | - Max uint96 is 7.9 \* 10^28.
39 | - Therefore any value capped by msg.value should never overflow uint96 assuming ETH total supply remains under 70,000,000,000 ETH.
40 | - There is currently ~2% inflation in ETH total supply (which fluctuates) and this value is expected to do down. We expect Ether will become deflationary before the supply reaches more than 500x current values.
41 | - 96 bits packs perfectly into a single slot with an address.
42 | - Dates: uint32
43 | - Current date in seconds is 1643973923 (1.6 \* 10^9).
44 | - Max uint32 is 4 \* 10^9.
45 | - Dates will not overflow uint32 until 2104.
46 | - To ensure we don't behave unexpectedly in the future, we should require dates are <= max uint32.
47 | - uint40 would allow enough space for any date, but it's an awkward size to pack with.
48 | - Sequence ID indexes: uint32
49 | - Our max sequence ID today is 149,819 auctions.
50 | - Max uint32 is 4 \* 10^9.
51 | - Indexes will not overflow uint32 until we see >28,000x growth. This is the equiv to ~300 per block for every block in Ethereum to date.
52 | - Basis points: uint16
53 | - Numbers which are by definition lower than the max uint value can be compressed appropriately.
54 | - Basis points is <= 10,000 which fits into uint16 (max of 65,536)
55 |
--------------------------------------------------------------------------------
/addresses.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line
2 | module.exports = {
3 | prod: {
4 | 1: {
5 | multisig: "0x9d9C46aCa6a2c5FF6824A92d521b6381f9f8F1a9",
6 | proxyAdminAddress: "0x72DE36c8ebEAcB6100C36249552e35fefF0EE099",
7 | treasury: "0x67Df244584b67E8C51B10aD610aAfFa9a402FdB6",
8 | nft721: "0x3B3ee1931Dc30C1957379FAc9aba94D1C48a5405",
9 | nftMarket: "0xcDA72070E455bb31C7690a170224Ce43623d0B6f",
10 | percentSplit: "0x00Fc4bB5349F1104D3252c36C308688651e3eb1F",
11 | proxy: "0x461AA494426e04fB2449068BaD5948a0d720aEf2",
12 | nftCollectionFactoryV1: "0x3B612a5B49e025a6e4bA4eE4FB1EF46D13588059",
13 | feth: "0x49128CF8ABE9071ee24540a296b5DED3F9D50443",
14 | middleware: "0xC0032194d7B298465D558ab1B91Ab2ca6991959C",
15 | nftDropMarket: "0x53F451165Ba6fdbe39A134673d13948261B2334A",
16 | nftCollectionFactoryV2: "0x612E2DadDc89d91409e40f946f9f7CfE422e777E",
17 | },
18 | },
19 | staging: {
20 | 3: {
21 | multisig: "0xFa73B9eEE034225A8b3C6F5ecd35b625406F1385",
22 | proxyAdminAddress: "0x9aCe89b0C3b310eC4B47406195706D9C89c8e916",
23 | treasury: "0x32dAa472Ed66Ebbd14407783B34DD269ea20d664",
24 | nft721: "0x020eDedBE9F6A825D5548bdeF1266d9c77F4f58A",
25 | nftMarket: "0x7b503e206dB34148aD77e00afE214034EDF9E3fF",
26 | },
27 | 4: {
28 | multisig: "0x18285ACA38F68d29dA62B0EB0A810789cD0D9F5C",
29 | proxyAdminAddress: "0x11A6e3C7c4FDb2f9bB5870f259bda80F963De7Aa",
30 | treasury: "0x4C6ca976Dec660738502870a9A8F9c01340D37B0",
31 | nft721: "0xB2eEa20701c9EB673aBF9205209dE2e6c2f00959",
32 | nftMarket: "0x21b700d637551f15078E11871a3c0dcCf283D1e7",
33 | },
34 | 5: {
35 | multisig: "0xd80733B381869a0C63DB3Ca132eF52221738C440",
36 | proxyAdminAddress: "0x1cED484fF01ed99Cf08aaF9Ad916417c951Ac2c3",
37 | treasury: "0x3c5cE17ae82f39977C3DBF56433Dc8c0D9B5F2d7",
38 | nft721: "0x44458837ac4036337e5Ce46Ce28A744e05e02016",
39 | nftMarket: "0xeB1bD095061bbDb1aD065524628812cae63e4222",
40 | percentSplit: "0xCca271c0a163FD398DE7b86fB6e8f32054301C5f",
41 | proxy: "0x462B9095Ecd465BA35CecBc08930b7445919d2D1",
42 | nftCollectionFactoryV1: "0x889C3635E09B0a2d1603690288c1784155cf2CA6",
43 | feth: "0xf73Ad14f849cd9a0a1fc14Ad1A01650a9FDdE79F",
44 | middleware: "0x3436838C494fb6D8Ee9d5B97cFf0E54af5447EB3",
45 | nftCollectionFactoryV2: "0x8b9788Fd08496e791c066c12021F25a8Eb20CC0b",
46 | nftDropMarket: "0xe43562f11737443F760dBf885fa0D30c45C6927B",
47 | },
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/contracts/FoundationTreasury.sol:
--------------------------------------------------------------------------------
1 | /*
2 | ・
3 | * ★
4 | ・ 。
5 | ・ ゚☆ 。
6 | * ★ ゚・。 * 。
7 | * ☆ 。・゚*.。
8 | ゚ *.。☆。★ ・
9 | * ゚。·*・。 ゚*
10 | ☆゚・。°*. ゚
11 | ・ ゚*。・゚★。
12 | ・ *゚。 *
13 | ・゚*。★・
14 | ☆∴。 *
15 | * ★ 。
16 | ` .-:::::-.` `-::---...```
17 | `-:` .:+ssssoooo++//:.` .-/+shhhhhhhhhhhhhyyyssooo:
18 | .--::. .+ossso+/////++/:://-` .////+shhhhhhhhhhhhhhhhhhhhhy
19 | `-----::. `/+////+++///+++/:--:/+/- -////+shhhhhhhhhhhhhhhhhhhhhy
20 | `------:::-` `//-.``.-/+ooosso+:-.-/oso- -////+shhhhhhhhhhhhhhhhhhhhhy
21 | .--------:::-` :+:.` .-/osyyyyyyso++syhyo.-////+shhhhhhhhhhhhhhhhhhhhhy
22 | `-----------:::-. +o+:-.-:/oyhhhhhhdhhhhhdddy:-////+shhhhhhhhhhhhhhhhhhhhhy
23 | .------------::::-- `oys+/::/+shhhhhhhdddddddddy/-////+shhhhhhhhhhhhhhhhhhhhhy
24 | .--------------:::::-` +ys+////+yhhhhhhhddddddddhy:-////+yhhhhhhhhhhhhhhhhhhhhhy
25 | `----------------::::::-`.ss+/:::+oyhhhhhhhhhhhhhhho`-////+shhhhhhhhhhhhhhhhhhhhhy
26 | .------------------:::::::.-so//::/+osyyyhhhhhhhhhys` -////+shhhhhhhhhhhhhhhhhhhhhy
27 | `.-------------------::/:::::..+o+////+oosssyyyyyyys+` .////+shhhhhhhhhhhhhhhhhhhhhy
28 | .--------------------::/:::.` -+o++++++oooosssss/. `-//+shhhhhhhhhhhhhhhhhhhhyo
29 | .------- ``````.......--` `-/+ooooosso+/-` `./++++///:::--...``hhhhyo
30 | `````
31 | *
32 | ・ 。
33 | ・ ゚☆ 。
34 | * ★ ゚・。 * 。
35 | * ☆ 。・゚*.。
36 | ゚ *.。☆。★ ・
37 | * ゚。·*・。 ゚*
38 | ☆゚・。°*. ゚
39 | ・ ゚*。・゚★。
40 | ・ *゚。 *
41 | ・゚*。★・
42 | ☆∴。 *
43 | ・ 。
44 | */
45 |
46 | // SPDX-License-Identifier: MIT OR Apache-2.0
47 |
48 | pragma solidity ^0.8.12;
49 |
50 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
51 |
52 | import "./mixins/treasury/AdminRoleEnumerable.sol";
53 | import "./mixins/treasury/CollateralManagement.sol";
54 | import "./mixins/treasury/OperatorRoleEnumerable.sol";
55 |
56 | /**
57 | * @title Manage revenue and roles for Foundation.
58 | * @notice All fees generated by the market are forwarded to this contract.
59 | * It also defines the Admin and Operator roles which are used in other contracts.
60 | * @author batu-inal & HardlyDifficult
61 | */
62 | contract FoundationTreasury is Initializable, AdminRoleEnumerable, OperatorRoleEnumerable, CollateralManagement {
63 | // solhint-disable-next-line no-empty-blocks
64 | constructor() initializer {
65 | // Initialize the template to avoid confusion.
66 | // https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680/6
67 | }
68 |
69 | /**
70 | * @notice Called one time after deployment to initialize the contract.
71 | * @param admin The account to add as the initial admin.
72 | */
73 | function initialize(address admin) external initializer {
74 | AdminRoleEnumerable._initializeAdminRole(admin);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/contracts/NFTDropCollection.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
6 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
7 | import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
8 | import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
9 | import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
10 | import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
11 | import "@openzeppelin/contracts/utils/Strings.sol";
12 |
13 | import "./interfaces/internal/INFTDropCollectionInitializer.sol";
14 | import "./interfaces/internal/INFTDropCollectionMint.sol";
15 |
16 | import "./mixins/collections/CollectionRoyalties.sol";
17 | import "./mixins/collections/SequentialMintCollection.sol";
18 | import "./mixins/roles/AdminRole.sol";
19 | import "./mixins/roles/MinterRole.sol";
20 | import "./mixins/shared/ContractFactory.sol";
21 |
22 | /**
23 | * @title A contract to batch mint a collection of NFTs.
24 | * @notice A 10% royalty to the creator is included which may be split with collaborators.
25 | * @dev A collection can have up to 4,294,967,295 (2^32-1) tokens
26 | * @author batu-inal & HardlyDifficult
27 | */
28 | contract NFTDropCollection is
29 | INFTDropCollectionInitializer,
30 | INFTDropCollectionMint,
31 | IGetRoyalties,
32 | IGetFees,
33 | IRoyaltyInfo,
34 | ITokenCreator,
35 | ContractFactory,
36 | Initializable,
37 | ContextUpgradeable,
38 | ERC165Upgradeable,
39 | AccessControlUpgradeable,
40 | AdminRole,
41 | MinterRole,
42 | ERC721Upgradeable,
43 | ERC721BurnableUpgradeable,
44 | SequentialMintCollection,
45 | CollectionRoyalties
46 | {
47 | using Strings for uint256;
48 |
49 | /****** Slot 0 (after inheritance) ******/
50 | /**
51 | * @notice The address to pay the proceeds/royalties for the collection.
52 | * @dev If this is set to address(0) then the proceeds go to the creator.
53 | */
54 | address payable private paymentAddress;
55 | /**
56 | * @notice Whether the collection is revealed or not.
57 | */
58 | bool public isRevealed;
59 | // 88 bits free space
60 |
61 | /****** Slot 1 ******/
62 | /**
63 | * @notice The base URI used for all NFTs in this collection.
64 | * @dev The `.json` is appended to this to obtain an NFT's `tokenURI`.
65 | * e.g. The URI for `tokenId`: "1" with `baseURI`: "ipfs://foo/" is "ipfs://foo/1.json".
66 | * @return The base URI used by this collection.
67 | */
68 | string public baseURI;
69 |
70 | /****** End of storage ******/
71 |
72 | /**
73 | * @notice Emitted when the collection is revealed.
74 | * @param baseURI The base URI for the collection.
75 | * @param isRevealed Whether the collection is revealed.
76 | */
77 | event URIUpdated(string baseURI, bool isRevealed);
78 |
79 | modifier validBaseURI(string calldata baseURI_) {
80 | require(bytes(baseURI_).length > 0, "NFTDropCollection: `baseURI_` must be set");
81 | _;
82 | }
83 |
84 | modifier onlyWhileUnrevealed() {
85 | require(!isRevealed, "NFTDropCollection: Already revealed");
86 | _;
87 | }
88 |
89 | /**
90 | * @notice Initialize the template's immutable variables.
91 | * @param _contractFactory The factory which will be used to create collection contracts.
92 | */
93 | constructor(address _contractFactory)
94 | ContractFactory(_contractFactory) // solhint-disable-next-line no-empty-blocks
95 | {}
96 |
97 | /**
98 | * @notice Called by the contract factory on creation.
99 | * @param _creator The creator of this collection.
100 | * This account is the default admin for this collection.
101 | * @param _name The collection's `name`.
102 | * @param _symbol The collection's `symbol`.
103 | * @param baseURI_ The base URI for the collection.
104 | * @param _isRevealed Whether the collection is revealed or not.
105 | * @param _maxTokenId The max token id for this collection.
106 | * @param _approvedMinter An optional address to grant the MINTER_ROLE.
107 | * Set to address(0) if only admins should be granted permission to mint.
108 | * @param _paymentAddress The address that will receive royalties and mint payments.
109 | */
110 | function initialize(
111 | address payable _creator,
112 | string calldata _name,
113 | string calldata _symbol,
114 | string calldata baseURI_,
115 | bool _isRevealed,
116 | uint32 _maxTokenId,
117 | address _approvedMinter,
118 | address payable _paymentAddress
119 | ) external initializer onlyContractFactory validBaseURI(baseURI_) {
120 | // Initialize the NFT
121 | __ERC721_init(_name, _symbol);
122 | _initializeSequentialMintCollection(_creator, _maxTokenId);
123 |
124 | // Initialize royalties
125 | if (_paymentAddress != address(0)) {
126 | // If no payment address was defined, `.owner` will be returned in getTokenCreatorPaymentAddress() below.
127 | paymentAddress = _paymentAddress;
128 | }
129 |
130 | // Initialize URI
131 | baseURI = baseURI_;
132 | isRevealed = _isRevealed;
133 |
134 | // Initialize access control
135 | AdminRole._initializeAdminRole(_creator);
136 | if (_approvedMinter != address(0)) {
137 | MinterRole._initializeMinterRole(_approvedMinter);
138 | }
139 | }
140 |
141 | /**
142 | * @notice Allows the collection admin to burn a specific token if they currently own the NFT.
143 | * @param tokenId The ID of the NFT to burn.
144 | * @dev The function here asserts `onlyAdmin` while the super confirms ownership.
145 | */
146 | function burn(uint256 tokenId) public override onlyAdmin {
147 | super.burn(tokenId);
148 | }
149 |
150 | /**
151 | * @notice Mint `count` number of NFTs for the `to` address.
152 | * @dev This is only callable by an address with either the MINTER_ROLE or the DEFAULT_ADMIN_ROLE.
153 | * @param count The number of NFTs to mint.
154 | * @param to The address to mint the NFTs for.
155 | * @return firstTokenId The tokenId for the first NFT minted.
156 | * The other minted tokens are assigned sequentially, so `firstTokenId` - `firstTokenId + count - 1` were minted.
157 | */
158 | function mintCountTo(uint16 count, address to) external onlyMinterOrAdmin returns (uint256 firstTokenId) {
159 | require(count != 0, "NFTDropCollection: `count` must be greater than 0");
160 |
161 | unchecked {
162 | // If +1 overflows then +count would also overflow, since count > 0.
163 | firstTokenId = latestTokenId + 1;
164 | }
165 | latestTokenId = latestTokenId + count;
166 | uint256 lastTokenId = latestTokenId;
167 | require(lastTokenId <= maxTokenId, "NFTDropCollection: Exceeds max tokenId");
168 |
169 | for (uint256 i = firstTokenId; i <= lastTokenId; ) {
170 | _safeMint(to, i);
171 | unchecked {
172 | ++i;
173 | }
174 | }
175 | }
176 |
177 | /**
178 | * @notice Allows a collection admin to reveal the collection's final content.
179 | * @dev Once revealed, the collection's content is immutable.
180 | * Use `updatePreRevealContent` to update content while unrevealed.
181 | * @param baseURI_ The base URI of the final content for this collection.
182 | */
183 | function reveal(string calldata baseURI_) external onlyAdmin validBaseURI(baseURI_) onlyWhileUnrevealed {
184 | isRevealed = true;
185 |
186 | // Set the new base URI.
187 | baseURI = baseURI_;
188 | emit URIUpdated(baseURI_, true);
189 | }
190 |
191 | /**
192 | * @notice Allows a collection admin to destroy this contract only if
193 | * no NFTs have been minted yet or the minted NFTs have been burned.
194 | * @dev Once destructed, a new collection could be deployed to this address (although that's discouraged).
195 | */
196 | function selfDestruct() external onlyAdmin {
197 | _selfDestruct();
198 | }
199 |
200 | /**
201 | * @notice Allows the owner to set a max tokenID.
202 | * This provides a guarantee to collectors about the limit of this collection contract.
203 | * @dev Once this value has been set, it may be decreased but can never be increased.
204 | * This max may be more than the final `totalSupply` if 1 or more tokens were burned.
205 | * @param _maxTokenId The max tokenId to set, all NFTs must have a tokenId less than or equal to this value.
206 | */
207 | function updateMaxTokenId(uint32 _maxTokenId) external onlyAdmin {
208 | _updateMaxTokenId(_maxTokenId);
209 | }
210 |
211 | /**
212 | * @notice Allows a collection admin to update the pre-reveal content.
213 | * @dev Use `reveal` to reveal the final content for this collection.
214 | * @param baseURI_ The base URI of the pre-reveal content.
215 | */
216 | function updatePreRevealContent(string calldata baseURI_)
217 | external
218 | validBaseURI(baseURI_)
219 | onlyWhileUnrevealed
220 | onlyAdmin
221 | {
222 | baseURI = baseURI_;
223 | emit URIUpdated(baseURI_, false);
224 | }
225 |
226 | function _burn(uint256 tokenId) internal override(ERC721Upgradeable, SequentialMintCollection) {
227 | super._burn(tokenId);
228 | }
229 |
230 | /**
231 | * @inheritdoc CollectionRoyalties
232 | */
233 | function getTokenCreatorPaymentAddress(
234 | uint256 /* tokenId */
235 | ) public view override returns (address payable creatorPaymentAddress) {
236 | creatorPaymentAddress = paymentAddress;
237 | if (creatorPaymentAddress == address(0)) {
238 | creatorPaymentAddress = owner;
239 | }
240 | }
241 |
242 | /**
243 | * @notice Get the number of tokens which can still be minted.
244 | * @return count The max number of additional NFTs that can be minted by this collection.
245 | */
246 | function numberOfTokensAvailableToMint() external view returns (uint256 count) {
247 | // Mint ensures that latestTokenId is always <= maxTokenId
248 | unchecked {
249 | count = maxTokenId - latestTokenId;
250 | }
251 | }
252 |
253 | /**
254 | * @inheritdoc IERC165Upgradeable
255 | */
256 | function supportsInterface(bytes4 interfaceId)
257 | public
258 | view
259 | override(ERC165Upgradeable, AccessControlUpgradeable, ERC721Upgradeable, CollectionRoyalties)
260 | returns (bool interfaceSupported)
261 | {
262 | interfaceSupported = (interfaceId == type(INFTDropCollectionMint).interfaceId ||
263 | super.supportsInterface(interfaceId));
264 | }
265 |
266 | /**
267 | * @inheritdoc IERC721MetadataUpgradeable
268 | */
269 | function tokenURI(uint256 tokenId) public view override returns (string memory uri) {
270 | _requireMinted(tokenId);
271 |
272 | uri = string.concat(baseURI, tokenId.toString(), ".json");
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/contracts/NFTDropMarket.sol:
--------------------------------------------------------------------------------
1 | /*
2 | ・
3 | * ★
4 | ・ 。
5 | ・ ゚☆ 。
6 | * ★ ゚・。 * 。
7 | * ☆ 。・゚*.。
8 | ゚ *.。☆。★ ・
9 |
10 | ` .-:::::-.` `-::---...```
11 | `-:` .:+ssssoooo++//:.` .-/+shhhhhhhhhhhhhyyyssooo:
12 | .--::. .+ossso+/////++/:://-` .////+shhhhhhhhhhhhhhhhhhhhhy
13 | `-----::. `/+////+++///+++/:--:/+/- -////+shhhhhhhhhhhhhhhhhhhhhy
14 | `------:::-` `//-.``.-/+ooosso+:-.-/oso- -////+shhhhhhhhhhhhhhhhhhhhhy
15 | .--------:::-` :+:.` .-/osyyyyyyso++syhyo.-////+shhhhhhhhhhhhhhhhhhhhhy
16 | `-----------:::-. +o+:-.-:/oyhhhhhhdhhhhhdddy:-////+shhhhhhhhhhhhhhhhhhhhhy
17 | .------------::::-- `oys+/::/+shhhhhhhdddddddddy/-////+shhhhhhhhhhhhhhhhhhhhhy
18 | .--------------:::::-` +ys+////+yhhhhhhhddddddddhy:-////+yhhhhhhhhhhhhhhhhhhhhhy
19 | `----------------::::::-`.ss+/:::+oyhhhhhhhhhhhhhhho`-////+shhhhhhhhhhhhhhhhhhhhhy
20 | .------------------:::::::.-so//::/+osyyyhhhhhhhhhys` -////+shhhhhhhhhhhhhhhhhhhhhy
21 | `.-------------------::/:::::..+o+////+oosssyyyyyyys+` .////+shhhhhhhhhhhhhhhhhhhhhy
22 | .--------------------::/:::.` -+o++++++oooosssss/. `-//+shhhhhhhhhhhhhhhhhhhhyo
23 | .------- ``````.......--` `-/+ooooosso+/-` `./++++///:::--...``hhhhyo
24 | `````
25 | *
26 | ・ 。
27 | ・ ゚☆ 。
28 | * ★ ゚・。 * 。
29 | * ☆ 。・゚*.。
30 | ゚ *.。☆。★ ・
31 | * ゚。·*・。 ゚*
32 | ☆゚・。°*. ゚
33 | ・ ゚*。・゚★。
34 | ・ *゚。 *
35 | ・゚*。★・
36 | ☆∴。 *
37 | ・ 。
38 | */
39 |
40 | // SPDX-License-Identifier: MIT OR Apache-2.0
41 |
42 | pragma solidity ^0.8.12;
43 |
44 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
45 | import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
46 |
47 | import "./mixins/shared/FETHNode.sol";
48 | import "./mixins/shared/FoundationTreasuryNode.sol";
49 | import "./mixins/shared/Gap10000.sol";
50 | import "./mixins/shared/MarketFees.sol";
51 | import "./mixins/shared/MarketSharedCore.sol";
52 | import "./mixins/shared/SendValueWithFallbackWithdraw.sol";
53 |
54 | import "./mixins/nftDropMarket/NFTDropMarketCore.sol";
55 | import "./mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol";
56 |
57 | error NFTDropMarket_NFT_Already_Minted();
58 |
59 | /**
60 | * @title A market for minting NFTs with Foundation.
61 | * @author batu-inal & HardlyDifficult
62 | */
63 | contract NFTDropMarket is
64 | Initializable,
65 | FoundationTreasuryNode,
66 | FETHNode,
67 | MarketSharedCore,
68 | NFTDropMarketCore,
69 | ReentrancyGuardUpgradeable,
70 | SendValueWithFallbackWithdraw,
71 | MarketFees,
72 | Gap10000,
73 | NFTDropMarketFixedPriceSale
74 | {
75 | /**
76 | * @notice Set immutable variables for the implementation contract.
77 | * @dev Using immutable instead of constants allows us to use different values on testnet.
78 | * @param treasury The Foundation Treasury contract address.
79 | * @param feth The FETH ERC-20 token contract address.
80 | * @param royaltyRegistry The Royalty Registry contract address.
81 | */
82 | constructor(
83 | address payable treasury,
84 | address feth,
85 | address royaltyRegistry
86 | )
87 | FoundationTreasuryNode(treasury)
88 | FETHNode(feth)
89 | MarketFees(
90 | /* protocolFeeInBasisPoints: */
91 | 1500,
92 | royaltyRegistry,
93 | /* assumePrimarySale: */
94 | true
95 | )
96 | initializer // solhint-disable-next-line no-empty-blocks
97 | {}
98 |
99 | /**
100 | * @notice Called once to configure the contract after the initial proxy deployment.
101 | * @dev This farms the initialize call out to inherited contracts as needed to initialize mutable variables.
102 | */
103 | function initialize() external initializer {
104 | ReentrancyGuardUpgradeable.__ReentrancyGuard_init();
105 | }
106 |
107 | /**
108 | * @inheritdoc MarketSharedCore
109 | * @dev Returns address(0) if the NFT has already been sold, otherwise checks for a listing in this market.
110 | */
111 | function _getSellerOf(address nftContract, uint256 tokenId)
112 | internal
113 | view
114 | override(MarketSharedCore, NFTDropMarketFixedPriceSale)
115 | returns (address payable seller)
116 | {
117 | // Check the current owner first in case it has been sold.
118 | try IERC721(nftContract).ownerOf(tokenId) returns (address owner) {
119 | if (owner != address(0)) {
120 | // If sold, return address(0) since that owner cannot sell via this market.
121 | return payable(address(0));
122 | }
123 | } catch // solhint-disable-next-line no-empty-blocks
124 | {
125 | // Fall through
126 | }
127 |
128 | return super._getSellerOf(nftContract, tokenId);
129 | }
130 |
131 | /**
132 | * @inheritdoc MarketSharedCore
133 | * @dev Reverts if the NFT has already been sold, otherwise checks for a listing in this market.
134 | */
135 | function _getSellerOrOwnerOf(address nftContract, uint256 tokenId)
136 | internal
137 | view
138 | override
139 | returns (address payable sellerOrOwner)
140 | {
141 | // Check the current owner first in case it has been sold.
142 | try IERC721(nftContract).ownerOf(tokenId) returns (address owner) {
143 | if (owner != address(0)) {
144 | // Once an NFT has been minted, it cannot be sold through this contract.
145 | revert NFTDropMarket_NFT_Already_Minted();
146 | }
147 | } catch // solhint-disable-next-line no-empty-blocks
148 | {
149 | // Fall through
150 | }
151 |
152 | sellerOrOwner = super._getSellerOf(nftContract, tokenId);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/contracts/NFTMarket.sol:
--------------------------------------------------------------------------------
1 | /*
2 | ・
3 | * ★
4 | ・ 。
5 | ・ ゚☆ 。
6 | * ★ ゚・。 * 。
7 | * ☆ 。・゚*.。
8 | ゚ *.。☆。★ ・
9 |
10 | ` .-:::::-.` `-::---...```
11 | `-:` .:+ssssoooo++//:.` .-/+shhhhhhhhhhhhhyyyssooo:
12 | .--::. .+ossso+/////++/:://-` .////+shhhhhhhhhhhhhhhhhhhhhy
13 | `-----::. `/+////+++///+++/:--:/+/- -////+shhhhhhhhhhhhhhhhhhhhhy
14 | `------:::-` `//-.``.-/+ooosso+:-.-/oso- -////+shhhhhhhhhhhhhhhhhhhhhy
15 | .--------:::-` :+:.` .-/osyyyyyyso++syhyo.-////+shhhhhhhhhhhhhhhhhhhhhy
16 | `-----------:::-. +o+:-.-:/oyhhhhhhdhhhhhdddy:-////+shhhhhhhhhhhhhhhhhhhhhy
17 | .------------::::-- `oys+/::/+shhhhhhhdddddddddy/-////+shhhhhhhhhhhhhhhhhhhhhy
18 | .--------------:::::-` +ys+////+yhhhhhhhddddddddhy:-////+yhhhhhhhhhhhhhhhhhhhhhy
19 | `----------------::::::-`.ss+/:::+oyhhhhhhhhhhhhhhho`-////+shhhhhhhhhhhhhhhhhhhhhy
20 | .------------------:::::::.-so//::/+osyyyhhhhhhhhhys` -////+shhhhhhhhhhhhhhhhhhhhhy
21 | `.-------------------::/:::::..+o+////+oosssyyyyyyys+` .////+shhhhhhhhhhhhhhhhhhhhhy
22 | .--------------------::/:::.` -+o++++++oooosssss/. `-//+shhhhhhhhhhhhhhhhhhhhyo
23 | .------- ``````.......--` `-/+ooooosso+/-` `./++++///:::--...``hhhhyo
24 | `````
25 | *
26 | ・ 。
27 | ・ ゚☆ 。
28 | * ★ ゚・。 * 。
29 | * ☆ 。・゚*.。
30 | ゚ *.。☆。★ ・
31 | * ゚。·*・。 ゚*
32 | ☆゚・。°*. ゚
33 | ・ ゚*。・゚★。
34 | ・ *゚。 *
35 | ・゚*。★・
36 | ☆∴。 *
37 | ・ 。
38 | */
39 |
40 | // SPDX-License-Identifier: MIT OR Apache-2.0
41 |
42 | pragma solidity ^0.8.12;
43 |
44 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
45 | import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
46 |
47 | import "./mixins/shared/Constants.sol";
48 | import "./mixins/shared/FETHNode.sol";
49 | import "./mixins/shared/FoundationTreasuryNode.sol";
50 | import "./mixins/shared/MarketFees.sol";
51 | import "./mixins/shared/MarketSharedCore.sol";
52 | import "./mixins/shared/SendValueWithFallbackWithdraw.sol";
53 |
54 | import "./mixins/nftMarket/NFTMarketAuction.sol";
55 | import "./mixins/nftMarket/NFTMarketBuyPrice.sol";
56 | import "./mixins/nftMarket/NFTMarketCore.sol";
57 | import "./mixins/nftMarket/NFTMarketOffer.sol";
58 | import "./mixins/nftMarket/NFTMarketPrivateSaleGap.sol";
59 | import "./mixins/nftMarket/NFTMarketReserveAuction.sol";
60 |
61 | /**
62 | * @title A market for NFTs on Foundation.
63 | * @notice The Foundation marketplace is a contract which allows traders to buy and sell NFTs.
64 | * It supports buying and selling via auctions, private sales, buy price, and offers.
65 | * @dev All sales in the Foundation market will pay the creator 10% royalties on secondary sales. This is not specific
66 | * to NFTs minted on Foundation, it should work for any NFT. If royalty information was not defined when the NFT was
67 | * originally deployed, it may be added using the [Royalty Registry](https://royaltyregistry.xyz/) which will be
68 | * respected by our market contract.
69 | * @author batu-inal & HardlyDifficult
70 | */
71 | contract NFTMarket is
72 | Initializable,
73 | FoundationTreasuryNode,
74 | FETHNode,
75 | MarketSharedCore,
76 | NFTMarketCore,
77 | ReentrancyGuardUpgradeable,
78 | SendValueWithFallbackWithdraw,
79 | MarketFees,
80 | NFTMarketAuction,
81 | NFTMarketReserveAuction,
82 | NFTMarketPrivateSaleGap,
83 | NFTMarketBuyPrice,
84 | NFTMarketOffer
85 | {
86 | /**
87 | * @notice Set immutable variables for the implementation contract.
88 | * @dev Using immutable instead of constants allows us to use different values on testnet.
89 | * @param treasury The Foundation Treasury contract address.
90 | * @param feth The FETH ERC-20 token contract address.
91 | * @param royaltyRegistry The Royalty Registry contract address.
92 | * @param duration The duration of the auction in seconds.
93 | */
94 | constructor(
95 | address payable treasury,
96 | address feth,
97 | address royaltyRegistry,
98 | uint256 duration
99 | )
100 | FoundationTreasuryNode(treasury)
101 | FETHNode(feth)
102 | MarketFees(
103 | /* protocolFeeInBasisPoints: */
104 | 500,
105 | royaltyRegistry,
106 | /* assumePrimarySale: */
107 | false
108 | )
109 | NFTMarketReserveAuction(duration)
110 | initializer // solhint-disable-next-line no-empty-blocks
111 | {}
112 |
113 | /**
114 | * @notice Called once to configure the contract after the initial proxy deployment.
115 | * @dev This farms the initialize call out to inherited contracts as needed to initialize mutable variables.
116 | */
117 | function initialize() external initializer {
118 | ReentrancyGuardUpgradeable.__ReentrancyGuard_init();
119 | NFTMarketAuction._initializeNFTMarketAuction();
120 | }
121 |
122 | /**
123 | * @inheritdoc NFTMarketCore
124 | */
125 | function _beforeAuctionStarted(address nftContract, uint256 tokenId)
126 | internal
127 | override(NFTMarketCore, NFTMarketBuyPrice, NFTMarketOffer)
128 | {
129 | // This is a no-op function required to avoid compile errors.
130 | super._beforeAuctionStarted(nftContract, tokenId);
131 | }
132 |
133 | /**
134 | * @inheritdoc NFTMarketCore
135 | */
136 | function _transferFromEscrow(
137 | address nftContract,
138 | uint256 tokenId,
139 | address recipient,
140 | address authorizeSeller
141 | ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
142 | // This is a no-op function required to avoid compile errors.
143 | super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller);
144 | }
145 |
146 | /**
147 | * @inheritdoc NFTMarketCore
148 | */
149 | function _transferFromEscrowIfAvailable(
150 | address nftContract,
151 | uint256 tokenId,
152 | address recipient
153 | ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) {
154 | // This is a no-op function required to avoid compile errors.
155 | super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient);
156 | }
157 |
158 | /**
159 | * @inheritdoc NFTMarketCore
160 | */
161 | function _transferToEscrow(address nftContract, uint256 tokenId)
162 | internal
163 | override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice)
164 | {
165 | // This is a no-op function required to avoid compile errors.
166 | super._transferToEscrow(nftContract, tokenId);
167 | }
168 |
169 | /**
170 | * @inheritdoc MarketSharedCore
171 | */
172 | function _getSellerOf(address nftContract, uint256 tokenId)
173 | internal
174 | view
175 | override(MarketSharedCore, NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice)
176 | returns (address payable seller)
177 | {
178 | // This is a no-op function required to avoid compile errors.
179 | seller = super._getSellerOf(nftContract, tokenId);
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/contracts/interfaces/dependencies/ens/IENS.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @title Interface for the main ENS contract
7 | */
8 | interface IENS {
9 | function setSubnodeOwner(
10 | bytes32 node,
11 | bytes32 label,
12 | address owner
13 | ) external returns (bytes32);
14 |
15 | function setResolver(bytes32 node, address resolver) external;
16 |
17 | function owner(bytes32 node) external view returns (address);
18 |
19 | function resolver(bytes32 node) external view returns (address);
20 | }
21 |
--------------------------------------------------------------------------------
/contracts/interfaces/dependencies/ens/IFIFSRegistrar.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @title Interface for the main ENS FIFSRegistrar contract.
7 | * @notice Used in testnet only.
8 | */
9 | interface IFIFSRegistrar {
10 | function register(bytes32 label, address owner) external;
11 | }
12 |
--------------------------------------------------------------------------------
/contracts/interfaces/dependencies/ens/IPublicResolver.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @title Interface for the main ENS resolver contract.
7 | */
8 | interface IPublicResolver {
9 | function setAddr(bytes32 node, address a) external;
10 |
11 | function name(bytes32 node) external view returns (string memory);
12 | }
13 |
--------------------------------------------------------------------------------
/contracts/interfaces/dependencies/ens/IReverseRegistrar.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @title Interface for the main ENS reverse registrar contract.
7 | */
8 | interface IReverseRegistrar {
9 | function setName(string memory name) external;
10 |
11 | function node(address addr) external pure returns (bytes32);
12 | }
13 |
--------------------------------------------------------------------------------
/contracts/interfaces/dependencies/tokens/IERC20Approve.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | interface IERC20Approve {
6 | function approve(address spender, uint256 amount) external returns (bool);
7 | }
8 |
--------------------------------------------------------------------------------
/contracts/interfaces/dependencies/tokens/IERC20IncreaseAllowance.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | interface IERC20IncreaseAllowance {
6 | function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
7 | }
8 |
--------------------------------------------------------------------------------
/contracts/interfaces/dependencies/tokens/IWETH9.sol:
--------------------------------------------------------------------------------
1 | /**
2 | * Mainnet: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
3 | * Goerli: 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6
4 | */
5 |
6 | // SPDX-License-Identifier: MIT OR Apache-2.0
7 |
8 | pragma solidity ^0.8.12;
9 |
10 | interface IWETH9 {
11 | function deposit() external payable;
12 |
13 | function transfer(address to, uint256 value) external returns (bool);
14 | }
15 |
--------------------------------------------------------------------------------
/contracts/interfaces/internal/IFethMarket.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @notice Interface for functions the market uses in FETH.
7 | * @author batu-inal & HardlyDifficult
8 | */
9 | interface IFethMarket {
10 | function depositFor(address account) external payable;
11 |
12 | function marketLockupFor(address account, uint256 amount) external payable returns (uint256 expiration);
13 |
14 | function marketWithdrawFrom(address from, uint256 amount) external;
15 |
16 | function marketWithdrawLocked(
17 | address account,
18 | uint256 expiration,
19 | uint256 amount
20 | ) external;
21 |
22 | function marketUnlockFor(
23 | address account,
24 | uint256 expiration,
25 | uint256 amount
26 | ) external;
27 |
28 | function marketChangeLockup(
29 | address unlockFrom,
30 | uint256 unlockExpiration,
31 | uint256 unlockAmount,
32 | address lockupFor,
33 | uint256 lockupAmount
34 | ) external payable returns (uint256 expiration);
35 | }
36 |
--------------------------------------------------------------------------------
/contracts/interfaces/internal/INFTCollectionInitializer.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @author batu-inal & HardlyDifficult
7 | */
8 | interface INFTCollectionInitializer {
9 | function initialize(
10 | address payable _creator,
11 | string memory _name,
12 | string memory _symbol
13 | ) external;
14 | }
15 |
--------------------------------------------------------------------------------
/contracts/interfaces/internal/INFTDropCollectionInitializer.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @author batu-inal & HardlyDifficult
7 | */
8 | interface INFTDropCollectionInitializer {
9 | function initialize(
10 | address payable _creator,
11 | string calldata _name,
12 | string calldata _symbol,
13 | string calldata _baseURI,
14 | bool isRevealed,
15 | uint32 _maxTokenId,
16 | address _approvedMinter,
17 | address payable _paymentAddress
18 | ) external;
19 | }
20 |
--------------------------------------------------------------------------------
/contracts/interfaces/internal/INFTDropCollectionMint.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @notice The required interface for collections to support the NFTDropMarket.
7 | * @dev This interface must be registered as a ERC165 supported interface to support the NFTDropMarket.
8 | * @author batu-inal & HardlyDifficult
9 | */
10 | interface INFTDropCollectionMint {
11 | function mintCountTo(uint16 count, address to) external returns (uint256 firstTokenId);
12 |
13 | function numberOfTokensAvailableToMint() external view returns (uint256 count);
14 | }
15 |
--------------------------------------------------------------------------------
/contracts/interfaces/internal/roles/IAdminRole.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @notice Interface for AdminRole which wraps the default admin role from
7 | * OpenZeppelin's AccessControl for easy integration.
8 | * @author batu-inal & HardlyDifficult
9 | */
10 | interface IAdminRole {
11 | function isAdmin(address account) external view returns (bool);
12 | }
13 |
--------------------------------------------------------------------------------
/contracts/interfaces/internal/roles/IHasRolesContract.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "./IRoles.sol";
6 |
7 | /**
8 | * @author batu-inal & HardlyDifficult
9 | */
10 | interface IHasRolesContract {
11 | function rolesManager() external returns (IRoles);
12 | }
13 |
--------------------------------------------------------------------------------
/contracts/interfaces/internal/roles/IOperatorRole.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @notice Interface for OperatorRole which wraps a role from
7 | * OpenZeppelin's AccessControl for easy integration.
8 | * @author batu-inal & HardlyDifficult
9 | */
10 | interface IOperatorRole {
11 | function isOperator(address account) external view returns (bool);
12 | }
13 |
--------------------------------------------------------------------------------
/contracts/interfaces/internal/roles/IRoles.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @notice Interface for a contract which implements admin roles.
7 | * @author batu-inal & HardlyDifficult
8 | */
9 | interface IRoles {
10 | function isAdmin(address account) external view returns (bool);
11 |
12 | function isOperator(address account) external view returns (bool);
13 | }
14 |
--------------------------------------------------------------------------------
/contracts/interfaces/standards/royalties/IGetFees.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @notice An interface for communicating fees to 3rd party marketplaces.
7 | * @dev Originally implemented in mainnet contract 0x44d6e8933f8271abcf253c72f9ed7e0e4c0323b3
8 | */
9 | interface IGetFees {
10 | /**
11 | * @notice Get the recipient addresses to which creator royalties should be sent.
12 | * @dev The expected royalty amounts are communicated with `getFeeBps`.
13 | * @param tokenId The ID of the NFT to get royalties for.
14 | * @return recipients An array of addresses to which royalties should be sent.
15 | */
16 | function getFeeRecipients(uint256 tokenId) external view returns (address payable[] memory recipients);
17 |
18 | /**
19 | * @notice Get the creator royalty amounts to be sent to each recipient, in basis points.
20 | * @dev The expected recipients are communicated with `getFeeRecipients`.
21 | * @param tokenId The ID of the NFT to get royalties for.
22 | * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
23 | */
24 | function getFeeBps(uint256 tokenId) external view returns (uint256[] memory royaltiesInBasisPoints);
25 | }
26 |
--------------------------------------------------------------------------------
/contracts/interfaces/standards/royalties/IGetRoyalties.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | interface IGetRoyalties {
6 | /**
7 | * @notice Get the creator royalties to be sent.
8 | * @dev The data is the same as when calling `getFeeRecipients` and `getFeeBps` separately.
9 | * @param tokenId The ID of the NFT to get royalties for.
10 | * @return recipients An array of addresses to which royalties should be sent.
11 | * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points.
12 | */
13 | function getRoyalties(uint256 tokenId)
14 | external
15 | view
16 | returns (address payable[] memory recipients, uint256[] memory royaltiesInBasisPoints);
17 | }
18 |
--------------------------------------------------------------------------------
/contracts/interfaces/standards/royalties/IOwnable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | interface IOwnable {
6 | /**
7 | * @dev Returns the address of the current owner.
8 | */
9 | function owner() external view returns (address);
10 | }
11 |
--------------------------------------------------------------------------------
/contracts/interfaces/standards/royalties/IRoyaltyInfo.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @notice Interface for EIP-2981: NFT Royalty Standard.
7 | * For more see: https://eips.ethereum.org/EIPS/eip-2981.
8 | */
9 | interface IRoyaltyInfo {
10 | /**
11 | * @notice Get the creator royalties to be sent.
12 | * @param tokenId The ID of the NFT to get royalties for.
13 | * @param salePrice The total price of the sale.
14 | * @return receiver The address to which royalties should be sent.
15 | * @return royaltyAmount The total amount that should be sent to the `receiver`.
16 | */
17 | function royaltyInfo(uint256 tokenId, uint256 salePrice)
18 | external
19 | view
20 | returns (address receiver, uint256 royaltyAmount);
21 | }
22 |
--------------------------------------------------------------------------------
/contracts/interfaces/standards/royalties/ITokenCreator.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | interface ITokenCreator {
6 | /**
7 | * @notice Returns the creator of this NFT collection.
8 | * @param tokenId The ID of the NFT to get the creator payment address for.
9 | * @return creator The creator of this collection.
10 | */
11 | function tokenCreator(uint256 tokenId) external view returns (address payable creator);
12 | }
13 |
--------------------------------------------------------------------------------
/contracts/interfaces/subgraph/IMarketDeprecatedAPIs.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @dev Historical (currently deprecated) APIs
7 | * that the subgraph depends on for historical transactions.
8 | */
9 | interface IMarketDeprecatedAPIs {
10 | function getIsPrimary(address nftContract, uint256 tokenId) external view returns (bool isPrimary);
11 |
12 | function getFees(
13 | address nftContract,
14 | uint256 tokenId,
15 | uint256 price
16 | )
17 | external
18 | view
19 | returns (
20 | uint256 totalFees,
21 | uint256 creatorRev,
22 | uint256 sellerRev
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/contracts/libraries/AddressLibrary.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
6 |
7 | struct CallWithoutValue {
8 | address target;
9 | bytes callData;
10 | }
11 |
12 | /**
13 | * @title A library for address helpers not already covered by the OZ library.
14 | * @author batu-inal & HardlyDifficult
15 | */
16 | library AddressLibrary {
17 | using AddressUpgradeable for address;
18 | using AddressUpgradeable for address payable;
19 |
20 | /**
21 | * @notice Calls an external contract with arbitrary data and parse the return value into an address.
22 | * @param externalContract The address of the contract to call.
23 | * @param callData The data to send to the contract.
24 | * @return contractAddress The address of the contract returned by the call.
25 | */
26 | function callAndReturnContractAddress(address externalContract, bytes calldata callData)
27 | internal
28 | returns (address payable contractAddress)
29 | {
30 | bytes memory returnData = externalContract.functionCall(callData);
31 | contractAddress = abi.decode(returnData, (address));
32 | require(contractAddress.isContract(), "InternalProxyCall: did not return a contract");
33 | }
34 |
35 | function callAndReturnContractAddress(CallWithoutValue calldata call)
36 | internal
37 | returns (address payable contractAddress)
38 | {
39 | contractAddress = callAndReturnContractAddress(call.target, call.callData);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/contracts/libraries/ArrayLibrary.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @title Helper functions for arrays.
7 | * @author batu-inal & HardlyDifficult
8 | */
9 | library ArrayLibrary {
10 | /**
11 | * @notice Reduces the size of an array if it's greater than the specified max size,
12 | * using the first maxSize elements.
13 | */
14 | function capLength(address payable[] memory data, uint256 maxLength) internal pure {
15 | if (data.length > maxLength) {
16 | assembly {
17 | mstore(data, maxLength)
18 | }
19 | }
20 | }
21 |
22 | /**
23 | * @notice Reduces the size of an array if it's greater than the specified max size,
24 | * using the first maxSize elements.
25 | */
26 | function capLength(uint256[] memory data, uint256 maxLength) internal pure {
27 | if (data.length > maxLength) {
28 | assembly {
29 | mstore(data, maxLength)
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/contracts/libraries/BytesLibrary.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | error BytesLibrary_Expected_Address_Not_Found();
6 | error BytesLibrary_Start_Location_Too_Large();
7 |
8 | /**
9 | * @title A library for manipulation of byte arrays.
10 | * @author batu-inal & HardlyDifficult
11 | */
12 | library BytesLibrary {
13 | /// @notice An address is 20 bytes long
14 | uint256 private constant ADDRESS_BYTES_LENGTH = 20;
15 |
16 | /// @notice A signature is 4 bytes long
17 | uint256 private constant SIGNATURE_BYTES_LENGTH = 4;
18 |
19 | /**
20 | * @dev Replace the address at the given location in a byte array if the contents at that location
21 | * match the expected address.
22 | */
23 | function replaceAtIf(
24 | bytes memory data,
25 | uint256 startLocation,
26 | address expectedAddress,
27 | address newAddress
28 | ) internal pure {
29 | unchecked {
30 | if (startLocation > type(uint256).max - ADDRESS_BYTES_LENGTH) {
31 | revert BytesLibrary_Start_Location_Too_Large();
32 | }
33 | bytes memory expectedData = abi.encodePacked(expectedAddress);
34 | bytes memory newData = abi.encodePacked(newAddress);
35 | uint256 dataLocation;
36 | for (uint256 i = 0; i < ADDRESS_BYTES_LENGTH; ++i) {
37 | dataLocation = startLocation + i;
38 | if (data[dataLocation] != expectedData[i]) {
39 | revert BytesLibrary_Expected_Address_Not_Found();
40 | }
41 | data[dataLocation] = newData[i];
42 | }
43 | }
44 | }
45 |
46 | /**
47 | * @dev Checks if the call data starts with the given function signature.
48 | */
49 | function startsWith(bytes memory callData, bytes4 functionSig) internal pure returns (bool) {
50 | // A signature is 4 bytes long
51 | if (callData.length < SIGNATURE_BYTES_LENGTH) {
52 | return false;
53 | }
54 | return bytes4(callData) == functionSig;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/contracts/libraries/LockedBalance.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @title Library that handles locked balances efficiently using bit packing.
7 | * @author batu-inal & HardlyDifficult
8 | */
9 | library LockedBalance {
10 | /// @dev Tracks an account's total lockup per expiration time.
11 | struct Lockup {
12 | uint32 expiration;
13 | uint96 totalAmount;
14 | }
15 |
16 | struct Lockups {
17 | /// @dev Mapping from key to lockups.
18 | /// i) A key represents 2 lockups. The key for a lockup is `index / 2`.
19 | /// For instance, elements with index 25 and 24 would map to the same key.
20 | /// ii) The `value` for the `key` is split into two 128bits which are used to store the metadata for a lockup.
21 | mapping(uint256 => uint256) lockups;
22 | }
23 |
24 | // Masks used to split a uint256 into two equal pieces which represent two individual Lockups.
25 | uint256 private constant last128BitsMask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
26 | uint256 private constant first128BitsMask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000;
27 |
28 | // Masks used to retrieve or set the totalAmount value of a single Lockup.
29 | uint256 private constant firstAmountBitsMask = 0xFFFFFFFF000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
30 | uint256 private constant secondAmountBitsMask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000;
31 |
32 | /**
33 | * @notice Clears the lockup at the index.
34 | */
35 | function del(Lockups storage lockups, uint256 index) internal {
36 | unchecked {
37 | if (index % 2 == 0) {
38 | index /= 2;
39 | lockups.lockups[index] = (lockups.lockups[index] & last128BitsMask);
40 | } else {
41 | index /= 2;
42 | lockups.lockups[index] = (lockups.lockups[index] & first128BitsMask);
43 | }
44 | }
45 | }
46 |
47 | /**
48 | * @notice Sets the Lockup at the provided index.
49 | */
50 | function set(
51 | Lockups storage lockups,
52 | uint256 index,
53 | uint256 expiration,
54 | uint256 totalAmount
55 | ) internal {
56 | unchecked {
57 | uint256 lockedBalanceBits = totalAmount | (expiration << 96);
58 | if (index % 2 == 0) {
59 | // set first 128 bits.
60 | index /= 2;
61 | lockups.lockups[index] = (lockups.lockups[index] & last128BitsMask) | (lockedBalanceBits << 128);
62 | } else {
63 | // set last 128 bits.
64 | index /= 2;
65 | lockups.lockups[index] = (lockups.lockups[index] & first128BitsMask) | lockedBalanceBits;
66 | }
67 | }
68 | }
69 |
70 | /**
71 | * @notice Sets only the totalAmount for a lockup at the index.
72 | */
73 | function setTotalAmount(
74 | Lockups storage lockups,
75 | uint256 index,
76 | uint256 totalAmount
77 | ) internal {
78 | unchecked {
79 | if (index % 2 == 0) {
80 | index /= 2;
81 | lockups.lockups[index] = (lockups.lockups[index] & firstAmountBitsMask) | (totalAmount << 128);
82 | } else {
83 | index /= 2;
84 | lockups.lockups[index] = (lockups.lockups[index] & secondAmountBitsMask) | totalAmount;
85 | }
86 | }
87 | }
88 |
89 | /**
90 | * @notice Returns the Lockup at the provided index.
91 | * @dev To get the lockup stored in the *first* 128 bits (first slot/lockup):
92 | * - we remove the last 128 bits (done by >> 128)
93 | * To get the lockup stored in the *last* 128 bits (second slot/lockup):
94 | * - we take the last 128 bits (done by % (2**128))
95 | * Once the lockup is obtained:
96 | * - get `expiration` by peaking at the first 32 bits (done by >> 96)
97 | * - get `totalAmount` by peaking at the last 96 bits (done by % (2**96))
98 | */
99 | function get(Lockups storage lockups, uint256 index) internal view returns (Lockup memory balance) {
100 | unchecked {
101 | uint256 lockupMetadata = lockups.lockups[index / 2];
102 | if (lockupMetadata == 0) {
103 | return balance;
104 | }
105 | uint128 lockedBalanceBits;
106 | if (index % 2 == 0) {
107 | // use first 128 bits.
108 | lockedBalanceBits = uint128(lockupMetadata >> 128);
109 | } else {
110 | // use last 128 bits.
111 | lockedBalanceBits = uint128(lockupMetadata % (2**128));
112 | }
113 | // unpack the bits to retrieve the Lockup.
114 | balance.expiration = uint32(lockedBalanceBits >> 96);
115 | balance.totalAmount = uint96(lockedBalanceBits % (2**96));
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/contracts/libraries/OZERC165Checker.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * From https://github.com/OpenZeppelin/openzeppelin-contracts
7 | * Copying the method below which is currently unreleased.
8 | */
9 |
10 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
11 |
12 | /**
13 | * @title Library to query ERC165 support.
14 | * @dev Library used to query support of an interface declared via {IERC165}.
15 | *
16 | * Note that these functions return the actual result of the query: they do not
17 | * `revert` if an interface is not supported. It is up to the caller to decide
18 | * what to do in these cases.
19 | */
20 | library OZERC165Checker {
21 | /**
22 | * @notice Query if a contract implements an interface, does not check ERC165 support
23 | * @param account The address of the contract to query for support of an interface
24 | * @param interfaceId The interface identifier, as specified in ERC-165
25 | * @return true if the contract at account indicates support of the interface with
26 | * identifier interfaceId, false otherwise
27 | * @dev Assumes that account contains a contract that supports ERC165, otherwise
28 | * the behavior of this method is undefined. This precondition can be checked
29 | * with {supportsERC165}.
30 | * Interface identification is specified in ERC-165.
31 | */
32 | function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
33 | bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);
34 | (bool success, bytes memory result) = account.staticcall{ gas: 30_000 }(encodedParams);
35 | if (result.length < 32) return false;
36 | return success && abi.decode(result, (uint256)) > 0;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/contracts/mixins/collections/CollectionRoyalties.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
6 |
7 | import "../../interfaces/standards/royalties/IGetFees.sol";
8 | import "../../interfaces/standards/royalties/IGetRoyalties.sol";
9 | import "../../interfaces/standards/royalties/IRoyaltyInfo.sol";
10 | import "../../interfaces/standards/royalties/ITokenCreator.sol";
11 |
12 | import "../shared/Constants.sol";
13 |
14 | /**
15 | * @title Defines various royalty APIs for broad marketplace support.
16 | * @author batu-inal & HardlyDifficult
17 | */
18 | abstract contract CollectionRoyalties is IGetRoyalties, IGetFees, IRoyaltyInfo, ITokenCreator, ERC165Upgradeable {
19 | /**
20 | * @inheritdoc IGetFees
21 | */
22 | function getFeeRecipients(uint256 tokenId) external view returns (address payable[] memory recipients) {
23 | recipients = new address payable[](1);
24 | recipients[0] = getTokenCreatorPaymentAddress(tokenId);
25 | }
26 |
27 | /**
28 | * @inheritdoc IGetFees
29 | * @dev The tokenId param is ignored since all NFTs return the same value.
30 | */
31 | function getFeeBps(
32 | uint256 /* tokenId */
33 | ) external pure returns (uint256[] memory royaltiesInBasisPoints) {
34 | royaltiesInBasisPoints = new uint256[](1);
35 | royaltiesInBasisPoints[0] = ROYALTY_IN_BASIS_POINTS;
36 | }
37 |
38 | /**
39 | * @inheritdoc IGetRoyalties
40 | */
41 | function getRoyalties(uint256 tokenId)
42 | external
43 | view
44 | returns (address payable[] memory recipients, uint256[] memory royaltiesInBasisPoints)
45 | {
46 | recipients = new address payable[](1);
47 | recipients[0] = getTokenCreatorPaymentAddress(tokenId);
48 | royaltiesInBasisPoints = new uint256[](1);
49 | royaltiesInBasisPoints[0] = ROYALTY_IN_BASIS_POINTS;
50 | }
51 |
52 | /**
53 | * @notice The address to pay the creator proceeds/royalties for the collection.
54 | * @param tokenId The ID of the NFT to get the creator payment address for.
55 | * @return creatorPaymentAddress The address to which royalties should be paid.
56 | */
57 | function getTokenCreatorPaymentAddress(uint256 tokenId)
58 | public
59 | view
60 | virtual
61 | returns (address payable creatorPaymentAddress);
62 |
63 | /**
64 | * @inheritdoc IRoyaltyInfo
65 | */
66 | function royaltyInfo(uint256 tokenId, uint256 salePrice)
67 | external
68 | view
69 | returns (address receiver, uint256 royaltyAmount)
70 | {
71 | receiver = getTokenCreatorPaymentAddress(tokenId);
72 | unchecked {
73 | royaltyAmount = salePrice / ROYALTY_RATIO;
74 | }
75 | }
76 |
77 | /**
78 | * @inheritdoc IERC165Upgradeable
79 | * @dev Checks the supported royalty interfaces.
80 | */
81 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool interfaceSupported) {
82 | interfaceSupported = (interfaceId == type(IRoyaltyInfo).interfaceId ||
83 | interfaceId == type(ITokenCreator).interfaceId ||
84 | interfaceId == type(IGetRoyalties).interfaceId ||
85 | interfaceId == type(IGetFees).interfaceId ||
86 | super.supportsInterface(interfaceId));
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/contracts/mixins/collections/SequentialMintCollection.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
6 | import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
7 |
8 | import "../../interfaces/standards/royalties/ITokenCreator.sol";
9 |
10 | /**
11 | * @title Extends the OZ ERC721 implementation for collections which mint sequential token IDs.
12 | * @author batu-inal & HardlyDifficult
13 | */
14 | abstract contract SequentialMintCollection is ITokenCreator, Initializable, ERC721BurnableUpgradeable {
15 | /****** Slot 0 (after inheritance) ******/
16 | /**
17 | * @notice The creator/owner of this NFT collection.
18 | * @dev This is the default royalty recipient if a different `paymentAddress` was not provided.
19 | * @return The collection's creator/owner address.
20 | */
21 | address payable public owner;
22 |
23 | /**
24 | * @notice The tokenId of the most recently created NFT.
25 | * @dev Minting starts at tokenId 1. Each mint will use this value + 1.
26 | * @return The most recently minted tokenId, or 0 if no NFTs have been minted yet.
27 | */
28 | uint32 public latestTokenId;
29 |
30 | /**
31 | * @notice The max tokenId which can be minted.
32 | * @dev This max may be less than the final `totalSupply` if 1 or more tokens were burned.
33 | * @return The max tokenId which can be minted.
34 | */
35 | uint32 public maxTokenId;
36 |
37 | /**
38 | * @notice Tracks how many tokens have been burned.
39 | * @dev This number is used to calculate the total supply efficiently.
40 | */
41 | uint32 private burnCounter;
42 |
43 | /****** End of storage ******/
44 |
45 | /**
46 | * @notice Emitted when the max tokenId supported by this collection is updated.
47 | * @param maxTokenId The new max tokenId. All NFTs in this collection will have a tokenId less than
48 | * or equal to this value.
49 | */
50 | event MaxTokenIdUpdated(uint256 indexed maxTokenId);
51 |
52 | /**
53 | * @notice Emitted when this collection is self destructed by the creator/owner/admin.
54 | * @param admin The account which requested this contract be self destructed.
55 | */
56 | event SelfDestruct(address indexed admin);
57 |
58 | modifier onlyOwner() {
59 | require(msg.sender == owner, "SequentialMintCollection: Caller is not owner");
60 | _;
61 | }
62 |
63 | function _initializeSequentialMintCollection(address payable _creator, uint32 _maxTokenId) internal onlyInitializing {
64 | owner = _creator;
65 | maxTokenId = _maxTokenId;
66 | }
67 |
68 | /**
69 | * @notice Allows the collection owner to destroy this contract only if
70 | * no NFTs have been minted yet or the minted NFTs have been burned.
71 | */
72 | function _selfDestruct() internal {
73 | require(totalSupply() == 0, "SequentialMintCollection: Any NFTs minted must be burned first");
74 |
75 | emit SelfDestruct(msg.sender);
76 | selfdestruct(payable(msg.sender));
77 | }
78 |
79 | /**
80 | * @notice Allows the owner to set a max tokenID.
81 | * This provides a guarantee to collectors about the limit of this collection contract, if applicable.
82 | * @dev Once this value has been set, it may be decreased but can never be increased.
83 | * @param _maxTokenId The max tokenId to set, all NFTs must have a tokenId less than or equal to this value.
84 | */
85 | function _updateMaxTokenId(uint32 _maxTokenId) internal {
86 | require(_maxTokenId != 0, "SequentialMintCollection: Max token ID may not be cleared");
87 | require(maxTokenId == 0 || _maxTokenId < maxTokenId, "SequentialMintCollection: Max token ID may not increase");
88 | require(latestTokenId <= _maxTokenId, "SequentialMintCollection: Max token ID must be >= last mint");
89 |
90 | maxTokenId = _maxTokenId;
91 | emit MaxTokenIdUpdated(_maxTokenId);
92 | }
93 |
94 | function _burn(uint256 tokenId) internal virtual override {
95 | unchecked {
96 | // Number of burned tokens cannot exceed latestTokenId which is the same size.
97 | ++burnCounter;
98 | }
99 | super._burn(tokenId);
100 | }
101 |
102 | /**
103 | * @inheritdoc ITokenCreator
104 | * @dev The tokenId param is ignored since all NFTs return the same value.
105 | */
106 | function tokenCreator(
107 | uint256 /* tokenId */
108 | ) external view returns (address payable creator) {
109 | creator = owner;
110 | }
111 |
112 | /**
113 | * @notice Returns the total amount of tokens stored by the contract.
114 | * @dev From the ERC-721 enumerable standard.
115 | * @return supply The total number of NFTs tracked by this contract.
116 | */
117 | function totalSupply() public view returns (uint256 supply) {
118 | unchecked {
119 | // Number of tokens minted is always >= burned tokens.
120 | supply = latestTokenId - burnCounter;
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/contracts/mixins/nftDropMarket/NFTDropMarketCore.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @title A place for common modifiers and functions used by various market mixins, if any.
7 | * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree.
8 | * @author batu-inal & HardlyDifficult
9 | */
10 | abstract contract NFTDropMarketCore {
11 | /**
12 | * @notice This empty reserved space is put in place to allow future versions to add new
13 | * variables without shifting down storage in the inheritance chain.
14 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
15 | */
16 | uint256[1_000] private __gap;
17 | }
18 |
--------------------------------------------------------------------------------
/contracts/mixins/nftDropMarket/NFTDropMarketFixedPriceSale.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
6 | import "@openzeppelin/contracts/access/IAccessControl.sol";
7 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
8 | import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
9 |
10 | import "../../interfaces/internal/INFTDropCollectionMint.sol";
11 |
12 | import "../../libraries/OZERC165Checker.sol";
13 | import "../shared/Constants.sol";
14 | import "../shared/MarketFees.sol";
15 |
16 | /// @param limitPerAccount The limit of tokens an account can purchase.
17 | error NFTDropMarketFixedPriceSale_Cannot_Buy_More_Than_Limit(uint256 limitPerAccount);
18 | error NFTDropMarketFixedPriceSale_Limit_Per_Account_Must_Be_Set();
19 | error NFTDropMarketFixedPriceSale_Mint_Permission_Required();
20 | error NFTDropMarketFixedPriceSale_Must_Buy_At_Least_One_Token();
21 | error NFTDropMarketFixedPriceSale_Must_Have_Sale_In_Progress();
22 | error NFTDropMarketFixedPriceSale_Must_Not_Be_Sold_Out();
23 | error NFTDropMarketFixedPriceSale_Must_Not_Have_Pending_Sale();
24 | error NFTDropMarketFixedPriceSale_Must_Support_Collection_Mint_Interface();
25 | error NFTDropMarketFixedPriceSale_Must_Support_ERC721();
26 | error NFTDropMarketFixedPriceSale_Only_Callable_By_Collection_Owner();
27 | /// @param mintCost The total cost for this purchase.
28 | error NFTDropMarketFixedPriceSale_Too_Much_Value_Provided(uint256 mintCost);
29 | error NFTDropMarketFixedPriceSale_Mint_Count_Mismatch(uint256 targetBalance);
30 |
31 | /**
32 | * @title Allows creators to list a drop collection for sale at a fixed price point.
33 | * @dev Listing a collection for sale in this market requires the collection to implement
34 | * the functions in `INFTDropCollectionMint` and to register that interface with ERC165.
35 | * Additionally the collection must implement access control, or more specifically:
36 | * `hasRole(bytes32(0), msg.sender)` must return true when called from the creator or admin's account
37 | * and `hasRole(keccak256("MINTER_ROLE", address(this)))` must return true for this market's address.
38 | * @author batu-inal & HardlyDifficult
39 | */
40 | abstract contract NFTDropMarketFixedPriceSale is MarketFees {
41 | using AddressUpgradeable for address;
42 | using AddressUpgradeable for address payable;
43 | using ERC165Checker for address;
44 | using OZERC165Checker for address;
45 |
46 | /**
47 | * @notice Configuration for the terms of the sale.
48 | * @dev This structure is packed in order to consume just a single slot.
49 | */
50 | struct FixedPriceSaleConfig {
51 | /**
52 | * @notice The seller for the drop.
53 | */
54 | address payable seller;
55 | /**
56 | * @notice The fixed price per NFT in the collection.
57 | * @dev The maximum price that can be set on an NFT is ~1.2M (2^80/10^18) ETH.
58 | */
59 | uint80 price;
60 | /**
61 | * @notice The max number of NFTs an account may have while minting.
62 | */
63 | uint16 limitPerAccount;
64 | }
65 |
66 | /**
67 | * @notice Stores the current sale information for all drop contracts.
68 | */
69 | mapping(address => FixedPriceSaleConfig) private nftContractToFixedPriceSaleConfig;
70 |
71 | /**
72 | * @notice The `role` type used to validate drop collections have granted this market access to mint.
73 | * @return `keccak256("MINTER_ROLE")`
74 | */
75 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
76 |
77 | /**
78 | * @notice Emitted when a collection is listed for sale.
79 | * @param nftContract The address of the NFT drop collection.
80 | * @param seller The address for the seller which listed this for sale.
81 | * @param price The price per NFT minted.
82 | * @param limitPerAccount The max number of NFTs an account may have while minting.
83 | */
84 | event CreateFixedPriceSale(
85 | address indexed nftContract,
86 | address indexed seller,
87 | uint256 price,
88 | uint256 limitPerAccount
89 | );
90 |
91 | /**
92 | * @notice Emitted when NFTs are minted from the drop.
93 | * @dev The total price paid by the buyer is `totalFees + creatorRev`.
94 | * @param nftContract The address of the NFT drop collection.
95 | * @param buyer The address of the buyer.
96 | * @param firstTokenId The tokenId for the first NFT minted.
97 | * The other minted tokens are assigned sequentially, so `firstTokenId` - `firstTokenId + count - 1` were minted.
98 | * @param count The number of NFTs minted.
99 | * @param totalFees The amount of ETH that was sent to Foundation & referrals for this sale.
100 | * @param creatorRev The amount of ETH that was sent to the creator for this sale.
101 | */
102 | event MintFromFixedPriceDrop(
103 | address indexed nftContract,
104 | address indexed buyer,
105 | uint256 indexed firstTokenId,
106 | uint256 count,
107 | uint256 totalFees,
108 | uint256 creatorRev
109 | );
110 |
111 | /**
112 | * @notice Create a fixed price sale drop.
113 | * @param nftContract The address of the NFT drop collection.
114 | * @param price The price per NFT minted.
115 | * Set price to 0 for a first come first serve airdrop-like drop.
116 | * @param limitPerAccount The max number of NFTs an account may have while minting.
117 | * @dev Notes:
118 | * a) The sale is final and can not be updated or canceled.
119 | * b) The sale is immediately kicked off.
120 | * c) Any collection that abides by `INFTDropCollectionMint` and `IAccessControl` is supported.
121 | */
122 | /* solhint-disable-next-line code-complexity */
123 | function createFixedPriceSale(
124 | address nftContract,
125 | uint80 price,
126 | uint16 limitPerAccount
127 | ) external {
128 | // Confirm the drop collection is supported
129 | if (!nftContract.supportsInterface(type(INFTDropCollectionMint).interfaceId)) {
130 | revert NFTDropMarketFixedPriceSale_Must_Support_Collection_Mint_Interface();
131 | }
132 | // The check above already confirmed general 165 support
133 | if (!nftContract.supportsERC165InterfaceUnchecked(type(IERC721).interfaceId)) {
134 | revert NFTDropMarketFixedPriceSale_Must_Support_ERC721();
135 | }
136 | if (INFTDropCollectionMint(nftContract).numberOfTokensAvailableToMint() == 0) {
137 | revert NFTDropMarketFixedPriceSale_Must_Not_Be_Sold_Out();
138 | }
139 |
140 | // Use the AccessControl interface to confirm the msg.sender has permissions to list.
141 | if (!IAccessControl(nftContract).hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
142 | revert NFTDropMarketFixedPriceSale_Only_Callable_By_Collection_Owner();
143 | }
144 | // And that this contract has permission to mint.
145 | if (!IAccessControl(nftContract).hasRole(MINTER_ROLE, address(this))) {
146 | revert NFTDropMarketFixedPriceSale_Mint_Permission_Required();
147 | }
148 |
149 | // Validate input params.
150 | if (limitPerAccount == 0) {
151 | revert NFTDropMarketFixedPriceSale_Limit_Per_Account_Must_Be_Set();
152 | }
153 | // Any price is supported, including 0.
154 |
155 | // Confirm this collection has not already been listed.
156 | FixedPriceSaleConfig storage saleConfig = nftContractToFixedPriceSaleConfig[nftContract];
157 | if (saleConfig.seller != payable(0)) {
158 | revert NFTDropMarketFixedPriceSale_Must_Not_Have_Pending_Sale();
159 | }
160 |
161 | // Save the sale details.
162 | saleConfig.seller = payable(msg.sender);
163 | saleConfig.price = price;
164 | saleConfig.limitPerAccount = limitPerAccount;
165 | emit CreateFixedPriceSale(nftContract, msg.sender, price, limitPerAccount);
166 | }
167 |
168 | /**
169 | * @notice Used to mint `count` number of NFTs from the collection.
170 | * @param nftContract The address of the NFT drop collection.
171 | * @param count The number of NFTs to mint.
172 | * @param buyReferrer The address which referred this purchase, or address(0) if n/a.
173 | * @return firstTokenId The tokenId for the first NFT minted.
174 | * The other minted tokens are assigned sequentially, so `firstTokenId` - `firstTokenId + count - 1` were minted.
175 | * @dev This call may revert if the collection has sold out, has an insufficient number of tokens available,
176 | * or if the market's minter permissions were removed.
177 | * If insufficient msg.value is included, the msg.sender's available FETH token balance will be used.
178 | */
179 | function mintFromFixedPriceSale(
180 | address nftContract,
181 | uint16 count,
182 | address payable buyReferrer
183 | ) external payable returns (uint256 firstTokenId) {
184 | // Validate input params.
185 | if (count == 0) {
186 | revert NFTDropMarketFixedPriceSale_Must_Buy_At_Least_One_Token();
187 | }
188 |
189 | FixedPriceSaleConfig memory saleConfig = nftContractToFixedPriceSaleConfig[nftContract];
190 |
191 | // Confirm that the buyer will not exceed the limit specified after minting.
192 | uint256 targetBalance = IERC721(nftContract).balanceOf(msg.sender) + count;
193 | if (targetBalance > saleConfig.limitPerAccount) {
194 | if (saleConfig.limitPerAccount == 0) {
195 | // Provide a more targeted error if the collection has not been listed.
196 | revert NFTDropMarketFixedPriceSale_Must_Have_Sale_In_Progress();
197 | }
198 | revert NFTDropMarketFixedPriceSale_Cannot_Buy_More_Than_Limit(saleConfig.limitPerAccount);
199 | }
200 |
201 | // Calculate the total cost, considering the `count` requested.
202 | uint256 mintCost;
203 | unchecked {
204 | // Can not overflow as 2^80 * 2^16 == 2^96 max which fits in 256 bits.
205 | mintCost = uint256(saleConfig.price) * count;
206 | }
207 |
208 | // The sale price is immutable so the buyer is aware of how much they will be paying when their tx is broadcasted.
209 | if (msg.value > mintCost) {
210 | // Since price is known ahead of time, if too much ETH is sent then something went wrong.
211 | revert NFTDropMarketFixedPriceSale_Too_Much_Value_Provided(mintCost);
212 | }
213 | // Withdraw from the user's available FETH balance if insufficient msg.value was included.
214 | _tryUseFETHBalance(mintCost, false);
215 |
216 | // Mint the NFTs.
217 | firstTokenId = INFTDropCollectionMint(nftContract).mintCountTo(count, msg.sender);
218 |
219 | if (IERC721(nftContract).balanceOf(msg.sender) != targetBalance) {
220 | revert NFTDropMarketFixedPriceSale_Mint_Count_Mismatch(targetBalance);
221 | }
222 |
223 | // Distribute revenue from this sale.
224 | (uint256 totalFees, uint256 creatorRev, ) = _distributeFunds(
225 | nftContract,
226 | firstTokenId,
227 | saleConfig.seller,
228 | mintCost,
229 | buyReferrer
230 | );
231 |
232 | emit MintFromFixedPriceDrop(nftContract, msg.sender, firstTokenId, count, totalFees, creatorRev);
233 | }
234 |
235 | /**
236 | * @notice Returns the max number of NFTs a given account may mint.
237 | * @param nftContract The address of the NFT drop collection.
238 | * @param user The address of the user which will be minting.
239 | * @return numberThatCanBeMinted How many NFTs the user can mint.
240 | */
241 | function getAvailableCountFromFixedPriceSale(address nftContract, address user)
242 | external
243 | view
244 | returns (uint256 numberThatCanBeMinted)
245 | {
246 | (, , uint256 limitPerAccount, uint256 numberOfTokensAvailableToMint, bool marketCanMint) = getFixedPriceSale(
247 | nftContract
248 | );
249 | if (!marketCanMint) {
250 | // No one can mint in the current state.
251 | return 0;
252 | }
253 | uint256 currentBalance = IERC721(nftContract).balanceOf(user);
254 | if (currentBalance >= limitPerAccount) {
255 | // User has exhausted their limit.
256 | return 0;
257 | }
258 |
259 | unchecked {
260 | numberThatCanBeMinted = limitPerAccount - currentBalance;
261 | }
262 | if (numberThatCanBeMinted > numberOfTokensAvailableToMint) {
263 | // User has more tokens available than the collection has available.
264 | numberThatCanBeMinted = numberOfTokensAvailableToMint;
265 | }
266 | }
267 |
268 | /**
269 | * @notice Returns details for a drop collection's fixed price sale.
270 | * @param nftContract The address of the NFT drop collection.
271 | * @return seller The address of the seller which listed this drop for sale.
272 | * This value will be address(0) if the collection is not listed or has sold out.
273 | * @return price The price per NFT minted.
274 | * @return limitPerAccount The max number of NFTs an account may have while minting.
275 | * @return numberOfTokensAvailableToMint The total number of NFTs that may still be minted.
276 | * @return marketCanMint True if this contract has permissions to mint from the given collection.
277 | */
278 | function getFixedPriceSale(address nftContract)
279 | public
280 | view
281 | returns (
282 | address payable seller,
283 | uint256 price,
284 | uint256 limitPerAccount,
285 | uint256 numberOfTokensAvailableToMint,
286 | bool marketCanMint
287 | )
288 | {
289 | try INFTDropCollectionMint(nftContract).numberOfTokensAvailableToMint() returns (uint256 count) {
290 | if (count != 0) {
291 | try IAccessControl(nftContract).hasRole(MINTER_ROLE, address(this)) returns (bool hasRole) {
292 | marketCanMint = hasRole;
293 | } catch {
294 | // The contract is not supported - return default values.
295 | return (payable(0), 0, 0, 0, false);
296 | }
297 |
298 | FixedPriceSaleConfig memory saleConfig = nftContractToFixedPriceSaleConfig[nftContract];
299 | seller = saleConfig.seller;
300 | price = saleConfig.price;
301 | limitPerAccount = saleConfig.limitPerAccount;
302 | numberOfTokensAvailableToMint = count;
303 | }
304 | // Else minted completed -- return default values.
305 | } catch // solhint-disable-next-line no-empty-blocks
306 | {
307 | // Contract not supported or self destructed - return default values
308 | }
309 | }
310 |
311 | /**
312 | * @inheritdoc MarketSharedCore
313 | * @dev Returns the seller for a collection if listed and not already sold out.
314 | */
315 | function _getSellerOf(
316 | address nftContract,
317 | uint256 /* tokenId */
318 | ) internal view virtual override returns (address payable seller) {
319 | (seller, , , , ) = getFixedPriceSale(nftContract);
320 | }
321 |
322 | /**
323 | * @notice This empty reserved space is put in place to allow future versions to add new
324 | * variables without shifting down storage in the inheritance chain.
325 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
326 | */
327 | uint256[1_000] private __gap;
328 | }
329 |
--------------------------------------------------------------------------------
/contracts/mixins/nftMarket/NFTMarketAuction.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
6 |
7 | /**
8 | * @title An abstraction layer for auctions.
9 | * @dev This contract can be expanded with reusable calls and data as more auction types are added.
10 | * @author batu-inal & HardlyDifficult
11 | */
12 | abstract contract NFTMarketAuction is Initializable {
13 | /**
14 | * @notice A global id for auctions of any type.
15 | */
16 | uint256 private nextAuctionId;
17 |
18 | /**
19 | * @notice Called once to configure the contract after the initial proxy deployment.
20 | * @dev This sets the initial auction id to 1, making the first auction cheaper
21 | * and id 0 represents no auction found.
22 | */
23 | function _initializeNFTMarketAuction() internal onlyInitializing {
24 | nextAuctionId = 1;
25 | }
26 |
27 | /**
28 | * @notice Returns id to assign to the next auction.
29 | */
30 | function _getNextAndIncrementAuctionId() internal returns (uint256) {
31 | // AuctionId cannot overflow 256 bits.
32 | unchecked {
33 | return nextAuctionId++;
34 | }
35 | }
36 |
37 | /**
38 | * @notice This empty reserved space is put in place to allow future versions to add new
39 | * variables without shifting down storage in the inheritance chain.
40 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
41 | */
42 | uint256[1_000] private __gap;
43 | }
44 |
--------------------------------------------------------------------------------
/contracts/mixins/nftMarket/NFTMarketCore.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
6 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
7 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
8 |
9 | import "../../interfaces/internal/IFethMarket.sol";
10 |
11 | import "../shared/Constants.sol";
12 | import "../shared/MarketSharedCore.sol";
13 |
14 | error NFTMarketCore_Seller_Not_Found();
15 |
16 | /**
17 | * @title A place for common modifiers and functions used by various NFTMarket mixins, if any.
18 | * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree.
19 | * @author batu-inal & HardlyDifficult
20 | */
21 | abstract contract NFTMarketCore is Initializable, MarketSharedCore {
22 | using AddressUpgradeable for address;
23 | using AddressUpgradeable for address payable;
24 |
25 | /**
26 | * @notice If there is a buy price at this amount or lower, accept that and return true.
27 | */
28 | function _autoAcceptBuyPrice(
29 | address nftContract,
30 | uint256 tokenId,
31 | uint256 amount
32 | ) internal virtual returns (bool);
33 |
34 | /**
35 | * @notice If there is a valid offer at the given price or higher, accept that and return true.
36 | */
37 | function _autoAcceptOffer(
38 | address nftContract,
39 | uint256 tokenId,
40 | uint256 minAmount
41 | ) internal virtual returns (bool);
42 |
43 | /**
44 | * @notice Notify implementors when an auction has received its first bid.
45 | * Once a bid is received the sale is guaranteed to the auction winner
46 | * and other sale mechanisms become unavailable.
47 | * @dev Implementors of this interface should update internal state to reflect an auction has been kicked off.
48 | */
49 | function _beforeAuctionStarted(
50 | address, /*nftContract*/
51 | uint256 /*tokenId*/ // solhint-disable-next-line no-empty-blocks
52 | ) internal virtual {
53 | // No-op
54 | }
55 |
56 | /**
57 | * @notice Cancel the `msg.sender`'s offer if there is one, freeing up their FETH balance.
58 | * @dev This should be used when it does not make sense to keep the original offer around,
59 | * e.g. if a collector accepts a Buy Price then keeping the offer around is not necessary.
60 | */
61 | function _cancelSendersOffer(address nftContract, uint256 tokenId) internal virtual;
62 |
63 | /**
64 | * @notice Transfers the NFT from escrow and clears any state tracking this escrowed NFT.
65 | * @param authorizeSeller The address of the seller pending authorization.
66 | * Once it's been authorized by one of the escrow managers, it should be set to address(0)
67 | * indicated that it's no longer pending authorization.
68 | */
69 | function _transferFromEscrow(
70 | address nftContract,
71 | uint256 tokenId,
72 | address recipient,
73 | address authorizeSeller
74 | ) internal virtual {
75 | if (authorizeSeller != address(0)) {
76 | revert NFTMarketCore_Seller_Not_Found();
77 | }
78 | IERC721(nftContract).transferFrom(address(this), recipient, tokenId);
79 | }
80 |
81 | /**
82 | * @notice Transfers the NFT from escrow unless there is another reason for it to remain in escrow.
83 | */
84 | function _transferFromEscrowIfAvailable(
85 | address nftContract,
86 | uint256 tokenId,
87 | address recipient
88 | ) internal virtual {
89 | _transferFromEscrow(nftContract, tokenId, recipient, address(0));
90 | }
91 |
92 | /**
93 | * @notice Transfers an NFT into escrow,
94 | * if already there this requires the msg.sender is authorized to manage the sale of this NFT.
95 | */
96 | function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual {
97 | IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
98 | }
99 |
100 | /**
101 | * @dev Determines the minimum amount when increasing an existing offer or bid.
102 | */
103 | function _getMinIncrement(uint256 currentAmount) internal pure returns (uint256) {
104 | uint256 minIncrement = currentAmount;
105 | unchecked {
106 | minIncrement /= MIN_PERCENT_INCREMENT_DENOMINATOR;
107 | }
108 | if (minIncrement == 0) {
109 | // Since minIncrement reduces from the currentAmount, this cannot overflow.
110 | // The next amount must be at least 1 wei greater than the current.
111 | return currentAmount + 1;
112 | }
113 |
114 | return minIncrement + currentAmount;
115 | }
116 |
117 | /**
118 | * @inheritdoc MarketSharedCore
119 | */
120 | function _getSellerOf(address nftContract, uint256 tokenId)
121 | internal
122 | view
123 | virtual
124 | override
125 | returns (address payable seller)
126 | // solhint-disable-next-line no-empty-blocks
127 | {
128 | // No-op by default
129 | }
130 |
131 | /**
132 | * @inheritdoc MarketSharedCore
133 | */
134 | function _getSellerOrOwnerOf(address nftContract, uint256 tokenId)
135 | internal
136 | view
137 | override
138 | returns (address payable sellerOrOwner)
139 | {
140 | sellerOrOwner = _getSellerOf(nftContract, tokenId);
141 | if (sellerOrOwner == address(0)) {
142 | sellerOrOwner = payable(IERC721(nftContract).ownerOf(tokenId));
143 | }
144 | }
145 |
146 | /**
147 | * @notice Checks if an escrowed NFT is currently in active auction.
148 | * @return Returns false if the auction has ended, even if it has not yet been settled.
149 | */
150 | function _isInActiveAuction(address nftContract, uint256 tokenId) internal view virtual returns (bool);
151 |
152 | /**
153 | * @notice This empty reserved space is put in place to allow future versions to add new
154 | * variables without shifting down storage in the inheritance chain.
155 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
156 | * @dev 50 slots were consumed by adding `ReentrancyGuard`.
157 | */
158 | uint256[450] private __gap;
159 | }
160 |
--------------------------------------------------------------------------------
/contracts/mixins/nftMarket/NFTMarketPrivateSaleGap.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @title Reserves space previously occupied by private sales.
7 | * @author batu-inal & HardlyDifficult
8 | */
9 | abstract contract NFTMarketPrivateSaleGap {
10 | // Original data:
11 | // bytes32 private __gap_was_DOMAIN_SEPARATOR;
12 | // mapping(address => mapping(uint256 => mapping(address => mapping(address => mapping(uint256 =>
13 | // mapping(uint256 => bool)))))) private privateSaleInvalidated;
14 | // uint256[999] private __gap;
15 |
16 | /**
17 | * @notice This empty reserved space is put in place to allow future versions to add new
18 | * variables without shifting down storage in the inheritance chain.
19 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
20 | * @dev 1 slot was consumed by privateSaleInvalidated.
21 | */
22 | uint256[1001] private __gap;
23 | }
24 |
--------------------------------------------------------------------------------
/contracts/mixins/roles/AdminRole.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
6 | import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
7 |
8 | /**
9 | * @title Defines a role for admin accounts.
10 | * @dev Wraps the default admin role from OpenZeppelin's AccessControl for easy integration.
11 | * @author batu-inal & HardlyDifficult
12 | */
13 | abstract contract AdminRole is Initializable, AccessControlUpgradeable {
14 | modifier onlyAdmin() {
15 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "AdminRole: caller does not have the Admin role");
16 | _;
17 | }
18 |
19 | function _initializeAdminRole(address admin) internal onlyInitializing {
20 | // Grant the role to a specified account
21 | _grantRole(DEFAULT_ADMIN_ROLE, admin);
22 | }
23 |
24 | /**
25 | * @notice Adds an account as an approved admin.
26 | * @dev Only callable by existing admins, as enforced by `grantRole`.
27 | * @param account The address to be approved.
28 | */
29 | function grantAdmin(address account) external {
30 | grantRole(DEFAULT_ADMIN_ROLE, account);
31 | }
32 |
33 | /**
34 | * @notice Removes an account from the set of approved admins.
35 | * @dev Only callable by existing admins, as enforced by `revokeRole`.
36 | * @param account The address to be removed.
37 | */
38 | function revokeAdmin(address account) external {
39 | revokeRole(DEFAULT_ADMIN_ROLE, account);
40 | }
41 |
42 | /**
43 | * @notice Checks if the account provided is an admin.
44 | * @param account The address to check.
45 | * @return approved True if the account is an admin.
46 | * @dev This call is used by the royalty registry contract.
47 | */
48 | function isAdmin(address account) public view returns (bool approved) {
49 | approved = hasRole(DEFAULT_ADMIN_ROLE, account);
50 | }
51 |
52 | /**
53 | * @notice This empty reserved space is put in place to allow future versions to add new
54 | * variables without shifting down storage in the inheritance chain.
55 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
56 | */
57 | uint256[1_000] private __gap;
58 | }
59 |
--------------------------------------------------------------------------------
/contracts/mixins/roles/MinterRole.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
6 | import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
7 |
8 | import "./AdminRole.sol";
9 |
10 | /**
11 | * @title Defines a role for minter accounts.
12 | * @dev Wraps a role from OpenZeppelin's AccessControl for easy integration.
13 | * @author batu-inal & HardlyDifficult
14 | */
15 | abstract contract MinterRole is Initializable, AccessControlUpgradeable, AdminRole {
16 | /**
17 | * @notice The `role` type used for approve minters.
18 | * @return `keccak256("MINTER_ROLE")`
19 | */
20 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
21 |
22 | modifier onlyMinterOrAdmin() {
23 | require(isMinter(msg.sender) || isAdmin(msg.sender), "MinterRole: Must have the minter or admin role");
24 | _;
25 | }
26 |
27 | function _initializeMinterRole(address minter) internal onlyInitializing {
28 | // Grant the role to a specified account
29 | _grantRole(MINTER_ROLE, minter);
30 | }
31 |
32 | /**
33 | * @notice Adds an account as an approved minter.
34 | * @dev Only callable by admins, as enforced by `grantRole`.
35 | * @param account The address to be approved.
36 | */
37 | function grantMinter(address account) external {
38 | grantRole(MINTER_ROLE, account);
39 | }
40 |
41 | /**
42 | * @notice Removes an account from the set of approved minters.
43 | * @dev Only callable by admins, as enforced by `revokeRole`.
44 | * @param account The address to be removed.
45 | */
46 | function revokeMinter(address account) external {
47 | revokeRole(MINTER_ROLE, account);
48 | }
49 |
50 | /**
51 | * @notice Checks if the account provided is an minter.
52 | * @param account The address to check.
53 | * @return approved True if the account is an minter.
54 | */
55 | function isMinter(address account) public view returns (bool approved) {
56 | approved = hasRole(MINTER_ROLE, account);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/contracts/mixins/shared/Constants.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /// Constant values shared across mixins.
6 |
7 | /**
8 | * @dev 100% in basis points.
9 | */
10 | uint256 constant BASIS_POINTS = 10_000;
11 |
12 | /**
13 | * @dev The default admin role defined by OZ ACL modules.
14 | */
15 | bytes32 constant DEFAULT_ADMIN_ROLE = 0x00;
16 |
17 | /**
18 | * @dev Cap the number of royalty recipients.
19 | * A cap is required to ensure gas costs are not too high when a sale is settled.
20 | */
21 | uint256 constant MAX_ROYALTY_RECIPIENTS = 5;
22 |
23 | /**
24 | * @dev The minimum increase of 10% required when making an offer or placing a bid.
25 | */
26 | uint256 constant MIN_PERCENT_INCREMENT_DENOMINATOR = BASIS_POINTS / 1_000;
27 |
28 | /**
29 | * @dev The gas limit used when making external read-only calls.
30 | * This helps to ensure that external calls does not prevent the market from executing.
31 | */
32 | uint256 constant READ_ONLY_GAS_LIMIT = 40_000;
33 |
34 | /**
35 | * @dev Default royalty cut paid out on secondary sales.
36 | * Set to 10% of the secondary sale.
37 | */
38 | uint96 constant ROYALTY_IN_BASIS_POINTS = 1_000;
39 |
40 | /**
41 | * @dev 10%, expressed as a denominator for more efficient calculations.
42 | */
43 | uint256 constant ROYALTY_RATIO = BASIS_POINTS / ROYALTY_IN_BASIS_POINTS;
44 |
45 | /**
46 | * @dev The gas limit to send ETH to multiple recipients, enough for a 5-way split.
47 | */
48 | uint256 constant SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS = 210_000;
49 |
50 | /**
51 | * @dev The gas limit to send ETH to a single recipient, enough for a contract with a simple receiver.
52 | */
53 | uint256 constant SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT = 20_000;
54 |
--------------------------------------------------------------------------------
/contracts/mixins/shared/ContractFactory.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
6 |
7 | /**
8 | * @title Stores a reference to the factory which is used to create contract proxies.
9 | * @author batu-inal & HardlyDifficult
10 | */
11 | abstract contract ContractFactory {
12 | using AddressUpgradeable for address;
13 |
14 | /**
15 | * @notice The address of the factory which was used to create this contract.
16 | * @return The factory contract address.
17 | */
18 | address public immutable contractFactory;
19 |
20 | modifier onlyContractFactory() {
21 | require(msg.sender == contractFactory, "ContractFactory: Caller is not the factory");
22 | _;
23 | }
24 |
25 | /**
26 | * @notice Initialize the template's immutable variables.
27 | * @param _contractFactory The factory which will be used to create these contracts.
28 | */
29 | constructor(address _contractFactory) {
30 | require(_contractFactory.isContract(), "ContractFactory: Factory is not a contract");
31 | contractFactory = _contractFactory;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/contracts/mixins/shared/FETHNode.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
6 |
7 | import "../../interfaces/internal/IFethMarket.sol";
8 |
9 | error FETHNode_FETH_Address_Is_Not_A_Contract();
10 | error FETHNode_Only_FETH_Can_Transfer_ETH();
11 |
12 | /**
13 | * @title A mixin for interacting with the FETH contract.
14 | * @author batu-inal & HardlyDifficult
15 | */
16 | abstract contract FETHNode {
17 | using AddressUpgradeable for address;
18 | using AddressUpgradeable for address payable;
19 |
20 | /// @notice The FETH ERC-20 token for managing escrow and lockup.
21 | IFethMarket internal immutable feth;
22 |
23 | constructor(address _feth) {
24 | if (!_feth.isContract()) {
25 | revert FETHNode_FETH_Address_Is_Not_A_Contract();
26 | }
27 |
28 | feth = IFethMarket(_feth);
29 | }
30 |
31 | /**
32 | * @notice Only used by FETH. Any direct transfer from users will revert.
33 | */
34 | receive() external payable {
35 | if (msg.sender != address(feth)) {
36 | revert FETHNode_Only_FETH_Can_Transfer_ETH();
37 | }
38 | }
39 |
40 | /**
41 | * @notice Withdraw the msg.sender's available FETH balance if they requested more than the msg.value provided.
42 | * @dev This may revert if the msg.sender is non-receivable.
43 | * This helper should not be used anywhere that may lead to locked assets.
44 | * @param totalAmount The total amount of ETH required (including the msg.value).
45 | * @param shouldRefundSurplus If true, refund msg.value - totalAmount to the msg.sender.
46 | */
47 | function _tryUseFETHBalance(uint256 totalAmount, bool shouldRefundSurplus) internal {
48 | if (totalAmount > msg.value) {
49 | // Withdraw additional ETH required from the user's available FETH balance.
50 | unchecked {
51 | // The if above ensures delta will not underflow.
52 | // Withdraw ETH from the user's account in the FETH token contract,
53 | // making the funds available in this contract as ETH.
54 | feth.marketWithdrawFrom(msg.sender, totalAmount - msg.value);
55 | }
56 | } else if (shouldRefundSurplus && totalAmount < msg.value) {
57 | // Return any surplus ETH to the user.
58 | unchecked {
59 | // The if above ensures this will not underflow
60 | payable(msg.sender).sendValue(msg.value - totalAmount);
61 | }
62 | }
63 | }
64 |
65 | /**
66 | * @notice Gets the FETH contract used to escrow offer funds.
67 | * @return fethAddress The FETH contract address.
68 | */
69 | function getFethAddress() external view returns (address fethAddress) {
70 | fethAddress = address(feth);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/contracts/mixins/shared/FoundationTreasuryNode.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
6 |
7 | import "../../interfaces/internal/roles/IAdminRole.sol";
8 | import "../../interfaces/internal/roles/IOperatorRole.sol";
9 |
10 | error FoundationTreasuryNode_Address_Is_Not_A_Contract();
11 | error FoundationTreasuryNode_Caller_Not_Admin();
12 | error FoundationTreasuryNode_Caller_Not_Operator();
13 |
14 | /**
15 | * @title A mixin that stores a reference to the Foundation treasury contract.
16 | * @notice The treasury collects fees and defines admin/operator roles.
17 | * @author batu-inal & HardlyDifficult
18 | */
19 | abstract contract FoundationTreasuryNode {
20 | using AddressUpgradeable for address payable;
21 |
22 | /// @dev This value was replaced with an immutable version.
23 | address payable private __gap_was_treasury;
24 |
25 | /// @notice The address of the treasury contract.
26 | address payable private immutable treasury;
27 |
28 | /// @notice Requires the caller is a Foundation admin.
29 | modifier onlyFoundationAdmin() {
30 | if (!IAdminRole(treasury).isAdmin(msg.sender)) {
31 | revert FoundationTreasuryNode_Caller_Not_Admin();
32 | }
33 | _;
34 | }
35 |
36 | /// @notice Requires the caller is a Foundation operator.
37 | modifier onlyFoundationOperator() {
38 | if (!IOperatorRole(treasury).isOperator(msg.sender)) {
39 | revert FoundationTreasuryNode_Caller_Not_Operator();
40 | }
41 | _;
42 | }
43 |
44 | /**
45 | * @notice Set immutable variables for the implementation contract.
46 | * @dev Assigns the treasury contract address.
47 | */
48 | constructor(address payable _treasury) {
49 | if (!_treasury.isContract()) {
50 | revert FoundationTreasuryNode_Address_Is_Not_A_Contract();
51 | }
52 | treasury = _treasury;
53 | }
54 |
55 | /**
56 | * @notice Gets the Foundation treasury contract.
57 | * @dev This call is used in the royalty registry contract.
58 | * @return treasuryAddress The address of the Foundation treasury contract.
59 | */
60 | function getFoundationTreasury() public view returns (address payable treasuryAddress) {
61 | treasuryAddress = treasury;
62 | }
63 |
64 | /**
65 | * @notice This empty reserved space is put in place to allow future versions to add new
66 | * variables without shifting down storage in the inheritance chain.
67 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
68 | */
69 | uint256[2_000] private __gap;
70 | }
71 |
--------------------------------------------------------------------------------
/contracts/mixins/shared/Gap10000.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | /**
6 | * @title A placeholder contract leaving room for new mixins to be added to the future.
7 | * @author batu-inal & HardlyDifficult
8 | */
9 | abstract contract Gap10000 {
10 | /**
11 | * @notice This empty reserved space is put in place to allow future versions to add new
12 | * variables without shifting down storage in the inheritance chain.
13 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
14 | */
15 | uint256[10_000] private __gap;
16 | }
17 |
--------------------------------------------------------------------------------
/contracts/mixins/shared/MarketSharedCore.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "./FETHNode.sol";
6 |
7 | /**
8 | * @title A place for common modifiers and functions used by various market mixins, if any.
9 | * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree.
10 | * @author batu-inal & HardlyDifficult
11 | */
12 | abstract contract MarketSharedCore is FETHNode {
13 | /**
14 | * @notice Checks who the seller for an NFT is if listed in this market.
15 | * @param nftContract The address of the NFT contract.
16 | * @param tokenId The id of the NFT.
17 | * @return seller The seller which listed this NFT for sale, or address(0) if not listed.
18 | */
19 | function getSellerOf(address nftContract, uint256 tokenId) external view returns (address payable seller) {
20 | seller = _getSellerOf(nftContract, tokenId);
21 | }
22 |
23 | /**
24 | * @notice Checks who the seller for an NFT is if listed in this market.
25 | */
26 | function _getSellerOf(address nftContract, uint256 tokenId) internal view virtual returns (address payable seller);
27 |
28 | /**
29 | * @notice Checks who the seller for an NFT is if listed in this market or returns the current owner.
30 | */
31 | function _getSellerOrOwnerOf(address nftContract, uint256 tokenId)
32 | internal
33 | view
34 | virtual
35 | returns (address payable sellerOrOwner);
36 |
37 | /**
38 | * @notice This empty reserved space is put in place to allow future versions to add new
39 | * variables without shifting down storage in the inheritance chain.
40 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
41 | */
42 | uint256[500] private __gap;
43 | }
44 |
--------------------------------------------------------------------------------
/contracts/mixins/shared/SendValueWithFallbackWithdraw.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
6 |
7 | import "./FETHNode.sol";
8 |
9 | /**
10 | * @title A mixin for sending ETH with a fallback withdraw mechanism.
11 | * @notice Attempt to send ETH and if the transfer fails or runs out of gas, store the balance
12 | * in the FETH token contract for future withdrawal instead.
13 | * @dev This mixin was recently switched to escrow funds in FETH.
14 | * Once we have confirmed all pending balances have been withdrawn, we can remove the escrow tracking here.
15 | * @author batu-inal & HardlyDifficult
16 | */
17 | abstract contract SendValueWithFallbackWithdraw is FETHNode {
18 | using AddressUpgradeable for address payable;
19 |
20 | /// @dev Removing old unused variables in an upgrade safe way.
21 | uint256 private __gap_was_pendingWithdrawals;
22 |
23 | /**
24 | * @notice Emitted when escrowed funds are withdrawn to FETH.
25 | * @param user The account which has withdrawn ETH.
26 | * @param amount The amount of ETH which has been withdrawn.
27 | */
28 | event WithdrawalToFETH(address indexed user, uint256 amount);
29 |
30 | /**
31 | * @notice Attempt to send a user or contract ETH.
32 | * If it fails store the amount owned for later withdrawal in FETH.
33 | * @dev This may fail when sending ETH to a contract that is non-receivable or exceeds the gas limit specified.
34 | */
35 | function _sendValueWithFallbackWithdraw(
36 | address payable user,
37 | uint256 amount,
38 | uint256 gasLimit
39 | ) internal {
40 | if (amount == 0) {
41 | return;
42 | }
43 | // Cap the gas to prevent consuming all available gas to block a tx from completing successfully
44 | // solhint-disable-next-line avoid-low-level-calls
45 | (bool success, ) = user.call{ value: amount, gas: gasLimit }("");
46 | if (!success) {
47 | // Store the funds that failed to send for the user in the FETH token
48 | feth.depositFor{ value: amount }(user);
49 | emit WithdrawalToFETH(user, amount);
50 | }
51 | }
52 |
53 | /**
54 | * @notice This empty reserved space is put in place to allow future versions to add new
55 | * variables without shifting down storage in the inheritance chain.
56 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
57 | */
58 | uint256[999] private __gap;
59 | }
60 |
--------------------------------------------------------------------------------
/contracts/mixins/treasury/AdminRoleEnumerable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
6 |
7 | import "./OZAccessControlUpgradeable.sol";
8 |
9 | /**
10 | * @title Defines a role for Foundation admin accounts.
11 | * @dev Wraps the default admin role from OpenZeppelin's AccessControl for easy integration.
12 | * @author batu-inal & HardlyDifficult
13 | */
14 | abstract contract AdminRoleEnumerable is Initializable, OZAccessControlUpgradeable {
15 | modifier onlyAdmin() {
16 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "AdminRole: caller does not have the Admin role");
17 | _;
18 | }
19 |
20 | function _initializeAdminRole(address admin) internal onlyInitializing {
21 | // Grant the role to a specified account
22 | _setupRole(DEFAULT_ADMIN_ROLE, admin);
23 | }
24 |
25 | /**
26 | * @notice Adds the account to the list of approved admins.
27 | * @dev Only callable by admins as enforced by `grantRole`.
28 | * @param account The address to be approved.
29 | */
30 | function grantAdmin(address account) external {
31 | grantRole(DEFAULT_ADMIN_ROLE, account);
32 | }
33 |
34 | /**
35 | * @notice Removes the account from the list of approved admins.
36 | * @dev Only callable by admins as enforced by `revokeRole`.
37 | * @param account The address to be removed from the approved list.
38 | */
39 | function revokeAdmin(address account) external {
40 | revokeRole(DEFAULT_ADMIN_ROLE, account);
41 | }
42 |
43 | /**
44 | * @notice Returns one of the admins by index.
45 | * @param index The index of the admin to return from 0 to getAdminMemberCount() - 1.
46 | * @return account The address of the admin.
47 | */
48 | function getAdminMember(uint256 index) external view returns (address account) {
49 | account = getRoleMember(DEFAULT_ADMIN_ROLE, index);
50 | }
51 |
52 | /**
53 | * @notice Checks how many accounts have been granted admin access.
54 | * @return count The number of accounts with admin access.
55 | */
56 | function getAdminMemberCount() external view returns (uint256 count) {
57 | count = getRoleMemberCount(DEFAULT_ADMIN_ROLE);
58 | }
59 |
60 | /**
61 | * @notice Checks if the account provided is an admin.
62 | * @param account The address to check.
63 | * @return approved True if the account is an admin.
64 | * @dev This call is used by the royalty registry contract.
65 | */
66 | function isAdmin(address account) external view returns (bool approved) {
67 | approved = hasRole(DEFAULT_ADMIN_ROLE, account);
68 | }
69 |
70 | /**
71 | * @notice This empty reserved space is put in place to allow future versions to add new
72 | * variables without shifting down storage in the inheritance chain.
73 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
74 | */
75 | uint256[1_000] private __gap;
76 | }
77 |
--------------------------------------------------------------------------------
/contracts/mixins/treasury/CollateralManagement.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
6 |
7 | import "./AdminRoleEnumerable.sol";
8 |
9 | error CollateralManagement_Cannot_Withdraw_To_Address_Zero();
10 | error CollateralManagement_Cannot_Withdraw_To_Self();
11 |
12 | /**
13 | * @title Enables deposits and withdrawals.
14 | * @author batu-inal & HardlyDifficult
15 | */
16 | abstract contract CollateralManagement is AdminRoleEnumerable {
17 | using AddressUpgradeable for address payable;
18 |
19 | /**
20 | * @notice Emitted when funds are withdrawn from this contract.
21 | * @param to The address which received the ETH withdrawn.
22 | * @param amount The amount of ETH which was withdrawn.
23 | */
24 | event FundsWithdrawn(address indexed to, uint256 amount);
25 |
26 | /**
27 | * @notice Accept native currency payments (i.e. fees)
28 | */
29 | // solhint-disable-next-line no-empty-blocks
30 | receive() external payable {}
31 |
32 | /**
33 | * @notice Allows an admin to withdraw funds.
34 | * @param to Address to receive the withdrawn funds
35 | * @param amount Amount to withdrawal or 0 to withdraw all available funds
36 | */
37 | function withdrawFunds(address payable to, uint256 amount) external onlyAdmin {
38 | if (amount == 0) {
39 | amount = address(this).balance;
40 | }
41 | if (to == address(0)) {
42 | revert CollateralManagement_Cannot_Withdraw_To_Address_Zero();
43 | } else if (to == address(this)) {
44 | revert CollateralManagement_Cannot_Withdraw_To_Self();
45 | }
46 | to.sendValue(amount);
47 |
48 | emit FundsWithdrawn(to, amount);
49 | }
50 |
51 | /**
52 | * @notice This empty reserved space is put in place to allow future versions to add new
53 | * variables without shifting down storage in the inheritance chain.
54 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
55 | */
56 | uint256[1_000] private __gap;
57 | }
58 |
--------------------------------------------------------------------------------
/contracts/mixins/treasury/OZAccessControlUpgradeable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | /**
4 | * Copied from https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts-upgradeable/v3.4.2-solc-0.7
5 | * Modified to support solc-8.
6 | * Using this instead of the new OZ implementation due to a change in storage slots used.
7 | * Also limited access of several functions as we will be using convenience wrappers.
8 | */
9 |
10 | pragma solidity ^0.8.12;
11 |
12 | import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
13 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
14 | import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
15 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
16 |
17 | // solhint-disable
18 |
19 | /**
20 | * @title Implements role-based access control mechanisms.
21 | * @dev Contract module that allows children to implement role-based access
22 | * control mechanisms.
23 | *
24 | * Roles are referred to by their `bytes32` identifier. These should be exposed
25 | * in the external API and be unique. The best way to achieve this is by
26 | * using `public constant` hash digests:
27 | *
28 | * ```
29 | * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
30 | * ```
31 | *
32 | * Roles can be used to represent a set of permissions. To restrict access to a
33 | * function call, use {hasRole}:
34 | *
35 | * ```
36 | * function foo() public {
37 | * require(hasRole(MY_ROLE, msg.sender));
38 | * ...
39 | * }
40 | * ```
41 | *
42 | * Roles can be granted and revoked dynamically via the {grantRole} and
43 | * {revokeRole} functions. Each role has an associated admin role, and only
44 | * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
45 | *
46 | * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
47 | * that only accounts with this role will be able to grant or revoke other
48 | * roles. More complex role relationships can be created by using
49 | * {_setRoleAdmin}.
50 | *
51 | * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
52 | * grant and revoke this role. Extra precautions should be taken to secure
53 | * accounts that have been granted it.
54 | */
55 | abstract contract OZAccessControlUpgradeable is Initializable, ContextUpgradeable {
56 | using EnumerableSet for EnumerableSet.AddressSet;
57 | using AddressUpgradeable for address;
58 |
59 | struct RoleData {
60 | EnumerableSet.AddressSet members;
61 | bytes32 adminRole;
62 | }
63 |
64 | mapping(bytes32 => RoleData) private _roles;
65 |
66 | bytes32 internal constant DEFAULT_ADMIN_ROLE = 0x00;
67 |
68 | /**
69 | * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
70 | *
71 | * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
72 | * {RoleAdminChanged} not being emitted signaling this.
73 | *
74 | * _Available since v3.1._
75 | */
76 | event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
77 |
78 | /**
79 | * @dev Emitted when `account` is granted `role`.
80 | *
81 | * `sender` is the account that originated the contract call, an admin role
82 | * bearer except when using {_setupRole}.
83 | */
84 | event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
85 |
86 | /**
87 | * @dev Emitted when `account` is revoked `role`.
88 | *
89 | * `sender` is the account that originated the contract call:
90 | * - if using `revokeRole`, it is the admin role bearer
91 | * - if using `renounceRole`, it is the role bearer (i.e. `account`)
92 | */
93 | event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
94 |
95 | /**
96 | * @dev Returns `true` if `account` has been granted `role`.
97 | */
98 | function hasRole(bytes32 role, address account) internal view returns (bool) {
99 | return _roles[role].members.contains(account);
100 | }
101 |
102 | /**
103 | * @dev Returns the number of accounts that have `role`. Can be used
104 | * together with {getRoleMember} to enumerate all bearers of a role.
105 | */
106 | function getRoleMemberCount(bytes32 role) internal view returns (uint256) {
107 | return _roles[role].members.length();
108 | }
109 |
110 | /**
111 | * @dev Returns one of the accounts that have `role`. `index` must be a
112 | * value between 0 and {getRoleMemberCount}, non-inclusive.
113 | *
114 | * Role bearers are not sorted in any particular way, and their ordering may
115 | * change at any point.
116 | *
117 | * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
118 | * you perform all queries on the same block. See the following
119 | * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
120 | * for more information.
121 | */
122 | function getRoleMember(bytes32 role, uint256 index) internal view returns (address) {
123 | return _roles[role].members.at(index);
124 | }
125 |
126 | /**
127 | * @dev Grants `role` to `account`.
128 | *
129 | * If `account` had not been already granted `role`, emits a {RoleGranted}
130 | * event.
131 | *
132 | * Requirements:
133 | *
134 | * - the caller must have ``role``'s admin role.
135 | */
136 | function grantRole(bytes32 role, address account) internal virtual {
137 | require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant");
138 |
139 | _grantRole(role, account);
140 | }
141 |
142 | /**
143 | * @dev Revokes `role` from `account`.
144 | *
145 | * If `account` had been granted `role`, emits a {RoleRevoked} event.
146 | *
147 | * Requirements:
148 | *
149 | * - the caller must have ``role``'s admin role.
150 | */
151 | function revokeRole(bytes32 role, address account) internal virtual {
152 | require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke");
153 |
154 | _revokeRole(role, account);
155 | }
156 |
157 | /**
158 | * @dev Grants `role` to `account`.
159 | *
160 | * If `account` had not been already granted `role`, emits a {RoleGranted}
161 | * event. Note that unlike {grantRole}, this function doesn't perform any
162 | * checks on the calling account.
163 | *
164 | * [WARNING]
165 | * ====
166 | * This function should only be called from the constructor when setting
167 | * up the initial roles for the system.
168 | *
169 | * Using this function in any other way is effectively circumventing the admin
170 | * system imposed by {AccessControl}.
171 | * ====
172 | */
173 | function _setupRole(bytes32 role, address account) internal {
174 | _grantRole(role, account);
175 | }
176 |
177 | function _grantRole(bytes32 role, address account) private {
178 | if (_roles[role].members.add(account)) {
179 | emit RoleGranted(role, account, _msgSender());
180 | }
181 | }
182 |
183 | function _revokeRole(bytes32 role, address account) private {
184 | if (_roles[role].members.remove(account)) {
185 | emit RoleRevoked(role, account, _msgSender());
186 | }
187 | }
188 |
189 | /**
190 | * @notice This empty reserved space is put in place to allow future versions to add new
191 | * variables without shifting down storage in the inheritance chain.
192 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
193 | */
194 | uint256[49] private __gap;
195 | }
196 |
--------------------------------------------------------------------------------
/contracts/mixins/treasury/OperatorRoleEnumerable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT OR Apache-2.0
2 |
3 | pragma solidity ^0.8.12;
4 |
5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
6 |
7 | import "./OZAccessControlUpgradeable.sol";
8 |
9 | /**
10 | * @title Defines a role for Foundation operator accounts.
11 | * @dev Wraps a role from OpenZeppelin's AccessControl for easy integration.
12 | * @author batu-inal & HardlyDifficult
13 | */
14 | abstract contract OperatorRoleEnumerable is Initializable, OZAccessControlUpgradeable {
15 | bytes32 private constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
16 |
17 | /**
18 | * @notice Adds the account to the list of approved operators.
19 | * @dev Only callable by admins as enforced by `grantRole`.
20 | * @param account The address to be approved.
21 | */
22 | function grantOperator(address account) external {
23 | grantRole(OPERATOR_ROLE, account);
24 | }
25 |
26 | /**
27 | * @notice Removes the account from the list of approved operators.
28 | * @dev Only callable by admins as enforced by `revokeRole`.
29 | * @param account The address to be removed from the approved list.
30 | */
31 | function revokeOperator(address account) external {
32 | revokeRole(OPERATOR_ROLE, account);
33 | }
34 |
35 | /**
36 | * @notice Returns one of the operator by index.
37 | * @param index The index of the operator to return from 0 to getOperatorMemberCount() - 1.
38 | * @return account The address of the operator.
39 | */
40 | function getOperatorMember(uint256 index) external view returns (address account) {
41 | account = getRoleMember(OPERATOR_ROLE, index);
42 | }
43 |
44 | /**
45 | * @notice Checks how many accounts have been granted operator access.
46 | * @return count The number of accounts with operator access.
47 | */
48 | function getOperatorMemberCount() external view returns (uint256 count) {
49 | count = getRoleMemberCount(OPERATOR_ROLE);
50 | }
51 |
52 | /**
53 | * @notice Checks if the account provided is an operator.
54 | * @param account The address to check.
55 | * @return approved True if the account is an operator.
56 | */
57 | function isOperator(address account) external view returns (bool approved) {
58 | approved = hasRole(OPERATOR_ROLE, account);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/docs/FNDMiddleware.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: FNDMiddleware
3 | description: Convenience methods to ease integration with other contracts.
4 | ---
5 |
6 |
7 | This will aggregate calls and format the output per the needs of our frontend or other consumers.
8 |
9 |
10 |
11 | ## Methods
12 |
13 | ### getAccountInfo
14 |
15 | ```solidity
16 | function getAccountInfo(address account) external view returns (uint256 ethBalance, uint256 availableFethBalance, uint256 lockedFethBalance, string ensName)
17 | ```
18 |
19 |
20 |
21 |
22 |
23 | **Parameters**
24 |
25 | | Name | Type | Description |
26 | |---|---|---|
27 | | account | `address` | |
28 |
29 | **Returns**
30 |
31 | | Name | Type | Description |
32 | |---|---|---|
33 | | ethBalance | `uint256` | |
34 | | availableFethBalance | `uint256` | |
35 | | lockedFethBalance | `uint256` | |
36 | | ensName | `string` | |
37 |
38 | ### getFees
39 |
40 | ```solidity
41 | function getFees(address nftContract, uint256 tokenId, uint256 price) external view returns (struct FNDMiddleware.FeeWithRecipient protocol, struct FNDMiddleware.Fee creator, struct FNDMiddleware.FeeWithRecipient owner, struct FNDMiddleware.RevSplit[] creatorRevSplit)
42 | ```
43 |
44 |
45 |
46 |
47 |
48 | **Parameters**
49 |
50 | | Name | Type | Description |
51 | |---|---|---|
52 | | nftContract | `address` | |
53 | | tokenId | `uint256` | |
54 | | price | `uint256` | |
55 |
56 | **Returns**
57 |
58 | | Name | Type | Description |
59 | |---|---|---|
60 | | protocol | `FNDMiddleware.FeeWithRecipient` | |
61 | | creator | `FNDMiddleware.Fee` | |
62 | | owner | `FNDMiddleware.FeeWithRecipient` | |
63 | | creatorRevSplit | `FNDMiddleware.RevSplit[]` | |
64 |
65 | ### getNFTDetailString
66 |
67 | ```solidity
68 | function getNFTDetailString(address nftContract, uint256 tokenId) external view returns (string details)
69 | ```
70 |
71 | Retrieves details about the current state of an NFT in the FND Market as a string.
72 |
73 | *This API is for investigations & convenience, it is not meant to be consumed by an app directly. Future upgrades may not be backwards compatible.*
74 |
75 | **Parameters**
76 |
77 | | Name | Type | Description |
78 | |---|---|---|
79 | | nftContract | `address` | |
80 | | tokenId | `uint256` | |
81 |
82 | **Returns**
83 |
84 | | Name | Type | Description |
85 | |---|---|---|
86 | | details | `string` | |
87 |
88 | ### getNFTDetails
89 |
90 | ```solidity
91 | function getNFTDetails(address nftContract, uint256 tokenId) external view returns (address owner, bool isInEscrow, address auctionBidder, uint256 auctionEndTime, uint256 auctionPrice, uint256 auctionId, uint256 buyPrice, uint256 offerAmount, address offerBuyer, uint256 offerExpiration)
92 | ```
93 |
94 | Retrieves details about the current state of an NFT in the FND Market.
95 |
96 |
97 |
98 | **Parameters**
99 |
100 | | Name | Type | Description |
101 | |---|---|---|
102 | | nftContract | `address` | The address of the NFT contract. |
103 | | tokenId | `uint256` | The id of the NFT. |
104 |
105 | **Returns**
106 |
107 | | Name | Type | Description |
108 | |---|---|---|
109 | | owner | `address` | The account which currently holds the NFT or has listed it for sale. |
110 | | isInEscrow | `bool` | True if the NFT is currently held in escrow by the Market (for an auction or buy price). |
111 | | auctionBidder | `address` | The current highest bidder for the auction, or address(0) if there's not an active auction. |
112 | | auctionEndTime | `uint256` | The time at which this auction will not accept any new bids, this is `0` until the first bid is placed. |
113 | | auctionPrice | `uint256` | The latest price of the NFT in this auction. This is set to the reserve price, and then to the highest bid once the auction has started. Returns `0` if there's no auction for this NFT. |
114 | | auctionId | `uint256` | The id of the auction, or 0 if no auction is found. |
115 | | buyPrice | `uint256` | The price at which you could buy this NFT. Returns max uint256 if there is no buy price set for this NFT (since a price of 0 is supported). |
116 | | offerAmount | `uint256` | The amount being offered for this NFT. Returns `0` if there is no offer or the most recent offer has expired. |
117 | | offerBuyer | `address` | The address of the buyer that made the current highest offer. Returns `address(0)` if there is no offer or the most recent offer has expired. |
118 | | offerExpiration | `uint256` | The timestamp that the current highest offer expires. Returns `0` if there is no offer or the most recent offer has expired. |
119 |
120 | ### getSellerOrOwnerOf
121 |
122 | ```solidity
123 | function getSellerOrOwnerOf(address nftContract, uint256 tokenId) external view returns (address payable ownerOrSeller)
124 | ```
125 |
126 | Checks who the seller for an NFT is, checking both markets or returning the current owner.
127 |
128 |
129 |
130 | **Parameters**
131 |
132 | | Name | Type | Description |
133 | |---|---|---|
134 | | nftContract | `address` | |
135 | | tokenId | `uint256` | |
136 |
137 | **Returns**
138 |
139 | | Name | Type | Description |
140 | |---|---|---|
141 | | ownerOrSeller | `address payable` | |
142 |
143 | ### getSplitShareLength
144 |
145 | ```solidity
146 | function getSplitShareLength(address payable recipient) external view returns (uint256 count)
147 | ```
148 |
149 |
150 |
151 |
152 |
153 | **Parameters**
154 |
155 | | Name | Type | Description |
156 | |---|---|---|
157 | | recipient | `address payable` | |
158 |
159 | **Returns**
160 |
161 | | Name | Type | Description |
162 | |---|---|---|
163 | | count | `uint256` | |
164 |
165 | ### getTokenCreator
166 |
167 | ```solidity
168 | function getTokenCreator(address nftContract, uint256 tokenId) external view returns (address creatorAddress)
169 | ```
170 |
171 |
172 |
173 |
174 |
175 | **Parameters**
176 |
177 | | Name | Type | Description |
178 | |---|---|---|
179 | | nftContract | `address` | |
180 | | tokenId | `uint256` | |
181 |
182 | **Returns**
183 |
184 | | Name | Type | Description |
185 | |---|---|---|
186 | | creatorAddress | `address` | |
187 |
188 | ### probeNFT
189 |
190 | ```solidity
191 | function probeNFT(address nftContract, uint256 tokenId) external payable returns (bytes32)
192 | ```
193 |
194 | Checks an NFT to confirm it will function correctly with our marketplace.
195 |
196 | *This should be called with as `call` to simulate the tx; never `sendTransaction`.*
197 |
198 | **Parameters**
199 |
200 | | Name | Type | Description |
201 | |---|---|---|
202 | | nftContract | `address` | |
203 | | tokenId | `uint256` | |
204 |
205 | **Returns**
206 |
207 | | Name | Type | Description |
208 | |---|---|---|
209 | | _0 | `bytes32` | 0 if the NFT is supported, otherwise a hash of the error reason. |
210 |
211 |
212 |
213 |
214 |
--------------------------------------------------------------------------------
/docs/FoundationTreasury.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: FoundationTreasury
3 | description: Manage revenue and roles for Foundation.
4 | ---
5 |
6 |
7 | All fees generated by the market are forwarded to this contract. It also defines the Admin and Operator roles which are used in other contracts.
8 |
9 |
10 |
11 | ## Methods
12 |
13 | ### getAdminMember
14 |
15 | ```solidity
16 | function getAdminMember(uint256 index) external view returns (address account)
17 | ```
18 |
19 | Returns one of the admins by index.
20 |
21 |
22 |
23 | **Parameters**
24 |
25 | | Name | Type | Description |
26 | |---|---|---|
27 | | index | `uint256` | The index of the admin to return from 0 to getAdminMemberCount() - 1. |
28 |
29 | **Returns**
30 |
31 | | Name | Type | Description |
32 | |---|---|---|
33 | | account | `address` | The address of the admin. |
34 |
35 | ### getAdminMemberCount
36 |
37 | ```solidity
38 | function getAdminMemberCount() external view returns (uint256 count)
39 | ```
40 |
41 | Checks how many accounts have been granted admin access.
42 |
43 |
44 |
45 |
46 | **Returns**
47 |
48 | | Name | Type | Description |
49 | |---|---|---|
50 | | count | `uint256` | The number of accounts with admin access. |
51 |
52 | ### getOperatorMember
53 |
54 | ```solidity
55 | function getOperatorMember(uint256 index) external view returns (address account)
56 | ```
57 |
58 | Returns one of the operator by index.
59 |
60 |
61 |
62 | **Parameters**
63 |
64 | | Name | Type | Description |
65 | |---|---|---|
66 | | index | `uint256` | The index of the operator to return from 0 to getOperatorMemberCount() - 1. |
67 |
68 | **Returns**
69 |
70 | | Name | Type | Description |
71 | |---|---|---|
72 | | account | `address` | The address of the operator. |
73 |
74 | ### getOperatorMemberCount
75 |
76 | ```solidity
77 | function getOperatorMemberCount() external view returns (uint256 count)
78 | ```
79 |
80 | Checks how many accounts have been granted operator access.
81 |
82 |
83 |
84 |
85 | **Returns**
86 |
87 | | Name | Type | Description |
88 | |---|---|---|
89 | | count | `uint256` | The number of accounts with operator access. |
90 |
91 | ### grantAdmin
92 |
93 | ```solidity
94 | function grantAdmin(address account) external nonpayable
95 | ```
96 |
97 | Adds the account to the list of approved admins.
98 |
99 | *Only callable by admins as enforced by `grantRole`.*
100 |
101 | **Parameters**
102 |
103 | | Name | Type | Description |
104 | |---|---|---|
105 | | account | `address` | The address to be approved. |
106 |
107 | ### grantOperator
108 |
109 | ```solidity
110 | function grantOperator(address account) external nonpayable
111 | ```
112 |
113 | Adds the account to the list of approved operators.
114 |
115 | *Only callable by admins as enforced by `grantRole`.*
116 |
117 | **Parameters**
118 |
119 | | Name | Type | Description |
120 | |---|---|---|
121 | | account | `address` | The address to be approved. |
122 |
123 | ### initialize
124 |
125 | ```solidity
126 | function initialize(address admin) external nonpayable
127 | ```
128 |
129 | Called one time after deployment to initialize the contract.
130 |
131 |
132 |
133 | **Parameters**
134 |
135 | | Name | Type | Description |
136 | |---|---|---|
137 | | admin | `address` | The account to add as the initial admin. |
138 |
139 | ### isAdmin
140 |
141 | ```solidity
142 | function isAdmin(address account) external view returns (bool approved)
143 | ```
144 |
145 | Checks if the account provided is an admin.
146 |
147 | *This call is used by the royalty registry contract.*
148 |
149 | **Parameters**
150 |
151 | | Name | Type | Description |
152 | |---|---|---|
153 | | account | `address` | The address to check. |
154 |
155 | **Returns**
156 |
157 | | Name | Type | Description |
158 | |---|---|---|
159 | | approved | `bool` | True if the account is an admin. |
160 |
161 | ### isOperator
162 |
163 | ```solidity
164 | function isOperator(address account) external view returns (bool approved)
165 | ```
166 |
167 | Checks if the account provided is an operator.
168 |
169 |
170 |
171 | **Parameters**
172 |
173 | | Name | Type | Description |
174 | |---|---|---|
175 | | account | `address` | The address to check. |
176 |
177 | **Returns**
178 |
179 | | Name | Type | Description |
180 | |---|---|---|
181 | | approved | `bool` | True if the account is an operator. |
182 |
183 | ### revokeAdmin
184 |
185 | ```solidity
186 | function revokeAdmin(address account) external nonpayable
187 | ```
188 |
189 | Removes the account from the list of approved admins.
190 |
191 | *Only callable by admins as enforced by `revokeRole`.*
192 |
193 | **Parameters**
194 |
195 | | Name | Type | Description |
196 | |---|---|---|
197 | | account | `address` | The address to be removed from the approved list. |
198 |
199 | ### revokeOperator
200 |
201 | ```solidity
202 | function revokeOperator(address account) external nonpayable
203 | ```
204 |
205 | Removes the account from the list of approved operators.
206 |
207 | *Only callable by admins as enforced by `revokeRole`.*
208 |
209 | **Parameters**
210 |
211 | | Name | Type | Description |
212 | |---|---|---|
213 | | account | `address` | The address to be removed from the approved list. |
214 |
215 | ### withdrawFunds
216 |
217 | ```solidity
218 | function withdrawFunds(address payable to, uint256 amount) external nonpayable
219 | ```
220 |
221 | Allows an admin to withdraw funds.
222 |
223 |
224 |
225 | **Parameters**
226 |
227 | | Name | Type | Description |
228 | |---|---|---|
229 | | to | `address payable` | Address to receive the withdrawn funds |
230 | | amount | `uint256` | Amount to withdrawal or 0 to withdraw all available funds |
231 |
232 |
233 |
234 | ## Events
235 |
236 | ### FundsWithdrawn
237 |
238 | ```solidity
239 | event FundsWithdrawn(address indexed to, uint256 amount)
240 | ```
241 |
242 | Emitted when funds are withdrawn from this contract.
243 |
244 |
245 |
246 | **Parameters**
247 |
248 | | Name | Type | Description |
249 | |---|---|---|
250 | | to `indexed` | `address` | |
251 | | amount | `uint256` | |
252 |
253 | ### Initialized
254 |
255 | ```solidity
256 | event Initialized(uint8 version)
257 | ```
258 |
259 |
260 |
261 |
262 |
263 | **Parameters**
264 |
265 | | Name | Type | Description |
266 | |---|---|---|
267 | | version | `uint8` | |
268 |
269 | ### RoleAdminChanged
270 |
271 | ```solidity
272 | event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)
273 | ```
274 |
275 |
276 |
277 |
278 |
279 | **Parameters**
280 |
281 | | Name | Type | Description |
282 | |---|---|---|
283 | | role `indexed` | `bytes32` | |
284 | | previousAdminRole `indexed` | `bytes32` | |
285 | | newAdminRole `indexed` | `bytes32` | |
286 |
287 | ### RoleGranted
288 |
289 | ```solidity
290 | event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)
291 | ```
292 |
293 |
294 |
295 |
296 |
297 | **Parameters**
298 |
299 | | Name | Type | Description |
300 | |---|---|---|
301 | | role `indexed` | `bytes32` | |
302 | | account `indexed` | `address` | |
303 | | sender `indexed` | `address` | |
304 |
305 | ### RoleRevoked
306 |
307 | ```solidity
308 | event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)
309 | ```
310 |
311 |
312 |
313 |
314 |
315 | **Parameters**
316 |
317 | | Name | Type | Description |
318 | |---|---|---|
319 | | role `indexed` | `bytes32` | |
320 | | account `indexed` | `address` | |
321 | | sender `indexed` | `address` | |
322 |
323 |
324 |
325 | ## Errors
326 |
327 | ### CollateralManagement_Cannot_Withdraw_To_Address_Zero
328 |
329 | ```solidity
330 | error CollateralManagement_Cannot_Withdraw_To_Address_Zero()
331 | ```
332 |
333 |
334 |
335 |
336 |
337 |
338 | ### CollateralManagement_Cannot_Withdraw_To_Self
339 |
340 | ```solidity
341 | error CollateralManagement_Cannot_Withdraw_To_Self()
342 | ```
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
--------------------------------------------------------------------------------
/docs/NFTCollectionFactory.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: NFTCollectionFactory
3 | description: A factory to create NFT collections.
4 | ---
5 |
6 |
7 | Call this factory to create NFT collections.
8 |
9 | *This creates and initializes an ERC-1167 minimal proxy pointing to an NFT collection contract implementation.*
10 |
11 | ## Methods
12 |
13 | ### adminUpdateNFTCollectionImplementation
14 |
15 | ```solidity
16 | function adminUpdateNFTCollectionImplementation(address _implementation) external nonpayable
17 | ```
18 |
19 | Allows Foundation to change the NFTCollection implementation used for future collections. This call will auto-increment the version. Existing collections are not impacted.
20 |
21 |
22 |
23 | **Parameters**
24 |
25 | | Name | Type | Description |
26 | |---|---|---|
27 | | _implementation | `address` | The new NFTCollection collection implementation address. |
28 |
29 | ### adminUpdateNFTDropCollectionImplementation
30 |
31 | ```solidity
32 | function adminUpdateNFTDropCollectionImplementation(address _implementation) external nonpayable
33 | ```
34 |
35 | Allows Foundation to change the NFTDropCollection implementation used for future collections. This call will auto-increment the version. Existing collections are not impacted.
36 |
37 |
38 |
39 | **Parameters**
40 |
41 | | Name | Type | Description |
42 | |---|---|---|
43 | | _implementation | `address` | The new NFTDropCollection collection implementation address. |
44 |
45 | ### createNFTCollection
46 |
47 | ```solidity
48 | function createNFTCollection(string name, string symbol, uint96 nonce) external nonpayable returns (address collection)
49 | ```
50 |
51 | Create a new collection contract.
52 |
53 | *The nonce must be unique for the msg.sender + implementation version, otherwise this call will revert.*
54 |
55 | **Parameters**
56 |
57 | | Name | Type | Description |
58 | |---|---|---|
59 | | name | `string` | The collection's `name`. |
60 | | symbol | `string` | The collection's `symbol`. |
61 | | nonce | `uint96` | An arbitrary value used to allow a creator to mint multiple collections with a counterfactual address. |
62 |
63 | **Returns**
64 |
65 | | Name | Type | Description |
66 | |---|---|---|
67 | | collection | `address` | The address of the newly created collection contract. |
68 |
69 | ### createNFTDropCollection
70 |
71 | ```solidity
72 | function createNFTDropCollection(string name, string symbol, string baseURI, bool isRevealed, uint32 maxTokenId, address approvedMinter, uint96 nonce) external nonpayable returns (address collection)
73 | ```
74 |
75 | Create a new drop collection contract.
76 |
77 | *The nonce must be unique for the msg.sender + implementation version, otherwise this call will revert.*
78 |
79 | **Parameters**
80 |
81 | | Name | Type | Description |
82 | |---|---|---|
83 | | name | `string` | The collection's `name`. |
84 | | symbol | `string` | The collection's `symbol`. |
85 | | baseURI | `string` | The base URI for the collection. |
86 | | isRevealed | `bool` | Whether the collection is revealed or not. |
87 | | maxTokenId | `uint32` | The max token id for this collection. |
88 | | approvedMinter | `address` | An optional address to grant the MINTER_ROLE. |
89 | | nonce | `uint96` | An arbitrary value used to allow a creator to mint multiple collections with a counterfactual address. |
90 |
91 | **Returns**
92 |
93 | | Name | Type | Description |
94 | |---|---|---|
95 | | collection | `address` | The address of the newly created collection contract. |
96 |
97 | ### createNFTDropCollectionWithPaymentAddress
98 |
99 | ```solidity
100 | function createNFTDropCollectionWithPaymentAddress(string name, string symbol, string baseURI, bool isRevealed, uint32 maxTokenId, address approvedMinter, uint96 nonce, address payable paymentAddress) external nonpayable returns (address collection)
101 | ```
102 |
103 | Create a new drop collection contract with a custom payment address.
104 |
105 | *All params other than `paymentAddress` are the same as in `createNFTDropCollection`. The nonce must be unique for the msg.sender + implementation version, otherwise this call will revert.*
106 |
107 | **Parameters**
108 |
109 | | Name | Type | Description |
110 | |---|---|---|
111 | | name | `string` | The collection's `name`. |
112 | | symbol | `string` | The collection's `symbol`. |
113 | | baseURI | `string` | The base URI for the collection. |
114 | | isRevealed | `bool` | Whether the collection is revealed or not. |
115 | | maxTokenId | `uint32` | The max token id for this collection. |
116 | | approvedMinter | `address` | An optional address to grant the MINTER_ROLE. |
117 | | nonce | `uint96` | An arbitrary value used to allow a creator to mint multiple collections with a counterfactual address. |
118 | | paymentAddress | `address payable` | The address that will receive royalties and mint payments. |
119 |
120 | **Returns**
121 |
122 | | Name | Type | Description |
123 | |---|---|---|
124 | | collection | `address` | The address of the newly created collection contract. |
125 |
126 | ### createNFTDropCollectionWithPaymentFactory
127 |
128 | ```solidity
129 | function createNFTDropCollectionWithPaymentFactory(string name, string symbol, string baseURI, bool isRevealed, uint32 maxTokenId, address approvedMinter, uint96 nonce, CallWithoutValue paymentAddressFactoryCall) external nonpayable returns (address collection)
130 | ```
131 |
132 |
133 |
134 |
135 |
136 | **Parameters**
137 |
138 | | Name | Type | Description |
139 | |---|---|---|
140 | | name | `string` | |
141 | | symbol | `string` | |
142 | | baseURI | `string` | |
143 | | isRevealed | `bool` | |
144 | | maxTokenId | `uint32` | |
145 | | approvedMinter | `address` | |
146 | | nonce | `uint96` | |
147 | | paymentAddressFactoryCall | `CallWithoutValue` | |
148 |
149 | **Returns**
150 |
151 | | Name | Type | Description |
152 | |---|---|---|
153 | | collection | `address` | |
154 |
155 | ### implementationNFTCollection
156 |
157 | ```solidity
158 | function implementationNFTCollection() external view returns (address)
159 | ```
160 |
161 | The address of the implementation all new NFTCollections will leverage.
162 |
163 | *When this is changed, `versionNFTCollection` is incremented.*
164 |
165 |
166 | **Returns**
167 |
168 | | Name | Type | Description |
169 | |---|---|---|
170 | | _0 | `address` | The implementation address for NFTCollection. |
171 |
172 | ### implementationNFTDropCollection
173 |
174 | ```solidity
175 | function implementationNFTDropCollection() external view returns (address)
176 | ```
177 |
178 | The address of the implementation all new NFTDropCollections will leverage.
179 |
180 | *When this is changed, `versionNFTDropCollection` is incremented.*
181 |
182 |
183 | **Returns**
184 |
185 | | Name | Type | Description |
186 | |---|---|---|
187 | | _0 | `address` | The implementation address for NFTDropCollection. |
188 |
189 | ### initialize
190 |
191 | ```solidity
192 | function initialize(uint32 _versionNFTCollection) external nonpayable
193 | ```
194 |
195 | Initializer called after contract creation.
196 |
197 | *This is used so that this factory will resume versions from where our original factory had left off.*
198 |
199 | **Parameters**
200 |
201 | | Name | Type | Description |
202 | |---|---|---|
203 | | _versionNFTCollection | `uint32` | The current implementation version for NFTCollections. |
204 |
205 | ### predictNFTCollectionAddress
206 |
207 | ```solidity
208 | function predictNFTCollectionAddress(address creator, uint96 nonce) external view returns (address collection)
209 | ```
210 |
211 | Returns the address of a collection given the current implementation version, creator, and nonce. This will return the same address whether the collection has already been created or not.
212 |
213 |
214 |
215 | **Parameters**
216 |
217 | | Name | Type | Description |
218 | |---|---|---|
219 | | creator | `address` | The creator of the collection. |
220 | | nonce | `uint96` | An arbitrary value used to allow a creator to mint multiple collections with a counterfactual address. |
221 |
222 | **Returns**
223 |
224 | | Name | Type | Description |
225 | |---|---|---|
226 | | collection | `address` | The address of the collection contract that would be created by this nonce. |
227 |
228 | ### predictNFTDropCollectionAddress
229 |
230 | ```solidity
231 | function predictNFTDropCollectionAddress(address creator, uint96 nonce) external view returns (address collection)
232 | ```
233 |
234 | Returns the address of an NFTDropCollection collection given the current implementation version, creator, and nonce. This will return the same address whether the collection has already been created or not.
235 |
236 |
237 |
238 | **Parameters**
239 |
240 | | Name | Type | Description |
241 | |---|---|---|
242 | | creator | `address` | The creator of the collection. |
243 | | nonce | `uint96` | An arbitrary value used to allow a creator to mint multiple collections with a counterfactual address. |
244 |
245 | **Returns**
246 |
247 | | Name | Type | Description |
248 | |---|---|---|
249 | | collection | `address` | The address of the collection contract that would be created by this nonce. |
250 |
251 | ### rolesManager
252 |
253 | ```solidity
254 | function rolesManager() external view returns (contract IRoles)
255 | ```
256 |
257 | The contract address which manages common roles.
258 |
259 | *Defines a centralized admin role definition for permissioned functions below.*
260 |
261 |
262 | **Returns**
263 |
264 | | Name | Type | Description |
265 | |---|---|---|
266 | | _0 | `contract IRoles` | The contract address with role definitions. |
267 |
268 | ### versionNFTCollection
269 |
270 | ```solidity
271 | function versionNFTCollection() external view returns (uint32)
272 | ```
273 |
274 | The implementation version of new NFTCollections.
275 |
276 | *This is auto-incremented each time `implementationNFTCollection` is changed.*
277 |
278 |
279 | **Returns**
280 |
281 | | Name | Type | Description |
282 | |---|---|---|
283 | | _0 | `uint32` | The current NFTCollection implementation version. |
284 |
285 | ### versionNFTDropCollection
286 |
287 | ```solidity
288 | function versionNFTDropCollection() external view returns (uint32)
289 | ```
290 |
291 | The implementation version of new NFTDropCollections.
292 |
293 | *This is auto-incremented each time `implementationNFTDropCollection` is changed.*
294 |
295 |
296 | **Returns**
297 |
298 | | Name | Type | Description |
299 | |---|---|---|
300 | | _0 | `uint32` | The current NFTDropCollection implementation version. |
301 |
302 |
303 |
304 | ## Events
305 |
306 | ### ImplementationNFTCollectionUpdated
307 |
308 | ```solidity
309 | event ImplementationNFTCollectionUpdated(address indexed implementation, uint256 indexed version)
310 | ```
311 |
312 | Emitted when the implementation of NFTCollection used by new collections is updated.
313 |
314 |
315 |
316 | **Parameters**
317 |
318 | | Name | Type | Description |
319 | |---|---|---|
320 | | implementation `indexed` | `address` | The new implementation contract address. |
321 | | version `indexed` | `uint256` | The version of the new implementation, auto-incremented. |
322 |
323 | ### ImplementationNFTDropCollectionUpdated
324 |
325 | ```solidity
326 | event ImplementationNFTDropCollectionUpdated(address indexed implementationNFTDropCollection, uint256 indexed version)
327 | ```
328 |
329 | Emitted when the implementation of NFTDropCollection used by new collections is updated.
330 |
331 |
332 |
333 | **Parameters**
334 |
335 | | Name | Type | Description |
336 | |---|---|---|
337 | | implementationNFTDropCollection `indexed` | `address` | The new implementation contract address. |
338 | | version `indexed` | `uint256` | The version of the new implementation, auto-incremented. |
339 |
340 | ### Initialized
341 |
342 | ```solidity
343 | event Initialized(uint8 version)
344 | ```
345 |
346 |
347 |
348 |
349 |
350 | **Parameters**
351 |
352 | | Name | Type | Description |
353 | |---|---|---|
354 | | version | `uint8` | |
355 |
356 | ### NFTCollectionCreated
357 |
358 | ```solidity
359 | event NFTCollectionCreated(address indexed collection, address indexed creator, uint256 indexed version, string name, string symbol, uint256 nonce)
360 | ```
361 |
362 | Emitted when a new NFTCollection is created from this factory.
363 |
364 |
365 |
366 | **Parameters**
367 |
368 | | Name | Type | Description |
369 | |---|---|---|
370 | | collection `indexed` | `address` | The address of the new NFT collection contract. |
371 | | creator `indexed` | `address` | The address of the creator which owns the new collection. |
372 | | version `indexed` | `uint256` | The implementation version used by the new collection. |
373 | | name | `string` | The name of the collection contract created. |
374 | | symbol | `string` | The symbol of the collection contract created. |
375 | | nonce | `uint256` | The nonce used by the creator when creating the collection, used to define the address of the collection. |
376 |
377 | ### NFTDropCollectionCreated
378 |
379 | ```solidity
380 | event NFTDropCollectionCreated(address indexed collection, address indexed creator, address indexed approvedMinter, string name, string symbol, string baseURI, bool isRevealed, uint256 maxTokenId, address paymentAddress, uint256 version, uint256 nonce)
381 | ```
382 |
383 | Emitted when a new NFTDropCollection is created from this factory.
384 |
385 |
386 |
387 | **Parameters**
388 |
389 | | Name | Type | Description |
390 | |---|---|---|
391 | | collection `indexed` | `address` | The address of the new NFT drop collection contract. |
392 | | creator `indexed` | `address` | The address of the creator which owns the new collection. |
393 | | approvedMinter `indexed` | `address` | An optional address to grant the MINTER_ROLE. |
394 | | name | `string` | The collection's `name`. |
395 | | symbol | `string` | The collection's `symbol`. |
396 | | baseURI | `string` | The base URI for the collection. |
397 | | isRevealed | `bool` | Whether the collection is revealed or not. |
398 | | maxTokenId | `uint256` | The max `tokenID` for this collection. |
399 | | paymentAddress | `address` | The address that will receive royalties and mint payments. |
400 | | version | `uint256` | The implementation version used by the new NFTDropCollection collection. |
401 | | nonce | `uint256` | The nonce used by the creator to create this collection. |
402 |
403 |
404 |
405 |
--------------------------------------------------------------------------------
/docs/PercentSplitETH.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: PercentSplitETH
3 | description: Auto-forward ETH to a pre-determined list of addresses.
4 | ---
5 |
6 |
7 | Deploys contracts which auto-forwards any ETH sent to it to a list of recipients considering their percent share of the payment received. ERC-20 tokens are also supported and may be split on demand by calling `splitERC20Tokens`. If another asset type is sent to this contract address such as an NFT, arbitrary calls may be made by one of the split recipients in order to recover them.
8 |
9 | *Uses create2 counterfactual addresses so that the destination is known from the terms of the split.*
10 |
11 | ## Methods
12 |
13 | ### createSplit
14 |
15 | ```solidity
16 | function createSplit(PercentSplitETH.Share[] shares) external nonpayable returns (contract PercentSplitETH splitInstance)
17 | ```
18 |
19 |
20 |
21 |
22 |
23 | **Parameters**
24 |
25 | | Name | Type | Description |
26 | |---|---|---|
27 | | shares | `PercentSplitETH.Share[]` | |
28 |
29 | **Returns**
30 |
31 | | Name | Type | Description |
32 | |---|---|---|
33 | | splitInstance | `contract PercentSplitETH` | |
34 |
35 | ### getPercentInBasisPointsByIndex
36 |
37 | ```solidity
38 | function getPercentInBasisPointsByIndex(uint256 index) external view returns (uint256 percentInBasisPoints)
39 | ```
40 |
41 | Returns a recipient's percent share in basis points.
42 |
43 |
44 |
45 | **Parameters**
46 |
47 | | Name | Type | Description |
48 | |---|---|---|
49 | | index | `uint256` | The index of the recipient to get the share of. |
50 |
51 | **Returns**
52 |
53 | | Name | Type | Description |
54 | |---|---|---|
55 | | percentInBasisPoints | `uint256` | The percent of the payment received by the recipient, in basis points. |
56 |
57 | ### getPredictedSplitAddress
58 |
59 | ```solidity
60 | function getPredictedSplitAddress(PercentSplitETH.Share[] shares) external view returns (address splitInstance)
61 | ```
62 |
63 |
64 |
65 |
66 |
67 | **Parameters**
68 |
69 | | Name | Type | Description |
70 | |---|---|---|
71 | | shares | `PercentSplitETH.Share[]` | |
72 |
73 | **Returns**
74 |
75 | | Name | Type | Description |
76 | |---|---|---|
77 | | splitInstance | `address` | |
78 |
79 | ### getShareLength
80 |
81 | ```solidity
82 | function getShareLength() external view returns (uint256 length)
83 | ```
84 |
85 | Returns how many recipients are part of this split.
86 |
87 |
88 |
89 |
90 | **Returns**
91 |
92 | | Name | Type | Description |
93 | |---|---|---|
94 | | length | `uint256` | The number of recipients in this split. |
95 |
96 | ### getShareRecipientByIndex
97 |
98 | ```solidity
99 | function getShareRecipientByIndex(uint256 index) external view returns (address payable recipient)
100 | ```
101 |
102 | Returns a recipient in this split.
103 |
104 |
105 |
106 | **Parameters**
107 |
108 | | Name | Type | Description |
109 | |---|---|---|
110 | | index | `uint256` | The index of the recipient to get. |
111 |
112 | **Returns**
113 |
114 | | Name | Type | Description |
115 | |---|---|---|
116 | | recipient | `address payable` | The recipient at the given index. |
117 |
118 | ### getShares
119 |
120 | ```solidity
121 | function getShares() external view returns (struct PercentSplitETH.Share[] shares)
122 | ```
123 |
124 | Returns a tuple with the terms of this split.
125 |
126 |
127 |
128 |
129 | **Returns**
130 |
131 | | Name | Type | Description |
132 | |---|---|---|
133 | | shares | `PercentSplitETH.Share[]` | The list of recipients and their share of the payment for this split. |
134 |
135 | ### initialize
136 |
137 | ```solidity
138 | function initialize(PercentSplitETH.Share[] shares) external nonpayable
139 | ```
140 |
141 |
142 |
143 |
144 |
145 | **Parameters**
146 |
147 | | Name | Type | Description |
148 | |---|---|---|
149 | | shares | `PercentSplitETH.Share[]` | |
150 |
151 | ### proxyCall
152 |
153 | ```solidity
154 | function proxyCall(address payable target, bytes callData) external nonpayable
155 | ```
156 |
157 | Allows the split recipients to make an arbitrary contract call.
158 |
159 | *This is provided to allow recovering from unexpected scenarios, such as receiving an NFT at this address. It will first attempt a fair split of ERC20 tokens before proceeding. This contract is built to split ETH payments. The ability to attempt to make other calls is here just in case other assets were also sent so that they don't get locked forever in the contract.*
160 |
161 | **Parameters**
162 |
163 | | Name | Type | Description |
164 | |---|---|---|
165 | | target | `address payable` | The address of the contract to call. |
166 | | callData | `bytes` | The data to send to the `target` contract. |
167 |
168 | ### splitERC20Tokens
169 |
170 | ```solidity
171 | function splitERC20Tokens(contract IERC20 erc20Contract) external nonpayable
172 | ```
173 |
174 | Anyone can call this function to split all available tokens at the provided address between the recipients.
175 |
176 | *This contract is built to split ETH payments. The ability to attempt to split ERC20 tokens is here just in case tokens were also sent so that they don't get locked forever in the contract.*
177 |
178 | **Parameters**
179 |
180 | | Name | Type | Description |
181 | |---|---|---|
182 | | erc20Contract | `contract IERC20` | The address of the ERC20 token contract to split tokens for. |
183 |
184 | ### splitETH
185 |
186 | ```solidity
187 | function splitETH() external nonpayable
188 | ```
189 |
190 | Allows any ETH stored by the contract to be split among recipients.
191 |
192 | *Normally ETH is forwarded as it comes in, but a balance in this contract is possible if it was sent before the contract was created or if self destruct was used.*
193 |
194 |
195 |
196 |
197 | ## Events
198 |
199 | ### ERC20Transferred
200 |
201 | ```solidity
202 | event ERC20Transferred(address indexed erc20Contract, address indexed account, uint256 amount)
203 | ```
204 |
205 | Emitted when an ERC20 token is transferred to a recipient through this split contract.
206 |
207 |
208 |
209 | **Parameters**
210 |
211 | | Name | Type | Description |
212 | |---|---|---|
213 | | erc20Contract `indexed` | `address` | The address of the ERC20 token contract. |
214 | | account `indexed` | `address` | The account which received payment. |
215 | | amount | `uint256` | The amount of ERC20 tokens sent to this recipient. |
216 |
217 | ### ETHTransferred
218 |
219 | ```solidity
220 | event ETHTransferred(address indexed account, uint256 amount)
221 | ```
222 |
223 | Emitted when ETH is transferred to a recipient through this split contract.
224 |
225 |
226 |
227 | **Parameters**
228 |
229 | | Name | Type | Description |
230 | |---|---|---|
231 | | account `indexed` | `address` | The account which received payment. |
232 | | amount | `uint256` | The amount of ETH payment sent to this recipient. |
233 |
234 | ### Initialized
235 |
236 | ```solidity
237 | event Initialized(uint8 version)
238 | ```
239 |
240 |
241 |
242 |
243 |
244 | **Parameters**
245 |
246 | | Name | Type | Description |
247 | |---|---|---|
248 | | version | `uint8` | |
249 |
250 | ### PercentSplitCreated
251 |
252 | ```solidity
253 | event PercentSplitCreated(address indexed contractAddress)
254 | ```
255 |
256 | Emitted when a new percent split contract is created from this factory.
257 |
258 |
259 |
260 | **Parameters**
261 |
262 | | Name | Type | Description |
263 | |---|---|---|
264 | | contractAddress `indexed` | `address` | The address of the new percent split contract. |
265 |
266 | ### PercentSplitShare
267 |
268 | ```solidity
269 | event PercentSplitShare(address indexed recipient, uint256 percentInBasisPoints)
270 | ```
271 |
272 | Emitted for each share of the split being defined.
273 |
274 |
275 |
276 | **Parameters**
277 |
278 | | Name | Type | Description |
279 | |---|---|---|
280 | | recipient `indexed` | `address` | The address of the recipient when payment to the split is received. |
281 | | percentInBasisPoints | `uint256` | The percent of the payment received by the recipient, in basis points. |
282 |
283 |
284 |
285 |
--------------------------------------------------------------------------------
/docs_template.sqrl:
--------------------------------------------------------------------------------
1 | ---
2 | {{@if (it.name)}}title: {{it.name}}{{/if}}
3 |
4 | {{@if (it.title)}}description: {{it.title}}{{/if}}
5 |
6 | ---
7 |
8 |
9 | {{@if (it.notice)}}{{it.notice}}{{/if}}
10 |
11 |
12 | {{@if (it.details)}}*{{it.details}}*{{/if}}
13 |
14 |
15 | {{@if (Object.keys(it.methods).length > 0)}}
16 | ## Methods
17 |
18 | {{@foreach(it.methods) => key, val}}
19 | ### {{key.split('(')[0]}}
20 |
21 |
22 | ```solidity
23 | {{val.code}}
24 |
25 | ```
26 |
27 | {{@if (val.notice)}}{{val.notice}}{{/if}}
28 |
29 |
30 | {{@if (val.details)}}*{{val.details}}*{{/if}}
31 |
32 |
33 | {{@if (Object.keys(val.inputs).length > 0)}}
34 | **Parameters**
35 |
36 | | Name | Type | Description |
37 | |---|---|---|
38 | {{@foreach(val.inputs) => key, val}}
39 | | {{key}} | `{{val.type}}` | {{@if (val.description)}}{{val.description}}{{/if}} |
40 | {{/foreach}}
41 | {{/if}}
42 |
43 | {{@if (Object.keys(val.outputs).length > 0)}}
44 | **Returns**
45 |
46 | | Name | Type | Description |
47 | |---|---|---|
48 | {{@foreach(val.outputs) => key, val}}
49 | | {{key}} | `{{val.type}}` | {{@if (val.description)}}{{val.description}}{{/if}} |
50 | {{/foreach}}
51 |
52 | {{/if}}
53 | {{/foreach}}
54 |
55 | {{/if}}
56 |
57 | {{@if (Object.keys(it.events).length > 0)}}
58 | ## Events
59 |
60 | {{@foreach(it.events) => key, val}}
61 | ### {{key}}
62 |
63 |
64 | ```solidity
65 | {{val.code}}
66 |
67 | ```
68 |
69 | {{@if (val.notice)}}{{val.notice}}{{/if}}
70 |
71 |
72 | {{@if (val.details)}}*{{val.details}}*{{/if}}
73 |
74 |
75 | {{@if (Object.keys(val.inputs).length > 0)}}
76 | **Parameters**
77 |
78 | | Name | Type | Description |
79 | |---|---|---|
80 | {{@foreach(val.inputs) => key, val}}
81 | | {{key}} {{@if (val.indexed)}}`indexed`{{/if}} | `{{val.type}}` | {{@if (val.description)}}{{val.description}}{{/if}} |
82 | {{/foreach}}
83 | {{/if}}
84 |
85 | {{/foreach}}
86 |
87 | {{/if}}
88 |
89 | {{@if (Object.keys(it.errors).length > 0)}}
90 | ## Errors
91 |
92 | {{@foreach(it.errors) => key, val}}
93 | ### {{key}}
94 |
95 |
96 | ```solidity
97 | {{val.code}}
98 |
99 | ```
100 |
101 | {{@if (val.notice)}}{{val.notice}}{{/if}}
102 |
103 |
104 | {{@if (val.details)}}*{{val.details}}*{{/if}}
105 |
106 |
107 | {{@if (Object.keys(val.inputs).length > 0)}}
108 | **Parameters**
109 |
110 | | Name | Type | Description |
111 | |---|---|---|
112 | {{@foreach(val.inputs) => key, val}}
113 | | {{key}} | `{{val.type}}` | {{@if (val.description)}}{{val.description}}{{/if}} |
114 | {{/foreach}}
115 | {{/if}}
116 |
117 | {{/foreach}}
118 |
119 | {{/if}}
--------------------------------------------------------------------------------
/hardhat.config.ts:
--------------------------------------------------------------------------------
1 | import "hardhat-exposed";
2 | import "@nomicfoundation/hardhat-chai-matchers";
3 | import "@typechain/hardhat";
4 | import "solidity-coverage";
5 | import "hardhat-deploy";
6 | import "hardhat-deploy-ethers";
7 | import "@openzeppelin/hardhat-upgrades";
8 | import "@nomiclabs/hardhat-etherscan";
9 | import "hardhat-preprocessor";
10 | import "hardhat-tracer";
11 | import "hardhat-storage-layout";
12 | import "hardhat-exposed";
13 | import "hardhat-gas-reporter";
14 | import "@primitivefi/hardhat-dodoc";
15 |
16 | import { HardhatUserConfig } from "hardhat/types";
17 |
18 | const config: HardhatUserConfig = {
19 | solidity: {
20 | version: "0.8.17",
21 | settings: {
22 | optimizer: {
23 | enabled: true,
24 | runs: 1337,
25 | },
26 | },
27 | },
28 | networks: {
29 | hardhat: {
30 | accounts: {
31 | count: 100,
32 | },
33 | },
34 | },
35 | typechain: {
36 | target: "ethers-v5",
37 | externalArtifacts: ["node_modules/@manifoldxyz/royalty-registry-solidity/build/contracts/*.json"],
38 | outDir: "src/typechain",
39 | },
40 | gasReporter: {
41 | excludeContracts: ["mocks/", "FoundationTreasury.sol", "ERC721.sol"],
42 | },
43 | dodoc: {
44 | runOnCompile: true,
45 | include: [
46 | "NFTCollection",
47 | "NFTDropCollection",
48 | "FETH",
49 | "FNDMiddleware",
50 | "NFTCollectionFactory",
51 | "NFTMarket",
52 | "NFTDropMarket",
53 | "FoundationTreasury",
54 | "PercentSplitETH",
55 | ],
56 | exclude: ["mixins", "mocks", "interfaces", "xyz/royalty-registry-solidity", "archive", "contracts-exposed"],
57 | templatePath: "./docs_template.sqrl",
58 | },
59 | };
60 |
61 | export default config;
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@f8n/fnd-protocol",
3 | "description": "Foundation smart contracts",
4 | "version": "2.3.0",
5 | "contributors": [
6 | {
7 | "name": "batu-inal",
8 | "url": "https://twitter.com/Ba2Inal"
9 | },
10 | {
11 | "name": "HardlyDifficult",
12 | "url": "https://twitter.com/HardlyDifficult"
13 | }
14 | ],
15 | "scripts": {
16 | "build": "hardhat typechain",
17 | "clean": "hardhat clean",
18 | "lint": "yarn prettier && yarn lint:sol && yarn lint:ts",
19 | "lint:sol": "solhint --config ./.solhint.json --fix \"contracts/**/*.sol\"",
20 | "lint:ts": "eslint --config ./.eslintrc.yaml --fix --ext .js,.ts .",
21 | "prettier": "prettier --config .prettierrc --write --list-different \"**/*.{js,json,md,sol,ts}\""
22 | },
23 | "devDependencies": {
24 | "@ensdomains/eth-ens-namehash": "2.0.15",
25 | "@ethersproject/abstract-signer": "5.7.0",
26 | "@ethersproject/bignumber": "5.7.0",
27 | "@manifoldxyz/royalty-registry-solidity": "1.0.9",
28 | "@nomicfoundation/hardhat-chai-matchers": "1.0.3",
29 | "@nomicfoundation/hardhat-toolbox": "1.0.2",
30 | "@nomiclabs/hardhat-ethers": "2.1.1",
31 | "@nomiclabs/hardhat-etherscan": "3.1.0",
32 | "@openzeppelin/contracts": "4.7.3",
33 | "@openzeppelin/contracts-upgradeable": "4.7.3",
34 | "@openzeppelin/hardhat-upgrades": "1.20.0",
35 | "@openzeppelin/test-helpers": "0.5.16",
36 | "@primitivefi/hardhat-dodoc": "0.2.3",
37 | "@typechain/ethers-v5": "9.0.0",
38 | "@typechain/hardhat": "6.1.3",
39 | "@types/chai": "4.3.3",
40 | "@types/fs-extra": "9.0.13",
41 | "@types/mocha": "9.1.1",
42 | "@types/node": "18.7.18",
43 | "@typescript-eslint/eslint-plugin": "5.37.0",
44 | "@typescript-eslint/parser": "5.37.0",
45 | "chai": "4.3.6",
46 | "chalk": "4.1.2",
47 | "coveralls": "3.1.1",
48 | "defender-admin-client": "1.31.1",
49 | "dotenv": "16.0.2",
50 | "eslint": "8.23.1",
51 | "eslint-config-prettier": "8.5.0",
52 | "eslint-plugin-no-only-tests": "3.0.0",
53 | "eslint-plugin-simple-import-sort": "8.0.0",
54 | "eslint-plugin-unused-imports": "2.0.0",
55 | "ethereum-waffle": "3.4.4",
56 | "ethereumjs-util": "7.1.5",
57 | "ethers": "5.7.1",
58 | "fs-extra": "10.1.0",
59 | "hardhat": "2.11.1",
60 | "hardhat-contract-sizer": "2.6.1",
61 | "hardhat-deploy": "0.11.15",
62 | "hardhat-deploy-ethers": "0.3.0-beta.13",
63 | "hardhat-exposed": "0.2.8",
64 | "hardhat-gas-reporter": "1.0.9",
65 | "hardhat-helpers": "0.1.28",
66 | "hardhat-output-validator": "0.1.19",
67 | "hardhat-preprocessor": "0.1.5",
68 | "hardhat-storage-layout": "0.1.6",
69 | "hardhat-tracer": "1.1.0-rc.9",
70 | "mocha": "10.0.0",
71 | "npm-package-json-lint": "6.3.0",
72 | "npm-package-json-lint-config-default": "5.0.0",
73 | "prettier": "2.7.1",
74 | "prettier-plugin-solidity": "1.0.0-dev.23",
75 | "shelljs": "0.8.5",
76 | "solhint": "3.3.7",
77 | "solhint-plugin-prettier": "0.0.5",
78 | "solidity-coverage": "0.8.2",
79 | "ts-node": "10.9.1",
80 | "typechain": "8.1.0",
81 | "typescript": "4.8.3"
82 | },
83 | "files": [
84 | "addresses.js",
85 | "subgraphEndpoints.js",
86 | "artifacts",
87 | "contracts"
88 | ],
89 | "license": "MIT",
90 | "repository": {
91 | "type": "git",
92 | "url": "https://github.com/f8n/fnd-protocol"
93 | },
94 | "bugs": {
95 | "url": "https://github.com/f8n/fnd-protocol/issues"
96 | },
97 | "homepage": "https://github.com/f8n/fnd-protocol"
98 | }
99 |
--------------------------------------------------------------------------------
/subgraph/codegen.yml:
--------------------------------------------------------------------------------
1 | overwrite: true
2 | schema:
3 | - "schema.graphql"
4 | - "scalars.graphql"
5 | generates:
6 | ../src/types/generated/subgraph.tsx:
7 | plugins:
8 | - "typescript"
9 |
--------------------------------------------------------------------------------
/subgraph/scalars.graphql:
--------------------------------------------------------------------------------
1 | scalar BigInt
2 | scalar BigDecimal
3 | scalar Bytes
--------------------------------------------------------------------------------
/subgraph/src/mappings/collections.ts:
--------------------------------------------------------------------------------
1 | import { Address, BigInt } from "@graphprotocol/graph-ts";
2 |
3 | import { CollectionCreated as CollectionCreatedEvent } from "../../generated/FNDCollectionFactory/FNDCollectionFactoryContract";
4 | import {
5 | NFTCollectionCreated as CollectionCreatedEventV2,
6 | NFTDropCollectionCreated as NFTDropCollectionCreatedEvent,
7 | } from "../../generated/NFTCollectionFactory/NFTCollectionFactoryContract";
8 | import { CollectionContract, NftDropCollectionContract } from "../../generated/schema";
9 | import {
10 | NFTCollection as NFTCollectionTemplate,
11 | NFTDropCollection as NFTDropCollectionTemplate,
12 | } from "../../generated/templates";
13 | import { loadOrCreateAccount } from "../shared/accounts";
14 | import { ZERO_ADDRESS_STRING } from "../shared/constants";
15 | import { loadOrCreateCreator } from "../shared/creators";
16 | import { loadOrCreateNFTContract } from "./nft";
17 |
18 | export function handleCollectionCreated(event: CollectionCreatedEvent): void {
19 | _handleCollectionCreated(
20 | event.params.collection,
21 | event.params.creator,
22 | event.params.version,
23 | event.address,
24 | event.block.timestamp,
25 | );
26 | }
27 |
28 | export function handleCollectionCreatedV2(event: CollectionCreatedEventV2): void {
29 | _handleCollectionCreated(
30 | event.params.collection,
31 | event.params.creator,
32 | event.params.version,
33 | event.address,
34 | event.block.timestamp,
35 | );
36 | }
37 |
38 | function _handleCollectionCreated(
39 | nftCollection: Address,
40 | creator: Address,
41 | version: BigInt,
42 | eventAddress: Address,
43 | eventTS: BigInt,
44 | ): void {
45 | NFTCollectionTemplate.create(nftCollection);
46 | let nftContract = loadOrCreateNFTContract(nftCollection);
47 | let collectionEntity = CollectionContract.load(nftCollection.toHex());
48 | if (collectionEntity) {
49 | collectionEntity.dateSelfDestructed = null;
50 | } else {
51 | collectionEntity = new CollectionContract(nftCollection.toHex());
52 | }
53 | collectionEntity.nftContract = nftContract.id;
54 | collectionEntity.creator = loadOrCreateCreator(creator).id;
55 | collectionEntity.version = eventAddress.toHex() + "-" + version.toString();
56 | collectionEntity.dateCreated = eventTS;
57 | collectionEntity.save();
58 | }
59 |
60 | export function handleNFTDropCollectionCreated(event: NFTDropCollectionCreatedEvent): void {
61 | NFTDropCollectionTemplate.create(event.params.collection);
62 | let nftDropCollection = NftDropCollectionContract.load(event.params.collection.toHex());
63 | if (nftDropCollection) {
64 | nftDropCollection.dateSelfDestructed = null;
65 | } else {
66 | nftDropCollection = new NftDropCollectionContract(event.params.collection.toHex());
67 | }
68 | let nftContract = loadOrCreateNFTContract(event.params.collection, /*fromNFTDropCollection=*/ true);
69 | nftDropCollection.nftContract = nftContract.id;
70 | nftDropCollection.creator = loadOrCreateCreator(event.params.creator).id;
71 | nftDropCollection.dateCreated = event.block.timestamp;
72 | if (event.params.approvedMinter.toHex() != ZERO_ADDRESS_STRING) {
73 | nftDropCollection.approvedMinter = loadOrCreateAccount(event.params.approvedMinter).id;
74 | }
75 | nftDropCollection.paymentAddress = loadOrCreateAccount(event.params.paymentAddress).id;
76 | nftDropCollection.version = event.address.toHex() + "-" + event.params.version.toString();
77 | nftDropCollection.save();
78 | }
79 |
--------------------------------------------------------------------------------
/subgraph/src/mappings/feth.ts:
--------------------------------------------------------------------------------
1 | import { BalanceLocked, BalanceUnlocked, ETHWithdrawn, Transfer } from "../../generated/Feth/FethContract";
2 | import { loadOrCreateAccount } from "../shared/accounts";
3 | import { ZERO_ADDRESS_STRING, ZERO_BIG_DECIMAL } from "../shared/constants";
4 | import { toETH } from "../shared/conversions";
5 | import { loadOrCreateFeth, loadOrCreateFethEscrow } from "../shared/feth";
6 |
7 | export function handleTransfer(event: Transfer): void {
8 | if (event.params.from.toHex() != ZERO_ADDRESS_STRING) {
9 | let from = loadOrCreateAccount(event.params.from);
10 | let fethFrom = loadOrCreateFeth(from, event.block);
11 | fethFrom.balanceInETH = fethFrom.balanceInETH.minus(toETH(event.params.amount));
12 | fethFrom.dateLastUpdated = event.block.timestamp;
13 | fethFrom.save();
14 | }
15 |
16 | let to = loadOrCreateAccount(event.params.to);
17 | let fethTo = loadOrCreateFeth(to, event.block);
18 | fethTo.balanceInETH = fethTo.balanceInETH.plus(toETH(event.params.amount));
19 | fethTo.dateLastUpdated = event.block.timestamp;
20 | fethTo.save();
21 | }
22 |
23 | export function handleETHWithdrawn(event: ETHWithdrawn): void {
24 | let from = loadOrCreateAccount(event.params.from);
25 | let fethFrom = loadOrCreateFeth(from, event.block);
26 | fethFrom.balanceInETH = fethFrom.balanceInETH.minus(toETH(event.params.amount));
27 | fethFrom.dateLastUpdated = event.block.timestamp;
28 | fethFrom.save();
29 | }
30 |
31 | export function handleBalanceLocked(event: BalanceLocked): void {
32 | let to = loadOrCreateAccount(event.params.account);
33 | let fethTo = loadOrCreateFeth(to, event.block);
34 | fethTo.balanceInETH = fethTo.balanceInETH.plus(toETH(event.params.valueDeposited));
35 | fethTo.dateLastUpdated = event.block.timestamp;
36 | fethTo.save();
37 | let escrow = loadOrCreateFethEscrow(event, to);
38 | if (escrow.dateRemoved) {
39 | escrow.amountInETH = toETH(event.params.amount);
40 | escrow.dateRemoved = null;
41 | escrow.transactionHashRemoved = null;
42 | } else {
43 | escrow.amountInETH = escrow.amountInETH.plus(toETH(event.params.amount));
44 | }
45 |
46 | escrow.dateExpiry = event.params.expiration;
47 | escrow.transactionHashCreated = event.transaction.hash;
48 | escrow.save();
49 | }
50 |
51 | export function handleBalanceUnlocked(event: BalanceUnlocked): void {
52 | let from = loadOrCreateAccount(event.params.account);
53 | let escrow = loadOrCreateFethEscrow(event, from);
54 | escrow.amountInETH = escrow.amountInETH.minus(toETH(event.params.amount));
55 | if (escrow.amountInETH.equals(ZERO_BIG_DECIMAL)) {
56 | escrow.transactionHashRemoved = event.transaction.hash;
57 | escrow.dateRemoved = event.block.timestamp;
58 | }
59 | escrow.save();
60 | }
61 |
--------------------------------------------------------------------------------
/subgraph/src/mappings/nft.ts:
--------------------------------------------------------------------------------
1 | import { Address, BigInt, ethereum, store } from "@graphprotocol/graph-ts";
2 |
3 | import {
4 | NFTOwnerMigrated,
5 | PaymentAddressMigrated,
6 | TokenCreatorPaymentAddressSet,
7 | TokenCreatorUpdated,
8 | } from "../../generated/NFT721Contract/NFT721Contract";
9 | import {
10 | CollectionContract,
11 | FixedPriceSaleMint,
12 | Nft,
13 | NftAccountApproval,
14 | NftContract,
15 | NftDropCollectionContract,
16 | NftTransfer,
17 | } from "../../generated/schema";
18 | import {
19 | Approval,
20 | ApprovalForAll,
21 | BaseURIUpdated,
22 | Minted,
23 | NFTCollection as NFTCollectionABI,
24 | SelfDestruct,
25 | Transfer,
26 | } from "../../generated/templates/NFTCollection/NFTCollection";
27 | import {
28 | MaxTokenIdUpdated,
29 | NFTDropCollection,
30 | URIUpdated,
31 | } from "../../generated/templates/NFTDropCollection/NFTDropCollection";
32 | import { loadOrCreateAccount } from "../shared/accounts";
33 | import { ZERO_ADDRESS_STRING, ZERO_BIG_DECIMAL } from "../shared/constants";
34 | import { loadOrCreateCreator } from "../shared/creators";
35 | import { recordNftEvent, removePreviousTransferEvent } from "../shared/events";
36 | import { getLogId } from "../shared/ids";
37 |
38 | export function loadOrCreateNFTContract(address: Address, fromNFTDropCollection: boolean = false): NftContract {
39 | let nftContract = NftContract.load(address.toHex());
40 | if (!nftContract) {
41 | if (fromNFTDropCollection) {
42 | nftContract = _createNFTContractFromNFTDropCollection(address);
43 | } else {
44 | nftContract = _createNFTContractFromCollection(address);
45 | }
46 | }
47 | return nftContract as NftContract;
48 | }
49 |
50 | function _createNFTContractFromCollection(address: Address): NftContract {
51 | let nftContract = new NftContract(address.toHex());
52 | let contract = NFTCollectionABI.bind(address);
53 | let nameResults = contract.try_name();
54 | if (!nameResults.reverted) {
55 | nftContract.name = nameResults.value;
56 | }
57 | let symbolResults = contract.try_symbol();
58 | if (!symbolResults.reverted) {
59 | nftContract.symbol = symbolResults.value;
60 | }
61 | nftContract.baseURI = "ipfs://";
62 | nftContract.save();
63 | return nftContract;
64 | }
65 |
66 | function _createNFTContractFromNFTDropCollection(address: Address): NftContract {
67 | let nftContract = new NftContract(address.toHex());
68 | let contract = NFTDropCollection.bind(address);
69 | let nameResults = contract.try_name();
70 | if (!nameResults.reverted) {
71 | nftContract.name = nameResults.value;
72 | }
73 | let symbolResults = contract.try_symbol();
74 | if (!symbolResults.reverted) {
75 | nftContract.symbol = symbolResults.value;
76 | }
77 | let baseURIResults = contract.try_baseURI();
78 | if (!baseURIResults.reverted) {
79 | nftContract.baseURI = baseURIResults.value;
80 | }
81 | let maxTokenIdResults = contract.try_maxTokenId();
82 | if (!maxTokenIdResults.reverted) {
83 | nftContract.maxTokenID = maxTokenIdResults.value;
84 | }
85 | nftContract.save();
86 | return nftContract;
87 | }
88 |
89 | function getNFTId(address: Address, id: BigInt): string {
90 | return address.toHex() + "-" + id.toString();
91 | }
92 |
93 | export function loadOrCreateNFT(address: Address, id: BigInt, event: ethereum.Event): Nft {
94 | let nftId = getNFTId(address, id);
95 | let nft = Nft.load(nftId);
96 | if (!nft) {
97 | nft = new Nft(nftId);
98 | nft.nftContract = loadOrCreateNFTContract(address).id;
99 | nft.tokenId = id;
100 | nft.dateMinted = event.block.timestamp;
101 | let contract = NFTCollectionABI.bind(address);
102 | let ownerResult = contract.try_ownerOf(id);
103 | if (!ownerResult.reverted) {
104 | nft.owner = loadOrCreateAccount(ownerResult.value).id;
105 | } else {
106 | nft.owner = loadOrCreateAccount(Address.zero()).id;
107 | }
108 | nft.ownedOrListedBy = nft.owner;
109 | nft.netSalesInETH = ZERO_BIG_DECIMAL;
110 | nft.netSalesPendingInETH = ZERO_BIG_DECIMAL;
111 | nft.netRevenueInETH = ZERO_BIG_DECIMAL;
112 | nft.netRevenuePendingInETH = ZERO_BIG_DECIMAL;
113 | nft.isFirstSale = true;
114 | let pathResult = contract.try_tokenURI(id);
115 | if (!pathResult.reverted) {
116 | nft.tokenIPFSPath = pathResult.value;
117 | }
118 | let creatorResult = contract.try_tokenCreator(id);
119 | if (!creatorResult.reverted) {
120 | nft.creator = loadOrCreateCreator(creatorResult.value).id;
121 | }
122 | nft.save();
123 | }
124 |
125 | return nft as Nft;
126 | }
127 |
128 | export function handleApproval(event: Approval): void {
129 | let nft = loadOrCreateNFT(event.address, event.params.tokenId, event);
130 | if (event.params.approved != Address.zero()) {
131 | nft.approvedSpender = loadOrCreateAccount(event.params.approved).id;
132 | } else {
133 | nft.approvedSpender = null;
134 | }
135 | nft.save();
136 | }
137 |
138 | export function handleApprovalForAll(event: ApprovalForAll): void {
139 | let id = event.address.toHex() + "-" + event.params.owner.toHex() + "-" + event.params.operator.toHex();
140 | if (event.params.approved) {
141 | let nft721AccountApproval = new NftAccountApproval(id);
142 | let nftContract = loadOrCreateNFTContract(event.address);
143 | nft721AccountApproval.nftContract = nftContract.id;
144 | nft721AccountApproval.owner = loadOrCreateAccount(event.params.owner).id;
145 | nft721AccountApproval.spender = loadOrCreateAccount(event.params.operator).id;
146 | nft721AccountApproval.save();
147 | } else {
148 | store.remove("NftAccountApproval", id);
149 | }
150 | }
151 |
152 | export function handleBaseURIUpdated(event: BaseURIUpdated): void {
153 | _handleBaseURIUpdated(event.address, event.params.baseURI);
154 | }
155 |
156 | export function handleURIUpdated(event: URIUpdated): void {
157 | _handleBaseURIUpdated(event.address, event.params.baseURI);
158 | }
159 |
160 | function _handleBaseURIUpdated(eventAddress: Address, baseURI: string): void {
161 | let nftContract = loadOrCreateNFTContract(eventAddress);
162 | nftContract.baseURI = baseURI;
163 | nftContract.save();
164 | }
165 |
166 | export function handleMaxTokenIdUpdated(event: MaxTokenIdUpdated): void {
167 | let nftContract = loadOrCreateNFTContract(event.address);
168 | nftContract.maxTokenID = event.params.maxTokenId;
169 | nftContract.save();
170 | }
171 |
172 | export function handleTokenCreatorUpdated(event: TokenCreatorUpdated): void {
173 | let nft = loadOrCreateNFT(event.address, event.params.tokenId, event);
174 | nft.creator = loadOrCreateCreator(event.params.toCreator).id;
175 | nft.save();
176 |
177 | if (event.params.fromCreator.toHex() != ZERO_ADDRESS_STRING) {
178 | recordNftEvent(
179 | event,
180 | nft as Nft,
181 | "CreatorMigrated",
182 | loadOrCreateAccount(event.params.fromCreator),
183 | null,
184 | null,
185 | null,
186 | loadOrCreateAccount(event.params.toCreator),
187 | );
188 | }
189 | }
190 |
191 | export function handleMinted(event: Minted): void {
192 | let nft = loadOrCreateNFT(event.address, event.params.tokenId, event);
193 | updateTokenIPFSPath(nft as Nft, event.params.tokenCID.toString(), event.block.timestamp);
194 |
195 | nft.creator = loadOrCreateCreator(event.params.creator).id;
196 | nft.save();
197 | let creatorAccount = loadOrCreateAccount(event.params.creator);
198 | recordNftEvent(event, nft as Nft, "Minted", creatorAccount, null, null, null, creatorAccount);
199 | }
200 |
201 | export function handleTransfer(event: Transfer): void {
202 | let nftContract = loadOrCreateNFTContract(event.address);
203 | let nftId = getNFTId(event.address, event.params.tokenId);
204 | let nft: Nft | null;
205 | if (event.params.from.toHex() == ZERO_ADDRESS_STRING) {
206 | // Mint
207 | nft = new Nft(nftId);
208 | nft.nftContract = nftContract.id;
209 | nft.tokenId = event.params.tokenId;
210 | nft.dateMinted = event.block.timestamp;
211 | nft.owner = loadOrCreateAccount(event.params.to).id;
212 | nft.ownedOrListedBy = nft.owner;
213 | nft.netSalesInETH = ZERO_BIG_DECIMAL;
214 | nft.netSalesPendingInETH = ZERO_BIG_DECIMAL;
215 | nft.netRevenueInETH = ZERO_BIG_DECIMAL;
216 | nft.netRevenuePendingInETH = ZERO_BIG_DECIMAL;
217 | nft.isFirstSale = true;
218 | nft.save();
219 | // Only for NftDropCollectionContract add mint event here.
220 | // NFTCollection handles this in the handleMint handler.
221 | let nftDropCollection = NftDropCollectionContract.load(nftContract.id);
222 | if (nftDropCollection) {
223 | if (event.transaction.from.toHexString() !== nftDropCollection.creator) {
224 | if (!FixedPriceSaleMint.load(event.transaction.hash.toHex())) {
225 | let fixedPriceSaleMint = new FixedPriceSaleMint(event.transaction.hash.toHex());
226 | fixedPriceSaleMint.fixedPriceSale = nftContract.id;
227 | fixedPriceSaleMint.save();
228 | }
229 | }
230 | let creatorAccount = loadOrCreateAccount(Address.fromString(nftDropCollection.creator));
231 | recordNftEvent(event, nft as Nft, "Minted", creatorAccount, null, null, null, creatorAccount);
232 | }
233 | } else {
234 | // Transfer or Burn
235 | nft = loadOrCreateNFT(event.address, event.params.tokenId, event);
236 | nft.owner = loadOrCreateAccount(event.params.to).id;
237 | nft.ownedOrListedBy = nft.owner;
238 |
239 | if (event.params.to.toHex() == ZERO_ADDRESS_STRING) {
240 | // Burn
241 | recordNftEvent(event, nft as Nft, "Burned", loadOrCreateAccount(event.params.from));
242 | } else {
243 | // Transfer
244 | recordNftEvent(
245 | event,
246 | nft as Nft,
247 | "Transferred",
248 | loadOrCreateAccount(event.params.from),
249 | null,
250 | null,
251 | null,
252 | loadOrCreateAccount(event.params.to),
253 | );
254 | }
255 | }
256 |
257 | let transfer = new NftTransfer(getLogId(event));
258 | transfer.nft = nftId;
259 | transfer.from = loadOrCreateAccount(event.params.from).id;
260 | transfer.to = loadOrCreateAccount(event.params.to).id;
261 | transfer.dateTransferred = event.block.timestamp;
262 | transfer.transactionHash = event.transaction.hash;
263 | transfer.save();
264 |
265 | if (event.params.from.toHex() == ZERO_ADDRESS_STRING) {
266 | nft.mintedTransfer = transfer.id;
267 | }
268 | nft.save();
269 | }
270 |
271 | function updateTokenIPFSPath(nft: Nft, tokenIPFSPath: string, _: BigInt): void {
272 | nft.tokenIPFSPath = tokenIPFSPath;
273 | nft.save();
274 | }
275 |
276 | export function handleTokenCreatorPaymentAddressSet(event: TokenCreatorPaymentAddressSet): void {
277 | let nft = loadOrCreateNFT(event.address, event.params.tokenId, event);
278 | nft.tokenCreatorPaymentAddress = event.params.toPaymentAddress;
279 | // This field resets to null if a PercentSplit does not exist at this address
280 | nft.percentSplit = event.params.toPaymentAddress.toHex();
281 | nft.save();
282 | }
283 |
284 | export function handlePaymentAddressMigrated(event: PaymentAddressMigrated): void {
285 | let nft = loadOrCreateNFT(event.address, event.params.tokenId, event);
286 | let originalAccount = loadOrCreateAccount(event.params.originalAddress);
287 | let newAccount = loadOrCreateAccount(event.params.newAddress);
288 |
289 | recordNftEvent(event, nft as Nft, "CreatorPaymentAddressMigrated", originalAccount, null, null, null, newAccount);
290 | }
291 |
292 | export function handleNFTOwnerMigrated(event: NFTOwnerMigrated): void {
293 | let nft = loadOrCreateNFT(event.address, event.params.tokenId, event);
294 | recordNftEvent(
295 | event,
296 | nft as Nft,
297 | "OwnerMigrated",
298 | loadOrCreateAccount(event.params.originalAddress),
299 | null,
300 | null,
301 | null,
302 | loadOrCreateAccount(event.params.newAddress),
303 | );
304 | removePreviousTransferEvent(event);
305 | }
306 |
307 | export function handleSelfDestruct(event: SelfDestruct): void {
308 | let collection = CollectionContract.load(event.address.toHex());
309 | if (collection) {
310 | collection.dateSelfDestructed = event.block.timestamp;
311 | collection.save();
312 | return;
313 | }
314 | let nftDropCollection = NftDropCollectionContract.load(event.address.toHex());
315 | if (nftDropCollection) {
316 | nftDropCollection.dateSelfDestructed = event.block.timestamp;
317 | nftDropCollection.save();
318 | }
319 | }
320 |
--------------------------------------------------------------------------------
/subgraph/src/mappings/nftDropMarket.ts:
--------------------------------------------------------------------------------
1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts";
2 |
3 | import {
4 | BuyReferralPaid,
5 | CreateFixedPriceSale,
6 | MintFromFixedPriceDrop,
7 | } from "../../generated/NFTDropMarket/NFTDropMarketContract";
8 | import { FixedPriceSale, FixedPriceSaleMint } from "../../generated/schema";
9 | import { loadOrCreateAccount } from "../shared/accounts";
10 | import { toETH } from "../shared/conversions";
11 | import { loadOrCreateNFTContract } from "./nft";
12 | import { loadOrCreateNFTMarketContract } from "./nftMarket";
13 |
14 | export function handleBuyReferralPaid(event: BuyReferralPaid): void {
15 | // FixedPriceSale
16 | let fixedPriceSaleMint = FixedPriceSaleMint.load(event.transaction.hash.toHex());
17 | if (fixedPriceSaleMint) {
18 | let fixedPriceSale = FixedPriceSale.load(fixedPriceSaleMint.fixedPriceSale);
19 | if (fixedPriceSale) {
20 | fixedPriceSaleMint.buyReferrer = loadOrCreateAccount(event.params.buyReferrer).id;
21 | fixedPriceSaleMint.buyReferrerFee = toETH(event.params.buyReferrerFee);
22 | if (event.params.buyReferrerFee) {
23 | if (fixedPriceSale.buyReferrerFee) {
24 | fixedPriceSale.buyReferrerFee = (fixedPriceSale.buyReferrerFee as BigDecimal).plus(
25 | toETH(event.params.buyReferrerFee),
26 | );
27 | } else {
28 | fixedPriceSale.buyReferrerFee = toETH(event.params.buyReferrerFee);
29 | }
30 | }
31 | fixedPriceSaleMint.save();
32 | fixedPriceSale.save();
33 | }
34 | return;
35 | }
36 | }
37 |
38 | export function handleCreateFixedPriceSale(event: CreateFixedPriceSale): void {
39 | let fixedPriceSale = new FixedPriceSale(event.params.nftContract.toHex());
40 | fixedPriceSale.nftMarketContract = loadOrCreateNFTMarketContract(event.address).id;
41 | fixedPriceSale.nftContract = loadOrCreateNFTContract(event.params.nftContract).id;
42 | fixedPriceSale.mintCount = BigInt.zero();
43 | fixedPriceSale.seller = loadOrCreateAccount(event.params.seller).id;
44 | fixedPriceSale.unitPriceInETH = toETH(event.params.price);
45 | fixedPriceSale.limitPerAccount = event.params.limitPerAccount;
46 | fixedPriceSale.amountInETH = BigDecimal.zero();
47 | fixedPriceSale.dateCreated = event.block.timestamp;
48 | fixedPriceSale.transactionHashCreated = event.transaction.hash;
49 | fixedPriceSale.creatorRevenueInETH = BigDecimal.zero();
50 | fixedPriceSale.foundationRevenueInETH = BigDecimal.zero();
51 | fixedPriceSale.foundationProtocolFeeInETH = BigDecimal.zero();
52 | fixedPriceSale.buyReferrerFee = BigDecimal.zero();
53 | fixedPriceSale.save();
54 | }
55 |
56 | export function handleMintFromFixedPriceDrop(event: MintFromFixedPriceDrop): void {
57 | let fixedPriceSale = FixedPriceSale.load(event.params.nftContract.toHex());
58 | if (fixedPriceSale) {
59 | let fixedPriceSaleMint = FixedPriceSaleMint.load(event.transaction.hash.toHex());
60 | if (fixedPriceSaleMint) {
61 | fixedPriceSaleMint.fixedPriceSale = fixedPriceSale.id;
62 | fixedPriceSaleMint.buyer = loadOrCreateAccount(event.params.buyer).id;
63 | fixedPriceSaleMint.count = event.params.count;
64 | fixedPriceSaleMint.firstTokenId = event.params.firstTokenId;
65 | fixedPriceSaleMint.amountInETH = toETH(event.params.creatorRev).plus(toETH(event.params.totalFees));
66 |
67 | // update Fixed Price Sale
68 | fixedPriceSale.mintCount = fixedPriceSale.mintCount.plus(event.params.count);
69 | fixedPriceSale.amountInETH = fixedPriceSale.amountInETH.plus(fixedPriceSaleMint.amountInETH as BigDecimal);
70 | fixedPriceSale.creatorRevenueInETH = fixedPriceSale.creatorRevenueInETH.plus(toETH(event.params.creatorRev));
71 | fixedPriceSale.foundationRevenueInETH = fixedPriceSale.foundationRevenueInETH.plus(toETH(event.params.totalFees));
72 | fixedPriceSale.foundationProtocolFeeInETH = fixedPriceSale.foundationProtocolFeeInETH.plus(
73 | toETH(event.params.totalFees).minus(fixedPriceSale.buyReferrerFee as BigDecimal),
74 | );
75 | fixedPriceSale.latestMint = fixedPriceSaleMint.id;
76 |
77 | fixedPriceSaleMint.save();
78 | fixedPriceSale.save();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/subgraph/src/mappings/percentSplit.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PercentSplitCreated as PercentSplitCreatedEvent,
3 | PercentSplitShare as PercentSplitShareEvent,
4 | } from "../../generated/PercentSplit/PercentSplitContract";
5 | import { PercentSplit, PercentSplitShare } from "../../generated/schema";
6 | import { PercentSplit as PercentSplitTemplate } from "../../generated/templates";
7 | import { loadOrCreateAccount } from "../shared/accounts";
8 | import { ONE_BIG_INT, ZERO_BIG_INT } from "../shared/constants";
9 | import { toPercent } from "../shared/conversions";
10 |
11 | export function handlePercentSplitCreated(event: PercentSplitCreatedEvent): void {
12 | let splitEntity = new PercentSplit(event.params.contractAddress.toHex());
13 | splitEntity.shareCount = ZERO_BIG_INT;
14 | splitEntity.dateCreated = event.block.timestamp;
15 | splitEntity.save();
16 | PercentSplitTemplate.create(event.params.contractAddress);
17 | }
18 |
19 | export function handlePercentSplitShare(event: PercentSplitShareEvent): void {
20 | let splitEntity = PercentSplit.load(event.address.toHex());
21 | if (!splitEntity) {
22 | return;
23 | }
24 |
25 | let shareEntity = new PercentSplitShare(event.address.toHex() + "-" + event.logIndex.toString());
26 | shareEntity.split = event.address.toHex();
27 | shareEntity.account = loadOrCreateAccount(event.params.recipient).id;
28 | shareEntity.shareInPercent = toPercent(event.params.percentInBasisPoints);
29 | shareEntity.indexOfShare = splitEntity.shareCount;
30 | shareEntity.save();
31 |
32 | splitEntity.shareCount = splitEntity.shareCount.plus(ONE_BIG_INT);
33 | splitEntity.save();
34 | }
35 |
--------------------------------------------------------------------------------
/subgraph/src/shared/accounts.ts:
--------------------------------------------------------------------------------
1 | import { Address } from "@graphprotocol/graph-ts";
2 |
3 | import { Account } from "../../generated/schema";
4 | import { ZERO_BIG_DECIMAL } from "./constants";
5 |
6 | export function loadOrCreateAccount(address: Address): Account {
7 | let addressHex = address.toHex();
8 | let account = Account.load(addressHex);
9 | if (!account) {
10 | account = new Account(addressHex);
11 | account.netRevenueInETH = ZERO_BIG_DECIMAL;
12 | account.netRevenuePendingInETH = ZERO_BIG_DECIMAL;
13 | account.save();
14 | }
15 | return account as Account;
16 | }
17 |
--------------------------------------------------------------------------------
/subgraph/src/shared/buyNow.ts:
--------------------------------------------------------------------------------
1 | import { Nft, NftMarketBuyNow } from "../../generated/schema";
2 |
3 | export function loadLatestBuyNow(nft: Nft): NftMarketBuyNow | null {
4 | if (!nft.mostRecentBuyNow) {
5 | return null;
6 | }
7 | let buyNow = NftMarketBuyNow.load(nft.mostRecentBuyNow as string);
8 | if (!buyNow || buyNow.status != "Open") {
9 | return null;
10 | }
11 | return buyNow;
12 | }
13 |
--------------------------------------------------------------------------------
/subgraph/src/shared/constants.ts:
--------------------------------------------------------------------------------
1 | import { BigInt } from "@graphprotocol/graph-ts";
2 |
3 | export const ZERO_BYTES_32_STRING = "0x0000000000000000000000000000000000000000000000000000000000000000";
4 | export let ZERO_BIG_INT = BigInt.fromI32(0);
5 | export let ONE_BIG_INT = BigInt.fromI32(1);
6 | export let ZERO_BIG_DECIMAL = ZERO_BIG_INT.toBigDecimal();
7 | export let BASIS_POINTS = BigInt.fromI32(10000);
8 | export let BASIS_POINTS_PER_PERCENT = BigInt.fromI32(100).toBigDecimal();
9 | export let WEI_PER_ETH = BigInt.fromI32(10).pow(18).toBigDecimal();
10 | export let ZERO_ADDRESS_STRING = "0x0000000000000000000000000000000000000000";
11 | export let ONE_MINUTE = BigInt.fromI32(60);
12 | export let TEN_MINUTES = ONE_MINUTE.times(BigInt.fromI32(10));
13 |
--------------------------------------------------------------------------------
/subgraph/src/shared/conversions.ts:
--------------------------------------------------------------------------------
1 | import { BigDecimal, BigInt } from "@graphprotocol/graph-ts";
2 |
3 | import { BASIS_POINTS_PER_PERCENT, WEI_PER_ETH } from "./constants";
4 |
5 | export function toPercent(basisPoints: BigInt): BigDecimal {
6 | return basisPoints.toBigDecimal().div(BASIS_POINTS_PER_PERCENT);
7 | }
8 |
9 | export function toETH(wei: BigInt): BigDecimal {
10 | return wei.toBigDecimal().div(WEI_PER_ETH);
11 | }
12 |
--------------------------------------------------------------------------------
/subgraph/src/shared/creators.ts:
--------------------------------------------------------------------------------
1 | import { Address } from "@graphprotocol/graph-ts";
2 |
3 | import { Creator } from "../../generated/schema";
4 | import { loadOrCreateAccount } from "./accounts";
5 | import { ZERO_BIG_DECIMAL } from "./constants";
6 |
7 | export function loadOrCreateCreator(address: Address): Creator {
8 | let account = loadOrCreateAccount(address);
9 | let creator = Creator.load(account.id);
10 | if (!creator) {
11 | creator = new Creator(account.id);
12 | creator.account = account.id;
13 | creator.netSalesInETH = ZERO_BIG_DECIMAL;
14 | creator.netSalesPendingInETH = ZERO_BIG_DECIMAL;
15 | creator.netRevenueInETH = ZERO_BIG_DECIMAL;
16 | creator.netRevenuePendingInETH = ZERO_BIG_DECIMAL;
17 | creator.save();
18 | }
19 | return creator as Creator;
20 | }
21 |
--------------------------------------------------------------------------------
/subgraph/src/shared/events.ts:
--------------------------------------------------------------------------------
1 | import { BigDecimal, BigInt, Bytes, ethereum, store } from "@graphprotocol/graph-ts";
2 |
3 | import {
4 | Account,
5 | Nft,
6 | NftHistory,
7 | NftMarketAuction,
8 | NftMarketBuyNow,
9 | NftMarketOffer,
10 | PrivateSale,
11 | } from "../../generated/schema";
12 | import { loadOrCreateAccount } from "./accounts";
13 | import { ONE_BIG_INT, ZERO_BIG_INT } from "./constants";
14 | import { getEventId, getPreviousEventId } from "./ids";
15 |
16 | export function recordNftEvent(
17 | event: ethereum.Event,
18 | nft: Nft,
19 | eventType: string,
20 | actorAccount: Account,
21 | auction: NftMarketAuction | null = null,
22 | marketplace: string | null = null,
23 | amountInETH: BigDecimal | null = null,
24 | nftRecipient: Account | null = null,
25 | dateOverride: BigInt | null = null,
26 | amountInTokens: BigInt | null = null,
27 | tokenAddress: Bytes | null = null,
28 | privateSale: PrivateSale | null = null,
29 | offer: NftMarketOffer | null = null,
30 | buyNow: NftMarketBuyNow | null = null,
31 | ): void {
32 | let historicalEvent = new NftHistory(getEventId(event, eventType));
33 | historicalEvent.nft = nft.id;
34 | historicalEvent.event = eventType;
35 | if (auction) {
36 | historicalEvent.auction = auction.id;
37 | }
38 | if (dateOverride) {
39 | historicalEvent.date = dateOverride as BigInt;
40 | } else {
41 | historicalEvent.date = event.block.timestamp;
42 | }
43 | historicalEvent.contractAddress = event.address;
44 | historicalEvent.transactionHash = event.transaction.hash;
45 | historicalEvent.actorAccount = actorAccount.id;
46 | historicalEvent.txOrigin = loadOrCreateAccount(event.transaction.from).id;
47 | if (nftRecipient) {
48 | historicalEvent.nftRecipient = nftRecipient.id;
49 | }
50 | historicalEvent.marketplace = marketplace;
51 | historicalEvent.amountInETH = amountInETH;
52 | historicalEvent.amountInTokens = amountInTokens;
53 | historicalEvent.tokenAddress = tokenAddress;
54 | if (privateSale) {
55 | historicalEvent.privateSale = privateSale.id;
56 | }
57 | if (offer) {
58 | historicalEvent.offer = offer.id;
59 | }
60 | if (buyNow) {
61 | historicalEvent.buyNow = buyNow.id;
62 | }
63 | historicalEvent.save();
64 | }
65 |
66 | export function removePreviousTransferEvent(event: ethereum.Event): void {
67 | // There may be multiple logs that occurred since the last transfer event
68 | for (let i = event.logIndex.minus(ONE_BIG_INT); i.ge(ZERO_BIG_INT); i = i.minus(ONE_BIG_INT)) {
69 | let previousEvent = NftHistory.load(getPreviousEventId(event, "Transferred", i));
70 | if (previousEvent) {
71 | store.remove("NftHistory", previousEvent.id);
72 | return;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/subgraph/src/shared/feth.ts:
--------------------------------------------------------------------------------
1 | import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts";
2 |
3 | import { Account, Feth, FethEscrow } from "../../generated/schema";
4 | import { ZERO_BIG_DECIMAL } from "./constants";
5 |
6 | interface EscrowEventParams {
7 | account: Address;
8 | amount: BigInt;
9 | expiration: BigInt;
10 | }
11 |
12 | interface EscrowEvent {
13 | params: EscrowEventParams;
14 | block: ethereum.Block;
15 | transaction: ethereum.Transaction;
16 | }
17 |
18 | export function getEscrowId(event: T): string {
19 | return event.params.account.toHex() + "-" + event.params.expiration.toString();
20 | }
21 |
22 | export function loadOrCreateFeth(account: Account, block: ethereum.Block): Feth {
23 | let feth = Feth.load(account.id);
24 | if (!feth) {
25 | feth = new Feth(account.id);
26 | feth.user = account.id;
27 | feth.balanceInETH = ZERO_BIG_DECIMAL;
28 | feth.dateLastUpdated = block.timestamp;
29 | }
30 | return feth;
31 | }
32 |
33 | export function loadOrCreateFethEscrow(event: T, account: Account): FethEscrow {
34 | const escrowId = getEscrowId(event);
35 | let fethEscrow = FethEscrow.load(escrowId);
36 | if (!fethEscrow) {
37 | fethEscrow = new FethEscrow(escrowId);
38 | fethEscrow.transactionHashCreated = event.transaction.hash;
39 | fethEscrow.amountInETH = ZERO_BIG_DECIMAL;
40 |
41 | // Placeholder for expiry (now), this should be immediately replaced by the actual expiry
42 | fethEscrow.dateExpiry = event.params.expiration;
43 | }
44 |
45 | // Ensure that the escrow is associated with the account's FETH balance
46 | let fethAccount = loadOrCreateFeth(account, event.block);
47 | fethAccount.save();
48 | fethEscrow.feth = fethAccount.id;
49 |
50 | return fethEscrow;
51 | }
52 |
--------------------------------------------------------------------------------
/subgraph/src/shared/ids.ts:
--------------------------------------------------------------------------------
1 | import { BigInt, ethereum } from "@graphprotocol/graph-ts";
2 |
3 | export function getLogId(event: ethereum.Event): string {
4 | return event.transaction.hash.toHex() + "-" + event.logIndex.toString();
5 | }
6 |
7 | export function getEventId(event: ethereum.Event, eventType: string): string {
8 | return getLogId(event) + "-" + eventType;
9 | }
10 |
11 | export function getPreviousEventId(event: ethereum.Event, eventType: string, logIndex: BigInt): string {
12 | return event.transaction.hash.toHex() + "-" + logIndex.toString() + "-" + eventType;
13 | }
14 |
--------------------------------------------------------------------------------
/subgraph/src/shared/offers.ts:
--------------------------------------------------------------------------------
1 | import { ethereum } from "@graphprotocol/graph-ts";
2 |
3 | import { Account, Nft, NftMarketOffer } from "../../generated/schema";
4 | import { recordNftEvent } from "./events";
5 |
6 | export function loadLatestOffer(nft: Nft): NftMarketOffer | null {
7 | if (!nft.mostRecentOffer) {
8 | return null;
9 | }
10 | let offer = NftMarketOffer.load(nft.mostRecentOffer as string);
11 | if (!offer || offer.status != "Open") {
12 | return null;
13 | }
14 | return offer;
15 | }
16 |
17 | export function outbidOrExpirePreviousOffer(
18 | event: ethereum.Event,
19 | nft: Nft,
20 | newBuyer: Account,
21 | newOffer: NftMarketOffer,
22 | ): boolean {
23 | let offer = loadLatestOffer(nft);
24 | if (!offer) {
25 | // New offer
26 | return false;
27 | }
28 |
29 | let isExpired = offer.dateExpires.lt(event.block.timestamp);
30 | if (isExpired) {
31 | // Previous offer expired
32 | offer.status = "Expired";
33 | offer.save();
34 | let buyer = Account.load(offer.buyer) as Account; // Buyer was set on offer made
35 | recordNftEvent(
36 | event,
37 | nft,
38 | "OfferExpired",
39 | buyer,
40 | null,
41 | "Foundation",
42 | null,
43 | null,
44 | event.block.timestamp,
45 | null,
46 | null,
47 | null,
48 | offer,
49 | );
50 | return false;
51 | }
52 |
53 | // Previous offer was outbid
54 | offer.status = "Outbid";
55 | offer.dateOutbid = event.block.timestamp;
56 | offer.transactionHashOutbid = event.transaction.hash;
57 | offer.offerOutbidBy = newOffer.id;
58 | newOffer.outbidOffer = offer.id;
59 | offer.save();
60 |
61 | let isIncreased = offer.buyer == newBuyer.id;
62 | return isIncreased;
63 | }
64 |
--------------------------------------------------------------------------------
/subgraph/src/shared/revenue.ts:
--------------------------------------------------------------------------------
1 | import { BigDecimal } from "@graphprotocol/graph-ts";
2 |
3 | import { Account, Creator, Nft } from "../../generated/schema";
4 |
5 | export function recordSale(
6 | nft: Nft,
7 | seller: Account,
8 | creatorRevenueInETH: BigDecimal | null,
9 | ownerRevenueInETH: BigDecimal | null,
10 | foundationRevenueInETH: BigDecimal | null,
11 | ): void {
12 | if (!creatorRevenueInETH || !ownerRevenueInETH || !foundationRevenueInETH) {
13 | // This should never occur
14 | return;
15 | }
16 | let amountInETH = creatorRevenueInETH.plus(ownerRevenueInETH).plus(foundationRevenueInETH);
17 |
18 | // Creator revenue & sales
19 | let creator: Creator | null;
20 | if (nft.creator) {
21 | creator = Creator.load(nft.creator as string);
22 | } else {
23 | creator = null;
24 | }
25 | if (creator) {
26 | creator.netRevenueInETH = creator.netRevenueInETH.plus(creatorRevenueInETH);
27 | creator.netSalesInETH = creator.netSalesInETH.plus(amountInETH);
28 | creator.save();
29 | }
30 |
31 | // Account revenue
32 | seller.netRevenueInETH = seller.netRevenueInETH.plus(ownerRevenueInETH);
33 | seller.save();
34 |
35 | // NFT revenue & sales
36 | nft.netSalesInETH = nft.netSalesInETH.plus(amountInETH);
37 | nft.netRevenueInETH = nft.netRevenueInETH.plus(creatorRevenueInETH);
38 | nft.isFirstSale = false;
39 | nft.lastSalePriceInETH = amountInETH;
40 | nft.save();
41 | }
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "forceConsistentCasingInFileNames": true,
5 | "lib": ["es5", "es6", "ES2021.String"],
6 | "jsx": "react",
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "noImplicitAny": true,
10 | "outDir": "dist",
11 | "resolveJsonModule": true,
12 | "sourceMap": true,
13 | "strict": true,
14 | "target": "es5"
15 | },
16 | "exclude": ["artifacts/**/*", "node_modules"],
17 | "include": [
18 | "./deploy",
19 | "hardhat*.config.ts",
20 | "helpers",
21 | "scripts/**/*",
22 | "src/**/*",
23 | "subgraph/**/*",
24 | "tasks/**/*",
25 | "test/**/*",
26 | "test-supplemental/**/*",
27 | "versions",
28 | "subgraph/tests",
29 | "test-subgraph-integration",
30 | "test-mainnet-fork",
31 | "addresses.js",
32 | "test-gas-stories/**/*"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------