Dynamic Editions

WARNING

Will not render on 3rd party platforms such as Opensea. This is due to the same origin policy. We have idea's on how to fix this in the coming months but may require some url's to be updated.

Work in progress

Improvement's on this page and more examples are in the works.

Overview

Making editions dynamic means that the rendered project reacts to external data in some way. To react to data and events on the blockchain we propose a solution that utilises the graph protocolopen in new window.

The general idea is that each project smart contract emits an event anytime something happens, for example if an edition is minted or transferred. This event is indexed by Olta's subgraph so the data can be easily be queried with GraphQLopen in new window.

Olta Subgraph

The olta subgraph is open-source and currently deployed on the hosted service. It will be migrated to decentralized solution once available on polygon.

It's open-source and in active development so if you have a feature request please raise an issue on githubopen in new window or reach out to us on discordopen in new window

Subgraph endpoints

chainurl
mumbai (testnet)https://api.thegraph.com/subgraphs/name/olta-art/mumbai-v1
polygonhttps://api.thegraph.com/subgraphs/name/olta-art/polygon-v1

If you already comftable with graphQL jump straight to the explorer

Examples

Let's go through some examples of how to find out the current owner of an edition.

for a full example with three.js check out spincubesopen in new window

Setup

First of we need a way to perform a simple graphQL query. For this example we will use a simple function found below.

graphQL query fetcher

// Copy this into a helper.js file

// Helps run HTTP requests.
export function downloader(timeout = 100 * 1000) {
  return async (url, options = {}) => {
    // Guard against unresponsive calls.
    const controller = new AbortController()

    const timer = setTimeout(() => {
      clearTimeout(timer)
      controller.abort()
    }, timeout)

    try {
      const response = await fetch(url, {
        signal: controller.signal,
        ...options,
      })

      if (response.ok) {
        return await response.json()
      }

      throw new Error(`${response.status}: ${response.statusText || "Error"}.`)
    } catch (e) {
      // Forward to caller, JSON parsing errors end up here too.
      throw e
    }
  }
}

// Helps run data queries.
export async function queryFetcher(url, query, settings = {}) {
  const download = downloader(20000)
  const options = {
    body: JSON.stringify({ query }),
    headers: {
      "Content-Type": "application/json",
    },
    method: "post",
    // Allow for overriding default options.
    ...settings,
  }

  try {
    const response = await download(url, options)

    response?.errors?.forEach((e) => {
      throw new Error(e.message)
    })

    return response?.data
  } catch (e) {
    throw e
  }
}

<!-- insert near the top of the <body> html tag -->

<script>
  // Helps run HTTP requests.
  function downloader(timeout = 100 * 1000) {
    return async (url, options = {}) => {
      // Guard against unresponsive calls.
      const controller = new AbortController()

      const timer = setTimeout(() => {
        clearTimeout(timer)
        controller.abort()
      }, timeout)

      try {
        const response = await fetch(url, {
          signal: controller.signal,
          ...options,
        })

        if (response.ok) {
          return await response.json()
        }

        throw new Error(`${response.status}: ${response.statusText || "Error"}.`)
      } catch (e) {
        // Forward to caller, JSON parsing errors end up here too.
        throw e
      }
    }
  }

  // Helps run data queries.
  async function queryFetcher(url, query, settings = {}) {
    const download = downloader(20000)
    const options = {
      body: JSON.stringify({ query }),
      headers: {
        "Content-Type": "application/json",
      },
      method: "post",
      // Allow for overriding default options.
      ...settings,
    }

    try {
      const response = await download(url, options)

      response?.errors?.forEach((e) => {
        throw new Error(e.message)
      })

      return response?.data
    } catch (e) {
      throw e
    }
  }
</script>

graphQL clients

if you need more functionality

Owner of an Edition

We can use the queryFetcher function above to retrieve data from the olta subgraph. Let's use data from a project already setup on mumbai. And fetch the owner address of the first edition minted.

// import queryFetcher from the setup above
import { queryFetcher } from "./helper.js"

// use the mumbai endpoint for testing
const subgraph = "https://api.thegraph.com/subgraphs/name/olta-art/mumbai-v1"

// info about the project
const project = "0x07428b7a16bf805787d3c3546c9b40a10dbd3a57"
const edition = 1

// query to fetch the owner of an edition
const query = `{
    edition(id:"${project}-${edition}"){
      owner {
        id
      }
    }
  }`


const response = await queryFetcher(subgraph, query)
const ownerAddress = response.edition.owner.id

// do stuff with owner address here

main.js

// use the mumbai endpoint for testing
const subgraph = "https://api.thegraph.com/subgraphs/name/olta-art/mumbai-v1"

// info about the project
const project = "0x07428b7a16bf805787d3c3546c9b40a10dbd3a57"
const edition = 1

// query to fetch the owner of an edition
const query = `{
    edition(id:"${project}-${edition}"){
      owner {
        id
      }
    }
  }`

queryFetcher(subgraph, query).then(response => {
  const ownerAddress = response.edition.owner.id

  // do stuff with owner address here
})

Use url params when live

When the artwork is live use the url params to construct your queries See url params

The returned owner address can then be used to change how your edition is rendered for example passing it into a seeded random function to change a color or property.

All editions of a project

How about fetching all the owner addresses of a project. This can be done by querying a project and mappingopen in new window it's editions to an array of owner addresses.

import { queryFetcher } from "./helper.js"

const subgraph = "https://api.thegraph.com/subgraphs/name/olta-art/mumbai-v1"
const project = "0x07428b7a16bf805787d3c3546c9b40a10dbd3a57"

// query to fetch owner id's of all editions
const query = `{
    project(id: "${project}") {
      editions {
        owner {
          id
        }
      }
    }
  }`

const response = await queryFetcher(subgraph, query)

// use map to extract the owner addresses into an array
const ownerAddresses = response.project.editions.map(edition => edition.owner.id)

ownerAddresses.forEach(ownerAddress => {
  // do stuff with each owner addresses here
})

main.js

const subgraph = "https://api.thegraph.com/subgraphs/name/olta-art/mumbai-v1"
const project = "0x07428b7a16bf805787d3c3546c9b40a10dbd3a57"

// query to fetch owner id's of all editions
const query = `{
    project(id: "${project}") {
      editions {
        owner {
          id
        }
      }
    }
  }`

queryFetcher(subgraph, query).then(response => {
  // use map to extract the owner addresses into an array
  const ownerAddresses = response.project.editions.map(edition => edition.owner.id)

  ownerAddresses.forEach(ownerAddress => {
    // do stuff with each owner addresses here
  })
})

Owner of an Edition via seed

For seeded projects it may be useful to find the owner via the seed.

This can be achieved using a filter, specifically the where filter.

where can be used on any plural entity (projects, editions) and can have multiple property's

const query = `{
    editions(where {project:"${project}" seed:"${seed}"){
      owner {
        id
      }
    }
  }`

Explorer

The Graphql explorer let's you easily sketch out queries and query subgraphs. The playground is a great place to find out more about the schema and the different data available.

chainexplorerplayground
mumbai (testnet)mumbai-v1open in new windowmumbai-v1open in new window
polygonpolygon-v1open in new windowpolygon-v1open in new window

To use:

click on the explorer tab top right corner. In here you can see all the data available. Click on the tick boxes to add to a query. ONce ready click the play button at the top to fetch the query.

Documentation can be quickly accessed by hovering over any entity or in the top right tab. Use the search bar to explore the schema.

To get started it try out some of the queries below

Queries

Some example queries to get started

Edition

Edition ID's are constructed from the project address and the edition number for example

"0x07428b7a16bf805787d3c3546c9b40a10dbd3a57-1"

If you only need to retrieve data about one edition it can be found like this

`query {
  edition(id: "${projectAddress}-${editionNumber}") {
    uri
    seed
    number
    id
    createdAtTransactionHash
    createdAtTimestamp
    createdAtBlockNumber
    burnedAtTimeStamp
    burnedAtBlockNumber
    owner {
      id
    }
    prevOwner {
      id
    }
    transfers {
      id
    }
    project {
      id
    }
  }
}`

try out ➔open in new window

`query {
  edition(id: "${projectAddress}-${editionNumber}") {
    owner: {
      id
    }
  }
}`

try out ➔open in new window

Note

owner, prevOwner, transfers and project can be expanded with more properties for example:

project {
  id
  editionSize
  description
  name
  royaltyBPS
  implementation
  lastAddedVersion {
    animation {
      url
    }
  }
}

Editions

To fetch an edition based on it's seed we need to use the where filter. This will return an array of editions.

`query{
  editions(where: {project: "${projectAddress}" seed: "${seed}"}) {
    uri
    seed
    number
    id
    createdAtTransactionHash
    createdAtTimestamp
    createdAtBlockNumber
    burnedAtTimeStamp
    burnedAtBlockNumber
    owner {
      id
    }
    prevOwner {
      id
    }
    transfers {
      id
    }
    project {
      id
    }
  }
}`

try out ➔open in new window (note: returns an empty array)

Other useful filters

`editions(where: {project: "${projectAddress}" owner: "${owner}"}) {`
// returns all editions of an owner for a given project
`editions(where: {project: "${projectAddress}" createdAtTimestamp_gt: "${timestamp}"}) {`
// returns all editions that were created after the timestamp for a given project
`editions(where: {owner: "${owner}"}) {`
// returns all editions of a given owner

Project

`query{
  project(id: "${projectAddress}") {
    id
    name
    description
    editionSize
    creator {
      id
    }
    royaltyBPS
    createdAtTimestamp
    createdAtBlockNumber
    implementation
    editions {
      id
    }
    totalBurned
    totalMinted
    totalSupply
    lastAddedVersion {
      animation {
        url
      }
      image {
        url
      }
      label
    }
  }
}`

try out ➔open in new window

User

The user entity id is the users wallet address (these can also be smart contracts)

collections is editions owned and creations are projects created

`query{
  user(id:"${userAddress}") {
    id
    curatedCreator
    collection{
      id
      number
      seed
      uri
    }
    creations {
      id
      name
      description
      editionSize
    }
  }
}`

try out ➔open in new window