- Plese take appropraite actions to upgrade to production level
- Mainnet contract interactions have not been tested. I will update this note once mainnet testing is completed
- A production level example of this template is provided at gm.zerobeings.xyz
Market gm ☕️ is a new NFT marketplace that allows you to buy, directly sell, and auction NFTs inside and outside the marketplace. This flexibility is achieved through the thirdWeb marketplace contract (contract audit report).
0.5% platform fee & EIP-2981 royalties honored, filetype flexibility, auto-generated IPFS site, and fully customizable.
To take a deeper dive into market gm ☕️ , check out the documents
- Twitter: https://twitter.com/Zero_beings
- Discord: https://discord.gg/rZMzVCx96W
- Medium: https://zerobeings.medium.com/
Follow the guide below to get started on your own NFT marketplace today! Otherwise you can mint a Zero Being and start using Market gm ☕️ today!
- Fork the project.
- Clone the project.
- Navigate to the project directory
cd zerb-nft-marketplace
. - Install dependencies with
npm install
. - Change
.env.example
to.env
and add your environment variables. - Change
mbox.example.json
andgbox.example.json
tombox.json
andgbox.json
. - Remove
mbox.json
andgbox.json
from.gitignore
- Run
nodemon
.
-
Change the NFT gating contract and user name.
-
Deploy your own thirdweb goerli marketplace contract.
-
Deploy your own thirdweb mainnet marketplace contract.
-
Update the marketplace contract address.
-
Update the
mbox.json
andgbox.json
files. -
Update
session.zerb.account
to new user reference throughout repository. -
Update the walletconnect parameter on the login page:
-
Customize!
A complete guide can be found at Skograd's PrivateParty
You will first need to update the private party authorization. Below is the current server authorization with a user 'zerb'
, contract name zerb
, a contract address '0x8FbA3ebe77D3371406a77EEaf40c89C1Ed55364a'
, and a balance call function
let balance = await contracts.zerb.methods.balanceOf(account).call()
party.add('zerb', {
contracts: {
zerb: {
address: '0x8FbA3ebe77D3371406a77EEaf40c89C1Ed55364a', //for zero beings
rpc: process.env.RPC,
abi: party.abi.erc721,
},
},
authorize: async (req, account, contracts) => {
let balance = await contracts.zerb.methods.balanceOf(account).call();
if (balance > 0) return { balance: balance };
else
throw new Error(
"You must own at least one 'Zero Being' mint at https://mint.zerobeings.xyz"
);
},
});
For example, let say you would like to change the user and the NFT gate, the changes would look something like this:
party.add('user', {
contracts: {
mycontract: {
address: '0x0........', //your collection address
rpc: process.env.RPC,
abi: party.abi.erc721,
},
},
authorize: async (req, account, contracts) => {
let balance = await contracts.mycontract.methods.balanceOf(account).call();
if (balance > 0) return { balance: balance };
else
throw new Error(
"You must own at least one 'Your NFT' mint at https://yourdomain.com"
);
},
});
There are total seven locations in which the goerli & mainnet contract addresses are explicitly used.
- server.js
- mbox.json
- gbox.json
- sbox.json
- mylistingsCard.ejs (IPFS Link)
- sepolia-myListingsCard.ejs (IPFS Link)
- goerli-mylistingsCard.ejs (IPFS Link)
- footerMarket.ejs
- goerli-footerMarket.ejs
- sepolia-footerMarket.ejs
// Marketplace contracts
// contracts are referenced in three locations in mbox.json for mainnet and gbox.json for goerli,
// the listingsCard and mybids for mainnet and goerli-listingsCard & goerli-mybids in the IPFS site link,
// in the const listed below, and finally the marketfooter files.
const gMarketContract = '<your goerli contract address>'; //goerli marketplace contract. Deploy from thirdweb dashboard.
const sepMarketContract = '<your sepolia contract address>'; //goerli marketplace contract. Deploy from thirdweb dashboard.
const mainnetMarketContract = '<your mainnet contract address>'; //ETH mainnet marketplace contract. Deploy from thirdweb dashboard
You will need to add your contract address to the mbox.json and gbox.json files.
The mbox.json file supports the connection to the mainnet.
{"contract":"<MarketContract>","network":"main"}
The gbox.json file supports the connection to the goerli-network.
{"contract":"<MarketContract>","network":"goerli"}
The sbox.json file supports the connection to the sepolia-network.
{"contract":"<MarketContract>","network":"sepolia"}
The listingCard partials contain a copy iframe button. Navigate to your thirdbed dashboard and copy the iframe code and paste it into this section. The default configuration is with written with a listing ID of 0
. This must be updated to <%=listings.id%>
to autolink the correct IPFS website.
Below is the example for the Market gm configuration.
<iframe src="https://gateway.ipfscdn.io/ipfs/QmbAgC8YwY36n8H2kuvSWsRisxDZ15QZw3xGZyk9aDvcv7/marketplace.html?contract=0x8F6502Aeae32D3B708236F8cB1eB2aa45429cE34&chain=%7B%22name%22%3A%22Ethereum+Mainnet%22%2C%22chain%22%3A%22ETH%22%2C%22rpc%22%3A%5B%22https%3A%2F%2Fethereum.rpc.thirdweb.com%2F5a9bc94b87f7cbbbfbbc234bf1e07f0adf5f3cf3012c9f26f9fc9820d64df93a%22%5D%2C%22nativeCurrency%22%3A%7B%22name%22%3A%22Ether%22%2C%22symbol%22%3A%22ETH%22%2C%22decimals%22%3A18%7D%2C%22shortName%22%3A%22eth%22%2C%22chainId%22%3A1%2C%22testnet%22%3Afalse%2C%22slug%22%3A%22ethereum%22%7D&listingId=<%=listings.id%>&theme=dark&primaryColor=blue&secondaryColor=blue"
width="600px"
height="600px"
style="max-width:100%;"
frameborder="0"></iframe>
If you would like to take a deep dive into the tools used to build this marketplace, review the reference documents. The additional resources section is a list of tools to help you launch and manage your NFT collections. Enjoy!
If this repository has been helpful, please consider minting a Zero Being.
-
Can any collection be listed on the marketplace? Yes, the marketplace contract is setup to allow sale of any NFT collection.
-
Can any wallet address list an NFT on the marketplace? Yes, any wallet can create a listing on the NFT marketplace
-
What is the platform fee for a sale on the marketplace? We charge a platform fee of 0.5% for each sale.
- Skogard Productions PrivateParty
- Skogard Productions factoria
- Alchemy NFT API
- Thirdweb marketplace contract technical documents
- Thirdweb typescript documentation typescript
- Rarible API
Below is a summary of the data structures fetched to build market gm.
nfts Data Structure
nfts:{
contract: { address: '0x8fba3ebe77d3371406a77eeaf40c89c1ed55364a' },
id: {
tokenId: '0x00000000000000000000000000000000000000000000000000000000000000e2',
tokenMetadata: [Object]
},
balance: '1',
title: 'Zero Beings #226',
description: '2022 Zero Beings',
tokenUri: {
raw: 'ipfs://bafybeidc4mw5k3iyzfz6msc37emanpdocar2dbfwkqmz5xrh7ctcs2htyi/226.json',
gateway: 'https://ipfs.io/ipfs/bafybeidc4mw5k3iyzfz6msc37emanpdocar2dbfwkqmz5xrh7ctcs2htyi/226.json'
},
media: [{
bytes: 220522,
format: "png",
gateway: "https://res.cloudinary.com/alchemyapi/image/upload/mainnet/3e61df4989b790d9957e7095cb643cc1.png",
raw: "ipfs://bafybeihvhk34lgwoh42qjkvz6obyeyls3236s5gojjukw3sq6ommg2eaye/00103.png",
thumbnail: "https://res.cloudinary.com/alchemyapi/image/upload/w_256,h_256/mainnet/3e61df4989b790d9957e7095cb643cc1.png",
}],
metadata: {
name: 'Zero Beings #226',
description: '2022 Zero Beings',
image: 'ipfs://bafybeidb57yyynti3qad3zcp2h4i4xj7ycabbvn4wnmsxwor7nxmagthna/00226.png',
attributes: [Array]
},
timeLastUpdated: '2022-08-19T17:10:35.128Z',
contractMetadata: { name: 'Zero Beings', symbol: 'ZERB', tokenType: 'ERC721' }
},
Collection Data Structure
{
"contract": {
"address": "0x8fba3ebe77d3371406a77eeaf40c89c1ed55364a"
},
"id": {
"tokenId": "0x0000000000000000000000000000000000000000000000000000000000000003",
"tokenMetadata": {
"tokenType": "ERC721"
}
},
"title": "Zero Beings #3",
"description": "2022 Zero Beings",
"tokenUri": {
"raw": "ipfs://bafybeidp7mzzgvcnedwqjw3vw3fljdk3zyjtfwvln3gmzahucytcx4wgme/3.json",
"gateway": "https://alchemy.mypinata.cloud/ipfs/bafybeidp7mzzgvcnedwqjw3vw3fljdk3zyjtfwvln3gmzahucytcx4wgme/3.json"
},
"media": [
{
"raw": "ipfs://bafybeihvhk34lgwoh42qjkvz6obyeyls3236s5gojjukw3sq6ommg2eaye/00003.png",
"gateway": "https://nft-cdn.alchemy.com/eth-mainnet/d4b09a580a837a5332c9cc56c5648a7c",
"thumbnail": "https://res.cloudinary.com/alchemyapi/image/upload/thumbnail/eth-mainnet/d4b09a580a837a5332c9cc56c5648a7c",
"format": "png",
"bytes": 501379
}
],
"metadata": {
"name": "Zero Beings #3",
"description": "2022 Zero Beings",
"image": "ipfs://bafybeihvhk34lgwoh42qjkvz6obyeyls3236s5gojjukw3sq6ommg2eaye/00003.png",
"attributes": [
{
"value": "Rainbow",
"trait_type": "The Underground"
},
{
"value": "Black",
"trait_type": "Background Space"
},
{
"value": "Orange",
"trait_type": "Zero Being"
},
{
"value": "Purple",
"trait_type": "Goggles"
},
{
"value": "Kepler-1229b",
"trait_type": "Home Planet"
},
{
"value": "Atomic",
"trait_type": "House"
},
{
"value": "Two",
"trait_type": "Rocket Type"
},
{
"value": "Ludicrous",
"trait_type": "Mode"
},
{
"value": "Rolly Polly Ace",
"trait_type": "Catchphrase"
}
]
},
"timeLastUpdated": "2022-12-31T19:45:44.361Z",
"contractMetadata": {
"name": "Zero Beings",
"symbol": "ZERB",
"tokenType": "ERC721",
"openSea": {
"floorPrice": 0.009,
"collectionName": "Zero Beings",
"safelistRequestStatus": "not_requested",
"imageUrl": "https://i.seadn.io/gcs/files/e223850b2d8848137a1ac957563d8cf2.gif?w=500&auto=format",
"description": "Phase 2 :: Series 2 (2022) | cc0 NFT collection | The Zero Beings are coming! Zero Beings work hard at having fun, doing good research, bringing utility, and public good to their community. Which being are you?",
"externalUrl": "https://www.zerobeings.xyz/",
"twitterUsername": "Zero_beings",
"discordUrl": "https://discord.gg/dWm4mw9Wkx",
"lastIngestedAt": "2022-12-28T11:38:46.000Z"
}
}
},
Individual nft Data
{
contract: { address: '0x9870da00643aea2be9df89d87efed0a2fdb5479e' },
id: { tokenId: '12', tokenMetadata: { tokenType: 'ERC721' } },
title: '',
description: '',
tokenUri: {
raw: 'ipfs://bafkreibdk5xsank2j7lzzds7ga57ncvthbadncmzuayaphievinu6bk3i4',
gateway: 'https://alchemy.mypinata.cloud/ipfs/bafkreibdk5xsank2j7lzzds7ga57ncvthbadncmzuayaphievinu6bk3i4'
},
media: [
{
raw: 'ipfs://bafybeifq7rj3ekf3obkocj2n55pjrfoe3i276cycmasfwvsyrvw544kgra',
gateway: 'https://nft-cdn.alchemy.com/eth-goerli/db1e8bbd8b6875d72134906ede1bbd98',
thumbnail: 'https://res.cloudinary.com/alchemyapi/image/upload/thumbnail/eth-goerli/db1e8bbd8b6875d72134906ede1bbd98',
format: 'png',
bytes: 381104
}
],
metadata: {
image: 'ipfs://bafybeifq7rj3ekf3obkocj2n55pjrfoe3i276cycmasfwvsyrvw544kgra'
},
timeLastUpdated: '2023-01-16T18:56:08.048Z',
contractMetadata: {
name: 'Test_Warp',
symbol: 'Twarp',
tokenType: 'ERC721',
openSea: { lastIngestedAt: '2023-01-21T14:37:29.000Z' }
}
}
Direct Listing Data Structure
[
{
assetContractAddress: '0x9870Da00643AeA2BE9dF89d87efeD0A2fdb5479e',
buyoutPrice: BigNumber { _hex: '0x2386f26fc10000', _isBigNumber: true },
currencyContractAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
buyoutCurrencyValuePerToken: {
name: 'Görli Ether',
symbol: 'GOR',
decimals: 18,
value: [BigNumber],
displayValue: '0.01'
},
id: '3',
tokenId: BigNumber { _hex: '0x0c', _isBigNumber: true },
quantity: BigNumber { _hex: '0x01', _isBigNumber: true },
startTimeInSeconds: BigNumber { _hex: '0x63a76964', _isBigNumber: true },
asset: {
image: 'https://gateway.ipfscdn.io/ipfs/bafybeifq7rj3ekf3obkocj2n55pjrfoe3i276cycmasfwvsyrvw544kgra',
id: '12',
uri: 'ipfs://bafkreibdk5xsank2j7lzzds7ga57ncvthbadncmzuayaphievinu6bk3i4'
},
secondsUntilEnd: BigNumber { _hex: '0x63b0a3e4', _isBigNumber: true },
sellerAddress: '0xbCdbe666a43437333CcC375C1E33461E260B57E6',
type: 0 // this represents the listing type 0=direct and 1=auction
},
]
Auction Data Structure
[
{
assetContractAddress: '0x9870Da00643AeA2BE9dF89d87efeD0A2fdb5479e',
buyoutPrice: BigNumber { _hex: '0x6a94d74f430000', _isBigNumber: true },
currencyContractAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
buyoutCurrencyValuePerToken: {
name: 'Görli Ether',
symbol: 'GOR',
decimals: 18,
value: [BigNumber],
displayValue: '0.03'
},
id: '8',
tokenId: BigNumber { _hex: '0x01', _isBigNumber: true },
quantity: BigNumber { _hex: '0x01', _isBigNumber: true },
startTimeInEpochSeconds: BigNumber { _hex: '0x63b9e530', _isBigNumber: true },
asset: {
image: 'https://gateway.ipfscdn.io/ipfs/bafybeifq7rj3ekf3obkocj2n55pjrfoe3i276cycmasfwvsyrvw544kgra',
id: '1',
uri: 'ipfs://bafkreibdk5xsank2j7lzzds7ga57ncvthbadncmzuayaphievinu6bk3i4'
},
reservePriceCurrencyValuePerToken: {
name: 'Görli Ether',
symbol: 'GOR',
decimals: 18,
value: [BigNumber],
displayValue: '0.0'
},
reservePrice: BigNumber { _hex: '0x00', _isBigNumber: true },
endTimeInEpochSeconds: BigNumber { _hex: '0x63c31fb0', _isBigNumber: true },
sellerAddress: '0xbCdbe666a43437333CcC375C1E33461E260B57E6',
type: 1
}
]
Offers
[
{
quantity: undefined,
pricePerToken: BigNumber { _hex: '0x038d7ea4c68000', _isBigNumber: true },
currencyContractAddress: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
buyerAddress: '0xbCdbe666a43437333CcC375C1E33461E260B57E6',
quantityDesired: BigNumber { _hex: '0x01', _isBigNumber: true },
currencyValue: {
name: 'Wrapped Ether',
symbol: 'WETH',
decimals: 18,
value: [BigNumber],
displayValue: '0.001'
},
listingId: BigNumber { _hex: '0x05', _isBigNumber: true }
}
]
Token Addresses
{
'1': {
name: 'Ether',
symbol: 'ETH',
decimals: 18,
wrapped: {
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
name: 'Wrapped Ether',
symbol: 'WETH'
}
},
'5': {
name: 'Görli Ether',
symbol: 'GOR',
decimals: 18,
wrapped: {
address: '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6',
name: 'Wrapped Ether',
symbol: 'WETH'
}
},
'10': {
name: 'Ether',
symbol: 'ETH',
decimals: 18,
wrapped: {
address: '0x4200000000000000000000000000000000000006',
name: 'Wrapped Ether',
symbol: 'WETH'
}
},
'56': {
name: 'Binance Chain Native Token',
symbol: 'BNB',
decimals: 18,
wrapped: {
address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',
name: 'Wrapped Binance Chain Token',
symbol: 'WBNB'
}
},
'97': {
name: 'Binance Chain Native Token',
symbol: 'TBNB',
decimals: 18,
wrapped: {
address: '0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd',
name: 'Wrapped Binance Chain Testnet Token',
symbol: 'WBNB'
}
},
'137': {
name: 'Matic',
symbol: 'MATIC',
decimals: 18,
wrapped: {
address: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
name: 'Wrapped Matic',
symbol: 'WMATIC'
}
},
'250': {
name: 'Fantom',
symbol: 'FTM',
decimals: 18,
wrapped: {
address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83',
name: 'Wrapped Fantom',
symbol: 'WFTM'
}
},
'420': {
name: 'Goerli Ether',
symbol: 'ETH',
decimals: 18,
wrapped: {
address: '0x4200000000000000000000000000000000000006',
name: 'Wrapped Ether',
symbol: 'WETH'
}
},
'4002': {
name: 'Fantom',
symbol: 'FTM',
decimals: 18,
wrapped: {
address: '0xf1277d1Ed8AD466beddF92ef448A132661956621',
name: 'Wrapped Fantom',
symbol: 'WFTM'
}
},
'31337': {
name: 'Ether',
symbol: 'ETH',
decimals: 18,
wrapped: {
address: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
name: 'Wrapped Ether',
symbol: 'WETH'
}
},
'42161': {
name: 'Ether',
symbol: 'ETH',
decimals: 18,
wrapped: {
address: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1',
name: 'Wrapped Ether',
symbol: 'WETH'
}
},
'43113': {
name: 'Avalanche',
symbol: 'AVAX',
decimals: 18,
wrapped: {
address: '0xd00ae08403B9bbb9124bB305C09058E32C39A48c',
name: 'Wrapped AVAX',
symbol: 'WAVAX'
}
},
'43114': {
name: 'Avalanche',
symbol: 'AVAX',
decimals: 18,
wrapped: {
address: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7',
name: 'Wrapped AVAX',
symbol: 'WAVAX'
}
},
'80001': {
name: 'Matic',
symbol: 'MATIC',
decimals: 18,
wrapped: {
address: '0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889',
name: 'Wrapped Matic',
symbol: 'WMATIC'
}
},
'421613': {
name: 'Arbitrum Goerli Ether',
symbol: 'AGOR',
decimals: 18,
wrapped: {
address: '0xe39Ab88f8A4777030A534146A9Ca3B52bd5D43A3',
name: 'Wrapped Ether',
symbol: 'WETH'
}
}
}
Fetch the contract royalties
Rarible API https://multichain-api.rarible.org/testnet/tag/item-controller#operation/getItemRoyaltiesById
{
royalties: [
{
account: 'ETHEREUM:0xbcdbe666a43437333ccc375c1e33461e260b57e6',
value: 500
}
]
}