zorbz - generative art with Zora's new API

In this you’ll learn step-by-step how we built this hackathon project from scratch.

The Zora API Hackathon

There have been a few new web3 releases recently:

  1. Zora API.

  2. Mint Songs V2 - Built on Zora V3.

  3. Zora Hackathon.

https://twitter.com/ourZORA/status/1534959028031303681?s=20&t=R_bw4AenmYE-bh1_Trkyqg

My Team

It started with me making a tiny post in the Zora Discord. Valcaholics reached out to me in dms. I invited Wayne. The rest is history. Here’s our squad:

What we built

Here’s the our hackathon submission:

  • Front-End Template - M1guel’s starter dApp.

    • NextJS + P5js - window undefined error.

  • P5js - generative art code.

  • Zora API.

    • zorb tokens query.

    • marketplace events query.

  • RainbowKit.

  • Minting functionality.

Getting Started

I’ve been really liking some of the apps M1guel has been releasing lately for Lens Protocol. I noticed he has a dapp-starter project that uses NextJS + Rainbowkit.

PS: You can skip ahead and use our finished code here.

git clone git@github.com:m1guelpf/dapp-starter.git
cd dapp-starter
yarn install
yarn dev

Open http://localhost:3000 with your browser to see the result.

P5js - Generative code.

If you’re not familiar with P5js it’s a common generative art library. You can learn more and use their easy-editor here.

this is the initial sketch valcoholics.eth made with one, repeated, zorb.

import React, { useState } from 'react'
import BaseSketch from 'react-p5'

const windowWidth = 500
const windowHeight = 500
let x = 50
let y = 50
const Sketch = props => {
    const [t, setT] = useState(0)
    const [zorb, setZorb] = useState()

    const setup = (p5, canvasParentRef) => {
        const canvas = p5.createCanvas(windowWidth, windowHeight).parent(canvasParentRef)
    }

    const draw = p5 => {
        const steps = 0.005
        setT((t += steps))
        const fluid = 0.01 //slider.value()*fx/5;
        const r = 24 //slider2.value();
        const w = 102
        const h = 102
        const mvx = 20
        const mvy = 20
        p5.background(0, w)
        for (x = 0; x < windowWidth; x += 25)
            for (y = 0; y < windowHeight; y += 20) {
                const n = _ => {
                    return p5.TAU * (t + p5.sin(p5.TAU * t - p5.dist(x, y, w / 2, h / 2) * fluid))
                }
                const ox = x + mvx * p5.sin(n())
                const oy = y + mvy * p5.cos(n())

                let nz = 100
                nz = p5.noise(x * fluid, y * fluid)
                if (zorb) {
                    p5.image(zorb, ox, oy, r, r)
                }
            }
    }

    const preload = p5 => {
        const testZorb = p5.loadImage(
            'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTAgMTEwIj48ZGVmcz48cmFkaWFsR3JhZGllbnQgaWQ9Imd6ciIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSg2Ni40NTc4IDI0LjM1NzUpIHNjYWxlKDc1LjI5MDgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgcj0iMSIgY3g9IjAiIGN5PSIwJSI+PHN0b3Agb2Zmc2V0PSIxNS42MiUiIHN0b3AtY29sb3I9ImhzbCgzMjYsIDczJSwgOTQlKSIgLz48c3RvcCBvZmZzZXQ9IjM5LjU4JSIgc3RvcC1jb2xvcj0iaHNsKDMyNSwgNzklLCA4NyUpIiAvPjxzdG9wIG9mZnNldD0iNzIuOTIlIiBzdG9wLWNvbG9yPSJoc2woMzE5LCA4OCUsIDc0JSkiIC8+PHN0b3Agb2Zmc2V0PSI5MC42MyUiIHN0b3AtY29sb3I9ImhzbCgzMTcsIDkyJSwgNjQlKSIgLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9ImhzbCgzMTYsIDkyJSwgNjMlKSIgLz48L3JhZGlhbEdyYWRpZW50PjwvZGVmcz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1LDUpIj48cGF0aCBkPSJNMTAwIDUwQzEwMCAyMi4zODU4IDc3LjYxNDIgMCA1MCAwQzIyLjM4NTggMCAwIDIyLjM4NTggMCA1MEMwIDc3LjYxNDIgMjIuMzg1OCAxMDAgNTAgMTAwQzc3LjYxNDIgMTAwIDEwMCA3Ny42MTQyIDEwMCA1MFoiIGZpbGw9InVybCgjZ3pyKSIgLz48cGF0aCBzdHJva2U9InJnYmEoMCwwLDAsMC4wNzUpIiBmaWxsPSJ0cmFuc3BhcmVudCIgc3Ryb2tlLXdpZHRoPSIxIiBkPSJNNTAsMC41YzI3LjMsMCw0OS41LDIyLjIsNDkuNSw0OS41Uzc3LjMsOTkuNSw1MCw5OS41UzAuNSw3Ny4zLDAuNSw1MFMyMi43LDAuNSw1MCwwLjV6IiAvPjwvZz48L3N2Zz4='
        )
        setZorb(testZorb)
    }
    return <BaseSketch setup={setup} draw={draw} preload={preload} />
}

export default Sketch

NextJS + P5js - window undefined error.

NextJS is server-side rendered. Meaning, when the page is initially loaded, it doesn’t have access to window. Window is what P5js uses to draw on the screen. Luckily NextJS makes it easy to skip the SSR and render P5js code easily:

import dynamic from 'next/dynamic'

const DynamicComponentWithNoSSR = dynamic(() => import('../components/Sketch'), { ssr: false })

const HomePage = () => (<DynamicComponentWithNoSSR zorbs={zorbs} zoraEvents={zoraEvents} />)

export default HomePage

Checking In - What you should have so far

one zorb repeated in the P5js animation

Zora API

We wanted to use the Zora API for 2 things:

  • zorb tokens query - get zorbs for our P5js animation.

  • marketplace events query - 1 zorb = 1 marketplace event.

How to use Zora API to get an array of zorb tokens images:

import { ZDK } from '@zoralabs/zdk'
const API_ENDPOINT = 'https://api.zora.co/graphql'
const zdk = new ZDK({ endpoint: API_ENDPOINT })

const args = { where: {
// zorb smart contract address
collectionAddresses: ['0xCa21d4228cDCc68D4e23807E5e370C07577Dd152']
}}

// query Zora API
const response = await zdk.tokens(args)

// get zorb images
let zorbz = response.tokens.nodes.map(zorb => zorb.token.image.url)

How to get events from Zora for a single day:

// date ex. "2022-01-01" 
const eventsArgs = (startDate, endDate) => ({
        where: {},
        filter: { timeFilter: { endDate, startDate }, eventTypes: 'V2_AUCTION_EVENT' },
        pagination: { limit: 500 },
})

const dailyEventArgs = eventsArgs(params.startDate, params.endDate)
const response = await zdk.events(dailyEventArgs)
const zoraEvents = response.events.nodes

Checking In - What you should have so far

moar zorbz

Rainbowkit

Rainbowkit is a beautiful & easy-to-use wallet interface for dApp developers. Remember how we used the dapp-starter project? That includes Rainbowkit out of the box.

To add Rainbowkit, all we need to do is put in a <ConnectWallet />:

src/pages/[tokenId].js

import ConnectWallet from '../components/ConnectWallet'

const TokenPage = () = (
...		
    <ConnectWallet />
....
)

export default TokenPage.

PS: add coolMode for the added effects.

Rainbowkit

Minting

You can mint from the zorbz project here:

https://zorbz.vercel.app/

Rainbowkit leverages the wagmi package for smart contract calls. Here’s how we use wagmi for simple tasks such as:

minting an NFT (abi).

import { useContract, useSigner } from 'wagmi'
import abi from './abi.json'

const TokenPage = () => {
  const { data: signer } = useSigner()
  const contract = useContract({
    addressOrName: '0x88d18451249d121A28637E4bE0B6BF7738729013',
    contractInterface: abi,
    signerOrProvider: signer,
  })

  const mintNft = async () => {
    await contract.mint( 
        1,
        'ipfs://QmfJZn2nA1FsSNgb2mQmvAM9sAvviQZHonivQB5bMwuFQX'
    );
  }

  return <button onClick={mintNft}>Mint</button>
};

export default TokenPage;

Your finished product.

Let me know if you have any questions on Lens or Twitter.

Loading...
highlight
Collect this post to permanently own it.
sweetman logo
Subscribe to sweetman and never miss a post.