diff --git a/README.md b/README.md index d221702e9..eebe8cc68 100644 --- a/README.md +++ b/README.md @@ -5,26 +5,16 @@ The browser game client for Influence. ## Test Environment 1. Initialize your .env file: ``` - echo "REACT_APP_API_URL=http://localhost:3001 - REACT_APP_IMAGES_URL=http://localhost:3001 - REACT_APP_BRIDGE_URL=http://localhost:4000 - REACT_APP_STARKNET_NETWORK=http://localhost:9000 - REACT_APP_ETHEREUM_EXPLORER_URL=https://etherscan.io - REACT_APP_STARKNET_EXPLORER_URL=https://voyager.online - REACT_APP_ETHEREUM_NFT_MARKET_URL=https://opensea.io - REACT_APP_STTARKNET_NFT_MARKET_URL=https://unframed.co - - # (this is the devnet >0.4 token address) - REACT_APP_ERC20_TOKEN_ADDRESS=0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 - REACT_APP_STARKNET_DISPATCHER= - - REACT_APP_HIDE_SOCIAL=false - - SKIP_PREFLIGHT_CHECK=true - " > .env + echo "BUFFER_GLOBAL=1 + SKIP_PREFLIGHT_CHECK=1 + + NODE_ENV=development + REACT_APP_CONFIG_ENV=prerelease + REACT_APP_APP_VERBOSELOGS=1" > .env ``` -1. Adjust or fill in any missing .env variables as needed. - > i.e. If you are running starknet devnet, `REACT_APP_STARKNET_DISPATCHER` value is available by running `node bin/printEnv.js` from the `starknet-contracts` project. +1. Adjust or fill in any missing .env variables as needed. Most values are preset in +`src/appConfig/prerelease.json`. However, if you need to overwrite any of these presets, +you can do so in your local env file by following the instructions in `src/appConfig/index.js` 1. Run `npm start`. ## Available Scripts diff --git a/app.json b/app.json index 228a35433..38c6e7de5 100644 --- a/app.json +++ b/app.json @@ -6,123 +6,28 @@ } ], "env": { - "REACT_APP_DEPLOYMENT": { - "description": "The specific deployment tag for the client (i.e. prerelease, production, etc.)", - "required": true - }, - "REACT_APP_API_URL": { - "description": "Base URL of the Influence API server, does not include trailing slash", - "required": true - }, - "REACT_APP_IMAGES_URL": { - "description": "Base URL of the Influence images server, does not include trailing slash", - "required": true - }, - "REACT_APP_BRIDGE_URL": { - "description": "Base URL of the Influence L1/L2 bridge", - "required": true - }, - "REACT_APP_ETHEREUM_NFT_MARKET_URL": { - "description": "Base URL for Ethereum NFT marketplace. Does not include trailing slash", - "required": true - }, - "REACT_APP_STARKNET_NFT_MARKET_URL": { - "description": "Base URL for Starknet NFT marketplace. Does not include trailing slash", - "required": true - }, - "REACT_APP_CHAIN_ID": { - "description": "Chain ID for Starknet (i.e. 0x534e5f474f45524c49 for Testnet)", - "required": true - }, - "REACT_APP_STARKNET_NETWORK": { - "description": "Base URL for Starknet network.", - "required": true - }, - "REACT_APP_ETHEREUM_EXPLORER_URL": { - "description": "Base URL for Ethereum block explorer. Does not include trailing slash", - "required": true - }, - "REACT_APP_STARKNET_EXPLORER_URL": { - "description": "Base URL for Starknet block explorer. Does not include trailing slash", - "required": true - }, - "REACT_APP_ERC20_TOKEN_ADDRESS": { - "description": "Address of ERC20 token for network.", - "required": true - }, - "REACT_APP_STARKNET_DISPATCHER": { - "required": true - }, - "REACT_APP_STARKNET_ESCROW": { - "required": true - }, - "REACT_APP_STARKNET_ASTEROID_TOKEN": { - "required": true - }, - "REACT_APP_STARKNET_CREWMATE_TOKEN": { - "required": true - }, - "REACT_APP_STARKNET_SHIP_TOKEN": { - "required": true - }, - "REACT_APP_CONTRACT_ASTEROID_TOKEN": { - "description": "Address of Asteroid token on L1", - "required": true - }, - "REACT_APP_CONTRACT_CREWMATE_TOKEN": { - "description": "Address of Crewmate token on L1", - "required": true - }, - "REACT_APP_CONTRACT_CREWMATE_TOKEN_V2": { - "description": "Address of Crewmate v2 token on L1", - "required": true - }, - "REACT_APP_ENABLE_DEV_TOOLS": { - "required": false - }, - "REACT_APP_HIDE_SOCIAL": { - "required": false - }, - "REACT_APP_CLOUDFRONT_IMAGE_URL": { - "required": true - }, - "REACT_APP_CLOUDFRONT_OTHER_URL": { - "required": true - }, - "REACT_APP_CLOUDFRONT_BUCKET": { - "required": true - }, - "REACT_APP_HELP_URL": { - "required": false - }, - "REACT_APP_LAYERSWAP_CLIENT_ID": { - "required": false - }, - "REACT_APP_RAMP_API_KEY": { + "REACT_APP_CONFIG_ENV": { + "description": "Override NODE_ENV in choosing which config to use (optional)", "required": false }, - "REACT_APP_ARGENT_WEB_WALLET_URL": { + "REACT_APP_API_CLIENTID_GTM": { + "description": "Google Tag Manager ID (optional)", "required": false }, - "REACT_APP_STARKNET_SWAY_TOKEN": { - "required": true - }, - "REACT_APP_AVNU_API_URL": { + "REACT_APP_API_CLIENTID_GOOGLE": { + "description": "Google API Key (optional)", "required": false }, - "REACT_APP_IPFS_URL": { - "required": true - }, - "REACT_APP_GOOGLE_API_KEY": { - "required": true - }, - "REACT_APP_SOCIAL_QUESTS_URL": { + "REACT_APP_API_CLIENTID_LAYERSWAP": { + "description": "Layerswap Client ID (optional)", "required": false }, - "REACT_APP_COLONIZATION_MISSIONS_URL": { + "REACT_APP_API_CLIENTID_RAMP": { + "description": "Ramp API Key (optional)", "required": false }, - "REACT_APP_COMMUNITY_MISSIONS_URL": { + "REACT_APP_STARKNET_PROVIDER": { + "description": "Custom Starknet rpc provider (optional)", "required": false } } diff --git a/opengraph.js b/opengraph.js index bc3fb53df..dd01d49c9 100644 --- a/opengraph.js +++ b/opengraph.js @@ -1,7 +1,9 @@ const axios = require('axios'); const probe = require('probe-image-size'); -const instance = axios.create({ baseURL: `${process.env.REACT_APP_API_URL}/og/data` }); +const { appConfig } = require('./src/appConfig'); + +const instance = axios.create({ baseURL: `${appConfig.get('Api.influence')}/og/data?t=1` }); const getOpengraphTags = async (originalUrl) => { const urlParts = originalUrl.split('/').slice(1); diff --git a/src/Game.js b/src/Game.js index 40df6c61e..c01643ae1 100644 --- a/src/Game.js +++ b/src/Game.js @@ -6,6 +6,7 @@ import { useDetectGPU } from '@react-three/drei'; import { initializeTagManager } from './gtm'; +import { appConfig } from '~/appConfig'; import FullpageInterstitial from '~/components/FullpageInterstitial'; import { ActionItemProvider } from '~/contexts/ActionItemContext'; import { ActivitiesProvider } from '~/contexts/ActivitiesContext'; @@ -62,7 +63,7 @@ const GlobalStyle = createGlobalStyle` } `; -const DISABLE_LAUNCHER_LANDING = true && process.env.NODE_ENV === 'development'; +const DISABLE_LAUNCHER_LANDING = appConfig.get('App.disableLauncherLanding'); const LauncherRedirect = () => { const { authenticated } = useSession(); diff --git a/src/ScreensizeWarning.js b/src/ScreensizeWarning.js index 963aa7886..55890b0a2 100644 --- a/src/ScreensizeWarning.js +++ b/src/ScreensizeWarning.js @@ -1,10 +1,11 @@ import { useState } from 'react'; +import { appConfig } from '~/appConfig'; import FullpageInterstitial from '~/components/FullpageInterstitial'; import useScreenSize from '~/hooks/useScreenSize'; import theme from '~/theme'; -const disableScreensizeWarning = process.env.NODE_ENV === 'development'; +const disableScreensizeWarning = appConfig.get('App.disableScreensizeWarning'); const ScreensizeWarning = () => { const { height, width } = useScreenSize(); diff --git a/src/appConfig/_default.json b/src/appConfig/_default.json new file mode 100644 index 000000000..17770b712 --- /dev/null +++ b/src/appConfig/_default.json @@ -0,0 +1,69 @@ +{ + "App": { + "deployment": "development", + "disableIntroAnimation": false, + "disableLauncherLanding": false, + "disableLaunchTrailer": false, + "defaultMuted": false, + "disableScreensizeWarning": false, + "enableDevTools": false, + "hideSocial": true, + "verboseLogs": false + }, + "Api": { + "argent": "", + "argentWebWallet": "", + "avnu": "", + "influence": "", + "influenceImage": "", + "ipfs": "", + "ramp": "", + "ClientId": { + "google": "", + "gtm": "", + "layerswap": "", + "ramp": "" + } + }, + "Cloudfront": { + "imageUrl": "", + "otherUrl": "", + "bucket": "" + }, + "Ethereum": { + "Address": { + "asteroidToken": "", + "crewmateToken": "" + } + }, + "Starknet": { + "chainId": "", + "provider": "", + "providerBackup": "", + "Address": { + "ethToken": "", + "usdcToken": "", + "asteroidToken": "", + "crewmateToken": "", + "dispatcher": "", + "escrow": "", + "shipToken": "", + "swayToken": "" + } + }, + "Url": { + "bridge": "", + "bugReport": "https://discord.gg/influenceth", + "colonizationMissions": "", + "communityMissions": "https://influence.wendash.com/", + "discord": "https://discord.gg/influenceth", + "ethereumExplorer": "", + "ethereumNftMarket": "", + "help": "https://discord.gg/influenceth", + "home": "https://influence.io", + "socialQuests": "", + "starknetExplorer": "", + "starknetNftMarket": "", + "wiki": "https://wiki.influenceth.io" + } +} \ No newline at end of file diff --git a/src/appConfig/index.js b/src/appConfig/index.js new file mode 100644 index 000000000..7946b5d28 --- /dev/null +++ b/src/appConfig/index.js @@ -0,0 +1,53 @@ +import { isPlainObject, reduce } from 'lodash'; +import defaultConfig from './_default.json'; +import prereleaseConfig from './prerelease.json'; +import productionConfig from './production.json'; + +// +// NOTES: +// - _default contains a list of all possible config values, though most are not filled in +// - prerelease and production contain the actual values for those environments +// - any value can be overridden by an environment variable... for example, to overwrite +// 'Api.ClientId.layerswap', you would set `REACT_APP_API_CLIENTID_LAYERSWAP` in your env + +function flattenObject(obj, parentKey = '', result = {}) { + return reduce(obj, (res, value, key) => { + const newKey = parentKey ? `${parentKey}.${key}` : key; + if (isPlainObject(value)) { + flattenObject(value, newKey, res); + } else { + res[newKey] = value; + } + return res; + }, result); +} + +// override default config with environment specific config +const configSelection = process.env.REACT_APP_CONFIG_ENV || process.env.NODE_ENV; +const rawConfig = { + ...flattenObject(defaultConfig), + ...flattenObject(configSelection === 'production' ? productionConfig : prereleaseConfig), +}; + +// override config with environment variables +const appConfigData = reduce( + rawConfig, + (res, v, key) => { + const overrideKey = `REACT_APP_${key.replace(/\./g, '_').toUpperCase()}`; + if (process.env.hasOwnProperty(overrideKey)) { + res[key] = process.env[overrideKey]; + } + return res; + }, + rawConfig +); + +const appConfig = { + get: (key) => { + if (!appConfigData.hasOwnProperty(key)) throw new Error(`Invalid appConfig key: "${key}"`); + return appConfigData[key]; + }, + has: (key) => appConfigData.hasOwnProperty(key), +}; + +export { appConfig }; diff --git a/src/appConfig/prerelease.json b/src/appConfig/prerelease.json new file mode 100644 index 000000000..7c2a85c87 --- /dev/null +++ b/src/appConfig/prerelease.json @@ -0,0 +1,48 @@ +{ + "App": { + "deployment": "prerelease", + "enableDevTools": 1, + "verboseLogs": true + }, + "Api": { + "argent": "https://api.hydrogen.argent47.net/v1", + "argentWebWallet": "https://web-v2.hydrogen.argent47.net", + "avnu": "https://sepolia.api.avnu.fi", + "influence": "https://api-prerelease.influenceth.io", + "influenceImage": "https://images-prerelease.influenceth.io", + "ipfs": "https://influence.infura-ipfs.io/ipfs", + "ramp": "https://app.demo.ramp.network" + }, + "Cloudfront": { + "imageUrl": "https://d2xo5vocah3zyk.cloudfront.net", + "otherUrl": "https://d1c1daundk1ax0.cloudfront.net/influence/prerelease", + "bucket": "unstoppablegames" + }, + "Ethereum": { + "Address": { + "asteroidToken": "0xA986C6D24222700f78c617ed49E0D78983D0f9AF", + "crewmateToken": "0xE5700883cf429bB63B69424447C7F86C8fdD1786" + } + }, + "Starknet": { + "chainId": "0x534e5f5345504f4c4941", + "provider": "https://free-rpc.nethermind.io/sepolia-juno/v0_7", + "Address": { + "ethToken": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "usdcToken": "0x053b40a647cedfca6ca84f542a0fe36736031905a9639a7f19a3c1e66bfd5080", + "asteroidToken": "0x0680710b95255a852ed9ead04d4c1ffcf4f0695e29fb5c327abe2b8cb305ba25", + "crewmateToken": "0x026b26dc1cd021d7a1e78615cdf9f8f7d19ddbec73a4187e37af1d57f9bcfdc6", + "dispatcher": "0x0517567ac7026ce129c950e6e113e437aa3c83716cd61481c6bb8c5057e6923e", + "escrow": "0x04541d894b5c0476d620e62c3a9be4719ba2dc652df84148be382a89619864e2", + "shipToken": "0x061645ea472d543200c28291c92d54066b1088de67069c1ff0ad2c4c05ef2ed8", + "swayToken": "0x0030058f19ed447208015f6430f0102e8ab82d6c291566d7e73fe8e613c3d2ed" + } + }, + "Url": { + "bridge": "https://assets-prerelease.influenceth.io", + "ethereumExplorer": "https://sepolia.etherscan.io", + "ethereumNftMarket": "https://testnets.opensea.io", + "starknetExplorer": "https://sepolia.starkscan.co", + "starknetNftMarket": "https://pyramid.market" + } +} \ No newline at end of file diff --git a/src/appConfig/production.json b/src/appConfig/production.json new file mode 100644 index 000000000..069bc4398 --- /dev/null +++ b/src/appConfig/production.json @@ -0,0 +1,48 @@ +{ + "App": { + "deployment": "production", + "hideSocial": false + }, + "Api": { + "argent": "https://cloud.argent-api.com/v1", + "argentWebWallet": "https://web.argent.xyz", + "avnu": "https://starknet.api.avnu.fi", + "influence": "https://api.influenceth.io", + "influenceImage": "https://images.influenceth.io", + "ipfs": "https://influence.infura-ipfs.io/ipfs", + "ramp": "https://app.ramp.network" + }, + "Cloudfront": { + "imageUrl": "https://d2xo5vocah3zyk.cloudfront.net", + "otherUrl": "https://d1c1daundk1ax0.cloudfront.net/influence/prerelease", + "bucket": "unstoppablegames" + }, + "Ethereum": { + "Address": { + "asteroidToken": "0x6e4c6D9B0930073e958ABd2ABa516b885260b8Ff", + "crewmateToken": "0x746Db7B1728aF413C4e2b98216C6171B2FC9D00e" + } + }, + "Starknet": { + "chainId": "0x534e5f4d41494e", + "provider": "https://free-rpc.nethermind.io/mainnet-juno/v0_7", + "Address": { + "ethToken": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "usdcToken": "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", + "asteroidToken": "0x0603cf837055c64d026a3c5a9e3a83036cea6c4a3f68a9e19f7a687d726fe817", + "crewmateToken": "0x0241b9c4ce12c06f49fee2ec7c16337386fa5185168f538a7631aacecdf3df74", + "dispatcher": "0x0422d33a3638dcc4c62e72e1d6942cd31eb643ef596ccac2351e0e21f6cd4bf4", + "escrow": "0x487a1c4d61c05d79e22ed90d4811b32277961707338c78d563a245749f57618", + "shipToken": "0x04369e47e647ab5fc4d36cee26590276b99a89a83fc3306462d21d366611fde3", + "swayToken": "0x004878d1148318a31829523ee9c6a5ee563af6cd87f90a30809e5b0d27db8a9b" + } + }, + "Url": { + "bridge": "https://assets.influenceth.io", + "ethereumExplorer": "https://etherscan.io", + "ethereumNftMarket": "https://opensea.io", + "help": "https://discord.gg/dkfNaSp2PW", + "starknetExplorer": "https://starkscan.co", + "starknetNftMarket": "https://pyramid.market" + } +} \ No newline at end of file diff --git a/src/components/BridgeModalDialog.js b/src/components/BridgeModalDialog.js index 166b64ef8..f99f0fa2b 100644 --- a/src/components/BridgeModalDialog.js +++ b/src/components/BridgeModalDialog.js @@ -1,6 +1,7 @@ import React from 'react'; import styled from 'styled-components'; +import { appConfig } from '~/appConfig'; import BrightButton from '~/components/BrightButton'; import IconButton from '~/components/IconButton'; import { CloseIcon } from '~/components/Icons'; @@ -77,7 +78,7 @@ const CloseButton = styled(IconButton)` const BridgeModalDialog = ({ onClose }) => { const openBridge = () => { - window.open(process.env.REACT_APP_BRIDGE_URL); + window.open(appConfig.get('Url.bridge')); onClose(); }; @@ -93,8 +94,8 @@ const BridgeModalDialog = ({ onClose }) => { Influence is now on Starknet, the L2 Ethereum Network!
All of your assets are still accounted for and can still be traded on either - L1 Ethereum (i.e. via OpenSea) - or Starknet (i.e. via Unframed) + L1 Ethereum (i.e. via OpenSea) + or Starknet (i.e. via Unframed)
In the interest of lower transaction fees, however, Influence itself will only be diff --git a/src/components/BrightButton.js b/src/components/BrightButton.js index 1cdcd20b9..9282a8a56 100644 --- a/src/components/BrightButton.js +++ b/src/components/BrightButton.js @@ -89,13 +89,13 @@ const Loader = styled.div` `} `; -const BrightButton = ({ attention, children, loading, ...props }) => { +const BrightButton = ({ attention, children, loading, innerProps = {}, ...props }) => { return ( <> {attention && } ); diff --git a/src/components/CrewmateCard.js b/src/components/CrewmateCard.js index 6bfc482dd..e699b3795 100644 --- a/src/components/CrewmateCard.js +++ b/src/components/CrewmateCard.js @@ -4,6 +4,7 @@ import styled, { css } from 'styled-components'; import pick from 'lodash/pick'; import { Crewmate } from '@influenceth/sdk'; +import { appConfig } from '~/appConfig'; import silhouette from '~/assets/images/silhouette.png'; import CrewmateCardOverlay, { cardTransitionSpeed, cardTransitionFunction } from '~/components/CrewmateCardOverlay'; import CrewClassIcon from '~/components/CrewClassIcon'; @@ -237,9 +238,9 @@ const CrewmateCard = ({ crewmate = {}, useExplicitAppearance, ...props }) => { if (props.width) options += `&width=${props.width}`; if (!useExplicitAppearance && crewmate?.id) { - url = `${process.env.REACT_APP_IMAGES_URL}/v2/crewmates/${crewmate.id}/image.png?bustOnly=true${options}`; + url = `${appConfig.get('Api.influenceImage')}/v2/crewmates/${crewmate.id}/image.png?bustOnly=true${options}`; } else if (safeBigInt(crewmate.Crewmate?.appearance || 0) > 0n) { - url = `${process.env.REACT_APP_IMAGES_URL}/v1/crew/provided/image.svg?bustOnly=true&options=${JSON.stringify( + url = `${appConfig.get('Api.influenceImage')}/v1/crew/provided/image.svg?bustOnly=true&options=${JSON.stringify( pick(crewmate.Crewmate, ['coll', 'class', 'title', 'appearance']) )}`; } @@ -264,7 +265,7 @@ export const CrewCaptainCard = ({ crewId, ...props }) => { if (props.width) options += `&width=${props.width}`; let imageUrl = useMemo(() => crewId - ? `${process.env.REACT_APP_IMAGES_URL}/v2/crews/${crewId}/captain/image.png?bustOnly=true${options}` + ? `${appConfig.get('Api.influenceImage')}/v2/crews/${crewId}/captain/image.png?bustOnly=true${options}` : silhouette, [crewId] ); diff --git a/src/components/MarketplaceLink.js b/src/components/MarketplaceLink.js index 99cebb403..6f50a813a 100644 --- a/src/components/MarketplaceLink.js +++ b/src/components/MarketplaceLink.js @@ -1,5 +1,7 @@ import { useCallback, useState, } from 'react'; +import { appConfig } from '~/appConfig'; + const MarketplaceLink = ({ assetType, chain, children, id }) => { const [referenceEl, setReferenceEl] = useState(); @@ -8,14 +10,14 @@ const MarketplaceLink = ({ assetType, chain, children, id }) => { // Ethereum > OpenSea if (chain === 'ETHEREUM') { - url = `${process.env.REACT_APP_ETHEREUM_NFT_MARKET_URL}/`; + url = `${appConfig.get('Url.ethereumNftMarket')}/`; // single asset if (assetType === 'asteroid' && id) { - url += `assets/${process.env.REACT_APP_CONTRACT_ASTEROID_TOKEN}/${id}`; + url += `assets/${appConfig.get('Ethereum.Address.asteroidToken')}/${id}`; } else if(assetType === 'crewmate' && id) { // TODO: swap to v2 once enough crewmates have been bridged // Message on OpenSea also directs to other contract - url += `assets/${process.env.REACT_APP_CONTRACT_CREWMATE_TOKEN}/${id}`; + url += `assets/${appConfig.get('Ethereum.Address.crewmateToken')}/${id}`; // collection (NOTE: these are not used currently) } else if (assetType === 'asteroid') { url += `influenceth-asteroids`; @@ -28,17 +30,17 @@ const MarketplaceLink = ({ assetType, chain, children, id }) => { // Starknet > Pyramid } else if (chain === 'STARKNET') { - url = `${process.env.REACT_APP_STARKNET_NFT_MARKET_URL}/`; + url = `${appConfig.get('Url.starknetNftMarket')}/`; // single asset if (assetType === 'asteroid' && id) { - url += `asset/${process.env.REACT_APP_STARKNET_ASTEROID_TOKEN}/${id}`; + url += `asset/${appConfig.get('Starknet.Address.asteroidToken')}/${id}`; } else if(assetType === 'crewmate' && id) { - url += `asset/${process.env.REACT_APP_STARKNET_CREWMATE_TOKEN}/${id}`; + url += `asset/${appConfig.get('Starknet.Address.crewmateToken')}/${id}`; // collection } else if (assetType === 'asteroid') { - url += `collection/${process.env.REACT_APP_STARKNET_ASTEROID_TOKEN}`; + url += `collection/${appConfig.get('Starknet.Address.asteroidToken')}`; } else if (assetType === 'crewmate') { - url += `collection/${process.env.REACT_APP_STARKNET_CREWMATE_TOKEN}`; + url += `collection/${appConfig.get('Starknet.Address.crewmateToken')}`; // account } else if (assetType === 'account') { url += `user/${id}`; diff --git a/src/contexts/ActivitiesContext.js b/src/contexts/ActivitiesContext.js index 0726acb60..af0ab6b99 100644 --- a/src/contexts/ActivitiesContext.js +++ b/src/contexts/ActivitiesContext.js @@ -11,6 +11,7 @@ import useWebsocket from '~/hooks/useWebsocket'; import { hydrateActivities } from '~/lib/activities'; import api from '~/lib/api'; import useSimulationState from '~/hooks/useSimulationState'; +import { appConfig } from '~/appConfig'; // TODO (enhancement): rather than invalidating, make optimistic updates to cache value directly // (i.e. update asteroid name wherever asteroid referenced rather than invalidating large query results) @@ -296,7 +297,7 @@ export function ActivitiesProvider({ children }) { if (debugInvalidation) console.log('deduped final invalidate', finalInvalidations); finalInvalidations.forEach((queryKey) => { - if (process.env.NODE_ENV !== 'production') console.log('invalidate', queryKey); + if (appConfig.get('App.verboseLogs')) console.log('invalidate', queryKey); queryClient.invalidateQueries({ queryKey, refetchType: 'active' }); }); } @@ -351,7 +352,7 @@ export function ActivitiesProvider({ children }) { }, [disconnected]); const onWSMessage = useCallback((message) => { - if (process.env.NODE_ENV !== 'production') console.log('onWSMessage (activities)', message); + if (appConfig.get('App.verboseLogs')) console.log('onWSMessage (activities)', message); const { type, body } = message; if (ignoreEventTypes.includes(type)) return; if (type === 'CURRENT_STARKNET_BLOCK_NUMBER') { @@ -363,7 +364,6 @@ export function ActivitiesProvider({ children }) { else if (blockNumber > 0 && body.previous > 0 && body.previous > blockNumber) { console.log(`Missed a block! (new: ${body.blockNumber}, server prev: ${body.previous}, local prev: ${blockNumber})`); - // if (process.env.NODE_ENV !== 'production') window.alert('Missed a block!'); setIsBlockMissing(true); } diff --git a/src/contexts/ChainTransactionContext.js b/src/contexts/ChainTransactionContext.js index 1a0725154..a29dfb713 100644 --- a/src/contexts/ChainTransactionContext.js +++ b/src/contexts/ChainTransactionContext.js @@ -5,6 +5,7 @@ import { hash, num, shortString, uint256 } from 'starknet'; import { fetchBuildExecuteTransaction, fetchQuotes } from '@avnu/avnu-sdk'; import * as gasless from '@avnu/gasless-sdk'; +import { appConfig } from '~/appConfig'; import useActivitiesContext from '~/hooks/useActivitiesContext'; import useCrewContext from '~/hooks/useCrewContext'; import useSession from '~/hooks/useSession'; @@ -432,7 +433,7 @@ const customConfigs = { utoken.high ], tokenAddress, - process.env.REACT_APP_STARKNET_SWAY_TOKEN, + appConfig.get('Starknet.Address.swayToken'), ), System.getFormattedCall( tokenAddress, @@ -466,7 +467,7 @@ const customConfigs = { const getSystemCallAndProcessedVars = (runSystem, rawVars, encodeEntrypoint = false, limitToVars = false, overrideCalldataLength = false) => { let vars = customConfigs[runSystem]?.preprocess ? customConfigs[runSystem].preprocess(rawVars) : rawVars; - const systemCall = System.getRunSystemCall(runSystem, vars, process.env.REACT_APP_STARKNET_DISPATCHER, limitToVars); + const systemCall = System.getRunSystemCall(runSystem, vars, appConfig.get('Starknet.Address.dispatcher'), limitToVars); if (encodeEntrypoint) { // used where nested (i.e. in escrow call) systemCall.entrypoint = hash.getSelectorFromName(systemCall.entrypoint); } @@ -575,7 +576,7 @@ export function ChainTransactionProvider({ children }) { ); console.log('simulation', simulation); - const tokens = await gasless.fetchGasTokenPrices({ baseUrl: process.env.REACT_APP_AVNU_API_URL }); + const tokens = await gasless.fetchGasTokenPrices({ baseUrl: appConfig.get('Api.avnu') }); console.log('fetchGasTokenPrices', tokens); gasToken = tokens.find((t) => Address.areEqual(t.tokenAddress, TOKEN.SWAY)); console.log('gasToken', gasToken); @@ -585,7 +586,7 @@ export function ChainTransactionProvider({ children }) { // TODO: figure out why some txs require this maxFee = gasless.getGasFeesInGasToken(simulation[0].suggestedMaxFee, gasToken) - * (process.env.REACT_APP_DEPLOYMENT === 'production' ? 3n : 1000n); + * (appConfig.get('App.deployment') === 'production' ? 3n : 100n); // (not sure why this would ever be negative, but it is on dev at least) if (maxFee < 0n) maxFee *= -1n; @@ -599,14 +600,14 @@ export function ChainTransactionProvider({ children }) { // Check if wallet has sufficient funds for gas fees // (skip this check in testnet since the allowed gas tokens are inconsistent) - // if (process.env.REACT_APP_DEPLOYMENT !== 'production' || gasTokenBalance >= maxFee) { + // if (appConfig.get('App.deployment') !== 'production' || gasTokenBalance >= maxFee) { if (canPayGasWithSway) { const { typedData, signature } = await getOutsideExecutionData(formattedCalls, gasToken.tokenAddress, maxFee, canUseSessionKey); return await gasless.fetchExecuteTransaction( accountAddress, JSON.stringify(typedData), signature, - { baseUrl: process.env.REACT_APP_AVNU_API_URL } + { baseUrl: appConfig.get('Api.avnu') } ); } } @@ -743,8 +744,8 @@ export function ChainTransactionProvider({ children }) { escrowAmount, depositSystemCall, withdrawSystemCall, - process.env.REACT_APP_STARKNET_ESCROW, - process.env.REACT_APP_STARKNET_SWAY_TOKEN, + appConfig.get('Starknet.Address.escrow'), + appConfig.get('Starknet.Address.swayToken'), ); console.log('runSystem via escrow deposit', runSystem, processedVars, escrowCall); @@ -765,8 +766,8 @@ export function ChainTransactionProvider({ children }) { rawVars.depositCaller, withdrawSystemCall, withdrawSystemData, - process.env.REACT_APP_STARKNET_ESCROW, - process.env.REACT_APP_STARKNET_SWAY_TOKEN, + appConfig.get('Starknet.Address.escrow'), + appConfig.get('Starknet.Address.swayToken'), ); console.log('runSystem via escrow withdrawal', runSystem, processedVars, escrowCall); @@ -789,8 +790,8 @@ export function ChainTransactionProvider({ children }) { t.recipient, t.amount, t.memo, - t.consumer || process.env.REACT_APP_STARKNET_DISPATCHER, - process.env.REACT_APP_STARKNET_SWAY_TOKEN, + t.consumer || appConfig.get('Starknet.Address.dispatcher'), + appConfig.get('Starknet.Address.swayToken'), )); }); } @@ -813,7 +814,7 @@ export function ChainTransactionProvider({ children }) { if (totalEscrow > 0n) { calls.unshift(System.getApproveErc20Call( - totalEscrow, process.env.REACT_APP_STARKNET_SWAY_TOKEN, process.env.REACT_APP_STARKNET_ESCROW + totalEscrow, appConfig.get('Starknet.Address.swayToken'), appConfig.get('Starknet.Address.escrow') )); } @@ -822,7 +823,7 @@ export function ChainTransactionProvider({ children }) { calls.unshift(System.getApproveErc20Call( totalPrice, totalPriceToken, - process.env.REACT_APP_STARKNET_DISPATCHER + appConfig.get('Starknet.Address.dispatcher') )); const wallet = walletRef.current; @@ -847,21 +848,21 @@ export function ChainTransactionProvider({ children }) { const slippageMult = (1 / (1 - slippage)); console.log({ totalPrice, balanceInTargetToken }); console.log({ - sellTokenAddress: totalPriceToken === process.env.REACT_APP_USDC_TOKEN_ADDRESS - ? process.env.REACT_APP_ERC20_TOKEN_ADDRESS - : process.env.REACT_APP_USDC_TOKEN_ADDRESS, + sellTokenAddress: totalPriceToken === appConfig.get('Starknet.Address.usdcToken') + ? appConfig.get('Starknet.Address.ethToken') + : appConfig.get('Starknet.Address.usdcToken'), buyTokenAddress: totalPriceToken, buyAmount: safeBigInt(Math.ceil(slippageMult * parseInt(totalPrice - balanceInTargetToken))), takerAddress: accountAddress, }); const quotes = await fetchQuotes({ - sellTokenAddress: totalPriceToken === process.env.REACT_APP_USDC_TOKEN_ADDRESS - ? process.env.REACT_APP_ERC20_TOKEN_ADDRESS - : process.env.REACT_APP_USDC_TOKEN_ADDRESS, + sellTokenAddress: totalPriceToken === appConfig.get('Starknet.Address.usdcToken') + ? appConfig.get('Starknet.Address.ethToken') + : appConfig.get('Starknet.Address.usdcToken'), buyTokenAddress: totalPriceToken, buyAmount: safeBigInt(Math.ceil(slippageMult * parseInt(totalPrice - balanceInTargetToken))), takerAddress: accountAddress, - }, { baseUrl: process.env.REACT_APP_AVNU_API_URL }); + }, { baseUrl: appConfig.get('Api.avnu') }); if (!quotes?.[0]) throw new Error('Insufficient swap liquidity'); // prepend swap calls @@ -870,7 +871,7 @@ export function ChainTransactionProvider({ children }) { accountAddress, slippage, true, - { baseUrl: process.env.REACT_APP_AVNU_API_URL } + { baseUrl: appConfig.get('Api.avnu') } ); console.log('prepend calls', swapTx.calls); @@ -1069,7 +1070,7 @@ export function ChainTransactionProvider({ children }) { // walletAccount.deployAccount({ contractAddress: accountAddress }); executeCalls([ System.getFormattedCall( - process.env.REACT_APP_ERC20_TOKEN_ADDRESS, + appConfig.get('Starknet.Address.ethToken'), 'transfer', [ { value: accountAddress, type: 'ContractAddress' }, diff --git a/src/contexts/CrewContext.js b/src/contexts/CrewContext.js index b04ad4b85..4061fa926 100644 --- a/src/contexts/CrewContext.js +++ b/src/contexts/CrewContext.js @@ -1,7 +1,8 @@ import { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useQuery, useQueryClient } from 'react-query'; -import { Crewmate, Entity, Inventory, Permission, Ship, System } from '@influenceth/sdk'; +import { Crewmate, Entity, Permission, Ship, System } from '@influenceth/sdk'; +import { appConfig } from '~/appConfig'; import api from '~/lib/api'; import useSession from '~/hooks/useSession'; import useConstants from '~/hooks/useConstants'; @@ -232,7 +233,7 @@ export function CrewProvider({ children }) { System.getRunSystemCall( 'CheckForRandomEvent', { caller_crew: { id: selectedCrew.id, label: selectedCrew.label }}, - process.env.REACT_APP_STARKNET_DISPATCHER + appConfig.get('Starknet.Address.dispatcher') ) ) .then((response) => { diff --git a/src/contexts/SessionContext.js b/src/contexts/SessionContext.js index 8d06d9159..68d283c70 100644 --- a/src/contexts/SessionContext.js +++ b/src/contexts/SessionContext.js @@ -18,6 +18,7 @@ import * as gasless from '@avnu/gasless-sdk'; import { getStarkKey, utils } from 'micro-starknet'; import { Address } from '@influenceth/sdk'; +import { appConfig } from '~/appConfig'; import LoginPrompt from '~/components/LoginPrompt'; import Reconnecting from '~/components/Reconnecting'; import api from '~/lib/api'; @@ -32,7 +33,7 @@ const getErrorMessage = (error) => { }; const isAllowedChain = (chain) => { - return areChainsEqual(chain, process.env.REACT_APP_CHAIN_ID); + return areChainsEqual(chain, appConfig.get('Starknet.chainId')); } const STATUSES = { @@ -45,11 +46,11 @@ const STATUSES = { // Methods allowed for Starknet sessions const allowedMethods = [ - { 'Contract Address': process.env.REACT_APP_STARKNET_DISPATCHER, selector: 'run_system' }, - { 'Contract Address': process.env.REACT_APP_STARKNET_SWAY_TOKEN, selector: 'transfer_with_confirmation' }, - { 'Contract Address': process.env.REACT_APP_STARKNET_SWAY_TOKEN, selector: 'transfer' }, - { 'Contract Address': process.env.REACT_APP_STARKNET_ESCROW, selector: 'withdraw' }, - { 'Contract Address': process.env.REACT_APP_STARKNET_ESCROW, selector: 'deposit' } + { 'Contract Address': appConfig.get('Starknet.Address.dispatcher'), selector: 'run_system' }, + { 'Contract Address': appConfig.get('Starknet.Address.swayToken'), selector: 'transfer_with_confirmation' }, + { 'Contract Address': appConfig.get('Starknet.Address.swayToken'), selector: 'transfer' }, + { 'Contract Address': appConfig.get('Starknet.Address.escrow'), selector: 'withdraw' }, + { 'Contract Address': appConfig.get('Starknet.Address.escrow'), selector: 'deposit' } ]; const SessionContext = createContext(); @@ -86,10 +87,10 @@ export function SessionProvider({ children }) { const authenticated = useMemo(() => status === STATUSES.AUTHENTICATED, [status]); const provider = useMemo(() => { - let nodeUrl = process.env.REACT_APP_STARKNET_PROVIDER; + let nodeUrl = appConfig.get('Starknet.provider'); - if (process.env.REACT_APP_STARKNET_PROVIDER_BACKUP && Math.random() > 0.5) { - nodeUrl = process.env.REACT_APP_STARKNET_PROVIDER_BACKUP; + if (appConfig.get('Starknet.providerBackup') && Math.random() > 0.5) { + nodeUrl = appConfig.get('Starknet.providerBackup'); } return new RpcProvider({ nodeUrl }); @@ -108,7 +109,7 @@ export function SessionProvider({ children }) { options: { url: typeof window !== 'undefined' ? window.location.href : '', dappName: 'Influence', - chainId: resolveChainId(process.env.REACT_APP_CHAIN_ID), + chainId: resolveChainId(appConfig.get('Starknet.chainId')), provider } }); @@ -118,8 +119,8 @@ export function SessionProvider({ children }) { if (isInArgentMobileAppBrowser()) { connectors.push(argentMobileConnector); } else { - if (enabledConnectors.webWallet && !!process.env.REACT_APP_ARGENT_WEB_WALLET_URL) { - connectors.push(new WebWalletConnector({ url: process.env.REACT_APP_ARGENT_WEB_WALLET_URL, provider })); + if (enabledConnectors.webWallet && !!appConfig.get('Api.argentWebWallet')) { + connectors.push(new WebWalletConnector({ url: appConfig.get('Api.argentWebWallet'), provider })); } if (enabledConnectors.argentX) connectors.push(new InjectedConnector({ options: { id: 'argentX', provider }})); @@ -155,7 +156,7 @@ export function SessionProvider({ children }) { try { await wallet.request({ type: 'wallet_switchStarknetChain', - params: { chainId: process.env.REACT_APP_CHAIN_ID } + params: { chainId: appConfig.get('Starknet.chainId') } }); } catch (e) { // (standardize error message here since different between wallets) throw new Error('Incorrect chain'); @@ -175,7 +176,7 @@ export function SessionProvider({ children }) { } catch(e) { if (e.message === 'Incorrect chain') { console.log(''); - setError(`Incorrect chain, please switch to ${resolveChainId(process.env.REACT_APP_CHAIN_ID)}`); + setError(`Incorrect chain, please switch to ${resolveChainId(appConfig.get('Starknet.chainId'))}`); } else if (e.message !== 'User rejected request') { @@ -302,8 +303,8 @@ export function SessionProvider({ children }) { }; const gasFees = { - tokenAddress: process.env.REACT_APP_ERC20_TOKEN_ADDRESS, - maxAmount: areChainsEqual(process.env.REACT_APP_CHAIN_ID, 'SN_MAIN') + tokenAddress: appConfig.get('Starknet.Address.ethToken'), + maxAmount: areChainsEqual(appConfig.get('Starknet.chainId'), 'SN_MAIN') ? '10000000000000000' : '100000000000000000' }; @@ -312,7 +313,7 @@ export function SessionProvider({ children }) { const metaData = { projectID: 'influence', txFees: [ gasFees ] }; const sessionParams = { allowedMethods, expiry, metaData, publicDappKey: dappKey.publicKey }; - const hexChainId = resolveChainId(process.env.REACT_APP_CHAIN_ID, 'hex'); + const hexChainId = resolveChainId(appConfig.get('Starknet.chainId'), 'hex'); console.log('waiting 2 seconds...'); await new Promise(resolve => setTimeout(resolve, 2000)); // deal with timeout delay from Argent const sessionSignature = await openSession({ @@ -421,10 +422,10 @@ export function SessionProvider({ children }) { accountSessionSignature: currentSession.sessionSignature, sessionRequest: currentSession.sessionRequest, provider, - chainId: resolveChainId(process.env.REACT_APP_CHAIN_ID, 'hex'), + chainId: resolveChainId(appConfig.get('Starknet.chainId'), 'hex'), address: currentSession.accountAddress, dappKey: currentSession.sessionDappKey, - argentSessionServiceBaseUrl: process.env.REACT_APP_ARGENT_API + argentSessionServiceBaseUrl: appConfig.get('Api.argent') }); setStarknetSession(offchainSessionAccount); @@ -483,7 +484,7 @@ export function SessionProvider({ children }) { if (currentSession?.isDeployed) { gasless.fetchAccountCompatibility( currentSession.accountAddress, - { baseUrl: process.env.REACT_APP_AVNU_API_URL } + { baseUrl: appConfig.get('Api.avnu') } ) .then((response) => { setIsFeeAbstractionCompatible(!!response?.isCompatible) @@ -512,7 +513,7 @@ export function SessionProvider({ children }) { calldata, gasTokenAddress, maxGasTokenAmount, - { baseUrl: process.env.REACT_APP_AVNU_API_URL } + { baseUrl: appConfig.get('Api.avnu') } ); let signature; @@ -520,7 +521,7 @@ export function SessionProvider({ children }) { if (canUseSessionKey && gameplay.useSessions && currentSession.sessionRequest) { const dappKey = currentSession.sessionDappKey; const sessionSignature = currentSession.sessionSignature; - const beService = new ArgentSessionService(dappKey.publicKey, sessionSignature, process.env.REACT_APP_ARGENT_API); + const beService = new ArgentSessionService(dappKey.publicKey, sessionSignature, appConfig.get('Api.argent')); const chainId = shortString.encodeShortString(connectedChainId); const sessionDappService = new SessionDappService(beService, chainId, dappKey); const { Calldata: feeCalldata } = typedData.message.Calls[0]; @@ -544,16 +545,6 @@ export function SessionProvider({ children }) { // Block management ------------------------------------------------------------------------------------------------- - // If using devnet, put "create block" on a timer since otherwise, blocks will not be advancing in the background - useEffect(() => { - if (process.env.REACT_APP_IS_DEVNET) { - let blockInterval = setInterval(() => { api.createDevnetBlock(); }, 15e3); - return () => { - if (blockInterval) clearInterval(blockInterval); - } - } - }, []); - // Argent is slow to put together it's final "starknet" object, so we check explicitly for getBlock method const canCheckBlock = useMemo(() => { return status >= STATUSES.CONNECTED && !!provider?.getBlock; diff --git a/src/contexts/WebsocketContext.js b/src/contexts/WebsocketContext.js index af5e2709e..74ee87677 100644 --- a/src/contexts/WebsocketContext.js +++ b/src/contexts/WebsocketContext.js @@ -1,6 +1,7 @@ import { createContext, useCallback, useEffect, useRef, useState } from 'react'; import { io } from 'socket.io-client'; +import { appConfig } from '~/appConfig'; import useSession from '~/hooks/useSession'; const WebsocketContext = createContext(); @@ -38,7 +39,7 @@ export function WebsocketProvider({ children }) { const roomKey = (room || '').includes('::') ? room : DEFAULT_ROOM; Object.values(messageHandlers.current).forEach((handler) => { if (handler.room === roomKey) { - if (process.env.NODE_ENV !== 'production') console.log('handleMessage', roomKey, { type, body, ...others }); + if (appConfig.get('App.verboseLogs')) console.log('handleMessage', roomKey, { type, body, ...others }); handler.callback({ type, body, ...others }); } }); @@ -97,7 +98,7 @@ export function WebsocketProvider({ children }) { config.transports = [ 'websocket' ]; if (token) config.query = `token=${token}`; - socket.current = new io(process.env.REACT_APP_API_URL, config); + socket.current = new io(appConfig.get('Api.influence'), config); socket.current.onAny(handleMessage); socket.current.on('connect', () => handleConnection(true)); socket.current.on('disconnect', () => handleConnection(false)); diff --git a/src/game/Audio.js b/src/game/Audio.js index 08546f816..1a4417d14 100644 --- a/src/game/Audio.js +++ b/src/game/Audio.js @@ -1,6 +1,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Howler, Howl } from 'howler'; +import { appConfig } from '~/appConfig'; import useStore from '~/hooks/useStore'; class Sound extends Howl { @@ -15,7 +16,7 @@ class Sound extends Howl { } } -const basePath = process.env.REACT_APP_CLOUDFRONT_OTHER_URL; +const basePath = appConfig.get('Cloudfront.otherUrl'); const sounds = { ambient: [ new Sound({ src: [ `${basePath}/music/ambient1.mp3` ], html5: true, preload: false, volume: 1.0 }), diff --git a/src/game/ChatListener.js b/src/game/ChatListener.js index af46e5ed5..3398befd1 100644 --- a/src/game/ChatListener.js +++ b/src/game/ChatListener.js @@ -1,4 +1,5 @@ import { useCallback, useEffect, useState } from 'react'; +import { appConfig } from '~/appConfig'; import useStore from '~/hooks/useStore'; import useWebsocket from '~/hooks/useWebsocket'; @@ -50,7 +51,7 @@ const ChatListener = () => { }, [disconnected]); const handleWSMessage = useCallback((message) => { - if (process.env.NODE_ENV !== 'production') console.log('onWSMessage (chat)', message); + if (appConfig.get('App.verboseLogs')) console.log('onWSMessage (chat)', message); const { type, body, message: errorMessage } = message; if (type === 'chat-message-received') { diff --git a/src/game/GpuContextLost.js b/src/game/GpuContextLost.js index 1f013142a..0d0eaeb5f 100644 --- a/src/game/GpuContextLost.js +++ b/src/game/GpuContextLost.js @@ -2,6 +2,7 @@ import { useCallback, useEffect } from 'react'; import { useThree } from '@react-three/fiber'; import styled from 'styled-components'; +import { appConfig } from '~/appConfig'; import useStore from '~/hooks/useStore'; import Button from '~/components/ButtonDumb'; import OnClickLink from '~/components/OnClickLink'; @@ -65,7 +66,7 @@ export const GpuContextLostMessage = () => { const dispatchLauncherPage = useStore(s => s.dispatchLauncherPage); const openHelpChannel = useCallback(() => { - window.open(process.env.REACT_APP_HELP_URL || 'https://discord.gg/influenceth', '_blank'); + window.open(appConfig.get('Url.help'), '_blank'); }, []); return ( diff --git a/src/game/Interface.js b/src/game/Interface.js index 4ede0c012..85b589b2a 100644 --- a/src/game/Interface.js +++ b/src/game/Interface.js @@ -4,7 +4,9 @@ import { Tooltip } from 'react-tooltip'; import { Switch, Route, Redirect } from 'react-router-dom'; import moment from 'moment'; +import { appConfig } from '~/appConfig'; import { InfluenceIcon, PurchaseAsteroidIcon } from '~/components/Icons'; +import useCrewContext from '~/hooks/useCrewContext'; import useScreenSize from '~/hooks/useScreenSize'; import useStore from '~/hooks/useStore'; import { openAccessJSTime } from '~/lib/utils'; @@ -33,7 +35,6 @@ import Cutscene from './Cutscene'; import Launcher from './Launcher'; import QueryLoader from './QueryLoader'; import theme from '~/theme'; -import useCrewContext from '~/hooks/useCrewContext'; const StyledInterface = styled.div` align-items: stretch; @@ -75,7 +76,7 @@ const MainContainer = styled.div` } `; -const DISABLE_INTRO_ANIMATION = true && process.env.NODE_ENV === 'development'; +const DISABLE_INTRO_ANIMATION = appConfig.get('App.disableIntroAnimation'); const Interface = () => { const { isLaunched } = useCrewContext(); diff --git a/src/game/Launcher.js b/src/game/Launcher.js index 7d864251c..1818e5777 100644 --- a/src/game/Launcher.js +++ b/src/game/Launcher.js @@ -3,6 +3,7 @@ import styled, { css, keyframes } from 'styled-components'; import { PuffLoader as Loader } from 'react-spinners'; import { Tooltip } from 'react-tooltip'; +import { appConfig } from '~/appConfig'; import useSession from '~/hooks/useSession'; import useStore from '~/hooks/useStore'; import { @@ -31,7 +32,7 @@ import Rewards from './launcher/Rewards'; import { fireTrackingEvent } from '~/lib/utils'; import theme from '~/theme'; -const DISABLE_LAUNCHER_TRAILER = true && process.env.NODE_ENV === 'development'; +const DISABLE_LAUNCH_TRAILER = appConfig.get('App.disableLaunchTrailer'); const footerHeight = 80; export const navMenuWidth = 250; @@ -424,10 +425,10 @@ const Launcher = (props) => { const onClickPlay = useCallback(() => { fireTrackingEvent('play', { externalId: accountAddress }); dispatchLauncherPage(); - if (!hasSeenIntroVideo && !DISABLE_LAUNCHER_TRAILER) { + if (!hasSeenIntroVideo && !DISABLE_LAUNCH_TRAILER) { dispatchSeenIntroVideo(true); dispatchCutscene( - `${process.env.REACT_APP_CLOUDFRONT_OTHER_URL}/videos/intro.m3u8`, + `${appConfig.get('Cloudfront.otherUrl')}/videos/intro.m3u8`, true ); } @@ -446,15 +447,15 @@ const Launcher = (props) => { }, []); const openHelpChannel = useCallback(() => { - window.open(process.env.REACT_APP_HELP_URL, '_blank', 'noopener'); + window.open(appConfig.get('Url.help'), '_blank', 'noopener'); }, []); const openAssetsPortal = useCallback(() => { - window.open(process.env.REACT_APP_BRIDGE_URL, '_blank', 'noopener'); + window.open(appConfig.get('Url.bridge'), '_blank', 'noopener'); }, []); const openWebWalletDashboard = useCallback(() => { - window.open(`${process.env.REACT_APP_ARGENT_WEB_WALLET_URL}`, '_blank', 'noopener'); + window.open(`${appConfig.get('Api.argentWebWallet')}`, '_blank', 'noopener'); }, []); return ( @@ -493,7 +494,7 @@ const Launcher = (props) => { - {process.env.REACT_APP_BRIDGE_URL && ( + {appConfig.get('Url.bridge') && ( Assets Portal @@ -505,7 +506,7 @@ const Launcher = (props) => { )} - {process.env.REACT_APP_HELP_URL && ( + {appConfig.get('Url.bugReport') && ( Bug Report @@ -581,9 +582,9 @@ const Launcher = (props) => { diff --git a/src/game/interface/MainMenu.js b/src/game/interface/MainMenu.js index b42a52350..0a160101a 100644 --- a/src/game/interface/MainMenu.js +++ b/src/game/interface/MainMenu.js @@ -5,6 +5,7 @@ import { MdFullscreen as FullscreenIcon, MdFullscreenExit as ExitFullscreenIcon import { ResetCameraIcon } from '~/components/Icons'; import screenfull from 'screenfull'; +import { appConfig } from '~/appConfig'; import { BackIcon } from '~/components/Icons'; import PrereleaseLogoSVG from '~/assets/images/logo-prerelease.svg'; import useStore from '~/hooks/useStore'; @@ -200,12 +201,12 @@ const MainMenu = () => { )} - {!onClickBack && process.env.REACT_APP_CHAIN_ID !== '0x534e5f5345504f4c4941' && ( + {!onClickBack && appConfig.get('Starknet.chainId') !== '0x534e5f5345504f4c4941' && ( )} - {!onClickBack && process.env.REACT_APP_CHAIN_ID === '0x534e5f5345504f4c4941' && ( + {!onClickBack && appConfig.get('Starknet.chainId') === '0x534e5f5345504f4c4941' && ( )} diff --git a/src/game/interface/RecruitCrewmate.js b/src/game/interface/RecruitCrewmate.js index 3f54baed4..45e41188d 100644 --- a/src/game/interface/RecruitCrewmate.js +++ b/src/game/interface/RecruitCrewmate.js @@ -1,6 +1,7 @@ import { useCallback, useEffect } from 'react'; import { useHistory, useParams } from 'react-router-dom'; +import { appConfig } from '~/appConfig'; import SelectHabitatDialog from '~/components/SelectHabitatDialog'; import SelectUninitializedCrewmateDialog from '~/components/SelectUninitializedCrewmateDialog'; import CrewAssignmentCreate from '~/game/interface/details/crewAssignments/Create'; @@ -41,7 +42,7 @@ const RecruitCrewmate = () => { useEffect(() => { if (!authenticated) history.push('/'); // Select a random habitat out of the first 100 - const habitat = process.env.REACT_APP_DEPLOYMENT === 'production' ? Math.ceil(Math.random() * 100) : 1; + const habitat = appConfig.get('App.deployment') === 'production' ? Math.ceil(Math.random() * 100) : 1; if (!locationId) history.push(`/recruit/${crewId}/${habitat}`); }, [authenticated, crewId, locationId]); diff --git a/src/game/interface/details/CrewAssignments.js b/src/game/interface/details/CrewAssignments.js index a6ef674bc..44b7d41f9 100644 --- a/src/game/interface/details/CrewAssignments.js +++ b/src/game/interface/details/CrewAssignments.js @@ -600,7 +600,7 @@ const CrewAssignments = () => { collection for these assignments
- Click here + Click here {' '}to acquire crewmates through trade, or click here to mint your own.
*/} diff --git a/src/game/interface/details/crewAssignments/Complete.js b/src/game/interface/details/crewAssignments/Complete.js index 418b79847..df7823186 100644 --- a/src/game/interface/details/crewAssignments/Complete.js +++ b/src/game/interface/details/crewAssignments/Complete.js @@ -383,7 +383,7 @@ // // -// {!process.env.REACT_APP_HIDE_SOCIAL && ( +// {!appConfig.get('App.hideSocial') && ( // // Share on Twitter // diff --git a/src/game/interface/details/crewAssignments/Create.js b/src/game/interface/details/crewAssignments/Create.js index aed045171..1becf6b60 100644 --- a/src/game/interface/details/crewAssignments/Create.js +++ b/src/game/interface/details/crewAssignments/Create.js @@ -1247,7 +1247,7 @@ const CrewAssignmentCreate = ({ backLocation, bookSession, coverImage, crewId, c const price = priceHelper.from(priceConstants.ADALIAN_PURCHASE_PRICE, priceConstants.ADALIAN_PURCHASE_TOKEN); if (price.usdcValue > wallet?.combinedBalance?.to(TOKEN.USDC)) { - // if (process.env.REACT_APP_CHAIN_ID === '0x534e5f5345504f4c4941' && ethClaimEnabled) { + // if (appConfig.get('Starknet.chainId') === '0x534e5f5345504f4c4941' && ethClaimEnabled) { // TODO: in sepolia, would be nice to remind there is a faucet *before* having to click through // } return { @@ -1558,7 +1558,7 @@ const CrewAssignmentCreate = ({ backLocation, bookSession, coverImage, crewId, c {/* - {!process.env.REACT_APP_HIDE_SOCIAL && ( + {!appConfig.get('App.hideSocial') && ( Share on Twitter diff --git a/src/game/interface/details/listViews/actionItems.js b/src/game/interface/details/listViews/actionItems.js index e1ccfcf0f..b5ccc10bb 100644 --- a/src/game/interface/details/listViews/actionItems.js +++ b/src/game/interface/details/listViews/actionItems.js @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import styled from 'styled-components'; import { Lot } from '@influenceth/sdk'; +import { appConfig } from '~/appConfig'; import useSession from '~/hooks/useSession'; import useCrewContext from '~/hooks/useCrewContext'; import { statuses } from '~/lib/actionItem'; @@ -133,9 +134,9 @@ const useColumns = () => { align: 'center', bodyStyle: { fontSize: '24px' }, selector: row => { - if (row.type === 'failed' && row.txHash && process.env.REACT_APP_STARKNET_EXPLORER_URL) { + if (row.type === 'failed' && row.txHash && appConfig.get('Url.starknetExplorer')) { return ( - diff --git a/src/game/interface/hud/ActionItem.js b/src/game/interface/hud/ActionItem.js index 9743c1963..1af52a89d 100644 --- a/src/game/interface/hud/ActionItem.js +++ b/src/game/interface/hud/ActionItem.js @@ -3,6 +3,7 @@ import { useHistory } from 'react-router-dom'; import styled, { css, keyframes } from 'styled-components'; import BarLoader from 'react-spinners/BarLoader'; +import { appConfig } from '~/appConfig'; import { CloseIcon as DismissIcon, EyeIcon } from '~/components/Icons'; import { FailedIcon, RandomEventIcon, ReadyIcon } from '~/components/AnimatedIcons'; import LiveTimer from '~/components/LiveTimer'; @@ -259,7 +260,7 @@ const ActionItem = ({ data, getActivityConfig }) => { }, dialogDelay) } - if (type === 'failed' && item.txHash && process.env.REACT_APP_STARKNET_EXPLORER_URL) { + if (type === 'failed' && item.txHash && appConfig.get('Url.starknetExplorer')) { try { navigator.clipboard.writeText(JSON.stringify(data)); createAlert({ @@ -268,7 +269,7 @@ const ActionItem = ({ data, getActivityConfig }) => { }); } catch (e) {} - window.open(`${process.env.REACT_APP_STARKNET_EXPLORER_URL}/tx/${item.txHash}`, '_blank'); + window.open(`${appConfig.get('Url.starknetExplorer')}/tx/${item.txHash}`, '_blank'); } }, [ goToAction, diff --git a/src/game/interface/hud/HudMenu.js b/src/game/interface/hud/HudMenu.js index a0a99ecbb..7c6dfb6bc 100644 --- a/src/game/interface/hud/HudMenu.js +++ b/src/game/interface/hud/HudMenu.js @@ -5,6 +5,7 @@ import styled from 'styled-components'; import { Building, Inventory, Lot } from '@influenceth/sdk'; import { BiWrench as WrenchIcon } from 'react-icons/bi'; +import { appConfig } from '~/appConfig'; import IconButton from '~/components/IconButton'; import { AsteroidSearchIcon, @@ -599,7 +600,7 @@ const HudMenu = () => { } ); - if (process.env.REACT_APP_ENABLE_DEV_TOOLS && showDevTools) { + if (appConfig.get('App.enableDevTools') && showDevTools) { menuButtons.push({ key: 'DEV_TOOLS', label: 'Dev Tools', diff --git a/src/game/interface/hud/TutorialMessage.js b/src/game/interface/hud/TutorialMessage.js index 8589c151a..d081e0d93 100644 --- a/src/game/interface/hud/TutorialMessage.js +++ b/src/game/interface/hud/TutorialMessage.js @@ -1,5 +1,6 @@ import styled from 'styled-components'; +import { appConfig } from '~/appConfig'; import Button from '~/components/ButtonAlt'; import ClipCorner from '~/components/ClipCorner'; import IconButton from '~/components/IconButton'; @@ -30,10 +31,10 @@ const CrewmateOverflow = styled.div` const CrewmateImage = styled.div` background-image: ${p => p.crewmateImageOptionString - ? `url("${process.env.REACT_APP_IMAGES_URL}/v1/crew/provided/image.svg?bustOnly=true&options=${escape(p.crewmateImageOptionString)}")` + ? `url("${appConfig.get('Api.influenceImage')}/v1/crew/provided/image.svg?bustOnly=true&options=${escape(p.crewmateImageOptionString)}")` : ( p.crewmateId - ? `url("${process.env.REACT_APP_IMAGES_URL}/v1/crew/${p.crewmateId}/image.svg?bustOnly=true")` + ? `url("${appConfig.get('Api.influenceImage')}/v1/crew/${p.crewmateId}/image.svg?bustOnly=true")` : 'none' ) }; diff --git a/src/game/interface/hud/WelcomeSimulation.js b/src/game/interface/hud/WelcomeSimulation.js index 2d6c9e822..7abdf7d06 100644 --- a/src/game/interface/hud/WelcomeSimulation.js +++ b/src/game/interface/hud/WelcomeSimulation.js @@ -4,6 +4,7 @@ import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; import Loader from 'react-spinners/PuffLoader'; +import { appConfig } from '~/appConfig'; import { ChevronDoubleDownIcon } from '~/components/Icons'; import { COACHMARK_IDS } from '~/contexts/CoachmarkContext'; import useCoachmarkRefSetter from '~/hooks/useCoachmarkRefSetter'; @@ -55,10 +56,10 @@ const Bubble = styled.div` const CrewmateImage = styled.div` background-image: ${p => p.crewmateImageOptionString - ? `url("${process.env.REACT_APP_IMAGES_URL}/v1/crew/provided/image.svg?bustOnly=true&options=${escape(p.crewmateImageOptionString)}")` + ? `url("${appConfig.get('Api.influenceImage')}/v1/crew/provided/image.svg?bustOnly=true&options=${escape(p.crewmateImageOptionString)}")` : ( p.crewmateId - ? `url("${process.env.REACT_APP_IMAGES_URL}/v1/crew/${p.crewmateId}/image.svg?bustOnly=true")` + ? `url("${appConfig.get('Api.influenceImage')}/v1/crew/${p.crewmateId}/image.svg?bustOnly=true")` : 'none' ) }; @@ -134,9 +135,7 @@ const WelcomeSimulation = () => { const handleSkip = useCallback(() => { fireTrackingEvent('simulation', { step: 'skip-to-login' }); - - // webWallet does not work from localhost... - login(process.env.NODE_ENV === 'development' ? undefined : { webWallet: true }); + login({ webWallet: true }); }, []); if (!currentStep) return null; diff --git a/src/game/interface/hud/actionDialogs/FormAgreement.js b/src/game/interface/hud/actionDialogs/FormAgreement.js index a2aaf9793..69f9852f6 100644 --- a/src/game/interface/hud/actionDialogs/FormAgreement.js +++ b/src/game/interface/hud/actionDialogs/FormAgreement.js @@ -4,6 +4,7 @@ import styled from 'styled-components'; import Clipboard from 'react-clipboard.js'; import numeral from 'numeral'; +import { appConfig } from '~/appConfig'; import { CheckIcon, CloseIcon, ExtendAgreementIcon, FormAgreementIcon, FormLotAgreementIcon, GiveNoticeIcon, LinkIcon, CancelAgreementIcon, LotControlIcon, PermissionIcon, RefreshIcon, SwayIcon, WarningOutlineIcon, WarningIcon } from '~/components/Icons'; import useCrewContext from '~/hooks/useCrewContext'; import useStore from '~/hooks/useStore'; @@ -467,7 +468,7 @@ const FormAgreement = ({ Custom Contracts are used by owners to share or delegate permissions in a flexible manner. They are written outside of the game client and may be viewed externally on{' '} - Starkscan. + Starkscan. { }, 100); }, [assetType]); - if (!process.env.REACT_APP_ENABLE_DEV_TOOLS || resetting) return null; + if (!appConfig.get('App.enableDevTools') || resetting) return null; return ( diff --git a/src/game/launcher/Help.js b/src/game/launcher/Help.js index 638fd99f3..ba7db8335 100644 --- a/src/game/launcher/Help.js +++ b/src/game/launcher/Help.js @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import Loader from 'react-spinners/PuffLoader'; +import { appConfig } from '~/appConfig'; import { PlayIcon } from '~/components/Icons'; import LauncherDialog from './components/LauncherDialog'; import SupportMenu from './components/SupportMenu'; @@ -124,7 +125,7 @@ const YoutubeFeed = ({ playlistId, title }) => { if (playlistId) { setLoading(true); try { - fetch(`https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=${playlistId}&maxResults=50&key=${process.env.REACT_APP_GOOGLE_API_KEY}`).then(async (response) => { + fetch(`https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=${playlistId}&maxResults=50&key=${appConfig.get('Api.ClientId.google')}`).then(async (response) => { const data = await response.json(); setVideos(data.items.filter((v) => !!v.snippet?.videoOwnerChannelId)); // (this filters out private videos) setLoading(false); @@ -191,7 +192,7 @@ const panes = [ }, { label: 'Game Wiki', - link: 'https://wiki.influenceth.io/' + link: appConfig.get('Url.wiki') } ]; diff --git a/src/game/launcher/Rewards.js b/src/game/launcher/Rewards.js index 1fa18917f..b78a5b44a 100644 --- a/src/game/launcher/Rewards.js +++ b/src/game/launcher/Rewards.js @@ -1,3 +1,4 @@ +import { appConfig } from '~/appConfig'; import LauncherDialog from './components/LauncherDialog'; import RecruitmentMenu from './components/RecruitmentMenu'; import RewardMissions from './components/RewardMissions'; @@ -9,11 +10,11 @@ const panes = [ label: 'Colonization Missions', pane: }, - false && process.env.REACT_APP_COMMUNITY_MISSIONS_URL && { + false && appConfig.get('Url.communityMissions') && { label: 'Community Missions', pane: }, - process.env.REACT_APP_SOCIAL_QUESTS_URL && { + appConfig.get('Url.socialQuests') && { label: 'Social Quests', pane: }, diff --git a/src/game/launcher/Store.js b/src/game/launcher/Store.js index c24678fd5..16ccc762c 100644 --- a/src/game/launcher/Store.js +++ b/src/game/launcher/Store.js @@ -15,6 +15,7 @@ import FaucetSKU from './store/FaucetSKU'; import StarterPackSKU from './store/StarterPackSKU'; import SwaySKU from './store/SwaySKU'; import SKULayout from './store/components/SKULayout'; +import { appConfig } from '~/appConfig'; const storeAssets = { packs: 'Starter Packs', @@ -22,7 +23,7 @@ const storeAssets = { crewmates: 'Crewmates', asteroids: 'Asteroids', }; -if (process.env.REACT_APP_CHAIN_ID === '0x534e5f5345504f4c4941') { +if (appConfig.get('Starknet.chainId') === '0x534e5f5345504f4c4941') { storeAssets.faucets = 'Faucets'; } diff --git a/src/game/launcher/components/RewardMissions.js b/src/game/launcher/components/RewardMissions.js index 5e7be0978..55cc0438e 100644 --- a/src/game/launcher/components/RewardMissions.js +++ b/src/game/launcher/components/RewardMissions.js @@ -2,6 +2,7 @@ import { useCallback, useState } from 'react'; import styled from 'styled-components'; import Loader from 'react-spinners/PuffLoader'; +import { appConfig } from '~/appConfig'; import AsteroidsHeroImage from '~/assets/images/sales/asteroids_hero.png'; import AsteroidSurfaceImage from '~/assets/images/hud_headers/Asteroid.png'; import ClipCorner from '~/components/ClipCorner'; @@ -327,7 +328,7 @@ const MissionWrapper = styled.div` const Mission = ({ mission, mode }) => { const createAlert = useStore(s => s.dispatchAlertLogged); const onClick = useCallback(() => { - const targetUrl = mode === 'community' ? process.env.REACT_APP_COMMUNITY_MISSIONS_URL : process.env.REACT_APP_COLONIZATION_MISSIONS_URL; + const targetUrl = mode === 'community' ? appConfig.get('Url.communityMissions') : appConfig.get('Url.colonizationMissions'); if (targetUrl) { window.open(targetUrl); } @@ -377,8 +378,8 @@ const RewardMissions = ({ mode }) => { {mode === 'colonization' && ( @@ -386,7 +387,7 @@ const RewardMissions = ({ mode }) => { {mode === 'community' && ( )} diff --git a/src/game/launcher/components/RewardQuests.js b/src/game/launcher/components/RewardQuests.js index a91f9f604..3177dde17 100644 --- a/src/game/launcher/components/RewardQuests.js +++ b/src/game/launcher/components/RewardQuests.js @@ -2,6 +2,7 @@ import { useState } from 'react'; import styled from 'styled-components'; import Loader from 'react-spinners/PuffLoader'; +import { appConfig } from '~/appConfig'; import Image1 from '~/assets/images/hud_headers/Asteroid.png'; import Image2 from '~/assets/images/hud_headers/SurfaceShip.png'; import Image3 from '~/assets/images/hud_headers/Building_9.png'; @@ -192,7 +193,7 @@ const quests = [ const Quest = ({ quest }) => { return ( - window.open(process.env.REACT_APP_SOCIAL_QUESTS_URL)}> + window.open(appConfig.get('Url.socialQuests'))}>
{quest.icon}
@@ -217,7 +218,7 @@ const RewardQuests = () => { {isLaunched && !error && !loading && ( <> {quests.map((quest) => )} diff --git a/src/game/launcher/components/SupportMenu.js b/src/game/launcher/components/SupportMenu.js index 128ca4c61..75eac8066 100644 --- a/src/game/launcher/components/SupportMenu.js +++ b/src/game/launcher/components/SupportMenu.js @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import styled from 'styled-components'; +import { appConfig } from '~/appConfig'; import { ChevronDoubleRightIcon, InfluenceIcon } from '~/components/Icons'; import ClipCorner from '~/components/ClipCorner'; @@ -76,7 +77,7 @@ const SupportWrapper = styled.div` const SupportMenu = () => { const goToDiscord = useCallback(() => { - window.open(process.env.REACT_APP_HELP_URL); + window.open(appConfig.get('Url.help')); }, []); return ( diff --git a/src/game/launcher/store/FundingFlow.js b/src/game/launcher/store/FundingFlow.js index 951729d88..b8a124cf4 100644 --- a/src/game/launcher/store/FundingFlow.js +++ b/src/game/launcher/store/FundingFlow.js @@ -4,8 +4,9 @@ import { createPortal } from 'react-dom'; import { PropagateLoader as Loader } from 'react-spinners'; import { RampInstantSDK } from '@ramp-network/ramp-instant-sdk'; +import { appConfig } from '~/appConfig'; import Button from '~/components/ButtonAlt'; -import { ChevronRightIcon, CloseIcon, LinkIcon, WalletIcon } from '~/components/Icons'; +import { CheckedIcon, ChevronRightIcon, CloseIcon, WalletIcon } from '~/components/Icons'; import Details from '~/components/DetailsV2'; import useSession from '~/hooks/useSession'; import BrightButton from '~/components/BrightButton'; @@ -228,18 +229,37 @@ const WaitingWrapper = styled.div` const RampWrapper = styled.div` background: linear-gradient(225deg, black, rgba(${p => p.theme.colors.mainRGB}, 0.3)); + position: relative; ${p => !p.display && ` height: 0; overflow: hidden; width: 0; `} - & > div { + & > div:last-child { height: 600px; width: 900px; } `; -const RAMP_PREPEND = process.env.NODE_ENV === 'production' ? '' : 'demo.'; +const AgeVerification = styled.div` + align-items: center; + background: rgba(${p => p.theme.hexToRGB(p.theme.colors.backgroundMain)}, 0.9); + backdrop-filter: blur(2px); + color: white; + display: flex; + flex-direction: column; + justify-content: center; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 2000; + & > h4 { + margin: 0 0 20px; + } +`; + const RAMP_PURCHASE_STATUS = { INITIALIZED: { statusText: 'The purchase has been initialized.', @@ -299,6 +319,8 @@ const RAMP_PURCHASE_STATUS = { }; export const FundingFlow = ({ totalPrice, onClose, onFunded }) => { + const dispatchAgeVerified = useStore(s => s.dispatchAgeVerified); + const ageVerified = useStore(s => s.ageVerified); const createAlert = useStore(s => s.dispatchAlertLogged); const { accountAddress, chainId, walletId } = useSession(); @@ -409,7 +431,7 @@ export const FundingFlow = ({ totalPrice, onClose, onFunded }) => { const checkRampPurchase = useCallback(async (purchase) => { try { const response = await fetch( - `https://api.${RAMP_PREPEND}ramp.network/api/host-api/purchase/${purchase.id}?secret=${purchase.purchaseViewToken}`, + `${appConfig.get('Api.ramp')}/api/host-api/purchase/${purchase.id}?secret=${purchase.purchaseViewToken}`, { method: 'GET', headers: { @@ -449,12 +471,12 @@ export const FundingFlow = ({ totalPrice, onClose, onFunded }) => { const embeddedRamp = new RampInstantSDK({ hostAppName: 'Influence', hostLogoUrl: window.location.origin + '/maskable-logo-192x192.png', - hostApiKey: process.env.REACT_APP_RAMP_API_KEY, + hostApiKey: appConfig.get('Api.ClientId.ramp'), userAddress: accountAddress, swapAsset: 'STARKNET_ETH', // TODO: STARKNET_USDC once enabled fiatCurrency: 'USD', fiatValue: Math.ceil(amount / 1e6), - url: process.env.NODE_ENV === 'production' ? undefined : `https://app.${RAMP_PREPEND}ramp.network`, + url: appConfig.get('Api.ramp'), variant: 'embedded-desktop', containerNode: document.getElementById('ramp-container') @@ -483,7 +505,7 @@ export const FundingFlow = ({ totalPrice, onClose, onFunded }) => { setLayerswapUrl( `https://layerswap.io/app/?${ new URLSearchParams({ - clientId: process.env.REACT_APP_LAYERSWAP_CLIENT_ID, + clientId: appConfig.get('Api.ClientId.layerswap'), amount, to: layerSwapChains[resolveChainId(chainId)]?.starknet, toAsset: 'USDC', @@ -581,7 +603,7 @@ export const FundingFlow = ({ totalPrice, onClose, onFunded }) => { {walletId === 'argentWebWallet' && ( - {process.env.REACT_APP_CHAIN_ID === '0x534e5f5345504f4c4941' && ( + {appConfig.get('Starknet.chainId') === '0x534e5f5345504f4c4941' && ( <>

Request Free ETH @@ -638,7 +660,7 @@ export const FundingFlow = ({ totalPrice, onClose, onFunded }) => { {walletId !== 'argentWebWallet' && ( - {process.env.REACT_APP_CHAIN_ID === '0x534e5f5345504f4c4941' && ( + {appConfig.get('Starknet.chainId') === '0x534e5f5345504f4c4941' && ( setWaiting(!!started)} /> @@ -664,9 +686,20 @@ export const FundingFlow = ({ totalPrice, onClose, onFunded }) => { {ramping && ( <> + {!ageVerified && ( + +

You must be over 18 years or older to make a purchase.

+ dispatchAgeVerified()} + success> + Confirm Age + +
+ )}
-
+
diff --git a/src/game/scene/asteroid/Lots.js b/src/game/scene/asteroid/Lots.js index 399941f83..b72121ca9 100644 --- a/src/game/scene/asteroid/Lots.js +++ b/src/game/scene/asteroid/Lots.js @@ -38,6 +38,7 @@ import theme from '~/theme'; import frag from './shaders/delivery.frag'; import vert from './shaders/delivery.vert'; +import { appConfig } from '~/appConfig'; const { MAX_LOTS_RENDERED } = constants; @@ -286,7 +287,7 @@ const Lots = ({ attachTo: overrideAttachTo, asteroidId, axis, cameraAltitude, ca }, [lotResultMap, lastLotUpdate, positionsReady]); const handleWSMessage = useCallback((message) => { - if (process.env.NODE_ENV !== 'production') console.log('onWSMessage (lots)', message); + if (appConfig.get('App.verboseLogs')) console.log('onWSMessage (lots)', message); const { type: eventType, body } = message; // pass the event to useMappedAsteroidLots hook to update scene diff --git a/src/gtm.js b/src/gtm.js index 4748c3c33..1cc31bc77 100644 --- a/src/gtm.js +++ b/src/gtm.js @@ -1,5 +1,7 @@ import TagManager from 'react-gtm-module'; +import { appConfig } from '~/appConfig'; + export const initializeTagManager = () => { - if (process.env.REACT_APP_GTM_ID) TagManager.initialize({ gtmId: process.env.REACT_APP_GTM_ID }); + if (appConfig.get('Api.ClientId.gtm')) TagManager.initialize({ gtmId: appConfig.get('Api.ClientId.gtm') }); }; \ No newline at end of file diff --git a/src/hooks/actionManagers/useNftSaleManager.js b/src/hooks/actionManagers/useNftSaleManager.js index 169b81738..903e1ef9c 100644 --- a/src/hooks/actionManagers/useNftSaleManager.js +++ b/src/hooks/actionManagers/useNftSaleManager.js @@ -1,11 +1,12 @@ import { useCallback, useContext, useMemo } from 'react'; import { Entity } from '@influenceth/sdk'; +import { appConfig } from '~/appConfig'; import ChainTransactionContext from '~/contexts/ChainTransactionContext'; import useCrewContext from '~/hooks/useCrewContext'; const tokens = { - [Entity.IDS.SHIP]: process.env.REACT_APP_STARKNET_SHIP_TOKEN, + [Entity.IDS.SHIP]: appConfig.get('Starknet.Address.shipToken'), // TODO: add any nft tokens want to support... }; diff --git a/src/hooks/useAnnotationContent.js b/src/hooks/useAnnotationContent.js index e711cc708..f1ea598c2 100644 --- a/src/hooks/useAnnotationContent.js +++ b/src/hooks/useAnnotationContent.js @@ -1,12 +1,14 @@ import axios from 'axios'; import { useQuery } from 'react-query'; +import { appConfig } from '~/appConfig'; + const useAnnotationContent = (annotation) => { const hash = annotation?.ipfs?.hash; return useQuery( ['annotation', hash], async () => { - const response = await axios.get(`${process.env.REACT_APP_IPFS_URL}/${hash}`); + const response = await axios.get(`${appConfig.get('Api.ipfs')}/${hash}`); return response?.data?.content; }, { enabled: !!hash } diff --git a/src/hooks/useBookTokens.js b/src/hooks/useBookTokens.js index 17e434f9f..eb483ce33 100644 --- a/src/hooks/useBookTokens.js +++ b/src/hooks/useBookTokens.js @@ -1,5 +1,6 @@ import { useMemo } from 'react'; +import { appConfig } from '~/appConfig'; import useCrewContext from '~/hooks/useCrewContext'; import { useSwayBalance } from '~/hooks/useWalletTokenBalance'; import formatters from '~/lib/formatters'; @@ -8,7 +9,7 @@ import { safeBigInt } from '~/lib/utils'; const useBookTokens = (bookId) => { const { captain, isLoading: crewIsLoading } = useCrewContext(); - const { data: dispatcherBalance, isLoading: swayIsLoading } = useSwayBalance(process.env.REACT_APP_STARKNET_DISPATCHER); + const { data: dispatcherBalance, isLoading: swayIsLoading } = useSwayBalance(appConfig.get('Starknet.Address.dispatcher')); const swayAmount = useMemo(() => { if (swayIsLoading) return null; diff --git a/src/hooks/useGetActivityConfig.js b/src/hooks/useGetActivityConfig.js index 9361c7afc..961680eea 100644 --- a/src/hooks/useGetActivityConfig.js +++ b/src/hooks/useGetActivityConfig.js @@ -1,6 +1,7 @@ import { useMemo } from 'react'; import { useQueryClient } from 'react-query'; +import { appConfig } from '~/appConfig'; import useCrewContext from '~/hooks/useCrewContext'; import activities, { getHydrationQueryKey } from '~/lib/activities'; @@ -36,9 +37,9 @@ const getActivityConfig = (queryClient, defaultViewingAs) => (activity, override const onBeforeReceived = config?.onBeforeReceived ? config.onBeforeReceived(activity) : (async () => {}); const logContent = config?.getLogContent ? config.getLogContent(activity, viewingAs, prepopped) : null; - if (logContent && activity.event.transactionHash) logContent.txLink = `${process.env.REACT_APP_STARKNET_EXPLORER_URL}/tx/${activity.event.transactionHash}`; + if (logContent && activity.event.transactionHash) logContent.txLink = `${appConfig.get('Url.starknetExplorer')}/tx/${activity.event.transactionHash}`; // TODO: support L1? __t is in event record, but is not included in activity record... - // `${process.env.REACT_APP_ETHEREUM_EXPLORER_URL}/tx/${activity.event?.transactionHash}` + // `${appConfig.get('Url.ethereumExplorer')}/tx/${activity.event?.transactionHash}` const requiresCrewTime = !!config?.requiresCrewTime; diff --git a/src/hooks/useStore.js b/src/hooks/useStore.js index 780597f6f..504ea5b9f 100644 --- a/src/hooks/useStore.js +++ b/src/hooks/useStore.js @@ -9,6 +9,7 @@ import constants from '~/lib/constants'; import { TOKEN } from '~/lib/priceUtils'; import { safeBigInt } from '~/lib/utils'; import SIMULATION_CONFIG from '~/simulation/simulationConfig'; +import { appConfig } from '~/appConfig'; export const STORE_NAME = 'influence'; @@ -49,6 +50,7 @@ const simulationStateDefault = { const useStore = create(subscribeWithSelector(persist((set, get) => ({ actionDialog: {}, + ageVerified: false, launcherPage: null, launcherSubpage: null, openHudMenu: null, @@ -148,8 +150,8 @@ const useStore = create(subscribeWithSelector(persist((set, get) => ({ }, sounds: { - music: process.env.NODE_ENV === 'development' ? 0 : 100, - effects: process.env.NODE_ENV === 'development' ? 0 : 100, + music: appConfig.get('App.defaultMuted') ? 0 : 100, + effects: appConfig.get('App.defaultMuted') ? 0 : 100, }, failedTransactions: [], @@ -766,6 +768,10 @@ const useStore = create(subscribeWithSelector(persist((set, get) => ({ } })), + dispatchAgeVerified: () => set(produce(state => { + state.ageVerified = true; + })), + // // SPECIAL GETTERS diff --git a/src/hooks/useSwapHelper.js b/src/hooks/useSwapHelper.js index 703223d55..cfbf9730e 100644 --- a/src/hooks/useSwapHelper.js +++ b/src/hooks/useSwapHelper.js @@ -1,6 +1,7 @@ import { useCallback, useMemo, useRef } from 'react'; import { fetchBuildExecuteTransaction } from '@avnu/avnu-sdk'; +import { appConfig } from '~/appConfig'; import useStore from '~/hooks/useStore'; import useWalletPurchasableBalances from '~/hooks/useWalletPurchasableBalances'; import { TOKEN } from '~/lib/priceUtils'; @@ -8,7 +9,7 @@ import usePriceHelper from '~/hooks/usePriceHelper'; import api from '~/lib/api'; import useSession from '~/hooks/useSession'; -const avnuOptions = { baseUrl: process.env.REACT_APP_AVNU_API_URL }; +const avnuOptions = { baseUrl: appConfig.get('Api.avnu') }; const useSwapHelper = () => { const { accountAddress } = useSession(); diff --git a/src/hooks/useTutorialSteps.js b/src/hooks/useTutorialSteps.js index c3419e533..230bfa6b7 100644 --- a/src/hooks/useTutorialSteps.js +++ b/src/hooks/useTutorialSteps.js @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo } from 'react'; import { FaYoutube as YoutubeIcon } from 'react-icons/fa'; import { Building, Permission, Inventory, Product } from '@influenceth/sdk'; +import { appConfig } from '~/appConfig'; import useCrewAgreements from '~/hooks/useCrewAgreements'; import useCrewBuildings from '~/hooks/useCrewBuildings'; import useCrewContext from '~/hooks/useCrewContext'; @@ -302,7 +303,7 @@ const useTutorialSteps = () => { <> It seems as though you have your space legs under you now, but if you ever need further help along the way, the Prime Council has created many resources for new Adalians to learn: visit our - {' '}Wiki, + {' '}Wiki, {' '}Youtube {' '}or Discord to learn more! diff --git a/src/hooks/useWebWorker.js b/src/hooks/useWebWorker.js index dace210e6..78f4b6e41 100644 --- a/src/hooks/useWebWorker.js +++ b/src/hooks/useWebWorker.js @@ -4,7 +4,7 @@ import Worker from 'worker-loader!../worker'; // eslint-disable-line let workerIds = 0; -// TODO: remove debug +// TODO: remove debug // let taskTotal = 0; // let taskTally = 0; // let resetPending = true; diff --git a/src/lib/activities.js b/src/lib/activities.js index 31bf42e8e..a4a991102 100644 --- a/src/lib/activities.js +++ b/src/lib/activities.js @@ -2,6 +2,7 @@ import { Address, Building, Delivery, Entity, Lot, Permission, Process, Product, import { AiFillEdit as NameIcon } from 'react-icons/ai'; import { BiTransfer as TransferIcon } from 'react-icons/bi'; +import { appConfig } from '~/appConfig'; import AddressLink from '~/components/AddressLink'; import EntityLink from '~/components/EntityLink'; import { @@ -52,8 +53,8 @@ import { RandomEventIcon } from '~/components/AnimatedIcons'; const addressMaxWidth = '100px'; const getNamedAddress = (address) => { - if (Address.areEqual(address, process.env.REACT_APP_STARKNET_ASTEROID_TOKEN)) return 'the Asteroid Bridge'; - else if (Address.areEqual(address, process.env.REACT_APP_STARKNET_CREWMATE_TOKEN)) return 'the Crewmate Bridge'; + if (Address.areEqual(address, appConfig.get('Starknet.Address.asteroidToken'))) return 'the Asteroid Bridge'; + else if (Address.areEqual(address, appConfig.get('Starknet.Address.crewmateToken'))) return 'the Crewmate Bridge'; } const getEntityName = (entity) => { diff --git a/src/lib/api.js b/src/lib/api.js index 699577ff5..aadb4bec8 100644 --- a/src/lib/api.js +++ b/src/lib/api.js @@ -3,6 +3,7 @@ import { Asteroid, Building, Deposit, Entity, Inventory, Order, Ship } from '@in import esb from 'elastic-builder'; import { executeSwap, fetchQuotes } from "@avnu/avnu-sdk"; +import { appConfig } from '~/appConfig'; import useStore from '~/hooks/useStore'; import { entityToAgreements, esbLocationQuery, esbPermissionQuery, safeBigInt, safeEntityId } from './utils'; import { TOKEN, TOKEN_SCALE } from './priceUtils'; @@ -11,7 +12,7 @@ import { TOKEN, TOKEN_SCALE } from './priceUtils'; const apiVersion = 'v2'; // pass initial config to axios -const config = { baseURL: process.env.REACT_APP_API_URL, headers: {} }; +const config = { baseURL: appConfig.get('Api.influence'), headers: {} }; const initialToken = useStore.getState().currentSession?.token; if (initialToken) config.headers = { Authorization: `Bearer ${initialToken}`}; const instance = axios.create(config); @@ -796,7 +797,7 @@ const api = { // AVNU endpoints getSwapQuote: async ({ sellToken, buyToken, amount, account }) => { - const options = { baseUrl: process.env.REACT_APP_AVNU_API_URL }; + const options = { baseUrl: appConfig.get('Api.avnu') }; return fetchQuotes({ sellTokenAddress: sellToken, buyTokenAddress: buyToken, @@ -806,7 +807,7 @@ const api = { }, executeSwaySwap: async ({ quote, account }) => { - const options = { baseUrl: process.env.REACT_APP_AVNU_API_URL }; + const options = { baseUrl: appConfig.get('Api.avnu') }; return executeSwap(account, quote, {}, options); }, diff --git a/src/lib/assetUtils.js b/src/lib/assetUtils.js index a8a3ee622..b34845616 100644 --- a/src/lib/assetUtils.js +++ b/src/lib/assetUtils.js @@ -1,5 +1,7 @@ import { Assets, Building, Product, Ship } from '@influenceth/sdk'; +import { appConfig } from '~/appConfig'; + const ASSET_CACHE = {}; export const getCloudfrontUrl = (rawSlug, { w, h, f } = {}) => { @@ -7,7 +9,7 @@ export const getCloudfrontUrl = (rawSlug, { w, h, f } = {}) => { ? window.btoa( JSON.stringify({ key: rawSlug, - bucket: process.env.REACT_APP_CLOUDFRONT_BUCKET, + bucket: appConfig.get('Cloudfront.bucket'), edits: { resize: { width: w, @@ -18,7 +20,7 @@ export const getCloudfrontUrl = (rawSlug, { w, h, f } = {}) => { }) ) : rawSlug; - return `${process.env.REACT_APP_CLOUDFRONT_IMAGE_URL}/${slug}`; + return `${appConfig.get('Cloudfront.imageUrl')}/${slug}`; } const getSlug = (assetName) => { @@ -26,7 +28,7 @@ const getSlug = (assetName) => { } const getIconUrl = ({ type, assetName, append, w, h, f } = {}) => { - const environment = process.env.REACT_APP_DEPLOYMENT || 'production'; + const environment = appConfig.get('App.deployment') || 'production'; return getCloudfrontUrl( `influence/${environment}/images/icons/${type}/${getSlug(assetName)}${append || ''}.png`, @@ -38,7 +40,7 @@ export const getModelUrl = ({ type, assetName, modelVersion, append } = {}) => { let slug = `models/${type}/${getSlug(assetName)}${append || ''}`; if (modelVersion) slug += `.v${modelVersion}`; slug += '.glb'; - return `${process.env.REACT_APP_CLOUDFRONT_OTHER_URL}/${slug}`; + return `${appConfig.get('Cloudfront.otherUrl')}/${slug}`; } export const BUILDING_SIZES = { diff --git a/src/lib/cacheKey.js b/src/lib/cacheKey.js index 93bce9536..b3257b2b2 100644 --- a/src/lib/cacheKey.js +++ b/src/lib/cacheKey.js @@ -1,4 +1,5 @@ import { Entity } from '@influenceth/sdk'; +import { appConfig } from '~/appConfig'; /* CACHE KEY DICTIONARY non-entity based @@ -103,7 +104,7 @@ const validFilterKeys = { } export const entitiesCacheKey = (label, filters) => { - if (process.env.NODE_ENV === 'development') { + if (appConfig.get('App.verboseLogs')) { if (typeof filters === 'object') { (Object.keys(filters).find((k) => { if (!validFilterKeys[label].includes(k)) { diff --git a/src/lib/getAlertContent.js b/src/lib/getAlertContent.js index 1f8b2d04e..814d7f1fb 100644 --- a/src/lib/getAlertContent.js +++ b/src/lib/getAlertContent.js @@ -1,3 +1,4 @@ +import { appConfig } from '~/appConfig'; import { UpdateIcon, WarningIcon, SettingsIcon, WalletIcon, ClipboardIcon } from '~/components/Icons'; const entries = { @@ -70,7 +71,7 @@ const entries = { return { icon, content, - txLink: txHash ? `${process.env.REACT_APP_STARKNET_EXPLORER_URL}/tx/${txHash}` : null, + txLink: txHash ? `${appConfig.get('Url.starknetExplorer')}/tx/${txHash}` : null, } }, }; diff --git a/src/lib/priceUtils.js b/src/lib/priceUtils.js index 696e4af6f..2ac630d53 100644 --- a/src/lib/priceUtils.js +++ b/src/lib/priceUtils.js @@ -1,12 +1,13 @@ import { Address } from '@influenceth/sdk'; +import { appConfig } from '~/appConfig'; import { EthIcon, SwayIcon } from '~/components/Icons'; import { safeBigInt } from './utils'; export const TOKEN = { - ETH: Address.toStandard(process.env.REACT_APP_ERC20_TOKEN_ADDRESS), - SWAY: Address.toStandard(process.env.REACT_APP_STARKNET_SWAY_TOKEN), - USDC: Address.toStandard(process.env.REACT_APP_USDC_TOKEN_ADDRESS), + ETH: Address.toStandard(appConfig.get('Starknet.Address.ethToken')), + SWAY: Address.toStandard(appConfig.get('Starknet.Address.swayToken')), + USDC: Address.toStandard(appConfig.get('Starknet.Address.usdcToken')), } export const TOKEN_SCALE = { diff --git a/src/lib/utils.js b/src/lib/utils.js index e062915ce..81550d364 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -3,6 +3,7 @@ import { Crew, Entity, Lot, Order, Permission, Processor, Time } from '@influenc import { shortString } from 'starknet'; import trim from 'lodash/trim'; +import { appConfig } from '~/appConfig'; import { ManufactureIcon, GrowIcon, AssembleIcon, RefineIcon } from '~/components/Icons'; import { TOKEN, TOKEN_SCALE } from './priceUtils'; @@ -412,7 +413,7 @@ export const safeBigInt = (unsafe) => { return 0n; } -export const openAccessJSTime = `${process.env.REACT_APP_CHAIN_ID}` === `0x534e5f4d41494e` ? 1719495000e3 : 0; +export const openAccessJSTime = `${appConfig.get('Starknet.chainId')}` === `0x534e5f4d41494e` ? 1719495000e3 : 0; export const displayTimeFractionDigits = 2; export const maxAnnotationLength = 750; diff --git a/src/simulation/useSimulationSteps.js b/src/simulation/useSimulationSteps.js index da4d37dfb..5366f3002 100644 --- a/src/simulation/useSimulationSteps.js +++ b/src/simulation/useSimulationSteps.js @@ -3,6 +3,7 @@ import { Building, Crewmate, Entity, Inventory, Permission, Process, Processor, import { useHistory } from 'react-router-dom'; import { BiTransfer as TransferIcon } from 'react-icons/bi'; +import { appConfig } from '~/appConfig'; import { ZOOM_IN_ANIMATION_TIME, ZOOM_OUT_ANIMATION_TIME, ZOOM_TO_PLOT_ANIMATION_MAX_TIME, ZOOM_TO_PLOT_ANIMATION_MIN_TIME } from '~/game/scene/Asteroid'; import useCrewAgreements from '~/hooks/useCrewAgreements'; import useCrewBuildings from '~/hooks/useCrewBuildings'; @@ -1038,7 +1039,7 @@ const useSimulationSteps = () => { You did it, congratulations! You are now officially ready to choose your career class and start your own crew.

- Remember: the Wiki + Remember: the Wiki {' '}and Discord {' '}are resources that are always available to you when seeking help from your fellow Adalians.

@@ -1054,9 +1055,7 @@ const useSimulationSteps = () => { disabled: connecting, onClick: () => { fireTrackingEvent('simulation', { step: 'login' }); - - // webWallet does not work from localhost... - login(process.env.NODE_ENV === 'development' ? undefined : { webWallet: true }); + login({ webWallet: true }); // TODO: add on-logged-in url }, }