-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feat/the-graph
- Loading branch information
Showing
35 changed files
with
14,097 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -159,3 +159,6 @@ ISTN | |
Zerion | ||
pepe | ||
PEPE | ||
Arweave | ||
Streamr | ||
TLDR |
411 changes: 411 additions & 0 deletions
411
tutorials/zkSync-RedStone-stable-price-marketplace-tutorial/TUTORIAL.md
Large diffs are not rendered by default.
Oops, something went wrong.
1 change: 1 addition & 0 deletions
1
tutorials/zkSync-RedStone-stable-price-marketplace-tutorial/code/.env.example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
WALLET_PRIVATE_KEY= |
21 changes: 21 additions & 0 deletions
21
tutorials/zkSync-RedStone-stable-price-marketplace-tutorial/code/LICENSE
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2022 RedStone Finance | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
101 changes: 101 additions & 0 deletions
101
tutorials/zkSync-RedStone-stable-price-marketplace-tutorial/code/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# Example dApp - Stable price NFT marketplace | ||
|
||
This repo is designed to show how to build a dApp that uses [RedStone oracles](https://redstone.finance/) on [zkSync](https://zksync.io/). | ||
|
||
The repo contains an implementation of an NFT marketplace dApp with so-called "stable" price. It means that sellers can create sell orders (offers), specifying the price amount in USD. But buyers are able to pay with native coins, the required amount of which is calculated dynamically at the moment of the order execution. Repo lacks few crucial parts which will demonstrate how to integrate RedStone oracles and deploy dApp on zkSync Era Testnet. | ||
|
||
## 🧑💻 Implementation | ||
|
||
We use [hardhat](https://hardhat.org/), version prepared for working on [zkSync](https://github.com/matter-labs/hardhat-zksync), and [ethers.js](https://docs.ethers.io/v5/) for deployment scripts and contract tests. Frontend is implemented in [React](https://reactjs.org/). | ||
|
||
### Code structure | ||
|
||
```bash | ||
├── contracts # Solidity contracts | ||
│ ├── ExampleNFT.sol # Example ERC721 contract | ||
│ ├── Marketplace.sol # Simple NFT marketplace contract | ||
│ ├── StableMarketplace.sol # NFT marketplace contract with stable price | ||
│ └── ... | ||
├── public # Folder with public html files and images for React app | ||
├── deploy # Contract deployment script | ||
├── src # React app source code | ||
│ ├── components | ||
│ │ ├── App.tsx # Main React component | ||
│ ├── core | ||
│ │ ├── blockchain.ts # TS module responsible for interaction with blockchain and contracts | ||
│ ├── config/ # Folder with contract ABIs and deployed contract addresses | ||
│ └── ... | ||
├── test # Contract tests | ||
└── ... | ||
``` | ||
|
||
### Contracts | ||
|
||
#### ExampleNFT.sol | ||
|
||
`ExampleNFT` is a simple ERC721 contract with automated sequential token id assignment | ||
|
||
```js | ||
function mint() external { | ||
_mint(msg.sender, nextTokenId); | ||
nextTokenId++; | ||
} | ||
``` | ||
|
||
This contract extends `ERC721Enumerable` implementation created by the `@openzeppelin` team, which adds view functions for listing all tokens and tokens owned by a user. | ||
|
||
#### Marketplace.sol | ||
|
||
`Marketplace` is an NFT marketplace contract, which allows to post sell orders for any NFT token that follows [EIP-721 non-fungible token standard](https://eips.ethereum.org/EIPS/eip-721). It has the following functions: | ||
|
||
```js | ||
|
||
// Created a new sell order | ||
// This function requires approval for transfer on the specified NFT token | ||
function postSellOrder(address nftContractAddress, uint256 tokenId, uint256 price) external {} | ||
|
||
// Only order creator can call this function | ||
function cancelOrder(uint256 orderId) external {} | ||
|
||
// Allows to get info about all orders (including canceled, and executed ones) | ||
function getAllOrders() public view returns (SellOrder[] memory) {} | ||
|
||
// Returns expected price in ETH for the given order | ||
function getPrice(uint256 orderId) public view returns (uint256) {} | ||
|
||
// Requires sending at least the minimal amount of ETH | ||
function buy(uint256 orderId) external payable {} | ||
|
||
``` | ||
|
||
The implementation is quite straightforward, so we won't describe it here. You can check the full contract code in the [contracts/Marketplace.sol.](contracts/Marketplace.sol) | ||
|
||
#### StableMarketplace.sol | ||
|
||
`StableMarketplace` is the marketplace contract with the stable price support. It extends the `Marketplace.sol` implementation and only overrides its `_getPriceFromOrder` function. | ||
This contract will integrate RedStone oracles functionalities and will be described later. | ||
|
||
### Frontend | ||
|
||
You can check the code of the React app in the `src` folder. We tried to simplify it as much as possible and leave only the core marketplace functions. | ||
|
||
The main UI logic is located in the `App.tsx` file, and the contract interaction logic is in the `blockchain.ts` file. | ||
|
||
If you take a look into the `blockchain.ts` file code, you'll notice that each contract call that needs to process RedStone data is made on a contract instance, that was wrapped by [@redstone-finance/evm-connector](https://www.npmjs.com/package/@redstone-finance/evm-connector). | ||
|
||
### Tests | ||
|
||
We've used hardhat test framework to contract tests. All the tests are located in the [test](test/) folder. | ||
|
||
## 🌎 Useful links | ||
|
||
- [Repo with examples](https://github.com/redstone-finance/redstone-evm-examples) | ||
- [RedStone Documentation](https://docs.redstone.finance/) | ||
- [RedStone Price Feeds](https://docs.redstone.finance/docs/smart-contract-devs/price-feeds) | ||
- [Data from any URL](https://docs.redstone.finance/docs/smart-contract-devs/custom-urls) | ||
- [NFT Data Feeds](https://docs.redstone.finance/docs/smart-contract-devs/nft-data-feeds) | ||
- [Randomness](https://docs.redstone.finance/docs/smart-contract-devs/randomness) | ||
|
||
## 🙋♂️ Need help? | ||
|
||
Please feel free to contact the RedStone team [on Discord](https://redstone.finance/discord) if you have any questions. |
16 changes: 16 additions & 0 deletions
16
tutorials/zkSync-RedStone-stable-price-marketplace-tutorial/code/contracts/ExampleNFT.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | ||
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; | ||
|
||
contract ExampleNFT is ERC721Enumerable { | ||
uint256 private nextTokenId = 1; | ||
|
||
constructor() ERC721("ExampleNFT", "ENFT") {} | ||
|
||
function mint() external { | ||
_mint(msg.sender, nextTokenId); | ||
nextTokenId++; | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
tutorials/zkSync-RedStone-stable-price-marketplace-tutorial/code/contracts/Marketplace.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | ||
|
||
contract Marketplace { | ||
enum OrderStatus { | ||
ACTIVE, | ||
CANCELED, | ||
EXECUTED | ||
} | ||
|
||
struct SellOrder { | ||
address nftContractAddress; | ||
uint256 tokenId; | ||
address creator; | ||
uint256 price; | ||
OrderStatus status; | ||
} | ||
|
||
SellOrder[] private sellOrders; | ||
|
||
function postSellOrder( | ||
address nftContractAddress, | ||
uint256 tokenId, | ||
uint256 price | ||
) external { | ||
// Check if tokenId is owned by tx sender | ||
IERC721 nftContract = IERC721(nftContractAddress); | ||
require(nftContract.ownerOf(tokenId) == msg.sender); | ||
|
||
// Transfer NFT token to the contract address | ||
// Sender needs to approve the transfer before posting sell order | ||
nftContract.transferFrom(msg.sender, address(this), tokenId); | ||
|
||
// Save order in the sellOrders mapping | ||
sellOrders.push( | ||
SellOrder( | ||
nftContractAddress, | ||
tokenId, | ||
msg.sender, | ||
price, | ||
OrderStatus.ACTIVE | ||
) | ||
); | ||
} | ||
|
||
function cancelOrder(uint256 orderId) external { | ||
SellOrder storage order = sellOrders[orderId]; | ||
|
||
// Only order creator can cancel the order | ||
require(order.creator == msg.sender); | ||
|
||
// Transfer NFT back to order creator | ||
IERC721 nftContract = IERC721(order.nftContractAddress); | ||
nftContract.transferFrom(address(this), msg.sender, order.tokenId); | ||
|
||
// Update order status | ||
order.status = OrderStatus.CANCELED; | ||
} | ||
|
||
function buy(uint256 orderId) external payable { | ||
// Order must exist and be in the active state | ||
SellOrder storage order = sellOrders[orderId]; | ||
require(order.status == OrderStatus.ACTIVE); | ||
|
||
// Check transfered ETH value | ||
uint256 expectedEthAmount = _getPriceFromOrder(order); | ||
require(expectedEthAmount <= msg.value); | ||
|
||
// Transfer NFT to buyer | ||
IERC721 nftContract = IERC721(order.nftContractAddress); | ||
nftContract.transferFrom(address(this), msg.sender, order.tokenId); | ||
|
||
// Mark order as executed | ||
order.status = OrderStatus.EXECUTED; | ||
} | ||
|
||
function _getPriceFromOrder( | ||
SellOrder memory order | ||
) internal view virtual returns (uint256) { | ||
return order.price; | ||
} | ||
|
||
// Getters for the UI | ||
function getPrice(uint256 orderId) public view returns (uint256) { | ||
SellOrder storage order = sellOrders[orderId]; | ||
return _getPriceFromOrder(order); | ||
} | ||
|
||
function getAllOrders() public view returns (SellOrder[] memory) { | ||
return sellOrders; | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
...ls/zkSync-RedStone-stable-price-marketplace-tutorial/code/contracts/StableMarketplace.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
import "./Marketplace.sol"; | ||
|
||
/* | ||
StableMarketplace contract should extend MainDemoConsumerBase contract | ||
For being able to use redstone oracles data, more inf: | ||
https://docs.redstone.finance/docs/smart-contract-devs/get-started/redstone-core#1-adjust-your-smart-contracts | ||
*/ | ||
contract StableMarketplace is Marketplace { | ||
/* | ||
`_getPriceFromOrder` function should uses the `getOracleNumericValueFromTxMsg` function, | ||
which fetches signed data from tx calldata and verifies its signature | ||
*/ | ||
function _getPriceFromOrder( | ||
SellOrder memory order | ||
) internal view override returns (uint256) { | ||
// TO IMPLEMENT | ||
} | ||
} |
71 changes: 71 additions & 0 deletions
71
tutorials/zkSync-RedStone-stable-price-marketplace-tutorial/code/deploy/deploy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import hre from "hardhat"; | ||
import fs from "fs"; | ||
import { ethers } from "ethers"; | ||
import { Wallet } from "zksync-web3"; | ||
import { HardhatRuntimeEnvironment } from "hardhat/types"; | ||
import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; | ||
import dotenv from "dotenv"; | ||
|
||
dotenv.config(); | ||
|
||
// load wallet private key from env file | ||
const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; | ||
|
||
if (!PRIVATE_KEY) | ||
throw "⛔️ Private key not detected! Add it to the .env file!"; | ||
|
||
|
||
export default async function (hre: HardhatRuntimeEnvironment) { | ||
const wallet = new Wallet(PRIVATE_KEY); | ||
const deployer = new Deployer(hre, wallet); | ||
|
||
const nftContract = await deployContract("ExampleNFT", deployer); | ||
const marketplaceContract = await deployContract("StableMarketplace", deployer); | ||
|
||
// Update JSON file with addresses | ||
updateAddressesFile({ | ||
nft: nftContract.address, | ||
marketplace: marketplaceContract.address, | ||
}); | ||
} | ||
|
||
export const deployContract = async (contractName: string, deployer: Deployer) => { | ||
console.log(`Running deploy script for the PriceChecker contract`); | ||
|
||
// const artifact = await deployer.loadArtifact("PriceChecker"); | ||
const artifact = await deployer.loadArtifact(contractName); | ||
|
||
// Estimate contract deployment fee | ||
const deploymentFee = await deployer.estimateDeployFee(artifact, []); | ||
|
||
// ⚠️ OPTIONAL: You can skip this block if your account already has funds in L2 | ||
// Deposit funds to L2 | ||
// const depositHandle = await deployer.zkWallet.deposit({ | ||
// to: deployer.zkWallet.address, | ||
// token: utils.ETH_ADDRESS, | ||
// amount: deploymentFee.mul(2), | ||
// }); | ||
// // Wait until the deposit is processed on zkSync | ||
// await depositHandle.wait(); | ||
|
||
// Deploy this contract. The returned object will be of a `Contract` type, similarly to ones in `ethers`. | ||
// `greeting` is an argument for contract constructor. | ||
const parsedFee = ethers.utils.formatEther(deploymentFee.toString()); | ||
console.log(`The deployment is estimated to cost ${parsedFee} ETH`); | ||
|
||
const contract = await deployer.deploy(artifact); | ||
|
||
// Show the contract info. | ||
const contractAddress = contract.address; | ||
console.log(`${artifact.contractName} was deployed to ${contractAddress}`); | ||
return contract; | ||
} | ||
|
||
function updateAddressesFile(addresses: { nft: string; marketplace: string }) { | ||
const addressesFilePath = `./src/config/${hre.network.name}-addresses.json`; | ||
console.log(`Saving addresses to ${addressesFilePath}`); | ||
fs.writeFileSync( | ||
addressesFilePath, | ||
JSON.stringify(addresses, null, 2) + "\n" | ||
); | ||
} |
Binary file added
BIN
+122 KB
...als/zkSync-RedStone-stable-price-marketplace-tutorial/code/docs/img/my-nfts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+90.1 KB
...ials/zkSync-RedStone-stable-price-marketplace-tutorial/code/docs/img/orders.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+273 KB
...-RedStone-stable-price-marketplace-tutorial/code/docs/img/redstone-requests.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+331 KB
...tone-stable-price-marketplace-tutorial/code/docs/img/stable-marketplace-app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions
42
tutorials/zkSync-RedStone-stable-price-marketplace-tutorial/code/hardhat.config.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { HardhatUserConfig } from "hardhat/config"; | ||
|
||
import "@matterlabs/hardhat-zksync-deploy"; | ||
import "@matterlabs/hardhat-zksync-solc"; | ||
import "@matterlabs/hardhat-zksync-verify"; | ||
import "@matterlabs/hardhat-zksync-chai-matchers" | ||
|
||
// dynamically changes endpoints for local tests | ||
const zkSyncTestnet = | ||
process.env.NODE_ENV == "test" | ||
? { | ||
url: "http://localhost:3050", | ||
ethNetwork: "http://localhost:8545", | ||
zksync: true, | ||
} | ||
: { | ||
url: "https://zksync2-testnet.zksync.dev", | ||
ethNetwork: "goerli", | ||
zksync: true, | ||
// contract verification endpoint | ||
verifyURL: | ||
"https://zksync2-testnet-explorer.zksync.dev/contract_verification", | ||
}; | ||
|
||
const config: HardhatUserConfig = { | ||
zksolc: { | ||
version: "latest", | ||
settings: {}, | ||
}, | ||
defaultNetwork: "zkSyncTestnet", | ||
networks: { | ||
hardhat: { | ||
zksync: false, | ||
}, | ||
zkSyncTestnet, | ||
}, | ||
solidity: { | ||
version: "0.8.17", | ||
}, | ||
}; | ||
|
||
export default config; |
Oops, something went wrong.