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 protocol.
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 GraphQL.
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 github or reach out to us on discord
Subgraph endpoints
chain | url |
---|---|
mumbai (testnet) | https://api.thegraph.com/subgraphs/name/olta-art/mumbai-v1 |
polygon | https://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 spincubes
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>
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 mapping 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.
chain | explorer | playground |
---|---|---|
mumbai (testnet) | mumbai-v1 | mumbai-v1 |
polygon | polygon-v1 | polygon-v1 |
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
}
}
}`
`query {
edition(id: "${projectAddress}-${editionNumber}") {
owner: {
id
}
}
}`
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 ➔ (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
}
}
}`
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
}
}
}`