multi-outfit generator out of beta!

This commit is contained in:
Mentor Palokaj 2022-01-13 16:50:07 +01:00
parent b9c1c6c162
commit b45f6eb3c8
19 changed files with 1121 additions and 24 deletions

View File

@ -3,4 +3,5 @@
- [ ] `./functions`: set Infura project ID through `firebase functions:config:set infura.projectid=`
- [ ] also set `integration.secret`
- [ ] also set `discord.webhookurl`
- [ ] also set `mailgun.api_key`, `mailgun.api_url`, `mailgun.from_domain`, `mailgun.from_email`
- [ ] `./functions/package.json`: dependencies for backend, run `npm i` in `./functions`

View File

@ -4,6 +4,7 @@ const { web2domain } = require( '../nft-media/rocketeer' )
const { setAvatar, resetAvatar } = require( '../integrations/avatar' )
const { rocketeerFromRequest, multipleRocketeersFromRequest } = require( '../integrations/rocketeers' )
const { generateNewOutfit, setPrimaryOutfit, generateMultipleNewOutfits } = require( '../integrations/changingroom' )
const { subscribe_address_to_notifications } = require( '../integrations/notifier' )
// ///////////////////////////////
// Specific Rocketeer instances
@ -24,6 +25,12 @@ app.delete( '/api/integrations/avatar/', resetAvatar )
app.post( '/api/rocketeer/:id/outfits', generateNewOutfit )
app.put( '/api/rocketeer/:id/outfits', setPrimaryOutfit )
/* ///////////////////////////////
// Notification API
// /////////////////////////////*/
app.post( '/api/notifications/:address', subscribe_address_to_notifications )
// ///////////////////////////////
// Static collection data
// ///////////////////////////////

View File

@ -3,6 +3,7 @@ const { getTotalSupply } = require( '../modules/contract' )
const { web2domain } = require( '../nft-media/rocketeer' )
const { rocketeerFromRequest, multipleRocketeersFromRequest } = require( '../integrations/rocketeers' )
const { generateNewOutfit, setPrimaryOutfit, generateMultipleNewOutfits } = require( '../integrations/changingroom' )
const { subscribe_address_to_notifications } = require( '../integrations/notifier' )
////////////////////////////////
// Specific Rocketeer instances
@ -17,6 +18,11 @@ app.post( '/testnetapi/rocketeer/:id/outfits', generateNewOutfit )
app.post( '/testnetapi/rocketeers/:address', generateMultipleNewOutfits )
app.put( '/testnetapi/rocketeer/:id/outfits', setPrimaryOutfit )
/* ///////////////////////////////
// Notification API
// /////////////////////////////*/
app.post( '/testnetapi/notifications/:address', subscribe_address_to_notifications )
// Collection data
app.get( '/testnetapi/collection', async ( req, res ) => res.json( {
totalSupply: await getTotalSupply( 'rinkeby' ).catch( f => 'error' ),

View File

@ -2,7 +2,7 @@ const functions = require( 'firebase-functions' )
const { discord } = functions.config()
const fetch = require( 'isomorphic-fetch' )
exports.notifyDiscordWebhook = async function( username, content, avatar_url, image_title, image_url ) {
exports.notify_discord_of_new_outfit = async function( username, content, avatar_url, image_title, image_url ) {
try {
@ -11,9 +11,12 @@ exports.notifyDiscordWebhook = async function( username, content, avatar_url, im
username,
content,
avatar_url,
embeds: [ {
title: image_title, image: { url: image_url }
} ]
allowed_mentions: {
parse: [ 'users' ]
},
embeds: [
{ title: 'Current outfit', thumbnail: { url: avatar_url } },
{ title: image_title, thumbnail: { url: image_url } } ]
}
// Construct request options

View File

@ -0,0 +1,60 @@
const { db } = require( '../modules/firebase' )
// Web3 APIs
const Web3 = require( 'web3' )
const web3 = new Web3()
/* ///////////////////////////////
// POST handler for notifier signup
// /////////////////////////////*/
exports.subscribe_address_to_notifications = 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` } )
// Lowercase the address
address = address.toLowerCase()
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, discord_handle, chainId } = messageObject
const network = chainId == '0x1' ? 'mainnet' : 'rinkeby'
if( signer.toLowerCase() !== confirmedSignatory.toLowerCase() || !discord_handle || !network ) {
throw new Error( `Invalid subscribeToAddress message with ${ signer }, ${confirmedSignatory}, ${discord_handle}, ${chainId}, ${network}` )
}
await db.collection( `${network}Notifications` ).doc( address ).set( {
discord_handle
} )
return res.json( {
success: true
} )
} catch( e ) {
// Log error for debugging
console.error( `POST subscribeToAddress ${ address }: `, e )
// Return error to frontend
return res.json( { error: e.mesage || e.toString() } )
}
}

View File

@ -0,0 +1,62 @@
const functions = require( 'firebase-functions' )
const juice = require('juice')
// Email package
const { mailgun } = functions.config()
const formData = require( 'form-data' )
const Mailgun = require( 'mailgun.js' )
const instance = new Mailgun( formData )
const mail = instance.client( {
username: 'api',
key: mailgun.api_key,
url: mailgun.api_url
})
// Email templates
const pug = require('pug')
const { promises: fs } = require( 'fs' )
const csso = require('csso')
async function compilePugToEmail( pugFile, rocketeer ) {
const [ emailPug, inlineNormalise, styleExtra, styleOutlook, rocketeerStyles ] = await Promise.all( [
fs.readFile( pugFile ),
fs.readFile( `${ __dirname }/../templates/css-resets/normalize.css`, 'utf8' ),
fs.readFile( `${ __dirname }/../templates/css-resets/extra.css`, 'utf8' ),
fs.readFile( `${ __dirname }/../templates/css-resets/outlook.css`, 'utf8' ),
fs.readFile( `${ __dirname }/../templates/rocketeers.css`, 'utf8' )
] )
const { css } = csso.minify( [ styleExtra, styleOutlook, inlineNormalise, rocketeerStyles ].join( '\n' ) )
const html = pug.render( emailPug, { rocketeer, headStyles: css } )
const emailifiedHtml = juice.inlineContent( html, [ inlineNormalise, rocketeerStyles ].join( '\n' ), { removeStyleTags: false } )
return emailifiedHtml
}
exports.send_email_outfit_available = async ( email, rocketeer ) => {
try {
rocketeer = { ...rocketeer, first_name: rocketeer.name.split( ' ' )[0] }
// Build email
const msg = {
to: email,
from: mailgun.from_email,
subject: `Outfit available for Rocketeer ${ rocketeer.name }`,
text: ( await fs.readFile( `${ __dirname }/../templates/outfit-available.txt`, 'utf8' ) ).replace( '%%name%%', rocketeer.name ),
html: await compilePugToEmail( `${ __dirname }/../templates/outfit-available.email.pug`, rocketeer ),
}
await mail.messages.create( mailgun.from_domain, msg )
} catch( e ) {
console.error( e )
}
}

View File

@ -2,7 +2,7 @@ const { db, dataFromSnap, FieldValue } = require( '../modules/firebase' )
const { getRgbArrayFromColorName, randomNumberBetween } = require( '../modules/helpers' )
const { getTokenIdsOfAddress } = require( '../modules/contract' )
const svgFromAttributes = require( './svg-generator' )
const { notifyDiscordWebhook } = require( '../integrations/discord' )
const { notify_discord_of_new_outfit } = require( '../integrations/discord' )
// ///////////////////////////////
// Rocketeer outfit generator
@ -104,9 +104,9 @@ async function generateNewOutfitFromId( id, network='mainnet' ) {
// Notify discord
const [ firstname ] = rocketeer.name.split( ' ' )
await notifyDiscordWebhook(
await notify_discord_of_new_outfit(
rocketeer.name,
`${ firstname } obtained a new outfit on ${ network }! \n\nView this Rocketeer on Opensea: https://opensea.io/assets/0xb3767b2033cf24334095dc82029dbf0e9528039d/${ id }.\n\nView all outfits on the Rocketeer toolkit: https://tools.rocketeer.fans/#/outfits/${ id }`,
`${ firstname } obtained a new outfit on ${ network }! \n\nView this Rocketeer on Opensea: https://opensea.io/assets/0xb3767b2033cf24334095dc82029dbf0e9528039d/${ id }.\n\nView all outfits on the Rocketeer toolkit: https://tools.rocketeer.fans/#/outfits/${ id }.`,
rocketeer.image,
`Outfit #${ available_outfits + 1 }`,
newOutfitSvg.replace( '.svg','.jpg' )

View File

@ -191,8 +191,7 @@
"@babel/helper-validator-identifier": {
"version": "7.15.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
"dev": true
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w=="
},
"@babel/helper-validator-option": {
"version": "7.14.5",
@ -268,8 +267,7 @@
"@babel/parser": {
"version": "7.15.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz",
"integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==",
"dev": true
"integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA=="
},
"@babel/template": {
"version": "7.15.4",
@ -331,7 +329,6 @@
"version": "7.15.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz",
"integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.14.9",
"to-fast-properties": "^2.0.0"
@ -1028,7 +1025,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"optional": true,
"requires": {
"event-target-shim": "^5.0.0"
}
@ -1089,8 +1085,7 @@
"ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"dev": true
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA=="
},
"ansi-regex": {
"version": "5.0.1",
@ -1130,6 +1125,11 @@
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
"optional": true
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@ -1149,6 +1149,11 @@
"safer-buffer": "^2.1.0"
}
},
"assert-never": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz",
"integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw=="
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
@ -1194,11 +1199,24 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
},
"babel-walk": {
"version": "3.0.0-canary-5",
"resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz",
"integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==",
"requires": {
"@babel/types": "^7.9.6"
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base-64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
},
"base-x": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
@ -1282,6 +1300,11 @@
}
}
},
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -1534,6 +1557,40 @@
}
}
},
"character-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
"integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=",
"requires": {
"is-regex": "^1.0.3"
}
},
"cheerio": {
"version": "1.0.0-rc.10",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz",
"integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==",
"requires": {
"cheerio-select": "^1.5.0",
"dom-serializer": "^1.3.2",
"domhandler": "^4.2.0",
"htmlparser2": "^6.1.0",
"parse5": "^6.0.1",
"parse5-htmlparser2-tree-adapter": "^6.0.1",
"tslib": "^2.2.0"
}
},
"cheerio-select": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz",
"integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==",
"requires": {
"css-select": "^4.1.3",
"css-what": "^5.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.2.0",
"domutils": "^2.7.0"
}
},
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
@ -1733,6 +1790,15 @@
"xdg-basedir": "^4.0.0"
}
},
"constantinople": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
"integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==",
"requires": {
"@babel/parser": "^7.6.0",
"@babel/types": "^7.6.1"
}
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
@ -2047,6 +2113,40 @@
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
"optional": true
},
"css-select": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz",
"integrity": "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==",
"requires": {
"boolbase": "^1.0.0",
"css-what": "^5.1.0",
"domhandler": "^4.3.0",
"domutils": "^2.8.0",
"nth-check": "^2.0.1"
}
},
"css-tree": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.0.4.tgz",
"integrity": "sha512-b4IS9ZUMtGBiNjzYbcj9JhYbyei99R3ai2CSxlu8GQDnoPA/P+NU85hAm0eKDc/Zp660rpK6tFJQ2OSdacMHVg==",
"requires": {
"mdn-data": "2.0.23",
"source-map-js": "^1.0.1"
}
},
"css-what": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz",
"integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw=="
},
"csso": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.2.tgz",
"integrity": "sha512-llFAe1UfFHy38ziX+YrPMGkn5MxdjzYtz0drvgnjRY/tLPmBRxotYTGO51BsKe9voQA074pEb0udV+piXH4scQ==",
"requires": {
"css-tree": "~2.0.4"
}
},
"cssom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
@ -2084,6 +2184,11 @@
"assert-plus": "^1.0.0"
}
},
"data-uri-to-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
"integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og=="
},
"data-urls": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.1.tgz",
@ -2229,11 +2334,31 @@
"esutils": "^2.0.2"
}
},
"doctypes": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
"integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk="
},
"dom-serializer": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
"integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
"entities": "^2.0.0"
}
},
"dom-walk": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
},
"domelementtype": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
},
"domexception": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
@ -2249,6 +2374,24 @@
}
}
},
"domhandler": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz",
"integrity": "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==",
"requires": {
"domelementtype": "^2.2.0"
}
},
"domutils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
"integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
"requires": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.2.0"
}
},
"dot-prop": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@ -2350,6 +2493,11 @@
"integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=",
"optional": true
},
"entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
},
"es-abstract": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz",
@ -2445,6 +2593,11 @@
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"escape-goat": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz",
"integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw=="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@ -2793,8 +2946,7 @@
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"optional": true
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"eventemitter3": {
"version": "4.0.4",
@ -2955,6 +3107,11 @@
"pend": "~1.2.0"
}
},
"fetch-blob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.2.tgz",
"integrity": "sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow=="
},
"file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -3097,12 +3254,12 @@
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
@ -3474,6 +3631,17 @@
"whatwg-encoding": "^2.0.0"
}
},
"htmlparser2": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
"integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.0.0",
"domutils": "^2.5.2",
"entities": "^2.0.0"
}
},
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
@ -3656,6 +3824,14 @@
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz",
"integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w=="
},
"is-core-module": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
"integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
"requires": {
"has": "^1.0.3"
}
},
"is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
@ -3664,6 +3840,15 @@
"has-tostringtag": "^1.0.0"
}
},
"is-expression": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz",
"integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==",
"requires": {
"acorn": "^7.1.1",
"object-assign": "^4.1.1"
}
},
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@ -3749,6 +3934,11 @@
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
},
"is-promise": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
},
"is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@ -3873,6 +4063,11 @@
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
},
"js-stringify": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
"integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -4094,6 +4289,34 @@
"verror": "1.10.0"
}
},
"jstransformer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
"integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=",
"requires": {
"is-promise": "^2.0.0",
"promise": "^7.0.1"
}
},
"juice": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/juice/-/juice-8.0.0.tgz",
"integrity": "sha512-LRCfXBOqI1wt+zYR/5xwDnf+ZyiJiDt44DGZaBSAVwZWyWv3BliaiGTLS6KCvadv3uw6XGiPPFcTfY7CdF7Z/Q==",
"requires": {
"cheerio": "^1.0.0-rc.3",
"commander": "^6.1.0",
"mensch": "^0.3.4",
"slick": "^1.12.2",
"web-resource-inliner": "^5.0.0"
},
"dependencies": {
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="
}
}
},
"jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
@ -4153,6 +4376,31 @@
"is-buffer": "^1.1.5"
}
},
"ky": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/ky/-/ky-0.25.1.tgz",
"integrity": "sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA=="
},
"ky-universal": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/ky-universal/-/ky-universal-0.8.2.tgz",
"integrity": "sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==",
"requires": {
"abort-controller": "^3.0.0",
"node-fetch": "3.0.0-beta.9"
},
"dependencies": {
"node-fetch": {
"version": "3.0.0-beta.9",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz",
"integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==",
"requires": {
"data-uri-to-buffer": "^3.0.1",
"fetch-blob": "^2.1.1"
}
}
}
},
"lazy-cache": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
@ -4298,6 +4546,21 @@
}
}
},
"mailgun.js": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-4.1.4.tgz",
"integrity": "sha512-ay56poetfErj/ftTZcRyyUv1eW+maQYAHdN0JYOSyTENhPJqm4N4SVGOwM6OTtFt0wBjdb8N9tPZaQLN7xOPwA==",
"requires": {
"base-64": "^1.0.0",
"bluebird": "^3.7.2",
"ky": "^0.25.1",
"ky-universal": "^0.8.2",
"url": "^0.11.0",
"url-join": "0.0.1",
"web-streams-polyfill": "^3.0.1",
"webpack-merge": "^5.4.0"
}
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@ -4317,11 +4580,21 @@
"safe-buffer": "^5.1.2"
}
},
"mdn-data": {
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.23.tgz",
"integrity": "sha512-IonVb7pfla2U4zW8rc7XGrtgq11BvYeCxWN8HS+KFBnLDE7XDK9AAMVhRuG6fj9BBsjc69Fqsp6WEActEdNTDQ=="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"mensch": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz",
"integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="
},
"merge-deep": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz",
@ -4566,6 +4839,14 @@
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz",
"integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA=="
},
"nth-check": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz",
"integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==",
"requires": {
"boolbase": "^1.0.0"
}
},
"number-to-bn": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz",
@ -4748,6 +5029,14 @@
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
"parse5-htmlparser2-tree-adapter": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
"integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
"requires": {
"parse5": "^6.0.1"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -4769,6 +5058,11 @@
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
@ -4846,6 +5140,14 @@
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"requires": {
"asap": "~2.0.3"
}
},
"promise-parallel-throttle": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/promise-parallel-throttle/-/promise-parallel-throttle-3.3.0.tgz",
@ -4915,6 +5217,118 @@
"safe-buffer": "^5.1.2"
}
},
"pug": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz",
"integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==",
"requires": {
"pug-code-gen": "^3.0.2",
"pug-filters": "^4.0.0",
"pug-lexer": "^5.0.1",
"pug-linker": "^4.0.0",
"pug-load": "^3.0.0",
"pug-parser": "^6.0.0",
"pug-runtime": "^3.0.1",
"pug-strip-comments": "^2.0.0"
}
},
"pug-attrs": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz",
"integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==",
"requires": {
"constantinople": "^4.0.1",
"js-stringify": "^1.0.2",
"pug-runtime": "^3.0.0"
}
},
"pug-code-gen": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz",
"integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==",
"requires": {
"constantinople": "^4.0.1",
"doctypes": "^1.1.0",
"js-stringify": "^1.0.2",
"pug-attrs": "^3.0.0",
"pug-error": "^2.0.0",
"pug-runtime": "^3.0.0",
"void-elements": "^3.1.0",
"with": "^7.0.0"
}
},
"pug-error": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz",
"integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ=="
},
"pug-filters": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz",
"integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==",
"requires": {
"constantinople": "^4.0.1",
"jstransformer": "1.0.0",
"pug-error": "^2.0.0",
"pug-walk": "^2.0.0",
"resolve": "^1.15.1"
}
},
"pug-lexer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz",
"integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==",
"requires": {
"character-parser": "^2.2.0",
"is-expression": "^4.0.0",
"pug-error": "^2.0.0"
}
},
"pug-linker": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz",
"integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==",
"requires": {
"pug-error": "^2.0.0",
"pug-walk": "^2.0.0"
}
},
"pug-load": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz",
"integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==",
"requires": {
"object-assign": "^4.1.1",
"pug-walk": "^2.0.0"
}
},
"pug-parser": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz",
"integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==",
"requires": {
"pug-error": "^2.0.0",
"token-stream": "1.0.0"
}
},
"pug-runtime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz",
"integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg=="
},
"pug-strip-comments": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz",
"integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==",
"requires": {
"pug-error": "^2.0.0"
}
},
"pug-walk": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz",
"integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@ -5059,6 +5473,11 @@
"strict-uri-encode": "^1.0.0"
}
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
},
"random-name": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/random-name/-/random-name-0.1.2.tgz",
@ -5140,6 +5559,16 @@
"uuid": "^3.3.2"
},
"dependencies": {
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@ -5164,6 +5593,16 @@
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
},
"resolve": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz",
"integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==",
"requires": {
"is-core-module": "^2.8.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -5444,6 +5883,11 @@
"is-fullwidth-code-point": "^3.0.0"
}
},
"slick": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz",
"integrity": "sha1-vQSN23TefRymkV+qSldXCzVQwtc="
},
"snakeize": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz",
@ -5456,6 +5900,11 @@
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
},
"source-map-js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
"integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -5580,6 +6029,11 @@
"has-flag": "^3.0.0"
}
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
"swarm-js": {
"version": "0.1.40",
"resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.40.tgz",
@ -5780,8 +6234,7 @@
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
"to-readable-stream": {
"version": "1.0.0",
@ -5793,6 +6246,11 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"token-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz",
"integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ="
},
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
@ -5919,6 +6377,27 @@
"punycode": "^2.1.0"
}
},
"url": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
"integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
"requires": {
"punycode": "1.3.2",
"querystring": "0.2.0"
},
"dependencies": {
"punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
}
}
},
"url-join": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz",
"integrity": "sha1-HbSK1CLTQCRpqH99l73r/k+x48g="
},
"url-parse-lax": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
@ -5985,6 +6464,11 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
"valid-data-url": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz",
"integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA=="
},
"varint": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz",
@ -6005,6 +6489,11 @@
"extsprintf": "^1.2.0"
}
},
"void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk="
},
"w3c-hr-time": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
@ -6021,6 +6510,45 @@
"xml-name-validator": "^4.0.0"
}
},
"web-resource-inliner": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-5.0.0.tgz",
"integrity": "sha512-AIihwH+ZmdHfkJm7BjSXiEClVt4zUFqX4YlFAzjL13wLtDuUneSaFvDBTbdYRecs35SiU7iNKbMnN+++wVfb6A==",
"requires": {
"ansi-colors": "^4.1.1",
"escape-goat": "^3.0.0",
"htmlparser2": "^4.0.0",
"mime": "^2.4.6",
"node-fetch": "^2.6.0",
"valid-data-url": "^3.0.0"
},
"dependencies": {
"domhandler": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz",
"integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
"requires": {
"domelementtype": "^2.0.1"
}
},
"htmlparser2": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
"integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0",
"domutils": "^2.0.0",
"entities": "^2.0.0"
}
}
}
},
"web-streams-polyfill": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA=="
},
"web3": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/web3/-/web3-1.6.0.tgz",
@ -6314,6 +6842,40 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"webpack-merge": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz",
"integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==",
"requires": {
"clone-deep": "^4.0.1",
"wildcard": "^2.0.0"
},
"dependencies": {
"clone-deep": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
"integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
"requires": {
"is-plain-object": "^2.0.4",
"kind-of": "^6.0.2",
"shallow-clone": "^3.0.0"
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
},
"shallow-clone": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
"integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
"requires": {
"kind-of": "^6.0.2"
}
}
}
},
"websocket": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
@ -6428,6 +6990,22 @@
"is-typed-array": "^1.1.7"
}
},
"wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
"integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw=="
},
"with": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz",
"integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==",
"requires": {
"@babel/parser": "^7.9.6",
"@babel/types": "^7.9.6",
"assert-never": "^1.2.1",
"babel-walk": "3.0.0-canary-5"
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",

View File

@ -3,6 +3,7 @@
"description": "Cloud Functions for Firebase",
"scripts": {
"lint": "eslint .",
"runtime": "firebase functions:config:get > .runtimeconfig.json",
"serve": "firebase emulators:start --only functions",
"shell": "NODE_ENV=development firebase functions:shell",
"start": "NODE_ENV=development npm run shell",
@ -18,12 +19,17 @@
"color-namer": "^1.4.0",
"convert-svg-to-jpeg": "^0.5.0",
"cors": "^2.8.5",
"csso": "^5.0.2",
"express": "^4.17.1",
"firebase-admin": "^10.0.0",
"firebase-functions": "^3.11.0",
"form-data": "^4.0.0",
"isomorphic-fetch": "^3.0.0",
"jsdom": "^18.0.0",
"juice": "^8.0.0",
"mailgun.js": "^4.1.4",
"promise-parallel-throttle": "^3.3.0",
"pug": "^3.0.2",
"puppeteer": "^12.0.0",
"puppeteer-extra": "^3.2.3",
"puppeteer-extra-plugin-stealth": "^2.9.0",

View File

@ -0,0 +1,21 @@
# The MIT License (MIT)
Copyright © Arthur Koch
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,67 @@
# normalize.email.css
CSS resets for HTML emails
It's just a little css library for best default email compatibility. You can use it with your favourite email framework and self-coded templates.
## What does it do?
- Preserves useful defaults for most email clients
- Makes native platform font styling
- Corrects some popular bugs
- Explains what code does using comments
Please let me know if comments not informative and must be detailed
## Contents
- normalize.css - must be inlined to your newsletter in production
- extra.css - must be placed between `<style>` tags in `<head>` of your newsletter in production
- outlook.css - must be placed between `<style>` tags with conditional comment in `<head>` of your newsletter in production. Check out example.html to learn correct code
## Example
``` html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- normalize.css contents must be inlined to newsletter -->
<link href="normalize.css" rel="stylesheet">
<link href="extra.css" rel="stylesheet">
<style>
/* Put extra.css contents here */
</style>
<!--[if (gte mso 9)|(IE)]>
<link href="outlook.css" rel="stylesheet">
/* Put outlook.css contents here */
<![endif]-->
<!-- Left title element empty to prevent viewing this text in subject line on Android 4 email clients -->
<title></title>
</head>
<body class="body">
<div class="webkit">
<!-- An example of bulletproof container with limited row length -->
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<!-- Add here this element -->
<!-- <th></th> -->
<!-- to align container to center -->
<th width="500" align="left">
<!-- Content here -->
<!-- You can use any HTML code which you prefer -->
</th>
<th></th>
</tr>
</table>
</div>
</body>
</html>
```

View File

@ -0,0 +1,59 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- normalize.css contents must be inlined to newsletter -->
<link href="normalize.css" rel="stylesheet">
<link href="extra.css" rel="stylesheet">
<style>
/* Put extra.css contents here */
</style>
<!--[if (gte mso 9)|(IE)]>
<link href="outlook.css" rel="stylesheet">
/* Put outlook.css contents here */
<![endif]-->
<!-- Left title element empty to prevent viewing this text in subject line on Android 4 email clients -->
<title></title>
</head>
<body class="body">
<div class="webkit">
<!-- An example of bulletproof container with limited row length -->
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<!-- Add here this element -->
<!-- <th></th> -->
<!-- to align container to center -->
<th width="500" align="left">
<p>You are receiving this because a known security researcher submitted proof of finding credentials for your npm user account on the internet.</p>
<p>In order to prevent unauthorized access, we've changed the password to your account and invalidated all of your active npm tokens.</p>
<p>Please click on the following link, or paste this into your browser to reset your password:</p>
<ul>
<li><a href="https://www.npmjs.com/forgot">https://www.npmjs.com/forgot</a></li>
</ul>
<p>When you reset your password please do not set it back to the old value.</p>
<p>We have no reason to believe that your account was compromised, but cannot be certain of this. This reset is preemptive, to prevent future compromise.</p>
<p>If you have questions:</p>
<ol>
<li>You can reply to this message or email <a href="support@npmjs.com">support@npmjs.com</a>.</li>
<li>You can also read more about this undertaking in&nbsp;our&nbsp;<a href="http://blog.npmjs.org/post/161515829950/credentials-resets">blog&nbsp;post</a>.</li>
</ol>
<p>Npm loves you.</p>
</th>
<th></th>
</tr>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,29 @@
/* Extra.css */
/* Contents of this file must be placed between <style> tags in <head> of your newsletter in production */
@media screen and (max-width: 600px) {
u + .body {
/* iOS Gmail viewport fix */
/* Make sure that your body element has .body class */
width: 100vw !important;
}
}
a[x-apple-data-detectors=true] {
/* Set default text color inheritance for auto-detected iOS links like date, time, address, etc */
color: inherit !important;
text-decoration: inherit !important;
border-bottom: none !important;
}
body {
/* Set native platform font styling */
font-family: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
font-size: 16px;
color: black;
}
.webkit {
/* Webkit and Microsoft font-size fix */
width: 100%;
table-layout: fixed;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}

View File

@ -0,0 +1,64 @@
/* Normalize.css */
/* Contents of this file must be inlined to your newsletter in production */
h1 a,
h2 a,
h3 a,
h4 a,
h5 a,
h6 a,
li a,
p a {
/* Set sexy underline styling for links except images */
text-decoration: none;
color: #2837b8 !important;
border-bottom: #d3d6f0 1px solid;
}
h1 {
/* Mail.ru <h1> styling fix */
font-size: 2em;
line-height: initial;
margin: 0.67em 0;
padding: 0;
}
table {
/* Null tables spaces */
border-spacing: 0;
border-collapse: collapse;
}
table td {
padding: 0;
}
table th {
padding: 0;
font-weight: normal;
}
img {
/* Flexible images fix + prevent any borders for images */
max-width: 100%;
border: 0;
outline: 0;
/* Set image's ALT text styling */
color: #2837b8;
font-size: 14px;
}
ol,
ul {
/* We don't touch horizontal margins to prevent hiding bullets in Oultook */
margin-top: 1em;
margin-bottom: 2em;
}
ol li,
ul li {
line-height: 1.6em;
margin: 0.5em 0;
}
p {
line-height: 1.6em;
margin: 1em 0;
}
span.code {
/* Monospace emphasis for code examples */
font-family: consolas, courier, monospace;
color: grey;
}

View File

@ -0,0 +1,24 @@
/* Outlook.css */
/* Contents of this file must be placed between <style> tags with conditional comment in <head> of your newsletter in production */
body {
/* Reset font styling. Useful when we links custom fonts to our newsletter */
font-family: Helvetica, Arial, sans-serif;
}
a {
/* Reset default links styling */
color: #2837b8;
text-decoration: underline;
}
h1, h2, h3, h4, h5, h6 {
/* Reset default headings margin */
margin: .5em 0;
}
img {
/* Scaled images fix */
-ms-interpolation-mode: bicubic;
}
table {
/* Null tables spaces */
border-collapse: collapse;
}

View File

@ -0,0 +1,17 @@
doctype html
html( lang='en' )
head
style= headStyles
body
h1 Outfit available for #{ rocketeer.name }
h2 Monthly outfit available for generation now.
p Your Rocketeer is eligible for a new outfit. You can generate it for free at the changing room.
img.avatar( src=rocketeer.image )
a.button( href='https://tools.rocketeer.fans/#/outfits/' + rocketeer.uid ) View #{ rocketeer.first_name }'s changing room
a.button( href='https://tools.rocketeer.fans/#/outfits/' ) View all available outfits
p If you have any questions or suggestions, please have a look at the FAQ here: https://rocketeer.fans/changingroom.
p For other questions or suggestions, please do not hesitate to reach out to us on discord: https://discord.gg/rocketeers
p ~ rocketeers.eth

View File

@ -0,0 +1,5 @@
Rocketeer %%name%% has a new outfit available!
You can generate it at https://tools.rocketeer.fans
Have fun!

View File

@ -0,0 +1,75 @@
body {
margin: 0;
font-family: 'Helvetica Neue', sans-serif;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ///////////////////////////////
// Flow text
// /////////////////////////////*/
html {
font-size: calc( 18px + .1vw );
}
/* ///////////////////////////////
// Brand styles
// /////////////////////////////*/
h1 {
font-size: 2rem;
font-weight: 500;
line-height: 1.2;
text-align: left;
}
h2 {
font-size: 1.5rem;
font-style: italic;
margin: 0 0 1rem;
line-height: 1.2;
font-weight: 400;
text-align: left;
}
p {
font-size: 1rem;
margin: 1rem 0;
line-height: 1.5rem;
text-align: left
}
a.button {
display: inline-block;
padding: 1rem 2rem;
margin: .5rem;
margin-left: 0;
text-decoration: none;
/*border: 2px solid var( --color-primary );*/
border: 2px solid black;
/*color: var( --color-primary );*/
color: black;
font-size: 1rem;
background: none;
border-radius: 5px;
}
input {
/*background: var( --color-backdrop );*/
background: rgba( 0, 0, 0, .05 );
border: none;
/*border-left: 2px solid var( --color-primary );*/
border-left: 2px solid black;
padding: 1rem;
margin: 1rem 10% 1rem 0;
width: 90%;
}
img.avatar {
height: 150px;
width: 150px;
border-radius: 50%;
display: block;
padding: 1rem 0;
}

View File

@ -28,6 +28,7 @@ export default function Verifier() {
const chainId = useChainId()
const [ rocketeer, setRocketeer ] = useState( )
const [ loading, setLoading ] = useState( )
const [ outfitsAvailable, setOutfitsAvailable ] = useState( 0 )
/* ///////////////////////////////
@ -179,6 +180,18 @@ export default function Verifier() {
}, [ rocketeerId, rocketeers.length ] )
useEffect( f => {
// Find the data for the clicked Rocketeer
const outfits = rocketeers.filter( ( { new_outfit_available } ) => new_outfit_available )
log( "Outfits available: ", outfits.length )
setOutfitsAvailable( outfits.length )
}, [ rocketeerId, rocketeers.length ] )
// ///////////////////////////////
// Rendering
// ///////////////////////////////
@ -190,6 +203,7 @@ export default function Verifier() {
<H1>Rocketeers</H1>
<Text>Click on a Rocketeer to manage its outfits</Text>
{ outfitsAvailable > 1 && <Button onClick={ generateByAddress }>Generate { outfitsAvailable } available outfits</Button> }
<Section direction="row">
{ rocketeers.map( ( { id, image, new_outfit_available } ) => {
@ -201,7 +215,6 @@ export default function Verifier() {
</Section>
<Text className="row">Rocketeers owned by: { address }.</Text>
<Sidenote onClick={ generateByAddress } className="row">_</Sidenote>
</Container>