mirror of
https://github.com/stronk-dev/RandomChad.git
synced 2025-07-05 10:35:08 +02:00
✨ draft update by address changing room
This commit is contained in:
parent
30057f43ed
commit
03c217b55b
@ -1,67 +1,21 @@
|
||||
const app = require( './express' )()
|
||||
const { getTotalSupply } = require( '../modules/contract' )
|
||||
const { safelyReturnRocketeer, web2domain, safelyReturnMultipleRocketeers } = require( '../nft-media/rocketeer' )
|
||||
const { web2domain } = require( '../nft-media/rocketeer' )
|
||||
const { setAvatar, resetAvatar } = require( '../integrations/avatar' )
|
||||
const { generateNewOutfit, setPrimaryOutfit } = require( '../integrations/changingroom' )
|
||||
const { rocketeerFromRequest, multipleRocketeersFromRequest } = require( '../integrations/rocketeers' )
|
||||
const { generateNewOutfit, setPrimaryOutfit, generateMultipleNewOutfits } = require( '../integrations/changingroom' )
|
||||
|
||||
// ///////////////////////////////
|
||||
// Specific Rocketeer instances
|
||||
// ///////////////////////////////
|
||||
app.get( '/api/rocketeer/:id', async ( req, res ) => {
|
||||
|
||||
// Parse the request
|
||||
let { id } = req.params
|
||||
if( !id ) return res.json( { error: `No ID specified in URL` } )
|
||||
|
||||
// Protect against malformed input
|
||||
id = Math.floor( Math.abs( id ) )
|
||||
if( typeof id !== 'number' ) return res.json( { error: `Malformed request` } )
|
||||
|
||||
// Set ID to string so firestore can handle it
|
||||
id = `${ id }`
|
||||
|
||||
try {
|
||||
|
||||
// Get old rocketeer if it exists
|
||||
const rocketeer = await safelyReturnRocketeer( id, 'mainnet' )
|
||||
|
||||
// Return the new rocketeer
|
||||
return res.json( rocketeer )
|
||||
|
||||
} catch( 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() } )
|
||||
|
||||
}
|
||||
|
||||
} )
|
||||
|
||||
app.get( '/api/rocketeers/', async ( req, res ) => {
|
||||
|
||||
try {
|
||||
|
||||
// Parse the request
|
||||
let { ids } = req.query
|
||||
ids = ids.split( ',' )
|
||||
if( ids.length > 100 ) throw new Error( 'Please do not ask for so much data at once :)' )
|
||||
const rocketeers = await safelyReturnMultipleRocketeers( ids, 'testnet' )
|
||||
return res.json( rocketeers )
|
||||
|
||||
} catch( e ) {
|
||||
return res.json( { error: e.message || e.toString() } )
|
||||
}
|
||||
|
||||
|
||||
} )
|
||||
app.get( '/api/rocketeer/:id', async ( req, res ) => rocketeerFromRequest( req, res, 'mainnet' ) )
|
||||
app.get( '/api/rocketeers/', async ( req, res ) => multipleRocketeersFromRequest( req, res, 'mainnet' ) )
|
||||
|
||||
/* ///////////////////////////////
|
||||
// VGR's dashboard integration
|
||||
// /////////////////////////////*/
|
||||
app.post( '/api/integrations/avatar/', setAvatar )
|
||||
app.post( '/api/rocketeers/:address', generateMultipleNewOutfits )
|
||||
app.delete( '/api/integrations/avatar/', resetAvatar )
|
||||
|
||||
/* ///////////////////////////////
|
||||
|
@ -1,66 +1,20 @@
|
||||
const app = require( './express' )()
|
||||
const { getTotalSupply } = require( '../modules/contract' )
|
||||
const { safelyReturnRocketeer, web2domain, safelyReturnMultipleRocketeers } = require( '../nft-media/rocketeer' )
|
||||
const { generateNewOutfit, setPrimaryOutfit } = require( '../integrations/changingroom' )
|
||||
const { web2domain } = require( '../nft-media/rocketeer' )
|
||||
const { rocketeerFromRequest, multipleRocketeersFromRequest } = require( '../integrations/rocketeers' )
|
||||
const { generateNewOutfit, setPrimaryOutfit, generateMultipleNewOutfits } = require( '../integrations/changingroom' )
|
||||
|
||||
////////////////////////////////
|
||||
// Specific Rocketeer instances
|
||||
////////////////////////////////
|
||||
app.get( '/testnetapi/rocketeer/:id', async ( req, res ) => {
|
||||
|
||||
// Parse the request
|
||||
let { id } = req.params
|
||||
if( !id ) return res.json( { error: `No ID specified in URL` } )
|
||||
|
||||
// Protect against malformed input
|
||||
id = Math.floor( Math.abs( id ) )
|
||||
if( typeof id !== 'number' ) return res.json( { error: `Malformed request` } )
|
||||
|
||||
// Set ID to string so firestore can handle it
|
||||
id = `${ id }`
|
||||
|
||||
try {
|
||||
|
||||
// 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 }: `, e )
|
||||
|
||||
// Return error to frontend
|
||||
return res.json( { error: e.mesage || e.toString() } )
|
||||
|
||||
}
|
||||
|
||||
} )
|
||||
|
||||
app.get( '/testnetapi/rocketeers/', async ( req, res ) => {
|
||||
|
||||
try {
|
||||
|
||||
// Parse the request
|
||||
let { ids } = req.query
|
||||
ids = ids.split( ',' )
|
||||
if( ids.length > 100 ) throw new Error( 'Please do not ask for so much data at once :)' )
|
||||
const rocketeers = await safelyReturnMultipleRocketeers( ids, 'testnet' )
|
||||
return res.json( rocketeers )
|
||||
|
||||
} catch( e ) {
|
||||
return res.json( { error: e.message || e.toString() } )
|
||||
}
|
||||
|
||||
|
||||
} )
|
||||
app.get( '/testnetapi/rocketeer/:id', ( req, res ) => rocketeerFromRequest( req, res, 'rinkeby' ) )
|
||||
app.get( '/testnetapi/rocketeers/', ( req, res ) => multipleRocketeersFromRequest( req, res, 'rinkeby' ) )
|
||||
|
||||
/* ///////////////////////////////
|
||||
// Changing room endpoints
|
||||
// /////////////////////////////*/
|
||||
app.post( '/testnetapi/rocketeer/:id/outfits', generateNewOutfit )
|
||||
app.post( '/testnetapi/rocketeers/:address', generateMultipleNewOutfits )
|
||||
app.put( '/testnetapi/rocketeer/:id/outfits', setPrimaryOutfit )
|
||||
|
||||
// Collection data
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { generateNewOutfitFromId } = require( '../nft-media/changing-room' )
|
||||
const { generateNewOutfitFromId, generateNewOutfitsByAddress } = require( '../nft-media/changing-room' )
|
||||
const { db, dataFromSnap } = require( '../modules/firebase' )
|
||||
|
||||
// Web3 APIs
|
||||
@ -67,6 +67,57 @@ exports.generateNewOutfit = async function( req, res ) {
|
||||
|
||||
}
|
||||
|
||||
/* ///////////////////////////////
|
||||
// POST handler for new avatars
|
||||
// /////////////////////////////*/
|
||||
exports.generateMultipleNewOutfits = async function( req, res ) {
|
||||
|
||||
// Parse the request
|
||||
let { address } = req.params
|
||||
if( !address ) return res.json( { error: `No address specified in URL` } )
|
||||
|
||||
// Protect against malformed input
|
||||
if( !address.match( /0x.{40}/ ) ) return res.json( { error: `Malformed request` } )
|
||||
|
||||
// ⚠️ WIP
|
||||
const network = 'rinkeby'
|
||||
if( !process.env.NODE_ENV == 'development' ) return res.json( { error: `This endpoint is not live yet. While I appreciate your enthusiasm please don't touch this one yet :)` } )
|
||||
|
||||
try {
|
||||
|
||||
// // Get request data
|
||||
// const { message, signature, signatory } = req.body
|
||||
// if( !message || !signatory || !signature ) throw new Error( `Malformed request` )
|
||||
|
||||
// // Decode message
|
||||
// const confirmedSignatory = web3.eth.accounts.recover( message, signature )
|
||||
// if( signatory.toLowerCase() !== confirmedSignatory.toLowerCase() ) throw new Error( `Bad signature` )
|
||||
|
||||
// // Validate message
|
||||
// const messageObject = JSON.parse( message )
|
||||
// let { signer, action, chainId } = messageObject
|
||||
// const network = chainId == '0x1' ? 'mainnet' : 'rinkeby'
|
||||
// if( signer.toLowerCase() !== confirmedSignatory.toLowerCase() || action != 'generateMultipleNewOutfits' || !network ) throw new Error( `Invalid setPrimaryOutfit message with ${ signer }, ${confirmedSignatory}, ${action}, ${chainId}, ${network}` )
|
||||
|
||||
// Check that the signer is the owner of the token
|
||||
const outfits = await generateNewOutfitsByAddress( address, network )
|
||||
|
||||
return res.json( outfits )
|
||||
|
||||
|
||||
|
||||
} catch( e ) {
|
||||
|
||||
// Log error for debugging
|
||||
console.error( `POST generateMultipleNewOutfits Changing room api error for ${ address }: `, e )
|
||||
|
||||
// Return error to frontend
|
||||
return res.json( { error: e.mesage || e.toString() } )
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ///////////////////////////////
|
||||
// PUT handler for changing the
|
||||
// current outfit
|
||||
|
53
functions/integrations/rocketeers.js
Normal file
53
functions/integrations/rocketeers.js
Normal file
@ -0,0 +1,53 @@
|
||||
const { safelyReturnRocketeer, safelyReturnMultipleRocketeers } = require( '../nft-media/rocketeer' )
|
||||
|
||||
exports.rocketeerFromRequest = async function( req, res, network='mainnet' ) {
|
||||
|
||||
|
||||
// Parse the request
|
||||
let { id } = req.params
|
||||
if( !id ) return res.json( { error: `No ID specified in URL` } )
|
||||
|
||||
// Protect against malformed input
|
||||
id = Math.floor( Math.abs( id ) )
|
||||
if( typeof id !== 'number' ) return res.json( { error: `Malformed request` } )
|
||||
|
||||
// Set ID to string so firestore can handle it
|
||||
id = `${ id }`
|
||||
|
||||
try {
|
||||
|
||||
// Get old rocketeer if it exists
|
||||
const rocketeer = await safelyReturnRocketeer( id, network )
|
||||
|
||||
// Return the new rocketeer
|
||||
return res.json( rocketeer )
|
||||
|
||||
} catch( e ) {
|
||||
|
||||
// Log error for debugging
|
||||
console.error( `${ network } api error for ${ id }: `, e )
|
||||
|
||||
// Return error to frontend
|
||||
return res.json( { error: e.mesage || e.toString() } )
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
exports.multipleRocketeersFromRequest = async function( req, res, network='mainnet' ) {
|
||||
|
||||
try {
|
||||
|
||||
// Parse the request
|
||||
let { ids } = req.query
|
||||
ids = ids.split( ',' )
|
||||
if( ids.length > 100 ) throw new Error( 'Please do not ask for so much data at once :)' )
|
||||
const rocketeers = await safelyReturnMultipleRocketeers( ids, network )
|
||||
return res.json( rocketeers )
|
||||
|
||||
} catch( e ) {
|
||||
return res.json( { error: e.message || e.toString() } )
|
||||
}
|
||||
|
||||
}
|
@ -24,27 +24,72 @@ const ABI = [
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokenId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ownerOf",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokenId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ownerOf",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "tokenOfOwnerByIndex",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
}
|
||||
]
|
||||
|
||||
// Total current supply, in accordance with ERC721 spec
|
||||
@ -71,8 +116,28 @@ async function getOwingAddressOfTokenId( id, network='mainnet' ) {
|
||||
|
||||
}
|
||||
|
||||
async function getTokenIdsOfAddress( address, network='mainnet' ) {
|
||||
|
||||
// Initialise contract connection
|
||||
const web3 = new Web3( `wss://${ network }.infura.io/ws/v3/${ infura.projectid }` )
|
||||
const contract = new web3.eth.Contract( ABI, contractAddress[ network ] )
|
||||
|
||||
// Get balance of address
|
||||
const balance = await contract.methods.balanceOf( address ).call()
|
||||
|
||||
// Get tokens of address
|
||||
const ids = await Promise.all( Array.from( { length: balance } ).map( async ( val, index ) => {
|
||||
const id = await contract.methods.tokenOfOwnerByIndex( address, index ).call()
|
||||
return id.toString()
|
||||
} ) )
|
||||
|
||||
return ids
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTotalSupply,
|
||||
contractAddress,
|
||||
getOwingAddressOfTokenId
|
||||
getOwingAddressOfTokenId,
|
||||
getTokenIdsOfAddress
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
const { db, dataFromSnap } = require( '../modules/firebase' )
|
||||
const { getRgbArrayFromColorName, randomNumberBetween } = require( '../modules/helpers' )
|
||||
const { getTokenIdsOfAddress } = require( '../modules/contract' )
|
||||
const svgFromAttributes = require( './svg-generator' )
|
||||
const Throttle = require( 'promise-parallel-throttle' )
|
||||
|
||||
// ///////////////////////////////
|
||||
// Rocketeer generator
|
||||
// ///////////////////////////////
|
||||
exports.generateNewOutfitFromId = async function( id, network='mainnet' ) {
|
||||
async function generateNewOutfitFromId( id, network='mainnet' ) {
|
||||
|
||||
|
||||
/* ///////////////////////////////
|
||||
@ -27,7 +29,7 @@ exports.generateNewOutfitFromId = async function( id, network='mainnet' ) {
|
||||
// Apply entropy levels based on edition status and outfits available
|
||||
const { value: edition } = rocketeer.attributes.find( ( { trait_type } ) => trait_type == "edition" )
|
||||
if( edition != 'regular' ) colorEntropy *= specialEditionMultiplier
|
||||
if( available_outfits ) colorEntropy *= ( entropyMultiplier * available_outfits )
|
||||
if( available_outfits ) colorEntropy *= ( entropyMultiplier ** available_outfits )
|
||||
|
||||
// Check whether this Rocketeer is allowed to change
|
||||
const timeUntilAllowedToChange = newOutfitAllowedInterval - ( Date.now() - last_outfit_change )
|
||||
@ -88,3 +90,31 @@ exports.generateNewOutfitFromId = async function( id, network='mainnet' ) {
|
||||
return newOutfitSvg
|
||||
|
||||
}
|
||||
|
||||
async function generateNewOutfitsByAddress( address, network='mainnet' ) {
|
||||
|
||||
|
||||
const ids = await getTokenIdsOfAddress( address, network )
|
||||
const queue = ids.map( id => function() {
|
||||
return generateNewOutfitFromId( id, network ).then( outfit => ( { id: id, src: outfit } ) ).catch( e => {
|
||||
console.log( e )
|
||||
return ( { id: id, error: e.message } )
|
||||
} )
|
||||
} )
|
||||
const outfits = await Throttle.all( queue, {
|
||||
maxInProgress: 2,
|
||||
progressCallback: ( { amountDone } ) => process.env.NODE_ENV == 'development' ? console.log( `Completed ${amountDone}/${queue.length}` ) : false
|
||||
} )
|
||||
|
||||
return {
|
||||
success: outfits.filter( ( { src } ) => src ),
|
||||
error: outfits.filter( ( { error } ) => error ),
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateNewOutfitFromId,
|
||||
generateNewOutfitsByAddress
|
||||
}
|
@ -151,13 +151,14 @@ async function safelyReturnRocketeer( id, network ) {
|
||||
|
||||
async function safelyReturnMultipleRocketeers( ids=[], network='mainnet' ) {
|
||||
|
||||
|
||||
// Chech if this is an illegal ID
|
||||
const invalidIds = await Promise.all( ids.map( id => isInvalidRocketeerId( id, network ) ) )
|
||||
if( invalidIds.includes( true ) ) throw invalidIds
|
||||
|
||||
// Get old rocketeers and append their ids
|
||||
const rocketeers = await Promise.all( ids.map( async id => ( {
|
||||
...await getExistingRocketeer( id ),
|
||||
...await getExistingRocketeer( id, network ),
|
||||
id: id
|
||||
} ) ) )
|
||||
|
||||
|
5
functions/package-lock.json
generated
5
functions/package-lock.json
generated
@ -4837,6 +4837,11 @@
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
|
||||
},
|
||||
"promise-parallel-throttle": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/promise-parallel-throttle/-/promise-parallel-throttle-3.3.0.tgz",
|
||||
"integrity": "sha512-tThe11SfFXlGMhuO2D+Nba6L8FJFM17w2zwlMV1kqaLfuT2E8NMtMF1WhJBZaSpWz6V76pP/bGAj8BXTAMOncw=="
|
||||
},
|
||||
"proto3-json-serializer": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.5.tgz",
|
||||
|
@ -22,6 +22,7 @@
|
||||
"firebase-admin": "^10.0.0",
|
||||
"firebase-functions": "^3.11.0",
|
||||
"jsdom": "^18.0.0",
|
||||
"promise-parallel-throttle": "^3.3.0",
|
||||
"puppeteer": "^12.0.0",
|
||||
"puppeteer-extra": "^3.2.3",
|
||||
"puppeteer-extra-plugin-stealth": "^2.9.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user