GithubHelp home page GithubHelp logo

zksync-tutorial-stable-price-marketplace's Introduction

RedStone oracles on zkSync

By the end of this tutorial you will understand how to integrate your dApp built on zkSync with RedStone oracles.

Example dApp - Stable price NFT marketplace

This repo is designed to show how to build a dApp that uses RedStone oracles on zkSync.

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 price amount in USD. But buyers are able to pay with native coins, required amount of which is calculated dynamically in 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, version prepared for working on zkSync, and ethers.js for deployment scripts and contract tests. Frontend is implemented in React.

Code structure

β”œβ”€β”€ 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

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. It has the following functions:

// 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.

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.

Tests

We've used hardhat test framework to contract tests. All the tests are located in the test folder.

πŸ”₯ Tutorial how to integrate RedStone oracles on zkSync

Prepare repo

1. Clone this repo

git clone https://github.com/redstone-finance/stable-price-marketplace
cd stable-price-marketplace

2. Install dependencies

yarn install

3. Run local zkSync node

You can run zkSync node in dockerized setup by following instructions presented here

Get familiar with the code

If you are not familiar with the code yet, please read implementation description

Integrate with RedStone Oracles

Now it is time to integrate RedStone Oracles into the marketplace. As you maybe noticed some parts of the code are missing the implementation. Let me give you instructions on how to integrate RedStone oracles.Β 

1. Adjust smart contract

First, we need to modify contracts as currently, they are not ready to receive transactions with RedStone data regarding price. If you are not familiar with our core model please read how to adjust your smart contracts.Β Take a look at the StableMarketplace contract. It is the marketplace contract with stable price support. It extends the Marketplace.sol implementation and only overrides its _getPriceFromOrder function. The contract should be extended by MainDemoConsumerBase which is imported from @redstone-finance/evm-connector. The _getPriceFromOrder function should use the getOracleNumericValueFromTxMsg function to get price data and calculate the final price based on the order price and the price of ETH. Full implementation can be seen below:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@redstone-finance/evm-connector/contracts/data-services/MainDemoConsumerBase.sol";
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, MainDemoConsumerBase {
  /*
    `_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) {
    uint256 ethPrice = getOracleNumericValueFromTxMsg(bytes32("ETH"));
    return (order.price / ethPrice) * (10 ** 8);
  }
}

2. Adjust dApp TypeScript code

The second thing to do is adjust the Typescript code of the dApp. Please take a look at the blockchain.ts file. Here you can find all functions required to make the marketplace work. But the function buy is not implemented. Here we will call the function from the contracts which require price data. To make it possible we need to wrap the contract instance with the RedStone framework.Β If you are not familiar with our core model please read how to adjust Typescript code. After wrapping the contract we will be able to callΒ the getPrice function from the StableMarketplace contract which eventually will call overridden _getPriceFromOrder. Now we are able to call the buy function from the StableMarketplace contract with the expected ETH amount to buy the NFT.Β Full implementation can be seen below:

import { WrapperBuilder } from "@redstone-finance/evm-connector";

async function getContractInstance(contractName) {
  ...
  return new ethers.Contract(address, abi, signer);
}

async function buy(orderId) {
  const marketplace = await getContractInstance("marketplace");

  // Wrapping marketplace contract instance.
  // It enables fetching data from redstone data pool
  // for each contract function call
  try {
    const wrappedMarketplaceContract = WrapperBuilder.wrap(
      marketplace
    ).usingDataService(
      {
        dataServiceId: "redstone-main-demo",
        uniqueSignersCount: 1,
        dataFeeds: ["ETH"],
      },
    );

    // Checking expected amount
    const expectedEthAmount = await wrappedMarketplaceContract.getPrice(orderId);

    // Sending buy tx
    const buyTx = await wrappedMarketplaceContract.wi(orderId, {
      value: expectedEthAmount.mul(101).div(100), // a buffer for price movements
    });
    await buyTx.wait();

    return buyTx;
  } catch { 
    const errText = "Error happened while buying the NFT";
    alert(errText);
  }
}

Test dApp locally

1. Check if tests pass

yarn test

2. Compile contracts

yarn compile

3. Deploy contracts on local blockchain

You need to populate .env file with private key for deployment e.g.

WALLET_PRIVATE_KEY=0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110

and then run

yarn deploy:local

4. Run react app

yarn app:start

The app should be running on http://localhost:3000

3. Configure metamask

3.1 Add local hardhat network to metamask

Select Networks dropdown -> Add network and enter the following details:

Network Name hardhat-local
New RPC URL http://localhost:3050
Chain ID 270
Currency Symbol ETH

Then hit the Save button.

3.2 Add local wallets to metamask
  • User 1: 0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3
  • User 2: 0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e

4. Explore the app in browser

stable-marketplace-app

Mint NFTs

After visiting the app first time you will see an almost empty screen with the + Mint new NFT link. Click this link to mint new NFTs. After the minting transaction confirmation you will see your NFT in the left column.

my-nfts

Post sell orders

Once you mint any NFTs, you can post sell order for each one of them. Click the SELL button and provide the USD value. You will be asked to confirm 2 transactions: for NFT transfer approval, and for the marketplace order creation. After their confirmation, you will see your order in the Orders column.

orders

Buy NFTs

You can also switch metamask account and buy the NFT. I would recommend to open the developer tools in browser at the network tab and explore network requests that are being sent before the buy transaction sending.

You should see at least 2 requests with the ETH price data and crypto signatures. This data along with signatures is being attached for each contract call, that wants to process redstone oracles data.

redstone-requests

πŸš€ What is RedStone?

RedStone is a data ecosystem that delivers frequently updated, reliable and diverse data for your dApps and smart contracts.

RedStone offers a radically different design of Oracles catering to the needs of modern DeFi protocols.

  • Data providers can avoid the requirement of continuous on-chain data delivery
  • Allow end users to self-deliver signed Oracle data on-chain
  • Use the decentralized Streamr network to deliver signed oracle data to the end users
  • Use token incentives to motivate data providers to maintain data integrity and uninterrupted service
  • Leverage the Arweave blockchain as cheap and permanent storage for archiving Oracle data and maintaining data providers' accountability

To learn more about RedStone oracles design check out the RedStone docs.

πŸ—οΈ Key facts

  • The modular architecture maintains data integrity from source to smart contracts
  • There are 3 different ways to integrate our service tailored to your needs
  • We provide feeds for more than 1000 assets integrating ~50 data sources
  • We are present on 20+ chains
  • RedStone has been live on mainnets since March 2022 with no downtime. Code was audited by ABDK, Packshield and L2Beat Co-Founder.
  • RedStone was a launch partner for DeltaPrime on Avalanche and delivered data feeds not available anywhere else. Thanks to that DeltaPrime became the top 3 fastest growing dApps according to DefiLama.

πŸ“ˆ What data is available

Thanks to our innovative architecture, we offer more than one thousand of pricing data feeds, including tokens, stocks, ETFs, commodities, and much more for a fraction of regular Oracles integration costs.

You can check available assets and data providers using app.redstone.finance.

πŸ”₯ How to use RedStone?

IMPORTANT: Please reach out to the RedStone team on Discord before using RedStone oracles in production dApps. We will be happy to help you with the integration and will set up a new pool of data provider nodes if there is a need.

Installation

Install @redstone-finance/evm-connector from NPM registry

# Using yarn
yarn add @redstone-finance/evm-connector

# Using NPM
npm install @redstone-finance/evm-connector

Usage

TLDR; You need to do 2 things:

  1. Adjust your smart contracts
  2. Adjust Javascript code of your dApp (it is required, otherwise you will get smart contract errors)

πŸ’‘ Note: Please don't use Remix to test RedStone oracles, as Remix does not support modifying transactions in the way that the evm-connector does.

1. Adjust your smart contracts

You need to apply a minimum change to the source code to enable smart contract to access data. Your contract needs to extend one of our custom base contracts, which can be found here.

We strongly recommend having some upgradeability mechanism for your contracts (it can be based on multisig, DAO, or anything else). This way, you can quickly switch to the latest trusted data providers in case of changes or problems with the current providers.

import "@redstone-finance/evm-connector/contracts/data-services/MainDemoConsumerBase.sol";

contract YourContractName is MainDemoConsumerBase {
  ...
}

After applying the mentioned change you will be able to access the data calling the local getOracleNumericValueFromTxMsg function. You should pass the data feed id converted to bytes32.

// Getting a single value
uint256 ethPrice = getOracleNumericValueFromTxMsg(bytes32("ETH"));

// Getting several values
bytes32[] memory dataFeedIds = new bytes32[](2);
dataFeedIds[0] = bytes32("ETH");
dataFeedIds[1] = bytes32("BTC");
uint256[] memory values = getOracleNumericValuesFromTxMsg(dataFeedIds);
uint256 ethPrice = values[0];
uint256 btcPrice = values[1];

You can see all available data feeds in our web app.

2. Adjust Javascript code of your dApp

You should also update the code responsible for submitting transactions. If you're using ethers.js, we've prepared a dedicated library to make the transition seamless.

Contract object wrapping

First, you need to import the wrapper code to your project

// Typescript
import { WrapperBuilder } from "@redstone-finance/evm-connector";

// Javascript
const { WrapperBuilder } = require("@redstone-finance/evm-connector");

Then you can wrap your ethers contract pointing to the selected Redstone data service id. You should also specify a number of unique signers, data feed identifiers, and (optionally) URLs for the redstone cache nodes.

const yourEthersContract = new ethers.Contract(address, abi, provider);

// Connecting all provider's prices (consumes more GAS)
const wrappedContract = WrapperBuilder.wrap(contract).usingDataService(
  {
    dataServiceId: "redstone-main-demo",
    uniqueSignersCount: 1,
    dataFeeds: ["ETH", "BTC"],
  },
);

Now you can access any of the contract's methods in exactly the same way as interacting with the ethers-js code:

wrappedContract.executeYourMethod();

Mock provider

If you'd like to use the wrapper in a test context, we recommend using a mock wrapper so that you can easily override the oracles values to test different scenarios. To use the mock wrapper just use the usingMockData(signedDataPackages) function instead of the usingDataService function. You can see examples of the mock wrapper usage here.

You can find more information in the RedStone documentation to learn how to integrate your zkSync dApp with RedStone oracles.

🌎 Useful links

πŸ™‹β€β™‚οΈ Need help?

Please feel free to contact the RedStone team on Discord if you have any questions.

zksync-tutorial-stable-price-marketplace's People

Contributors

cehali avatar idea404 avatar

Stargazers

majidrashidi avatar

Watchers

Piotr avatar  avatar

zksync-tutorial-stable-price-marketplace's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.