mirror of
https://github.com/stronk-dev/RandomChad.git
synced 2025-07-06 02:45:09 +02:00
Testnet and mainnet entries both dynamic
This commit is contained in:
parent
42aa39c343
commit
63d72cbfcb
@ -1,60 +1,7 @@
|
|||||||
const app = require( './express' )()
|
const app = require( './express' )()
|
||||||
const name = require( 'random-name' )
|
|
||||||
const { db } = require( './firebase' )
|
|
||||||
const { getTotalSupply } = require( './contract' )
|
const { getTotalSupply } = require( './contract' )
|
||||||
|
const { safelyReturnRocketeer, web2domain } = require( './rocketeer' )
|
||||||
|
|
||||||
// ///////////////////////////////
|
|
||||||
// Data sources
|
|
||||||
// ///////////////////////////////
|
|
||||||
const globalAttributes = [
|
|
||||||
{ trait_type: "Age", display_type: "number", values: [
|
|
||||||
{ value: 35, probability: .5 },
|
|
||||||
{ value: 45, probability: .25 },
|
|
||||||
{ value: 25, probability: .25 }
|
|
||||||
] }
|
|
||||||
]
|
|
||||||
const heavenlyBodies = [ "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", "the Moon", "the Sun" ]
|
|
||||||
const web2domain = 'https://rocketeer.fans'
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
|
||||||
// Rocketeer helpers
|
|
||||||
// ///////////////////////////////
|
|
||||||
|
|
||||||
// Pick random item from array with equal probability
|
|
||||||
const pickRandomArrayEntry = array => array[ Math.floor( Math.random() * array.length ) ]
|
|
||||||
|
|
||||||
// Pick random attributes based on global attribute array
|
|
||||||
function pickRandomAttributes( attributes ) {
|
|
||||||
|
|
||||||
// Decimal accuracy, if probabilities have the lowest 0.01 then 100 is enough, for 0.001 1000 is needed
|
|
||||||
const probabilityDecimals = 3
|
|
||||||
|
|
||||||
// Remap the trait so it has a 'lottery ticket box' based on probs
|
|
||||||
const attributeLottery = attributes.map( ( { values, ...attribute } ) => ( {
|
|
||||||
// Attribute meta stays the same
|
|
||||||
...attribute,
|
|
||||||
// Values are reduced from objects with probabilities to an array with elements
|
|
||||||
values: values.reduce( ( acc, val ) => {
|
|
||||||
|
|
||||||
const { probability, value } = val
|
|
||||||
|
|
||||||
// Map probabilities to a flat array of items
|
|
||||||
const amountToAdd = 10 * probabilityDecimals * probability
|
|
||||||
for ( let i = 0; i < amountToAdd; i++ ) acc.push( value )
|
|
||||||
return acc
|
|
||||||
|
|
||||||
}, [] )
|
|
||||||
} ) )
|
|
||||||
|
|
||||||
// Pick a random element from the lottery box array items
|
|
||||||
return attributeLottery.map( ( { values, ...attribute } ) => ( {
|
|
||||||
// Attribute meta stays the same
|
|
||||||
...attribute,
|
|
||||||
// Select random entry from array
|
|
||||||
value: pickRandomArrayEntry( values )
|
|
||||||
} ) )
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
// Specific Rocketeer instances
|
// Specific Rocketeer instances
|
||||||
@ -65,74 +12,32 @@ app.get( '/api/rocketeer/:id', async ( req, res ) => {
|
|||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
if( !id ) return res.json( { error: `No ID specified in URL` } )
|
if( !id ) return res.json( { error: `No ID specified in URL` } )
|
||||||
|
|
||||||
// Chech if this is an illegal ID
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Get the last know total supply
|
// Get old rocketeer if it exists
|
||||||
const { cachedTotalSupply } = await db.collection( 'meta' ).doc( 'contract' ).get().then( doc => doc.data() )
|
const rocketeer = await safelyReturnRocketeer( id, 'mainnet' )
|
||||||
|
|
||||||
// If the requested ID is larger than that, check if the new total supply is more
|
// Return the new rocketeer
|
||||||
if( cachedTotalSupply < id ) {
|
return res.json( rocketeer )
|
||||||
|
|
||||||
// Get net total supply through infura, if infura fails, return the cached value just in case
|
|
||||||
const totalSupply = await getTotalSupply().catch( f => cachedTotalSupply )
|
|
||||||
|
|
||||||
// Write new value to cache
|
|
||||||
await db.collection( 'meta' ).doc( 'contract' ).set( { cachedTotalSupply: totalSupply }, { merge: true } )
|
|
||||||
|
|
||||||
// If the requested ID is larger than total supply, exit
|
|
||||||
if( totalSupply < id ) return res.json( {
|
|
||||||
trace: 'total supply getter',
|
|
||||||
error: 'This Rocketeer does not yet exist.'
|
|
||||||
} )
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch( e ) {
|
|
||||||
return res.json( { trace: 'total supply getter', error: e.message || JSON.stringify( e ) } )
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get existing rocketeer if it exists
|
|
||||||
try {
|
|
||||||
|
|
||||||
const oldRocketeer = await db.collection( 'rocketeers' ).doc( id ).get().then( doc => doc.data() )
|
|
||||||
if( oldRocketeer ) return res.json( oldRocketeer )
|
|
||||||
|
|
||||||
} catch( e ) {
|
} catch( e ) {
|
||||||
return res.json( { trace: 'firestore rocketeer read',error: e.message || JSON.stringify( e ) } )
|
|
||||||
|
// Log error for debugging
|
||||||
|
console.error( `Mainnet api error for ${ id }: `, e )
|
||||||
|
|
||||||
|
// Return error to frontend
|
||||||
|
return res.json( { error: e.mesage || e.toString() } )
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The base object of a new Rocketeer
|
|
||||||
const rocketeer = {
|
|
||||||
name: `${ name.first() } ${ name.middle() } ${ name.last() } of ${ pickRandomArrayEntry( heavenlyBodies ) }`,
|
|
||||||
description: ``,
|
|
||||||
image: ``,
|
|
||||||
external_url: `${ web2domain }/api/rocketeer/${ id }`,
|
|
||||||
attributes: []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate randomized attributes
|
|
||||||
rocketeer.attributes = pickRandomAttributes( globalAttributes )
|
|
||||||
|
|
||||||
// TODO: Generate, compile and upload image
|
|
||||||
rocketeer.image = web2domain
|
|
||||||
|
|
||||||
// Save new Rocketeer
|
|
||||||
try {
|
|
||||||
await db.collection( 'rocketeers' ).doc( id ).set( rocketeer )
|
|
||||||
} catch( e ) {
|
|
||||||
return res.json( { trace: 'firestore rocketeer save', error: e.message || JSON.stringify( e ) } )
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the new rocketeer
|
|
||||||
return res.json( rocketeer )
|
|
||||||
|
|
||||||
} )
|
} )
|
||||||
|
|
||||||
|
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
// Static collection data
|
// Static collection data
|
||||||
// ///////////////////////////////
|
// ///////////////////////////////
|
||||||
app.get( '/api/collection', ( req, res ) => res.json( {
|
app.get( '/api/collection', async ( req, res ) => res.json( {
|
||||||
|
totalSupply: await getTotalSupply( 'mainnet' ).catch( f => 'error' ),
|
||||||
description: "A testnet collection",
|
description: "A testnet collection",
|
||||||
external_url: web2domain,
|
external_url: web2domain,
|
||||||
image: "https://rocketpool.net/images/rocket.png",
|
image: "https://rocketpool.net/images/rocket.png",
|
||||||
|
143
functions/modules/rocketeer.js
Normal file
143
functions/modules/rocketeer.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
const name = require( 'random-name' )
|
||||||
|
const { db } = require( './firebase' )
|
||||||
|
const { getTotalSupply } = require( './contract' )
|
||||||
|
|
||||||
|
// ///////////////////////////////
|
||||||
|
// Data sources
|
||||||
|
// ///////////////////////////////
|
||||||
|
const globalAttributes = [
|
||||||
|
{ trait_type: "Age", display_type: "number", values: [
|
||||||
|
{ value: 35, probability: .5 },
|
||||||
|
{ value: 45, probability: .25 },
|
||||||
|
{ value: 25, probability: .25 }
|
||||||
|
] }
|
||||||
|
]
|
||||||
|
const heavenlyBodies = [ "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", "the Moon", "the Sun" ]
|
||||||
|
const web2domain = 'https://rocketeer.fans'
|
||||||
|
|
||||||
|
// ///////////////////////////////
|
||||||
|
// Rocketeer helpers
|
||||||
|
// ///////////////////////////////
|
||||||
|
|
||||||
|
// Pick random item from array with equal probability
|
||||||
|
const pickRandomArrayEntry = array => array[ Math.floor( Math.random() * array.length ) ]
|
||||||
|
|
||||||
|
// Pick random attributes based on global attribute array
|
||||||
|
function pickRandomAttributes( attributes ) {
|
||||||
|
|
||||||
|
// Decimal accuracy, if probabilities have the lowest 0.01 then 100 is enough, for 0.001 1000 is needed
|
||||||
|
const probabilityDecimals = 3
|
||||||
|
|
||||||
|
// Remap the trait so it has a 'lottery ticket box' based on probs
|
||||||
|
const attributeLottery = attributes.map( ( { values, ...attribute } ) => ( {
|
||||||
|
// Attribute meta stays the same
|
||||||
|
...attribute,
|
||||||
|
// Values are reduced from objects with probabilities to an array with elements
|
||||||
|
values: values.reduce( ( acc, val ) => {
|
||||||
|
|
||||||
|
const { probability, value } = val
|
||||||
|
|
||||||
|
// Map probabilities to a flat array of items
|
||||||
|
const amountToAdd = 10 * probabilityDecimals * probability
|
||||||
|
for ( let i = 0; i < amountToAdd; i++ ) acc.push( value )
|
||||||
|
return acc
|
||||||
|
|
||||||
|
}, [] )
|
||||||
|
} ) )
|
||||||
|
|
||||||
|
// Pick a random element from the lottery box array items
|
||||||
|
return attributeLottery.map( ( { values, ...attribute } ) => ( {
|
||||||
|
// Attribute meta stays the same
|
||||||
|
...attribute,
|
||||||
|
// Select random entry from array
|
||||||
|
value: pickRandomArrayEntry( values )
|
||||||
|
} ) )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ///////////////////////////////
|
||||||
|
// Caching
|
||||||
|
// ///////////////////////////////
|
||||||
|
async function isInvalidRocketeerId( id, network='mainnet' ) {
|
||||||
|
|
||||||
|
// Chech if this is an illegal ID
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Get the last know total supply
|
||||||
|
const { cachedTotalSupply } = await db.collection( 'meta' ).doc( network ).get().then( doc => doc.data() ) || {}
|
||||||
|
|
||||||
|
// If the requested ID is larger than that, check if the new total supply is more
|
||||||
|
if( !cachedTotalSupply || cachedTotalSupply < id ) {
|
||||||
|
|
||||||
|
// Get net total supply through infura, if infura fails, return the cached value just in case
|
||||||
|
const totalSupply = await getTotalSupply( network )
|
||||||
|
|
||||||
|
// Write new value to cache
|
||||||
|
await db.collection( 'meta' ).doc( network ).set( { cachedTotalSupply: totalSupply }, { merge: true } )
|
||||||
|
|
||||||
|
// If the requested ID is larger than total supply, exit
|
||||||
|
if( totalSupply < id ) throw new Error( `Invalid ID ${ id }, total supply is ${ totalSupply }` )
|
||||||
|
|
||||||
|
// If all good, return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch( e ) {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getExistingRocketeer( id, network='mainnet' ) {
|
||||||
|
|
||||||
|
return db.collection( `${ network }Rocketeers` ).doc( id ).get().then( doc => doc.data() ).catch( f => false )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ///////////////////////////////
|
||||||
|
// Rocketeer generator
|
||||||
|
// ///////////////////////////////
|
||||||
|
async function generateRocketeer( id, network='mainnet' ) {
|
||||||
|
|
||||||
|
// The base object of a new Rocketeer
|
||||||
|
const rocketeer = {
|
||||||
|
name: `${ name.first() } ${ name.middle() } ${ name.last() } of ${ pickRandomArrayEntry( heavenlyBodies ) }`,
|
||||||
|
description: ``,
|
||||||
|
image: ``,
|
||||||
|
external_url: `${ web2domain }/${ network == 'mainnet' ? 'api' : 'testnetapi' }/rocketeer/${ id }`,
|
||||||
|
attributes: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate randomized attributes
|
||||||
|
rocketeer.attributes = pickRandomAttributes( globalAttributes )
|
||||||
|
|
||||||
|
// TODO: Generate, compile and upload image
|
||||||
|
rocketeer.image = web2domain
|
||||||
|
|
||||||
|
// Save new Rocketeer
|
||||||
|
await db.collection( `${ network }Rocketeers` ).doc( id ).set( rocketeer )
|
||||||
|
|
||||||
|
return rocketeer
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function safelyReturnRocketeer( id, network ) {
|
||||||
|
|
||||||
|
// Chech if this is an illegal ID
|
||||||
|
const invalidId = await isInvalidRocketeerId( id, network )
|
||||||
|
if( invalidId ) throw invalidId
|
||||||
|
|
||||||
|
// Get old rocketeer if it exists
|
||||||
|
const oldRocketeer = await getExistingRocketeer( id, network )
|
||||||
|
if( oldRocketeer ) return oldRocketeer
|
||||||
|
|
||||||
|
// If no old rocketeer exists, make a new one and save it
|
||||||
|
return generateRocketeer( id, network )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
web2domain: web2domain,
|
||||||
|
safelyReturnRocketeer: safelyReturnRocketeer
|
||||||
|
}
|
@ -1,24 +1,40 @@
|
|||||||
const app = require( './express' )()
|
const app = require( './express' )()
|
||||||
const { getTotalSupply } = require( './contract' )
|
const { getTotalSupply } = require( './contract' )
|
||||||
|
const { safelyReturnRocketeer, web2domain } = require( './rocketeer' )
|
||||||
|
|
||||||
// Specific Rocketeer instances
|
// Specific Rocketeer instances
|
||||||
app.get( '/testnetapi/rocketeer/:id', async ( req, res ) => res.json( {
|
app.get( '/testnetapi/rocketeer/:id', async ( req, res ) => {
|
||||||
description: "A testnet Rocketeer",
|
|
||||||
external_url: `https://openseacreatures.io/${ req.params.id }`,
|
// Parse the request
|
||||||
image: "https://rocketpool.net/images/rocket.png",
|
const { id } = req.params
|
||||||
name: `Rocketeer number ${ req.params.id }`,
|
if( !id ) return res.json( { error: `No ID specified in URL` } )
|
||||||
attributes: [
|
|
||||||
{ trait_type: "Occupation", value: "Rocketeer" },
|
try {
|
||||||
{ trait_type: "Age", display_type: "number", value: req.params.id + 42 }
|
|
||||||
]
|
// Get old rocketeer if it exists
|
||||||
} ) )
|
const rocketeer = await safelyReturnRocketeer( id, 'rinkeby' )
|
||||||
|
|
||||||
|
// Return the new rocketeer
|
||||||
|
return res.json( rocketeer )
|
||||||
|
|
||||||
|
} catch( e ) {
|
||||||
|
|
||||||
|
// Log error for debugging
|
||||||
|
console.error( `Testnet api error for ${ id }: `, Object.keys( e ) )
|
||||||
|
|
||||||
|
// Return error to frontend
|
||||||
|
return res.json( { error: e.mesage || e.toString() } )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} )
|
||||||
|
|
||||||
|
|
||||||
// Collection data
|
// Collection data
|
||||||
app.get( '/testnetapi/collection', async ( req, res ) => res.json( {
|
app.get( '/testnetapi/collection', async ( req, res ) => res.json( {
|
||||||
totalSupply: await getTotalSupply( 'rinkeby' ),
|
totalSupply: await getTotalSupply( 'rinkeby' ).catch( f => 'error' ),
|
||||||
description: "A testnet collection",
|
description: "A testnet collection",
|
||||||
external_url: `https://openseacreatures.io/`,
|
external_url: web2domain,
|
||||||
image: "https://rocketpool.net/images/rocket.png",
|
image: "https://rocketpool.net/images/rocket.png",
|
||||||
name: `Rocketeer collection`,
|
name: `Rocketeer collection`,
|
||||||
seller_fee_basis_points: 0,
|
seller_fee_basis_points: 0,
|
||||||
|
5018
website/package-lock.json
generated
5018
website/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -20,41 +20,41 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/actuallymentor/static-webpage-boilerplate-webpack-browsersync#readme",
|
"homepage": "https://github.com/actuallymentor/static-webpage-boilerplate-webpack-browsersync#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.13.15",
|
"@babel/core": "^7.15.8",
|
||||||
"@babel/polyfill": "^7.12.1",
|
"@babel/polyfill": "^7.12.1",
|
||||||
"@babel/preset-env": "^7.13.15",
|
"@babel/preset-env": "^7.15.8",
|
||||||
"autoprefixer": "^10.2.5",
|
"autoprefixer": "^10.3.7",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"browser-sync": "^2.26.14",
|
"browser-sync": "^2.27.5",
|
||||||
"browser-sync-webpack-plugin": "^2.3.0",
|
"browser-sync-webpack-plugin": "^2.3.0",
|
||||||
"css-loader": "^5.2.1",
|
"css-loader": "^5.2.7",
|
||||||
"cssnano": "^5.0.0",
|
"cssnano": "^5.0.8",
|
||||||
"del": "^6.0.0",
|
"del": "^6.0.0",
|
||||||
"doiuse": "^4.4.1",
|
"doiuse": "^4.4.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.6.0",
|
||||||
"html-minifier": "^4.0.0",
|
"html-minifier": "^4.0.0",
|
||||||
"ip": "^1.1.5",
|
"ip": "^1.1.5",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"node-sass": "^5.0.0",
|
"node-sass": "^5.0.0",
|
||||||
"postcss": "^8.2.10",
|
"postcss": "^8.3.9",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"sharp": "^0.28.1",
|
"sharp": "^0.29.1",
|
||||||
"sitemap": "^6.4.0",
|
"sitemap": "^6.4.0",
|
||||||
"webpack": "^5.31.2"
|
"webpack": "^5.58.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.13.14",
|
"@babel/eslint-parser": "^7.15.8",
|
||||||
"@babel/plugin-transform-runtime": "^7.13.15",
|
"@babel/plugin-transform-runtime": "^7.15.8",
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"eslint": "^7.24.0",
|
"eslint": "^7.32.0",
|
||||||
"esm": "^3.2.25",
|
"esm": "^3.2.25",
|
||||||
"mocha": "^8.3.2",
|
"mocha": "^8.4.0",
|
||||||
"recursive-readdir": "^2.2.2",
|
"recursive-readdir": "^2.2.2",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-promise-native": "^1.0.9",
|
"request-promise-native": "^1.0.9",
|
||||||
"webpack-cli": "^4.6.0"
|
"webpack-cli": "^4.9.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"fsevents": "^2.3.2"
|
"fsevents": "^2.3.2"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user