const name = require( 'random-name' ) const { db } = require( './firebase' ) const { getTotalSupply } = require( './contract' ) // /////////////////////////////// // Attribute 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' const lorem = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' // /////////////////////////////// // 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: lorem, image: ``, external_url: `https://viewer.rocketeer.fans/?rocketeer=${ id }` + network == 'mainnet' ? '' : '&testnet=true', attributes: [] } // Generate randomized attributes rocketeer.attributes = pickRandomAttributes( globalAttributes ) // TODO: Generate, compile and upload image rocketeer.image = `${web2domain}/assets/draft-rocketeer.png` // 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 }