├── .nvmrc ├── src ├── components │ ├── crowdsaleDetails │ │ └── utils.js │ ├── invest │ │ ├── constants.js │ │ ├── QRPaymentProcess.js │ │ ├── CountdownTimer.js │ │ ├── InvestForm.js │ │ └── CountdownTimer.spec.js │ ├── stepFour │ │ └── constants.js │ ├── index.js │ ├── Common │ │ ├── config.js │ │ ├── NoWeb3.spec.js │ │ ├── PreventRefresh.js │ │ ├── CrowdsaleSupply.js │ │ ├── CrowdsaleEndTime.js │ │ ├── CrowdsaleStartTime.js │ │ ├── DisplayField.js │ │ ├── WhenFieldChanges.js │ │ ├── DisplayTextArea.js │ │ ├── InputField2.js │ │ ├── CrowdsaleRate.js │ │ ├── Loader.js │ │ ├── ModalContainer.js │ │ ├── RadioInputField.js │ │ ├── Error.js │ │ ├── StepNavigation.js │ │ ├── NoWeb3.js │ │ ├── TokenName.js │ │ ├── __snapshots__ │ │ │ ├── BigNumberInput.spec.js.snap │ │ │ ├── NumericInput.spec.js.snap │ │ │ ├── AddressInput.spec.js.snap │ │ │ ├── RadioInputField.spec.js.snap │ │ │ └── ReservedTokensItem.spec.js.snap │ │ ├── ReservedTokensItem.js │ │ ├── TokenTicker.js │ │ ├── TokenDecimals.js │ │ ├── InputField.js │ │ ├── AddressInput.js │ │ ├── WhitelistItem.js │ │ ├── ReservedTokensItem.spec.js │ │ ├── CrowdsalesList.js │ │ ├── TokenDecimals.spec.js │ │ ├── TokenName.spec.js │ │ ├── TokenTicker.spec.js │ │ ├── TxProgressStatus.js │ │ └── RadioInputField.spec.js │ ├── stepThree │ │ ├── utils.js │ │ ├── utils.spec.js │ │ └── GasPriceInput.js │ ├── Header │ │ └── index.js │ ├── manage │ │ ├── AboutCrowdsale.js │ │ ├── __snapshots__ │ │ │ ├── AboutCrowdsale.spec.js.snap │ │ │ └── FinalizeCrowdsaleStep.spec.js.snap │ │ ├── AboutCrowdsale.spec.js │ │ ├── ReadOnlyWhitelistAddresses.js │ │ ├── FinalizeCrowdsaleStep.js │ │ ├── ManageForm.js │ │ ├── ReadOnlyWhitelistAddresses.spec.js │ │ ├── utils.spec.js │ │ ├── FinalizeCrowdsaleStep.spec.js │ │ ├── DistributeTokensStep.js │ │ └── ManageTierBlock.spec.js │ ├── IncompleteDeploy.js │ ├── Footer │ │ └── index.js │ ├── Home │ │ └── index.spec.js │ └── stepTwo │ │ ├── StepTwoForm.js │ │ └── index.js ├── assets │ ├── stylesheets │ │ ├── application │ │ │ ├── home │ │ │ │ ├── base.scss │ │ │ │ ├── _placeholders.scss │ │ │ │ ├── crowdsale.scss │ │ │ │ ├── process.scss │ │ │ │ └── crowdsale-modal.scss │ │ │ ├── _variables.scss │ │ │ ├── _mixins.scss │ │ │ ├── _placeholders.scss │ │ │ ├── header.scss │ │ │ ├── step-icons.scss │ │ │ ├── footer.scss │ │ │ ├── steps │ │ │ │ ├── content.scss │ │ │ │ ├── radiosinline.scss │ │ │ │ ├── navigation.scss │ │ │ │ ├── total-funds.scss │ │ │ │ ├── radios.scss │ │ │ │ ├── whitelist.scss │ │ │ │ ├── base.scss │ │ │ │ └── reservedtokenslist.scss │ │ │ ├── fonts.scss │ │ │ ├── stats.scss │ │ │ ├── manage.scss │ │ │ ├── socials.scss │ │ │ ├── base.scss │ │ │ ├── loading.scss │ │ │ ├── flex-table.scss │ │ │ └── controls.scss │ │ └── application.scss │ └── images │ │ ├── bg.png │ │ ├── qr.jpg │ │ ├── copy.png │ │ ├── logos.png │ │ ├── copy@2x.png │ │ ├── delete.png │ │ ├── loading.png │ │ ├── select.png │ │ ├── socials.png │ │ ├── delete@2x.png │ │ ├── loading@2x.png │ │ ├── logos@2x.png │ │ ├── select@2x.png │ │ ├── socials@2x.png │ │ ├── step-icons.png │ │ ├── payment-loader.png │ │ ├── payment-success.png │ │ ├── step-icons@2x.png │ │ ├── payment-loader@2x.png │ │ ├── socials │ │ ├── github@2x.png │ │ ├── reddit@2x.png │ │ ├── oracles@2x.png │ │ ├── telegram@2x.png │ │ └── twitter@2x.png │ │ ├── payment-success@2x.png │ │ └── step-icons │ │ ├── publish@2x.png │ │ ├── token-setup@2x.png │ │ ├── crowdsale-page@2x.png │ │ ├── crowdsale-setup@2x.png │ │ └── crowdsale- contract@2x.png ├── react-web3 │ ├── index.js │ ├── Web3Provider.spec.js │ └── __snapshots__ │ │ └── Web3Provider.spec.js.snap ├── favicons │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-256x256.png │ ├── browserconfig.xml │ └── manifest.json ├── utils │ ├── __mocks__ │ │ ├── gasPrice.json │ │ └── api.js │ ├── api.js │ ├── cancelDeploy.js │ ├── copy.js │ ├── executeSequentially.js │ ├── processWhitelist.js │ ├── microservices.js │ ├── blockchainHelpers.spec.js │ ├── processReservedTokens.js │ └── utils.spec.js ├── stores │ ├── InvestStore.js │ ├── StepTwoValidationStore.js │ ├── autosave.js │ ├── GeneralStore.js │ ├── PricingStrategyStore.js │ ├── ContractStore.js │ ├── StatsStore.js │ ├── CrowdsaleStore.js │ ├── ReservedTokenStore.js │ ├── CrowdsalePageStore.js │ ├── TokenStore.js │ ├── index.js │ ├── GasPriceStore.js │ └── Web3Store.js ├── index.js ├── App.spec.js └── App.js ├── assets └── javascripts │ ├── application │ └── main.js │ └── vendor │ └── index.js ├── docs ├── invest.png ├── wizard-1.png ├── wizard-2.png ├── wizard-3.png ├── wizard-4.png ├── wizard-5.png ├── crowdsale.png ├── invest-qr.png ├── wizard-5-2.png ├── github_head.png ├── metamask-rpc.png ├── MetaMaskInvest.png ├── fileStructure1.png ├── fileStructure2.png ├── Distribute-tokens.gif ├── TW_StateOfTheDapps.png ├── wizard-deployment.png ├── MyEtherWalletChooseNetwork.png ├── MyEtherWalletSelectFunction.png ├── WizardFinalStepFileStructure.png ├── FlatPricingExt-verify-contract.png ├── MyEtherWalletAttachToContract.png ├── MyEtherWalletConnectToMetaMask.png ├── SafeMathLibExt-verify-contract.png ├── CrowdsaleTokenExt-verify-contract.png ├── MyEtherWalletGenerateTransactionBuy.png ├── MyEtherWalletGenerateTransactionBasic.png ├── MyEtherWalletGenerateTransactionExtended.png ├── ReservedTokensFinalizeAgent-verify-contract.png └── MintedTokenCappedCrowdsaleExt-verify-contract.png ├── public ├── favicon.ico ├── manifest.json ├── bundle.js ├── invest.html ├── crowdsale.html └── index.html ├── start_testrpc.sh ├── tsconfig.json ├── .babelrc ├── .editorconfig ├── scripts ├── addExtendedCode.js ├── helpers │ └── deployContract.js ├── deployRegistry.js ├── test.js ├── POAExtendedCrowdSale.sol ├── compileContract.js └── start.js ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js └── paths.js ├── .eslintrc ├── .env.example ├── .gitignore ├── .gitmodules ├── .github └── ISSUE_TEMPLATE.md ├── .travis.yml ├── LICENSE └── gulpfile.js /.nvmrc: -------------------------------------------------------------------------------- 1 | v8.9.1 2 | -------------------------------------------------------------------------------- /src/components/crowdsaleDetails/utils.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/javascripts/application/main.js: -------------------------------------------------------------------------------- 1 | window.abi = require("ethereumjs-abi"); 2 | -------------------------------------------------------------------------------- /assets/javascripts/vendor/index.js: -------------------------------------------------------------------------------- 1 | //=require jquery.min.js 2 | //=require jquery.pietimer.js -------------------------------------------------------------------------------- /docs/invest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/invest.png -------------------------------------------------------------------------------- /docs/wizard-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/wizard-1.png -------------------------------------------------------------------------------- /docs/wizard-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/wizard-2.png -------------------------------------------------------------------------------- /docs/wizard-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/wizard-3.png -------------------------------------------------------------------------------- /docs/wizard-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/wizard-4.png -------------------------------------------------------------------------------- /docs/wizard-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/wizard-5.png -------------------------------------------------------------------------------- /src/assets/stylesheets/application/home/base.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | min-height: 600px; 3 | } 4 | -------------------------------------------------------------------------------- /docs/crowdsale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/crowdsale.png -------------------------------------------------------------------------------- /docs/invest-qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/invest-qr.png -------------------------------------------------------------------------------- /docs/wizard-5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/wizard-5-2.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /docs/github_head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/github_head.png -------------------------------------------------------------------------------- /docs/metamask-rpc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/metamask-rpc.png -------------------------------------------------------------------------------- /src/react-web3/index.js: -------------------------------------------------------------------------------- 1 | import Web3Provider from './Web3Provider' 2 | 3 | export { Web3Provider } 4 | -------------------------------------------------------------------------------- /docs/MetaMaskInvest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/MetaMaskInvest.png -------------------------------------------------------------------------------- /docs/fileStructure1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/fileStructure1.png -------------------------------------------------------------------------------- /docs/fileStructure2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/fileStructure2.png -------------------------------------------------------------------------------- /src/assets/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/bg.png -------------------------------------------------------------------------------- /src/assets/images/qr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/qr.jpg -------------------------------------------------------------------------------- /src/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/favicons/favicon.ico -------------------------------------------------------------------------------- /start_testrpc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | nohup node_modules/.bin/testrpc --gasLimit 0x989680 &>/dev/null & 3 | -------------------------------------------------------------------------------- /docs/Distribute-tokens.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/Distribute-tokens.gif -------------------------------------------------------------------------------- /docs/TW_StateOfTheDapps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/TW_StateOfTheDapps.png -------------------------------------------------------------------------------- /docs/wizard-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/wizard-deployment.png -------------------------------------------------------------------------------- /src/assets/images/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/copy.png -------------------------------------------------------------------------------- /src/assets/images/logos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/logos.png -------------------------------------------------------------------------------- /src/assets/images/copy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/copy@2x.png -------------------------------------------------------------------------------- /src/assets/images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/delete.png -------------------------------------------------------------------------------- /src/assets/images/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/loading.png -------------------------------------------------------------------------------- /src/assets/images/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/select.png -------------------------------------------------------------------------------- /src/assets/images/socials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/socials.png -------------------------------------------------------------------------------- /src/assets/images/delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/delete@2x.png -------------------------------------------------------------------------------- /src/assets/images/loading@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/loading@2x.png -------------------------------------------------------------------------------- /src/assets/images/logos@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/logos@2x.png -------------------------------------------------------------------------------- /src/assets/images/select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/select@2x.png -------------------------------------------------------------------------------- /src/assets/images/socials@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/socials@2x.png -------------------------------------------------------------------------------- /src/assets/images/step-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/step-icons.png -------------------------------------------------------------------------------- /src/components/invest/constants.js: -------------------------------------------------------------------------------- 1 | export const alertOptions = { 2 | offset: '80px 14', 3 | position: 'top right' 4 | }; -------------------------------------------------------------------------------- /src/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /src/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /src/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /src/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/MyEtherWalletChooseNetwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/MyEtherWalletChooseNetwork.png -------------------------------------------------------------------------------- /docs/MyEtherWalletSelectFunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/MyEtherWalletSelectFunction.png -------------------------------------------------------------------------------- /docs/WizardFinalStepFileStructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/WizardFinalStepFileStructure.png -------------------------------------------------------------------------------- /src/assets/images/payment-loader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/payment-loader.png -------------------------------------------------------------------------------- /src/assets/images/payment-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/payment-success.png -------------------------------------------------------------------------------- /src/assets/images/step-icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/step-icons@2x.png -------------------------------------------------------------------------------- /src/assets/stylesheets/application/_variables.scss: -------------------------------------------------------------------------------- 1 | $header-height: 80px; 2 | $footer-height: 60px; 3 | $iframe-height: 600px; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "allowJs": true 5 | } 6 | } -------------------------------------------------------------------------------- /docs/FlatPricingExt-verify-contract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/FlatPricingExt-verify-contract.png -------------------------------------------------------------------------------- /docs/MyEtherWalletAttachToContract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/MyEtherWalletAttachToContract.png -------------------------------------------------------------------------------- /docs/MyEtherWalletConnectToMetaMask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/MyEtherWalletConnectToMetaMask.png -------------------------------------------------------------------------------- /docs/SafeMathLibExt-verify-contract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/SafeMathLibExt-verify-contract.png -------------------------------------------------------------------------------- /src/assets/images/payment-loader@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/payment-loader@2x.png -------------------------------------------------------------------------------- /src/assets/images/socials/github@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/socials/github@2x.png -------------------------------------------------------------------------------- /src/assets/images/socials/reddit@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/socials/reddit@2x.png -------------------------------------------------------------------------------- /src/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/favicons/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/favicons/android-chrome-256x256.png -------------------------------------------------------------------------------- /docs/CrowdsaleTokenExt-verify-contract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/CrowdsaleTokenExt-verify-contract.png -------------------------------------------------------------------------------- /src/assets/images/payment-success@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/payment-success@2x.png -------------------------------------------------------------------------------- /src/assets/images/socials/oracles@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/socials/oracles@2x.png -------------------------------------------------------------------------------- /src/assets/images/socials/telegram@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/socials/telegram@2x.png -------------------------------------------------------------------------------- /src/assets/images/socials/twitter@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/socials/twitter@2x.png -------------------------------------------------------------------------------- /docs/MyEtherWalletGenerateTransactionBuy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/MyEtherWalletGenerateTransactionBuy.png -------------------------------------------------------------------------------- /src/assets/images/step-icons/publish@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/step-icons/publish@2x.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-1", 5 | "react" 6 | ], 7 | "plugins": ["transform-decorators-legacy"] 8 | } 9 | -------------------------------------------------------------------------------- /docs/MyEtherWalletGenerateTransactionBasic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/MyEtherWalletGenerateTransactionBasic.png -------------------------------------------------------------------------------- /src/assets/images/step-icons/token-setup@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/step-icons/token-setup@2x.png -------------------------------------------------------------------------------- /docs/MyEtherWalletGenerateTransactionExtended.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/MyEtherWalletGenerateTransactionExtended.png -------------------------------------------------------------------------------- /docs/ReservedTokensFinalizeAgent-verify-contract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/ReservedTokensFinalizeAgent-verify-contract.png -------------------------------------------------------------------------------- /src/assets/images/step-icons/crowdsale-page@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/step-icons/crowdsale-page@2x.png -------------------------------------------------------------------------------- /src/assets/images/step-icons/crowdsale-setup@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/step-icons/crowdsale-setup@2x.png -------------------------------------------------------------------------------- /docs/MintedTokenCappedCrowdsaleExt-verify-contract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/docs/MintedTokenCappedCrowdsaleExt-verify-contract.png -------------------------------------------------------------------------------- /src/assets/images/step-icons/crowdsale- contract@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poanetwork/token-wizard/HEAD/src/assets/images/step-icons/crowdsale- contract@2x.png -------------------------------------------------------------------------------- /src/components/stepFour/constants.js: -------------------------------------------------------------------------------- 1 | export const alertOptions = { time: 10000, position: 'top right' } 2 | export const fileDownloadedToasterMsg = "A file with contracts and metadata downloaded on your computer" -------------------------------------------------------------------------------- /src/utils/__mocks__/gasPrice.json: -------------------------------------------------------------------------------- 1 | { 2 | "fast": 16.2, 3 | "slow": 4.0, 4 | "standard": 4.0, 5 | "instant": 40.0, 6 | "health": true, 7 | "block_time": 14.528, 8 | "block_number": 4949682 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/api.js: -------------------------------------------------------------------------------- 1 | import { GAS_PRICE } from './constants' 2 | 3 | export const gasPriceValues = (endpoint = '') => { 4 | return fetch(`${GAS_PRICE.API.URL}/${endpoint}`) 5 | .then(response => response.json()) 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/home/_placeholders.scss: -------------------------------------------------------------------------------- 1 | %home-items { 2 | @extend %full-width; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: center; 6 | position: absolute; 7 | height: 50%; 8 | box-sizing: border-box; 9 | } 10 | -------------------------------------------------------------------------------- /src/stores/InvestStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | 3 | class InvestStore { 4 | 5 | @observable tokensToInvest; 6 | 7 | @action setProperty = (property, value) => { 8 | this[property] = value 9 | } 10 | } 11 | 12 | export default InvestStore; 13 | -------------------------------------------------------------------------------- /src/utils/__mocks__/api.js: -------------------------------------------------------------------------------- 1 | export const gasPriceValues = (endpoint = 'gasPrice') => { 2 | return new Promise((resolve, reject) => { 3 | try { 4 | const data = require(`./${endpoint}`) 5 | resolve(data) 6 | } catch (e) { 7 | reject('no data') 8 | } 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /scripts/addExtendedCode.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | 3 | function addExtendedCode(extensionFilePath, content, cb) { 4 | fs.readFile(extensionFilePath, "utf8", function(err, extendedContent) { 5 | if (err) { 6 | return cb(err); 7 | } 8 | content = content + extendedContent; 9 | cb(null, content); 10 | }); 11 | } 12 | 13 | module.exports = addExtendedCode; -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /scripts/helpers/deployContract.js: -------------------------------------------------------------------------------- 1 | module.exports = deployContract 2 | 3 | function deployContract(web3, abi, bin, from, gas = 1500000, gasPrice = '5000000000') { 4 | const Contract = new web3.eth.Contract(abi, { from }) 5 | 6 | return Contract 7 | .deploy({ 8 | data: bin 9 | }) 10 | .send({ 11 | from, 12 | gas, 13 | gasPrice 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/cancelDeploy.js: -------------------------------------------------------------------------------- 1 | import { cancellingIncompleteDeploy } from './alerts' 2 | 3 | const cancelDeploy = () => { 4 | return cancellingIncompleteDeploy() 5 | .then(result => { 6 | if (result.value) { 7 | localStorage.clear() 8 | window.location = '/' // go to home 9 | } 10 | 11 | return result.value 12 | }) 13 | } 14 | 15 | export default cancelDeploy 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "plugins": [ 4 | "dependencies" 5 | ], 6 | "rules": { 7 | "indent": ["error", 2, { 8 | "SwitchCase": 1 9 | }], 10 | "no-mixed-spaces-and-tabs": "error", 11 | "no-trailing-spaces": "error", 12 | "dependencies/no-cycles": "error", 13 | "dependencies/no-unresolved": ["error", { 14 | "ignore": ["web3"] 15 | }] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_REGISTRY_ADDRESS='{"1":"0x445b4c58113C784640a978cfE39b0065118B91A5","3":"0xD4543Fe1ded1637768516A3Ef35614F91F715370","4":"0x9839a9ab98150d257667b871ea3be4cb988ce7b7","42":"0xef5879ad4ba3fa0312875426902dd384d8c63fcd","12648430":"0xe78a0f7e598cc8b0bb87894b0f60dd2a88d6a8ab","77":"0x50ec0e8c8f93468c44227dc2aecf61b286f43db8","99":"0x4316f704989157162de90997ce1a05cff3118680"}' 2 | REACT_APP_INFURA_TOKEN='kEpzZR9fIyO3a8gTqJcI' -------------------------------------------------------------------------------- /src/assets/stylesheets/application/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin image-2x($image, $width: 100%, $height: 100%) { 2 | @media (min--moz-device-pixel-ratio: 1.3), 3 | (-o-min-device-pixel-ratio: 2.6/2), 4 | (-webkit-min-device-pixel-ratio: 1.3), 5 | (min-device-pixel-ratio: 1.3), 6 | (min-resolution: 1.3dppx) { 7 | background-image: url($image); 8 | background-size: $width $height; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | /node_modules 6 | 7 | # testing 8 | /coverage 9 | 10 | # production 11 | /build 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | public/contracts/ 25 | .env 26 | 27 | .idea 28 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/_placeholders.scss: -------------------------------------------------------------------------------- 1 | %clear { 2 | display: block; 3 | clear: both; 4 | 5 | &:before, 6 | &:after { 7 | content: ''; 8 | display: block; 9 | clear: both; 10 | } 11 | } 12 | 13 | %full-width { 14 | left: 0; 15 | right: 0; 16 | } 17 | 18 | %logos { 19 | @include image-2x('../images/logos@2x.png', 182px, 59px); 20 | display: block; 21 | background-image: url(../images/logos.png); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { Footer } from './Footer'; 2 | export { Header } from './Header'; 3 | export { Home } from './Home'; 4 | export { stepOne } from './stepOne' 5 | export { stepTwo } from './stepTwo' 6 | export { stepThree } from './stepThree' 7 | export { stepFour } from './stepFour' 8 | export { Crowdsale } from './crowdsale' 9 | export { Invest } from './invest' 10 | export { Manage } from './manage' 11 | export { Stats } from './stats' 12 | -------------------------------------------------------------------------------- /src/components/Common/config.js: -------------------------------------------------------------------------------- 1 | // DEMO CONFIG (DO NOT DELETE THIS EXAMPLE): 2 | /*const networks = { 3 | mainnet: 1, 4 | morden: 2, 5 | ropsten: 3, 6 | rinkeby: 4, 7 | kovan: 42, 8 | sokolPOA: 77, 9 | corePOA: 99 10 | } 11 | 12 | export const CrowdsaleConfig = { 13 | crowdsaleContractURL: '0x338f47C1AA1DBdB56DfEA17cc376C0b21f4fbDa2', 14 | networkID: networks.sokolPOA, 15 | showHeaderAndFooterInIframe: true 16 | };*/ 17 | 18 | export const CrowdsaleConfig = {}; 19 | -------------------------------------------------------------------------------- /src/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "icons": [ 4 | { 5 | "src": "/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "/android-chrome-256x256.png", 11 | "sizes": "256x256", 12 | "type": "image/png" 13 | } 14 | ], 15 | "theme_color": "#ffffff", 16 | "background_color": "#ffffff", 17 | "display": "standalone" 18 | } -------------------------------------------------------------------------------- /src/components/stepThree/utils.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export function defaultCompanyStartDate() { 4 | let crowdsaleStartDate = moment().add(5, 'minutes'); 5 | let crowdsaleStartDateFormatted = crowdsaleStartDate.format('YYYY-MM-DDTHH:mm'); 6 | return crowdsaleStartDateFormatted; 7 | } 8 | 9 | export const defaultCompanyEndDate = (startDate) => { 10 | const crowdsaleEndDate = moment(startDate).add(4, 'days').startOf('day') 11 | return crowdsaleEndDate.format('YYYY-MM-DDTHH:mm') 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/copy.js: -------------------------------------------------------------------------------- 1 | import Clipboard from 'clipboard'; 2 | 3 | export function copy(cls) { 4 | var clipboard = new Clipboard('.' + cls); 5 | 6 | clipboard.on('success', function(e) { 7 | console.info('Action:', e.action); 8 | console.info('Text:', e.text); 9 | console.info('Trigger:', e.trigger); 10 | 11 | e.clearSelection(); 12 | }); 13 | 14 | clipboard.on('error', function(e) { 15 | console.error('Action:', e.action); 16 | console.error('Trigger:', e.trigger); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Common/NoWeb3.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Adapter from 'enzyme-adapter-react-15' 3 | import { configure, mount } from 'enzyme' 4 | import NoWeb3 from './NoWeb3' 5 | 6 | configure({ adapter: new Adapter() }) 7 | 8 | describe('NoWeb3', () => { 9 | 10 | it('provides information if no wallet found', () => { 11 | const wrapper = mount() 12 | expect(wrapper.find('.title').text()).toEqual('Wallet not found, or access to Ethereum account not granted') 13 | }) 14 | 15 | }) 16 | -------------------------------------------------------------------------------- /src/components/Common/PreventRefresh.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | export class PreventRefresh extends Component { 4 | onUnload = (e) => { 5 | e.returnValue = "You'll lose current deployment, are you sure?" 6 | } 7 | 8 | componentDidMount () { 9 | window.addEventListener('beforeunload', this.onUnload) 10 | } 11 | 12 | componentWillUnmount () { 13 | window.removeEventListener('beforeunload', this.onUnload) 14 | } 15 | 16 | render () { 17 | return
18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../../assets/stylesheets/application.css'; 3 | import { Link } from 'react-router-dom' 4 | import { displayHeaderAndFooterInIframe } from '../../utils/utils' 5 | 6 | export const Header = () => { 7 | const displayHeader = displayHeaderAndFooterInIframe() 8 | 9 | const header = ( 10 |
11 |
12 | 13 |
14 |
15 | ) 16 | 17 | return displayHeader ? header : null 18 | } 19 | -------------------------------------------------------------------------------- /src/stores/StepTwoValidationStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import autosave from './autosave' 3 | 4 | class StepTwoValidationStore { 5 | 6 | @observable name; 7 | @observable ticker; 8 | @observable decimals; 9 | 10 | constructor() { 11 | this.name = 'EMPTY' 12 | this.ticker = 'EMPTY' 13 | this.decimals = 'EMPTY' 14 | 15 | autosave(this, 'StepTwoValidationStore') 16 | } 17 | 18 | @action property = (property, value) => { 19 | this[property] = value 20 | } 21 | } 22 | 23 | export default StepTwoValidationStore; 24 | -------------------------------------------------------------------------------- /src/stores/autosave.js: -------------------------------------------------------------------------------- 1 | import * as mobx from 'mobx'; 2 | import storage from 'store2' 3 | 4 | export default function autosave(store, storageKey, deserialize = x => x) { 5 | let firstRun = true 6 | 7 | mobx.autorun(`autorun for ${storageKey}`, () => { 8 | if (firstRun) { 9 | const existingStore = storage.get(storageKey) 10 | 11 | if (existingStore) { 12 | mobx.extendObservable(store, deserialize(existingStore)) 13 | } 14 | 15 | firstRun = false 16 | } 17 | 18 | storage.set(storageKey, mobx.toJS(store)) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Common/CrowdsaleSupply.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Field } from 'react-final-form' 3 | import { InputField2 } from './InputField2' 4 | import { isPositive } from '../../utils/validations' 5 | import { DESCRIPTION, TEXT_FIELDS } from '../../utils/constants' 6 | 7 | export const CrowdsaleSupply = ({ ...props }) => ( 8 | 16 | ) 17 | -------------------------------------------------------------------------------- /src/stores/GeneralStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import { GAS_PRICE } from '../utils/constants'; 3 | import autosave from './autosave' 4 | 5 | class GeneralStore { 6 | 7 | @observable networkId; 8 | @observable gasPrice = GAS_PRICE.FAST.PRICE; 9 | 10 | constructor() { 11 | autosave(this, 'GeneralStore') 12 | } 13 | 14 | @action setProperty = (property, value) => { 15 | this[property] = value 16 | } 17 | 18 | @action setGasPrice = (gasPrice) => { 19 | this.gasPrice = gasPrice 20 | } 21 | } 22 | 23 | export default GeneralStore; 24 | -------------------------------------------------------------------------------- /src/components/Common/CrowdsaleEndTime.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Field } from 'react-final-form' 3 | import { InputField2 } from './InputField2' 4 | import { validateTierEndDate } from '../../utils/validations' 5 | import { DESCRIPTION, TEXT_FIELDS } from '../../utils/constants' 6 | 7 | export const CrowdsaleEndTime = ({ index, ...props }) => ( 8 | 16 | ) 17 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | @extend %full-width; 3 | position: absolute; 4 | top: 0; 5 | height: $header-height; 6 | background-image: url(../images/bg.png); 7 | background-size: cover; 8 | background-position: center center; 9 | 10 | .logo { 11 | @extend %logos; 12 | width: 182px; 13 | height: 35px; 14 | margin-top: ($header-height - 35px) / 2; 15 | background-position: 0 -25px; 16 | } 17 | } 18 | 19 | @media (max-height: $iframe-height) { 20 | .header { 21 | position: relative; 22 | top: 0; 23 | height: auto; 24 | padding: 1px 0 21px; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Common/CrowdsaleStartTime.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Field } from 'react-final-form' 3 | import { InputField2 } from './InputField2' 4 | import { validateTierStartDate } from '../../utils/validations' 5 | import { DESCRIPTION, TEXT_FIELDS } from '../../utils/constants' 6 | 7 | export const CrowdsaleStartTime = ({ index, ...props }) => ( 8 | 16 | ) 17 | -------------------------------------------------------------------------------- /src/components/Common/DisplayField.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../assets/stylesheets/application.css'; 3 | 4 | export const DisplayField = props => { 5 | return ( 6 |
7 |
8 |

{props.title}

9 |

{props.value}

10 |

{props.description}

11 |
12 |
13 | 14 |
15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/Common/WhenFieldChanges.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Field } from 'react-final-form' 3 | import { OnChange } from 'react-final-form-listeners' 4 | 5 | export const WhenFieldChanges = ({ field, becomes, set, to }) => ( 6 | 7 | {( 8 | // No subscription. We only use Field to get to the change function 9 | { input: { onChange } } 10 | ) => ( 11 | 12 | {value => { 13 | if (value === becomes) { 14 | onChange(to) 15 | } 16 | }} 17 | 18 | )} 19 | 20 | ) 21 | 22 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/home/crowdsale.scss: -------------------------------------------------------------------------------- 1 | .crowdsale { 2 | @extend %home-items; 3 | top: 0; 4 | padding-top: $header-height; 5 | text-align: center; 6 | 7 | .container { 8 | padding: 0 40px; 9 | box-sizing: border-box; 10 | } 11 | 12 | .title { 13 | margin-bottom: 20px; 14 | text-transform: uppercase; 15 | font-size: 24px; 16 | font-weight: bold; 17 | } 18 | 19 | .description { 20 | margin-bottom: 25px; 21 | color: #8197a2; 22 | line-height: 24px; 23 | font-size: 13px; 24 | } 25 | 26 | .buttons { 27 | font-size: 0; 28 | } 29 | 30 | .button { 31 | margin: 0 10px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/solidity-flattener"] 2 | path = submodules/solidity-flattener 3 | url = https://github.com/poanetwork/solidity-flattener 4 | branch = master 5 | [submodule "submodules/poa-web3-1.0"] 6 | path = submodules/poa-web3-1.0 7 | url = https://github.com/poanetwork/web3.js 8 | branch = 1.0 9 | [submodule "submodules/poa-token-market-net-ico"] 10 | path = submodules/poa-token-market-net-ico 11 | url = https://github.com/poanetwork/ico 12 | branch = wizard 13 | [submodule "submodules/token-wizard-test-automation"] 14 | path = submodules/token-wizard-test-automation 15 | url = https://github.com/poanetwork/token-wizard-test-automation 16 | branch = master 17 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | -------------------------------------------------------------------------------- /src/components/Common/DisplayTextArea.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../assets/stylesheets/application.css'; 3 | 4 | export const DisplayTextArea = props => { 5 | return ( 6 |
7 |
8 |
9 |

{props.label}

10 |
11 |
12 | 13 |
14 |
15 |
{props.value}
16 |

{props.description}

17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import './application/variables'; 2 | @import './application/mixins'; 3 | @import './application/placeholders'; 4 | @import './application/fonts'; 5 | @import './application/base'; 6 | @import './application/header'; 7 | @import './application/footer'; 8 | @import './application/socials'; 9 | @import './application/step-icons'; 10 | @import './application/controls'; 11 | @import './application/invest'; 12 | @import './application/payment-process'; 13 | @import './application/home/*'; 14 | @import './application/steps/*'; 15 | @import './application/loading'; 16 | @import './application/flex-table'; 17 | @import './application/manage'; 18 | @import './application/stats'; 19 | -------------------------------------------------------------------------------- /src/components/manage/AboutCrowdsale.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | export const AboutCrowdsale = ({ name, ticker, address, networkId }) => ( 5 |
6 |
7 |

{name} ({ticker}) Settings

8 |

9 | The most important and exciting part of the crowdsale process. Here you can define parameters of your crowdsale 10 | campaign. 11 |

12 | 13 | Crowdsale page 14 | 15 |
16 | ) 17 | -------------------------------------------------------------------------------- /src/components/Common/InputField2.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Error } from './Error' 3 | 4 | export const InputField2 = (props) => ( 5 |
6 | 7 | 16 |

{props.description}

17 | 18 |
19 | ) 20 | 21 | -------------------------------------------------------------------------------- /src/utils/executeSequentially.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Executes sequentially a list of promises 3 | * @param {Array} list - List of promises to be executed 4 | * @returns {Promise} - If fails, returns the err with the index of the function in the list 5 | */ 6 | const executeSequentially = (list, startAt = 0, cb = () => {}) => { 7 | return list.slice(startAt).reduce((promise, fn, index) => { 8 | return promise.then(() => { 9 | return Promise.resolve() 10 | .then(() => { 11 | cb(startAt + index) 12 | return fn() 13 | }) 14 | .catch((err) => Promise.reject([err, startAt + index])) 15 | }) 16 | }, Promise.resolve()) 17 | 18 | } 19 | 20 | export default executeSequentially 21 | -------------------------------------------------------------------------------- /scripts/deployRegistry.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3') 2 | const fs = require('fs') 3 | const deployContract = require('./helpers/deployContract') 4 | 5 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) 6 | 7 | const registryPath = 'public/contracts/Registry_flat' 8 | 9 | const registryAbi = JSON.parse(fs.readFileSync(`${registryPath}.abi`).toString()) 10 | let registryBin = fs.readFileSync(`${registryPath}.bin`).toString() 11 | 12 | if (registryBin.slice(0, 2) !== '0x' && registryBin.slice(0, 2) !== '0X') { 13 | registryBin = '0x' + registryBin 14 | } 15 | 16 | web3.eth.getAccounts() 17 | .then((accounts) => { 18 | return deployContract(web3, registryAbi, registryBin, accounts[0]) 19 | }) 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | If you are reporting a problem with Token Wizard, please include the following information: 2 | 3 | ### Which network did you use? (Mainnet, Kovan, Rinkeby, etc.) 4 | 5 | *Your answer* 6 | 7 | ### If you were able to create it, what is the URL of your crowdsale? 8 | 9 | *Your answer* 10 | 11 | ### Do you have screenshots showing the problem? 12 | 13 | *Your answer* 14 | 15 | ### Do you see errors in the dev console? If yes, please include a screenshot 16 | 17 | To open the dev console in Google Chrome, press F12, or go to `View -> Developer -> Developer Tools`, and open the Console tab 18 | 19 | If you see errors, please right click on them and "Save as..". Zip saved file and attach it to the Issue. 20 | 21 | *Your answer* 22 | 23 | --- 24 | -------------------------------------------------------------------------------- /src/components/Common/CrowdsaleRate.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Field } from 'react-final-form' 3 | import { InputField2 } from './InputField2' 4 | import { composeValidators, isInteger, isLessOrEqualThan, isPositive } from '../../utils/validations' 5 | import { DESCRIPTION, TEXT_FIELDS } from '../../utils/constants' 6 | 7 | export const CrowdsaleRate = ({ ...props }) => ( 8 | 20 | ) 21 | -------------------------------------------------------------------------------- /src/components/Common/Loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../../assets/stylesheets/application.css'; 3 | 4 | export const Loader = ({show}) => { 5 | return
6 |
7 |
8 |
9 |
Do not refresh the webpage
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/step-icons.scss: -------------------------------------------------------------------------------- 1 | .step-icons { 2 | @include image-2x('../images/step-icons@2x.png', 100px, 480px); 3 | background-image: url(../images/step-icons.png); 4 | 5 | &_crowdsale-contract { 6 | width: 80px; 7 | height: 100px; 8 | background-position: 0 0; 9 | } 10 | 11 | &_crowdsale-page { 12 | width: 100px; 13 | height: 80px; 14 | background-position: 0 -100px; 15 | } 16 | 17 | &_crowdsale-setup { 18 | width: 88px; 19 | height: 100px; 20 | background-position: 0 -180px; 21 | } 22 | 23 | &_publish { 24 | width: 100px; 25 | height: 100px; 26 | background-position: 0 -280px; 27 | } 28 | 29 | &_token-setup { 30 | width: 100px; 31 | height: 100px; 32 | background-position: 0 -380px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/stores/PricingStrategyStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import autosave from './autosave' 3 | 4 | class PricingStrategyStore { 5 | 6 | @observable strategies; 7 | 8 | constructor(strategies = []) { 9 | this.strategies = strategies; 10 | 11 | autosave(this, 'PricingStrategyStore') 12 | } 13 | 14 | @action addStrategy = (strategy) => { 15 | this.strategies.push(strategy) 16 | } 17 | 18 | @action setStrategyProperty = (value, property, index) => { 19 | let newStrategy = {...this.strategies[index]} 20 | newStrategy[property] = value 21 | this.strategies[index] = newStrategy; 22 | } 23 | 24 | @action removeStrategy = (index) => { 25 | this.strategies.splice(index,1) 26 | } 27 | } 28 | 29 | export default PricingStrategyStore; 30 | -------------------------------------------------------------------------------- /src/stores/ContractStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import autosave from './autosave' 3 | 4 | class ContractStore { 5 | 6 | @observable token; 7 | @observable crowdsale; 8 | @observable pricingStrategy; 9 | @observable nullFinalizeAgent; 10 | @observable finalizeAgent; 11 | @observable safeMathLib; 12 | @observable registry; 13 | 14 | constructor() { 15 | autosave(this, 'ContractStore') 16 | } 17 | 18 | @action setContract = (contractName, contractObj) => { 19 | this[contractName] = contractObj; 20 | } 21 | 22 | @action setContractProperty = (contractName, property, value) => { 23 | let newContract = Object.assign({}, this[contractName]) 24 | newContract[property] = value 25 | this[contractName] = newContract; 26 | } 27 | } 28 | 29 | export default ContractStore; 30 | -------------------------------------------------------------------------------- /src/components/manage/__snapshots__/AboutCrowdsale.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AboutCrowdsale should render the component 1`] = ` 4 |
7 |
10 |

13 | MyToken 14 | ( 15 | MTK 16 | ) Settings 17 |

18 |

21 | The most important and exciting part of the crowdsale process. Here you can define parameters of your crowdsale campaign. 22 |

23 | 28 | Crowdsale page 29 | 30 |
31 | `; 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | 5 | install: 6 | - npm install 7 | 8 | before_script: 9 | - npm run installWeb3 10 | - export DISPLAY=:99.0 11 | - sh -e /etc/init.d/xvfb start 12 | - sleep 3 13 | - wget -N http://chromedriver.storage.googleapis.com/2.30/chromedriver_linux64.zip -P ~/ 14 | - unzip ~/chromedriver_linux64.zip -d ~/ 15 | - rm ~/chromedriver_linux64.zip 16 | - sudo mv -f ~/chromedriver /usr/local/share/ 17 | - sudo chmod +x /usr/local/share/chromedriver 18 | - sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver 19 | 20 | script: 21 | - npm run lint 22 | - npm test 23 | - npm run coveralls 24 | 25 | after_script: 26 | - sudo kill `sudo lsof -t -i:8545` 27 | 28 | after_success: 29 | - bash <(curl -s https://copilot.blackducksoftware.com/ci/travis/scripts/upload) 30 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | const argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | @extend %full-width; 3 | position: absolute; 4 | bottom: 0; 5 | height: $footer-height; 6 | background-image: url(../images/bg.png); 7 | background-size: cover; 8 | background-position: center center; 9 | color: #fff; 10 | 11 | .container { 12 | position: relative; 13 | } 14 | 15 | .logo, 16 | .socials { 17 | transform: translateY(-50%); 18 | position: absolute; 19 | z-index: 1; 20 | top: 50%; 21 | } 22 | 23 | .logo { 24 | @extend %logos; 25 | left: 0; 26 | width: 126px; 27 | height: 24px; 28 | background-position: 0 0; 29 | } 30 | 31 | .rights { 32 | color: #fff; 33 | line-height: $footer-height; 34 | text-align: center; 35 | font-size: 12px; 36 | } 37 | 38 | .socials { 39 | right: 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Common/ModalContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../../assets/stylesheets/application.css' 3 | 4 | export const ModalContainer = (props) => { 5 | if (props.showModal) 6 | return ( 7 |
8 |
9 | {props.title 10 | ?

{props.title}

11 | : null 12 | } 13 | {props.description 14 | ?

{props.description}

15 | : null 16 | } 17 | {props.children} 18 | {props.hideModal 19 | ?
props.hideModal()}>
20 | : null 21 | } 22 |
23 |
24 | ) 25 | return null 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Common/RadioInputField.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../assets/stylesheets/application.css'; 3 | 4 | export const RadioInputField = (props) => { 5 | const inputs = props.items 6 | .map((item, index) => ( 7 | 16 | )) 17 | 18 | return ( 19 |
20 | 21 |
22 | { inputs } 23 |
24 |

{props.description}

25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/manage/AboutCrowdsale.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StaticRouter } from 'react-router' 3 | import { AboutCrowdsale } from './AboutCrowdsale' 4 | import renderer from 'react-test-renderer' 5 | import Adapter from 'enzyme-adapter-react-15' 6 | import { configure } from 'enzyme' 7 | 8 | configure({ adapter: new Adapter() }) 9 | 10 | describe('AboutCrowdsale', () => { 11 | it('should render the component', () => { 12 | const aboutCrowdsaleParams = { 13 | name: 'MyToken', 14 | ticker: 'MTK', 15 | address: '0x0000000000000000000000000000000000000001', 16 | networkId: '12648430', 17 | } 18 | 19 | expect(renderer.create( 20 | 21 | 22 | 23 | ).toJSON()).toMatchSnapshot() 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/components/Common/Error.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Field } from 'react-final-form' 3 | 4 | const defaultErrorStyles = { 5 | color: 'red', 6 | fontWeight: 'bold', 7 | fontSize: '12px', 8 | width: '100%', 9 | height: '20px', 10 | } 11 | 12 | export const Error = ({ name, errorStyle }) => ( 13 | { 17 | const errors = [].concat(error) 18 | 19 | return ( 20 | 21 | { 22 | errors.length 23 | ? errors.map((error, index) => ( 24 |

{(!pristine || touched) && error}

25 | )) 26 | : null 27 | } 28 |
29 | ) 30 | }} 31 | /> 32 | ) 33 | -------------------------------------------------------------------------------- /src/stores/StatsStore.js: -------------------------------------------------------------------------------- 1 | import { action, observable } from 'mobx' 2 | 3 | class StatsStore { 4 | @observable totalEthRaised 5 | @observable totalCrowdsales 6 | @observable percentageOfWhitelisted 7 | @observable percentageOfFinalized 8 | @observable percentageOfMultiTiers 9 | @observable percentageOfReserved 10 | @observable totalInvolvedContributorsAmount 11 | @observable maxTiersAmount 12 | @observable maxEthRaised 13 | 14 | constructor () { 15 | this.totalEthRaised = 0 16 | this.totalCrowdsales = 0 17 | this.percentageOfWhitelisted = 0 18 | this.percentageOfFinalized = 0 19 | this.percentageOfMultiTiers = 0 20 | this.totalInvolvedContributorsAmount = 0 21 | this.maxTiersAmount = 0 22 | this.maxEthRaised = 0 23 | } 24 | 25 | @action setProperty = (property, value) => { 26 | this[property] = value 27 | } 28 | } 29 | 30 | export default StatsStore -------------------------------------------------------------------------------- /src/components/Common/StepNavigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../../assets/stylesheets/application.css'; 3 | import { getStepClass } from '../../utils/utils' 4 | import { NAVIGATION_STEPS } from '../../utils/constants' 5 | const { CROWDSALE_CONTRACT, TOKEN_SETUP, CROWDSALE_SETUP, PUBLISH, CROWDSALE_PAGE } = NAVIGATION_STEPS 6 | 7 | export const StepNavigation = ({activeStep}) => ( 8 |
9 |
10 |
Crowdsale Contract
11 |
Token Setup
12 |
Crowdsale Setup
13 |
Publish
14 |
Crowdsale Page
15 |
16 |
17 | ) -------------------------------------------------------------------------------- /src/components/Common/NoWeb3.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class NoWeb3 extends Component { 4 | render () { 5 | return ( 6 |
7 |
8 |
9 |
10 |

Wallet not found, or access to Ethereum account not granted

11 |

12 | If a Wallet extension is installed on your web browser, please verify that the access to Ethereum account has been granted and is available for the corresponding domain. 13 | Check Token Wizard GitHub for the instruction. 14 |

15 |
16 |
17 |
18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/steps/content.scss: -------------------------------------------------------------------------------- 1 | .steps-content { 2 | padding: 30px 30px 0; 3 | border-radius: 8px; 4 | border: 1px solid #eee; 5 | background-color: #fff; 6 | 7 | .about-step { 8 | position: relative; 9 | min-height: 100px; 10 | padding-left: 120px; 11 | padding-bottom: 30px; 12 | 13 | .steps:not(.steps_publish) & { 14 | margin-bottom: 30px; 15 | border-bottom: 1px solid #eee; 16 | } 17 | 18 | .step-icons { 19 | position: absolute; 20 | left: 0; 21 | top: 0; 22 | } 23 | 24 | .title { 25 | display: block; 26 | margin-bottom: 10px; 27 | text-transform: uppercase; 28 | font-size: 20px; 29 | font-weight: bold; 30 | } 31 | 32 | .description { 33 | line-height: 24px; 34 | font-size: 13px; 35 | } 36 | } 37 | 38 | .description { 39 | color: #8197a2; 40 | line-height: 20px; 41 | font-size: 12px; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Common/TokenName.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { composeValidators, isRequired, isMaxLength, isMatchingPattern } from '../../utils/validations' 3 | import { TEXT_FIELDS } from '../../utils/constants' 4 | import { Field } from 'react-final-form' 5 | import { InputField2 } from './InputField2' 6 | 7 | export const TokenName = ({ errorStyle }) => ( 8 | { 10 | const errors = composeValidators( 11 | isRequired(), 12 | isMaxLength()(30), 13 | isMatchingPattern('Name should have at least one character')(/.*\S.*/) 14 | )(value) 15 | 16 | if (errors) return errors.shift() 17 | }} 18 | component={InputField2} 19 | side="left" 20 | name="name" 21 | type="text" 22 | description="The name of your token. Will be used by Etherscan and other tokenbrowsers. Be afraid of trademarks." 23 | label={TEXT_FIELDS.NAME} 24 | errorStyle={errorStyle} 25 | /> 26 | ) 27 | -------------------------------------------------------------------------------- /src/components/Common/__snapshots__/BigNumberInput.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BigNumberInput Should render the component 1`] = ` 4 |
7 | 12 | 23 |

26 | Exchange rate Ethereum to Tokens. If it's 100, then for 1 Ether you can buy 100 tokens 27 |

28 |

40 |

41 | `; 42 | -------------------------------------------------------------------------------- /src/components/Common/__snapshots__/NumericInput.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`NumericInput Should render the component 1`] = ` 4 |
7 | 12 | 23 |

26 | Refers to how divisible a token can be, from 0 (not at all divisible) to 18 (pretty much continuous). 27 |

28 |

40 |

41 | `; 42 | -------------------------------------------------------------------------------- /src/components/Common/ReservedTokensItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../assets/stylesheets/application.css'; 3 | import classNames from 'classnames' 4 | 5 | const ReservedTokensItem = props => { 6 | return ( 7 |
8 |
9 | {props.addr} 10 | {props.dim} 11 | {props.val} 12 |
13 | {props.readOnly 14 | ? null 15 | : 20 | } 21 |
22 | ); 23 | }; 24 | 25 | export default ReservedTokensItem; 26 | -------------------------------------------------------------------------------- /src/components/manage/ReadOnlyWhitelistAddresses.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const ReadOnlyWhitelistAddresses = ({ tier }) => { 4 | if (!tier.whitelist.length) { 5 | return ( 6 |
7 |
8 | no addresses loaded 9 |
10 |
11 | ) 12 | } 13 | 14 | return ( 15 |
16 | {tier.whitelist.map(item => ( 17 |
18 |
19 | {item.addr} 20 | {item.min} 21 | {item.max} 22 |
23 |
24 | ))} 25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/steps/radiosinline.scss: -------------------------------------------------------------------------------- 1 | .steps { 2 | .radios-inline { 3 | height: 36px; 4 | vertical-align: middle; 5 | margin-bottom: 15px; 6 | 7 | input { 8 | display: none; 9 | 10 | &:checked + .title:before { 11 | background-color: #642f9c; 12 | } 13 | } 14 | } 15 | 16 | .radio-inline { 17 | cursor: pointer; 18 | position: relative; 19 | display: table-cell; 20 | padding-left: 30px; 21 | padding-right: 20px; 22 | line-height: 36px; 23 | 24 | &:last-child { 25 | margin-bottom: 30px; 26 | } 27 | 28 | .title { 29 | position: relative; 30 | display: block; 31 | 32 | &:before { 33 | content: ''; 34 | position: absolute; 35 | left: -29px; 36 | top: 50%; 37 | width: 10px; 38 | height: 10px; 39 | margin-top: -8.5px; 40 | border: 5px solid #fff; 41 | box-shadow: 0 0 0 1px #cdd5da; 42 | border-radius: 50%; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/IncompleteDeploy.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router-dom' 3 | import cancelDeploy from '../utils/cancelDeploy' 4 | 5 | class CheckIncompleteDeploy extends Component { 6 | cancel() { 7 | cancelDeploy() 8 | } 9 | 10 | render() { 11 | return ( 12 |
13 |
14 |
15 |
16 |

Welcome to Token Wizard

17 |

18 | You have an incomplete deploy. 19 |

20 |
21 | Resume 22 |
Cancel
23 |
24 |
25 |
26 |
27 |
28 | ) 29 | } 30 | } 31 | 32 | export default CheckIncompleteDeploy 33 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), local('OpenSans'), url(https://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3ZBw1xU1rKptJj_0jans920.woff2) format('woff2'); 6 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 7 | } 8 | 9 | @font-face { 10 | font-family: 'Open Sans'; 11 | font-style: normal; 12 | font-weight: 700; 13 | src: local('Open Sans Bold'), local('OpenSans-Bold'), url(https://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzBampu5_7CjHW5spxoeN3Vs.woff2) format('woff2'); 14 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 15 | } 16 | 17 | html, 18 | body { 19 | color: #333; 20 | line-height: 1; 21 | font-size: 14px; 22 | font-family: 'Open Sans', sans-serif; 23 | -webkit-font-smoothing: antialiased; 24 | } -------------------------------------------------------------------------------- /src/components/Common/TokenTicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { composeValidators, isMatchingPattern, isRequired, isMaxLength } from '../../utils/validations' 3 | import { DESCRIPTION, TEXT_FIELDS } from '../../utils/constants' 4 | import { Field } from 'react-final-form' 5 | import { InputField2 } from './InputField2' 6 | 7 | export const TokenTicker = ({ errorStyle }) => ( 8 | { 10 | const errors = composeValidators( 11 | isRequired(), 12 | isMaxLength('Please enter a valid ticker between 1-5 characters')(5), 13 | isMatchingPattern('Only alphanumeric characters')(/^[a-zA-Z0-9]*$/) 14 | )(value) 15 | 16 | if (errors) return errors.shift() 17 | }} 18 | component={InputField2} 19 | parse={(value) => value || ''} 20 | side="right" 21 | name="ticker" 22 | type="text" 23 | description={`${DESCRIPTION.TOKEN_TICKER} There are 11,881,376 combinations for 26 english letters. Be hurry.`} 24 | label={TEXT_FIELDS.TICKER} 25 | errorStyle={errorStyle} 26 | /> 27 | ) 28 | -------------------------------------------------------------------------------- /src/components/Common/__snapshots__/AddressInput.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AddressInput Should render the component 1`] = ` 4 |
7 | 12 | 23 |

26 | Where the money goes after investors transactions. Immediately after each transaction. We recommend to setup a multisig wallet with hardware based signers. 27 |

28 |

40 |

41 | `; 42 | -------------------------------------------------------------------------------- /src/components/Common/__snapshots__/RadioInputField.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Should render the component 1`] = ` 4 |
7 | 12 |
15 | 30 | 45 |
46 |

49 | Some Description 50 |

51 |
52 | `; 53 | -------------------------------------------------------------------------------- /src/components/Common/TokenDecimals.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { composeValidators, isLessOrEqualThan, isNonNegative, isRequired } from '../../utils/validations' 3 | import { TEXT_FIELDS } from '../../utils/constants' 4 | import { Field } from 'react-final-form' 5 | import { InputField2 } from './InputField2' 6 | import { acceptPositiveIntegerOnly } from '../../utils/utils' 7 | 8 | export const TokenDecimals = ({ disabled, errorStyle }) => ( 9 | { 11 | const errors = composeValidators( 12 | isRequired(), 13 | isNonNegative(), 14 | isLessOrEqualThan("Should not be greater than 18")(18) 15 | )(value) 16 | 17 | if (errors) return errors.shift() 18 | }} 19 | component={InputField2} 20 | parse={acceptPositiveIntegerOnly} 21 | side="left" 22 | name="decimals" 23 | type="text" 24 | description="Refers to how divisible a token can be, from 0 (not at all divisible) to 18 (pretty much continuous)." 25 | label={TEXT_FIELDS.DECIMALS} 26 | disabled={disabled} 27 | errorStyle={errorStyle} 28 | /> 29 | ) 30 | -------------------------------------------------------------------------------- /public/bundle.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o (http://ilee.co.uk/) 5 | */ 6 | var replaceall=function(a,b,c){return b=b.replace(/\$/g,"$$$$"),c.replace(new RegExp(a.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|<>\-\&])/g,"\\$&"),"g"),b)};"undefined"!=typeof exports&&("undefined"!=typeof module&&module.exports&&(exports=module.exports=replaceall),exports.replaceall=replaceall); 7 | },{}],2:[function(require,module,exports){ 8 | //let web3 = require("web3"); 9 | let reaplaceAll = require("replaceall"); 10 | 11 | //window.web3 = web3; 12 | window.reaplaceAll = reaplaceAll; 13 | },{"replaceall":1}]},{},[2]); 14 | -------------------------------------------------------------------------------- /src/components/manage/FinalizeCrowdsaleStep.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import classNames from 'classnames' 4 | 5 | export const FinalizeCrowdsaleStep = ({ disabled, handleClick }) => ( 6 |
7 |
8 |
!
9 |

Finalize Crowdsale

10 |

11 | Finalize - Finalization is the last step of the crowdsale. 12 | You can make it only after the end of the last tier. After finalization, it's not possible to update tiers, buy 13 | tokens. All tokens will be movable, reserved tokens will be issued. 14 |

15 | !disabled ? handleClick() : undefined}> 16 | 23 | Finalize Crowdsale 24 | 25 | 26 |
27 |
28 | ) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 POA Network 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../../assets/stylesheets/application.css'; 3 | import { Link } from 'react-router-dom' 4 | import moment from "moment"; 5 | import { displayHeaderAndFooterInIframe } from '../../utils/utils' 6 | 7 | export const Footer = () => { 8 | const displayFooter = displayHeaderAndFooterInIframe() 9 | 10 | const footer = ( 11 | 23 | ) 24 | 25 | return displayFooter ? footer : null 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Common/InputField.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../assets/stylesheets/application.css'; 3 | import {VALIDATION_TYPES} from '../../utils/constants'; 4 | const {INVALID} = VALIDATION_TYPES; 5 | 6 | export const InputField = props => { 7 | const errorStyle = { 8 | color: 'red', 9 | fontWeight: 'bold', 10 | fontSize: '12px', 11 | width: '100%', 12 | height: '10px', 13 | }; 14 | 15 | const error = props.valid === INVALID ? props.errorMessage : ''; 16 | 17 | return ( 18 |
19 | 20 | 31 |

{props.description}

32 | { props.pristine ?

:

{error}

} 33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/react-web3/Web3Provider.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Adapter from 'enzyme-adapter-react-15' 3 | import { configure, mount, shallow, render } from 'enzyme' 4 | import renderer from 'react-test-renderer' 5 | import Web3Provider from './Web3Provider' 6 | 7 | configure({ adapter: new Adapter() }) 8 | 9 | describe('Web3Provider', () => { 10 | 11 | it(`should render web3Provider screen`, () => { 12 | // Given 13 | const component = renderer.create() 14 | 15 | // When 16 | const tree = component.toJSON() 17 | 18 | // Then 19 | expect(tree).toMatchSnapshot() 20 | }) 21 | 22 | it('renders without crashing', () => { 23 | // Given 24 | const wrapper = mount() 25 | 26 | // Given 27 | const tree = wrapper.html() 28 | 29 | // Then 30 | expect(tree).toMatchSnapshot() 31 | }) 32 | 33 | it('should render screen with render without throwing an error', () => { 34 | // When 35 | const wrapper = render() 36 | 37 | // Given 38 | const tree = wrapper.html() 39 | 40 | // Then 41 | expect(tree).toMatchSnapshot() 42 | }) 43 | 44 | }) 45 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Web3 from 'web3' 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | import { useStrict } from 'mobx'; 7 | import { Provider } from 'mobx-react'; 8 | import * as stores from './stores'; 9 | import 'airbnb-js-shims' 10 | import 'font-awesome/css/font-awesome.css' 11 | 12 | useStrict(true); 13 | 14 | if (!process.env['REACT_APP_REGISTRY_ADDRESS']) { 15 | throw new Error('REACT_APP_REGISTRY_ADDRESS env variable is not present') 16 | } 17 | 18 | const devEnvironment = process.env.NODE_ENV === 'development'; 19 | if (devEnvironment && !window.web3) { 20 | window.web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); 21 | } 22 | 23 | function renderApp(App) { 24 | ReactDOM.render( 25 | 26 | 27 | , 28 | document.getElementById('root') 29 | ); 30 | } 31 | 32 | renderApp(App) 33 | 34 | if (module.hot) { 35 | module.hot.accept(() => { 36 | const NextApp = require('./App').default 37 | renderApp(NextApp) 38 | }) 39 | } 40 | 41 | 42 | registerServiceWorker(); 43 | -------------------------------------------------------------------------------- /src/stores/CrowdsaleStore.js: -------------------------------------------------------------------------------- 1 | import { action, observable } from 'mobx' 2 | import autosave from './autosave' 3 | 4 | class CrowdsaleStore { 5 | @observable crowdsales 6 | @observable maximumSellableTokens 7 | @observable maximumSellableTokensInWei 8 | @observable supply 9 | @observable selected 10 | 11 | constructor () { 12 | this.reset() 13 | autosave(this, 'CrowdsaleStore') 14 | } 15 | 16 | @action reset = () => { 17 | this.crowdsales = [] 18 | this.selected = { 19 | updatable: false, 20 | initialTiersValues: [] 21 | } 22 | } 23 | 24 | @action setCrowdsales = (crowdsales) => { 25 | this.crowdsales = crowdsales 26 | } 27 | 28 | @action setProperty = (property, value) => { 29 | this[property] = value 30 | } 31 | 32 | @action setSelectedProperty = (property, value) => { 33 | const currentCrowdsale = Object.assign({}, this.selected) 34 | 35 | currentCrowdsale[property] = value 36 | this.selected = currentCrowdsale 37 | } 38 | 39 | @action addInitialTierValues = (initialValues) => { 40 | this.selected.initialTiersValues.push(initialValues) 41 | } 42 | } 43 | 44 | export default CrowdsaleStore 45 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/steps/navigation.scss: -------------------------------------------------------------------------------- 1 | .steps-navigation { 2 | margin-bottom: 30px; 3 | padding: 30px 0; 4 | border-bottom: 1px solid #eee; 5 | background-color: #fff; 6 | 7 | .container { 8 | display: flex; 9 | justify-content: space-between; 10 | } 11 | 12 | .step-navigation { 13 | position: relative; 14 | padding-left: 30px; 15 | color: #8197a2; 16 | text-transform: uppercase; 17 | line-height: 20px; 18 | font-size: 11px; 19 | font-weight: bold; 20 | 21 | @for $i from 1 through 5 { 22 | &:nth-child(#{$i}):before { 23 | content: "#{$i}"; 24 | } 25 | } 26 | 27 | &:before { 28 | transform: translateY(-50%); 29 | position: absolute; 30 | width: 20px; 31 | height: 20px; 32 | left: 0; 33 | top: 50%; 34 | border-radius: 50%; 35 | background-color: fade-out(#8197a2, 0.6); 36 | color: #fff; 37 | line-height: 20px; 38 | text-align: center; 39 | font-size: 11px; 40 | font-weight: bold; 41 | } 42 | 43 | &_active { 44 | color: #333; 45 | 46 | &:before { 47 | background-color: #08b3f2; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/stats.scss: -------------------------------------------------------------------------------- 1 | .stats { 2 | margin: 30px auto; 3 | padding-top: 30px; 4 | padding-left: 30px; 5 | border-radius: 8px; 6 | border: 1px solid #eee; 7 | background-color: #fff; 8 | 9 | &.container { 10 | min-width: 960px; 11 | max-width: 1000px; 12 | width: auto; 13 | } 14 | 15 | .title { 16 | display: block; 17 | margin-bottom: 10px; 18 | text-transform: uppercase; 19 | font-size: 20px; 20 | font-weight: bold; 21 | margin-bottom: 50px; 22 | } 23 | 24 | .stats-items { 25 | position: relative; 26 | 27 | &-i { 28 | margin-bottom: 30px; 29 | } 30 | 31 | &-title { 32 | margin-bottom: 10px; 33 | color: #642f9c; 34 | font-weight: bold; 35 | } 36 | 37 | &-description { 38 | color: #8197a2; 39 | font-size: 12px; 40 | } 41 | } 42 | 43 | &-table { 44 | display: table; 45 | 46 | &-cell { 47 | display: table-cell; 48 | max-width: 293px; 49 | padding-right: 50px; 50 | 51 | &_left { 52 | position: relative; 53 | max-width: 670px; 54 | padding-right: 30px; 55 | border-right: 1px solid #eee; 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/utils/processWhitelist.js: -------------------------------------------------------------------------------- 1 | import { isAddress, validateWhitelistMin, validateWhitelistMax } from './validations' 2 | 3 | /** 4 | * Execute a callback with each valid whitelist item in the given list 5 | * 6 | * @param {Object} whitelistInformation 7 | * @param {Array} whitelistInformation.rows Array of whitelist items. Each element in the array has the structure 8 | * `[address, min, max]`, for example: `['0x1234567890123456789012345678901234567890', '1', '10']` 9 | * @param {Number} whitelistInformation.decimals Amount of decimals allowed for the min and max values 10 | * @param {Function} cb The function to be called with each valid item 11 | * @returns {Object} Object with a `called` property, indicating the number of times the callback was called 12 | */ 13 | export default function ({ rows, decimals }, cb) { 14 | let called = 0 15 | rows.forEach((row) => { 16 | if (row.length !== 3) return 17 | 18 | const [addr, min, max] = row 19 | 20 | if ( 21 | isAddress()(addr) || 22 | validateWhitelistMin({ min, max, decimals }) || 23 | validateWhitelistMax({ min, max, decimals }) 24 | ) return 25 | 26 | cb({ addr, min, max }) 27 | called++ 28 | }) 29 | 30 | return { called } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/components/manage/ManageForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { FormSpy } from 'react-final-form' 4 | import { FieldArray } from 'react-final-form-arrays' 5 | import { ManageTierBlock } from './ManageTierBlock' 6 | import classNames from 'classnames' 7 | 8 | export const ManageForm = ({ 9 | handleSubmit, 10 | invalid, 11 | pristine, 12 | handleChange, 13 | canSave, 14 | ...props, 15 | }) => ( 16 |
17 | 18 | {({ fields }) => ( 19 | 23 | )} 24 | 25 | 26 | 27 |
28 |
29 | 30 | Save 38 | 39 |
40 |
41 | 42 | 43 | ) 44 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/manage.scss: -------------------------------------------------------------------------------- 1 | .manage { 2 | min-height: 600px; 3 | 4 | .warning-logo { 5 | color: #642F9C !important; 6 | border-color: #642F9C !important; 7 | position: absolute; 8 | left: 0; 9 | top: 0; 10 | display: flex; 11 | } 12 | 13 | .white-list-item-container { 14 | .remove { 15 | display: inline-flex; 16 | margin-top: 8px; 17 | } 18 | 19 | .swal2-icon { 20 | width: 14px; 21 | height: 14px; 22 | line-height: 14px; 23 | border: 2px solid transparent; 24 | } 25 | 26 | .warning-logo { 27 | color: orange !important; 28 | border-color: orange !important; 29 | position: relative; 30 | float: right; 31 | font-size: 14px; 32 | font-weight: bold; 33 | } 34 | } 35 | 36 | .steps-content { 37 | margin-top: 30px; 38 | } 39 | 40 | .description { 41 | margin-bottom: 20px; 42 | } 43 | 44 | .crowdsale-page-link { 45 | color: #08b3f2; 46 | text-decoration: none; 47 | font-size: 13px; 48 | } 49 | 50 | .divisor { 51 | margin-bottom: 30px; 52 | border-bottom: 1px solid #eee; 53 | } 54 | 55 | .no-arrow { 56 | background-image: none; 57 | padding: 0 15px; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/microservices.js: -------------------------------------------------------------------------------- 1 | import { findConstructor, toFixed } from '../utils/utils' 2 | import { getconstructorParams } from '../stores/utils' 3 | 4 | export const getEncodedABIClientSide = (web3, abi, vals, crowdsaleNum, isCrowdsale) => { 5 | const abiConstructor = findConstructor(abi) 6 | let params = getconstructorParams(abiConstructor, vals, crowdsaleNum, isCrowdsale) 7 | 8 | return getABIEncoded(web3, params.types, params.vals) 9 | } 10 | 11 | const getABIEncoded = (web3, types, vals) => { 12 | return new Promise((resolve, reject) => { 13 | if (vals) { 14 | for (let i = 0; i < vals.length; i++) { 15 | let val = vals[i] 16 | if (Array.isArray(val)) { 17 | for (let j = 0; j < val.length; j++) { 18 | if (val[j]) { 19 | vals[i][j] = toFixed(val[j]) 20 | } 21 | } 22 | } 23 | } 24 | } 25 | 26 | console.log(types) 27 | console.log(vals) 28 | 29 | try { 30 | let encoded = web3.eth.abi.encodeParameters(types, vals) 31 | let ABIEncodedRaw = encoded.toString('hex') 32 | let ABIEncoded = ABIEncodedRaw.indexOf('0x') > -1 ? ABIEncodedRaw.substr(2) : ABIEncodedRaw 33 | resolve(ABIEncoded) 34 | } catch (e) { 35 | reject(e) 36 | } 37 | 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/blockchainHelpers.spec.js: -------------------------------------------------------------------------------- 1 | import { getNetWorkNameById } from './blockchainHelpers'; 2 | 3 | describe('blockchainHelpers', () => { 4 | describe('getNetWorkNameById', () => { 5 | it('should work for the mainnet', () => { 6 | expect(getNetWorkNameById(1)).toEqual('Mainnet'); 7 | }); 8 | 9 | it('should work for morden', () => { 10 | expect(getNetWorkNameById(2)).toEqual('Morden'); 11 | }); 12 | 13 | it('should work for ropsten', () => { 14 | expect(getNetWorkNameById(3)).toEqual('Ropsten'); 15 | }); 16 | 17 | it('should work for rinkeby', () => { 18 | expect(getNetWorkNameById(4)).toEqual('Rinkeby'); 19 | }); 20 | 21 | it('should work for kovan', () => { 22 | expect(getNetWorkNameById(42)).toEqual('Kovan'); 23 | }); 24 | 25 | it('should work for sokol', () => { 26 | expect(getNetWorkNameById(77)).toEqual('Sokol'); 27 | }); 28 | 29 | it('should work for core POA', () => { 30 | expect(getNetWorkNameById(99)).toEqual('Core_POA'); 31 | }); 32 | 33 | it('should return null for unknown network ids', () => { 34 | expect(getNetWorkNameById(5)).toEqual(null); 35 | expect(getNetWorkNameById(43)).toEqual(null); 36 | expect(getNetWorkNameById(1000)).toEqual(null); 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const gulp = require("gulp"); 4 | const sass = require("gulp-sass"); 5 | const sassGlob = require("gulp-sass-glob"); 6 | const autoprefixer = require("gulp-autoprefixer"); 7 | const uglifycss = require("gulp-uglifycss"); 8 | const include = require("gulp-include"); 9 | const addsrc = require("gulp-add-src"); 10 | const order = require("gulp-order"); 11 | const concat = require("gulp-concat"); 12 | const uglify = require("gulp-uglify"); 13 | const gutil = require("gulp-util"); 14 | 15 | gulp.task("javascript", function() { 16 | return gulp.src("assets/javascripts/application/*.js") 17 | .pipe(addsrc("assets/javascripts/vendor/index.js")) 18 | .pipe(order([ 19 | "assets/javascripts/vendor/index.js", 20 | "assets/javascripts/application/*.js" 21 | ], {base: "."})) 22 | .pipe(include()) 23 | .pipe(concat("application.js")) 24 | //.pipe(uglify()) 25 | .pipe(gulp.dest("public")); 26 | }); 27 | 28 | gulp.task("sass", function() { 29 | return gulp.src(["src/assets/stylesheets/*.scss"]) 30 | .pipe(sassGlob()) 31 | .pipe(sass().on("error", sass.logError)) 32 | .pipe(autoprefixer()) 33 | .pipe(uglifycss()) 34 | .pipe(gulp.dest("src/assets/stylesheets/")); 35 | }); 36 | 37 | gulp.task("watch", function() { 38 | gulp.watch("src/assets/stylesheets/**/*.scss", ["sass"]); 39 | }); 40 | -------------------------------------------------------------------------------- /src/components/Common/AddressInput.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Web3 from 'web3' 3 | import { VALIDATION_TYPES } from '../../utils/constants' 4 | import { InputField } from './InputField' 5 | 6 | const { INVALID, VALID } = VALIDATION_TYPES 7 | 8 | export class AddressInput extends Component { 9 | constructor (props) { 10 | super(props) 11 | 12 | this.state = { 13 | address: props.address || '', 14 | pristine: props.pristine !== undefined ? props.pristine : true, 15 | valid: props.valid || INVALID, 16 | } 17 | } 18 | 19 | updateWalletAddress = address => { 20 | const newState = { 21 | address, 22 | pristine: false, 23 | valid: Web3.utils.isAddress(address) ? VALID : INVALID, 24 | } 25 | 26 | this.setState(newState) 27 | this.props.onChange(newState) 28 | } 29 | 30 | render () { 31 | const { address, description, errorMessage, pristine, side, title, valid } = this.props 32 | 33 | return ( 34 | this.updateWalletAddress(e.target.value)} 43 | description={description} 44 | /> 45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/stores/ReservedTokenStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | import autosave from './autosave' 3 | 4 | class ReservedTokenStore { 5 | 6 | @observable tokens; 7 | 8 | constructor(tokens = []) { 9 | this.tokens = tokens; 10 | 11 | autosave(this, 'ReservedTokenStore') 12 | } 13 | 14 | @action addToken = (token) => { 15 | const currentToken = this.tokens.find(t => t.addr === token.addr && t.dim === token.dim) 16 | 17 | if (currentToken) { 18 | const index = this.tokens.indexOf(currentToken) 19 | this.tokens[index] = token 20 | } else { 21 | this.tokens.push(token); 22 | } 23 | } 24 | 25 | @action setTokenProperty = (index, property, value) => { 26 | let newToken = {...this.tokens[index]}; 27 | newToken[property] = value; 28 | this.tokens[index] = newToken; 29 | } 30 | 31 | @action removeToken = (index) => { 32 | this.tokens.splice(index,1); 33 | } 34 | 35 | @action clearAll = () => { 36 | this.tokens.splice(0); 37 | } 38 | 39 | findToken(inputToken) { 40 | return this.tokens.find((token) => { 41 | if (inputToken['dim'] === token['dim'] && inputToken['addr'] === token['addr'] && inputToken['val'] === token['val']) { 42 | return true; 43 | } 44 | 45 | return false; 46 | }); 47 | } 48 | } 49 | 50 | export default ReservedTokenStore; 51 | -------------------------------------------------------------------------------- /src/components/Home/index.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Adapter from 'enzyme-adapter-react-15' 3 | import { configure, mount, render } from 'enzyme' 4 | import { MemoryRouter } from 'react-router' 5 | import renderer from 'react-test-renderer' 6 | 7 | import { Home } from './index' 8 | 9 | configure({ adapter: new Adapter() }) 10 | 11 | describe('Home Index', () => { 12 | 13 | it(`should render home screen`, () => { 14 | // Given 15 | const component = renderer.create( 16 | 17 | 18 | 19 | ) 20 | 21 | // When 22 | const tree = component.toJSON() 23 | 24 | // Then 25 | expect(tree).toMatchSnapshot() 26 | }) 27 | 28 | it('renders without crashing', () => { 29 | // Given 30 | const wrapper = mount( 31 | 32 | 33 | 34 | ) 35 | 36 | // Given 37 | const tree = wrapper.html() 38 | 39 | // Then 40 | expect(tree).toMatchSnapshot() 41 | }) 42 | 43 | it('should render screen with render without throwing an error', () => { 44 | // When 45 | const wrapper = render( 46 | 47 | 48 | 49 | ) 50 | 51 | // Given 52 | const tree = wrapper.html() 53 | 54 | // Then 55 | expect(tree).toMatchSnapshot() 56 | }) 57 | 58 | }) 59 | -------------------------------------------------------------------------------- /src/components/stepThree/utils.spec.js: -------------------------------------------------------------------------------- 1 | import { defaultCompanyStartDate, defaultCompanyEndDate } from './utils' 2 | import MockDate from 'mockdate' 3 | import moment from 'moment' 4 | 5 | beforeEach(() => { 6 | const currentTime = '2018-03-05T11:00:00' 7 | MockDate.set(currentTime) 8 | }) 9 | 10 | describe('defaultCompanyStartDate', () => { 11 | it('Should return a day formatted as: YYYY-MM-DDTHH:mm', () => { 12 | const isFormatOk = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/ 13 | const startDate = defaultCompanyStartDate() 14 | 15 | expect(isFormatOk.test(startDate)).toBeTruthy() 16 | }) 17 | 18 | it('Should return a day 5 minutes in the future', () => { 19 | const startDate = defaultCompanyStartDate() 20 | 21 | expect(moment().add(5, 'minutes').isSame(startDate)).toBeTruthy() 22 | }) 23 | }) 24 | 25 | describe('defaultComanyEndDate', () => { 26 | it('Should return a day formatted as: YYYY-MM-DDTHH:mm', () => { 27 | const isFormatOk = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/ 28 | const startDate = defaultCompanyStartDate() 29 | const endDate = defaultCompanyEndDate(startDate) 30 | 31 | expect(isFormatOk.test(endDate)).toBeTruthy() 32 | }) 33 | 34 | it('Should return a date 4 days in the future, at 00:00', () => { 35 | const startDate = defaultCompanyStartDate() 36 | const endDate = defaultCompanyEndDate(startDate) 37 | 38 | expect(moment().add(4, 'days').startOf('day').isSame(endDate)).toBeTruthy() 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/steps/total-funds.scss: -------------------------------------------------------------------------------- 1 | .total-funds { 2 | .right { 3 | text-align: right; 4 | } 5 | 6 | &-title { 7 | margin-bottom: 15px; 8 | color: #642f9c; 9 | font-size: 24px; 10 | font-weight: bold; 11 | } 12 | 13 | &-description { 14 | color: #8197a2; 15 | font-size: 14px; 16 | } 17 | 18 | &-chart { 19 | position: relative; 20 | z-index: 1; 21 | height: 20px; 22 | margin-bottom: 30px; 23 | border-radius: 10px; 24 | background-color: #f5f5f5; 25 | 26 | &-active { 27 | position: absolute; 28 | left: 0; 29 | top: 0; 30 | bottom: 0; 31 | border-radius: 10px; 32 | background-image: linear-gradient(to right, #7738b9, #853ecf); 33 | } 34 | 35 | &-container { 36 | position: relative; 37 | } 38 | 39 | &-division { 40 | position: absolute; 41 | z-index: 0; 42 | top: -5px; 43 | bottom: -5px; 44 | width: 1px; 45 | background-color: #ddd; 46 | 47 | @for $i from 1 through 9 { 48 | &:nth-child(#{$i}) { 49 | left: $i * 10%; 50 | } 51 | } 52 | } 53 | } 54 | 55 | &-statistics { 56 | .left, 57 | .right { 58 | width: 50%; 59 | /*padding-right: 10px;*/ 60 | box-sizing: border-box; 61 | } 62 | 63 | .title, 64 | .hash { 65 | margin-bottom: 10px; 66 | color: #642f9c; 67 | font-weight: bold; 68 | } 69 | 70 | .title { 71 | font-size: 16px; 72 | } 73 | 74 | .hash { 75 | font-size: 14px; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/socials.scss: -------------------------------------------------------------------------------- 1 | .socials { 2 | font-size: 0; 3 | 4 | .social { 5 | transition: 0.3s background-color; 6 | position: relative; 7 | display: inline-block; 8 | vertical-align: top; 9 | width: 30px; 10 | height: 30px; 11 | border-radius: 50%; 12 | background-color: fade-out(#fff, 0.8); 13 | 14 | &:not(:first-child) { 15 | margin-left: 10px; 16 | } 17 | 18 | &:hover { 19 | background-color: fade-out(#fff, 0.6); 20 | } 21 | 22 | &:before { 23 | @include image-2x('../images/socials@2x.png', 16px, 69px); 24 | transform: translate(-50%, -50%); 25 | content: ''; 26 | position: absolute; 27 | left: 50%; 28 | top: 50%; 29 | background-image: url(../images/socials.png); 30 | } 31 | 32 | &_github { 33 | &:before { 34 | width: 16px; 35 | height: 16px; 36 | background-position: 0 0; 37 | } 38 | } 39 | 40 | &_oracles { 41 | &:before { 42 | width: 16px; 43 | height: 14px; 44 | background-position: 0 -16px; 45 | } 46 | } 47 | 48 | &_reddit { 49 | &:before { 50 | width: 15px; 51 | height: 13px; 52 | background-position: 0 -30px; 53 | } 54 | } 55 | 56 | &_telegram { 57 | &:before { 58 | width: 16px; 59 | height: 14px; 60 | background-position: 0 -43px; 61 | } 62 | } 63 | 64 | &_twitter { 65 | &:before { 66 | width: 15px; 67 | height: 12px; 68 | background-position: 0 -57px; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/stepTwo/StepTwoForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { FormSpy } from 'react-final-form' 3 | import { TokenName } from '../Common/TokenName' 4 | import { TokenTicker } from '../Common/TokenTicker' 5 | import { TokenDecimals } from '../Common/TokenDecimals' 6 | import { ReservedTokensInputBlock } from '../Common/ReservedTokensInputBlock' 7 | 8 | const errorStyle = { 9 | color: 'red', 10 | fontWeight: 'bold', 11 | fontSize: '12px', 12 | width: '100%', 13 | height: '20px', 14 | } 15 | 16 | export const StepTwoForm = ({ 17 | id, 18 | handleSubmit, 19 | disableDecimals, 20 | updateTokenStore, 21 | tokens, 22 | decimals, 23 | addReservedTokensItem, 24 | removeReservedToken, 25 | clearAll 26 | }) => { 27 | return ( 28 |
29 |
30 | 31 | 32 | 33 |
34 |
35 |

Reserved tokens

36 |
37 | 44 | 45 | 48 | 49 | 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/stores/CrowdsalePageStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | 3 | class CrowdsalePageStore { 4 | 5 | @observable maximumSellableTokens; 6 | @observable maximumSellableTokensInWei; 7 | @observable investors; 8 | @observable ethRaised; 9 | @observable weiRaised; 10 | @observable rate; 11 | @observable tokensSold; 12 | @observable tokenAmountOf; 13 | @observable tiers = [] 14 | @observable ticks = [] 15 | 16 | @action setProperty = (property, value) => { 17 | this[property] = value 18 | } 19 | 20 | @action addTier = (tier) => { 21 | this.tiers.push(tier) 22 | this.sortTiers() 23 | this.buildTicksCollection() 24 | } 25 | 26 | @action sortTiers = () => { 27 | this.tiers = this.tiers.sort((previous, current) => current.startDate >= previous.startDate ? -1 : 1) 28 | } 29 | 30 | @action buildTicksCollection = () => { 31 | // assumes that list is sorted 32 | this.ticks = this.tiers.reduce((ticks, tier, index) => { 33 | const previousTickIndex = ticks.findIndex(tick => tick.type === 'end' && tick.time === tier.startDate) 34 | 35 | if (previousTickIndex === -1) { 36 | ticks.push({ 37 | type: 'start', 38 | time: tier.startDate, 39 | order: index + 1 40 | }) 41 | } 42 | 43 | ticks.push({ 44 | type: 'end', 45 | time: tier.endDate, 46 | order: index + 1 47 | }) 48 | 49 | return ticks 50 | }, []) 51 | .filter(tick => (tick.time - Date.now()) > 0) 52 | } 53 | 54 | @action extractNextTick = () => { 55 | return this.ticks.shift() 56 | } 57 | } 58 | 59 | export default CrowdsalePageStore; 60 | -------------------------------------------------------------------------------- /src/components/invest/QRPaymentProcess.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import QRCode from 'qrcode.react'; 3 | import { CopyToClipboard } from 'react-copy-to-clipboard'; 4 | 5 | const QRPaymentProcess = ({ crowdsaleAddress }) => { 6 | return ( 7 |
8 |
9 |
10 | 11 |
12 |

13 | { crowdsaleAddress } 14 |

15 | 16 | 17 | e.preventDefault()} className="payment-process-copy">Copy Address 18 | 19 | 20 | {/*
Waiting for payment
*/} 21 |
22 |

Important

23 |

24 | Send ethers to the crowdsale address with a MethodID: 0xa6f2ae3a 25 |

26 |
27 |
28 | { /*
29 |
30 |

31 | Your Project tokens were sent to 32 |

33 |

34 | 0x6b0770d930bB22990c83fBBfcba6faB129AD7E385 35 |

36 | See it on the blockchain 37 |
*/ } 38 |
39 | ) 40 | } 41 | 42 | export default QRPaymentProcess; 43 | -------------------------------------------------------------------------------- /src/components/Common/__snapshots__/ReservedTokensItem.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ReservedTokensItem should render the component with delete button enabled 1`] = ` 4 |
7 |
10 | 13 | 0x1111111111111111111111111111111111111111 14 | 15 | 18 | percentage 19 | 20 | 23 | 12.1234 24 | 25 |
26 |
29 | 32 | 35 | 36 |
37 |
38 | `; 39 | 40 | exports[`ReservedTokensItem should render the component with no delete button 1`] = ` 41 |
44 |
47 | 50 | 0x1111111111111111111111111111111111111111 51 | 52 | 55 | percentage 56 | 57 | 60 | 12.1234 61 | 62 |
63 |
64 | `; 65 | -------------------------------------------------------------------------------- /src/utils/processReservedTokens.js: -------------------------------------------------------------------------------- 1 | import { composeValidators, isAddress, isDecimalPlacesNotGreaterThan, isPositive, isRequired } from './validations' 2 | 3 | /** 4 | * Execute a callback with each valid whitelist item in the given list 5 | * 6 | * @param {Object} reservedTokensInformation 7 | * @param {Array} reservedTokensInformation.rows Array of whitelist items. Each element in the array has the structure 8 | * `[address, dim, val]`, for example: `['0x1234567890123456789012345678901234567890', '1', '10']` 9 | * @param {Number} reservedTokensInformation.decimals Amount of decimals allowed for the dim and val values 10 | * @param {Function} cb The function to be called with each valid item 11 | * @returns {Object} Object with a `called` property, indicating the number of times the callback was called 12 | */ 13 | export default function ({ rows, decimals }, cb) { 14 | let called = 0 15 | rows.forEach((row) => { 16 | if (row.length !== 3) return 17 | 18 | let valueErrors = undefined 19 | let [addr, dim, val] = row 20 | dim = dim.trim().toLowerCase() 21 | val = val.trim() 22 | 23 | // `dim` must be either 'tokens' or 'percentage' 24 | if (dim === 'tokens') { 25 | valueErrors = composeValidators( 26 | isRequired(), 27 | isPositive(), 28 | isDecimalPlacesNotGreaterThan(`Decimals should not exceed ${decimals} places`)(decimals) 29 | )(val) 30 | } else if (dim === 'percentage') { 31 | valueErrors = composeValidators( 32 | isRequired(), 33 | isPositive() 34 | )(val) 35 | } else { 36 | return console.error(`unrecognized dimension '${dim}'`) 37 | } 38 | 39 | if (isAddress()(addr) || valueErrors) return 40 | 41 | cb({ addr, dim, val }) 42 | 43 | called++ 44 | }) 45 | 46 | return { called } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /public/invest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 23 | Token Wizard 24 | 25 | 26 | 29 |
30 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/crowdsale.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 23 | Token Wizard 24 | 25 | 26 | 29 |
30 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/react-web3/__snapshots__/Web3Provider.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Web3Provider renders without crashing 1`] = `"
Do not refresh the webpage
"`; 4 | 5 | exports[`Web3Provider should render screen with render without throwing an error 1`] = `"
Do not refresh the webpage
"`; 6 | 7 | exports[`Web3Provider should render web3Provider screen 1`] = ` 8 |
11 |
14 |
17 |
20 | Do not refresh the webpage 21 |
22 |
23 |
26 |
29 |
32 |
35 |
38 |
41 |
44 |
45 |
46 | `; 47 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/home/process.scss: -------------------------------------------------------------------------------- 1 | @keyframes show-process { 2 | from { 3 | opacity: 0; 4 | transform: scale(0); 5 | } 6 | 7 | to { 8 | opacity: 1; 9 | transition: scale(1); 10 | } 11 | } 12 | 13 | .process { 14 | @extend %home-items; 15 | bottom: 0; 16 | padding-bottom: $footer-height; 17 | background-color: #f5f5f7; 18 | font-size: 0; 19 | 20 | .step-icons { 21 | transform: translateX(-50%); 22 | position: absolute; 23 | left: 50%; 24 | top: 0; 25 | 26 | &_crowdsale-page { 27 | top: 10px; 28 | } 29 | } 30 | 31 | .process-item { 32 | opacity: 0; 33 | animation-name: show-process; 34 | animation-duration: 1s; 35 | animation-fill-mode: forwards; 36 | position: relative; 37 | display: inline-block; 38 | vertical-align: top; 39 | width: 20%; 40 | padding: 130px 10px 0; 41 | box-sizing: border-box; 42 | text-align: center; 43 | 44 | @for $i from 1 through 5 { 45 | &:nth-child(#{$i}) { 46 | animation-delay: #{200 * $i}ms; 47 | 48 | &:after { 49 | content: "#{$i}"; 50 | } 51 | } 52 | } 53 | 54 | &:after { 55 | position: absolute; 56 | width: 32px; 57 | height: 32px; 58 | right: 28px; 59 | top: -16px; 60 | border-radius: 50%; 61 | border: 4px solid #fff; 62 | background-color: #08b3f2; 63 | color: #fff; 64 | line-height: 32px; 65 | text-align: center; 66 | font-size: 14px; 67 | font-weight: bold; 68 | } 69 | } 70 | 71 | .title { 72 | margin-bottom: 10px; 73 | text-transform: uppercase; 74 | font-size: 14px; 75 | font-weight: bold; 76 | } 77 | 78 | .description { 79 | color: #8197a2; 80 | line-height: 18px; 81 | font-size: 12px; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/steps/radios.scss: -------------------------------------------------------------------------------- 1 | .steps { 2 | .radios { 3 | input { 4 | display: none; 5 | 6 | &:checked + .title:before { 7 | background-color: #642f9c; 8 | } 9 | } 10 | } 11 | 12 | .radio { 13 | cursor: pointer; 14 | position: relative; 15 | display: block; 16 | margin-bottom: 60px; 17 | padding-left: 45px; 18 | 19 | &:not(:last-child):after { 20 | cursor: default; 21 | content: ''; 22 | position: absolute; 23 | left: 0; 24 | right: 0; 25 | bottom: -29px; 26 | height: 1px; 27 | background-color: #eee; 28 | } 29 | 30 | &:last-child { 31 | margin-bottom: 30px; 32 | } 33 | 34 | .title { 35 | position: relative; 36 | display: block; 37 | margin-bottom: 5px; 38 | line-height: 20px; 39 | text-transform: uppercase; 40 | font-weight: bold; 41 | 42 | &:before { 43 | content: ''; 44 | position: absolute; 45 | left: -45px; 46 | top: 50%; 47 | width: 10px; 48 | height: 10px; 49 | margin-top: -5px; 50 | border: 5px solid #fff; 51 | box-shadow: 0 0 0 1px #cdd5da; 52 | border-radius: 50%; 53 | } 54 | 55 | &_soon { 56 | &:after { 57 | content: 'soon'; 58 | display: inline-block; 59 | vertical-align: top; 60 | margin-left: 5px; 61 | padding: 0 5px; 62 | border-radius: 2px; 63 | background-color: #642f9c; 64 | color: #fff; 65 | line-height: 20px; 66 | text-transform: uppercase; 67 | font-size: 10px; 68 | font-weight: bold; 69 | } 70 | } 71 | } 72 | 73 | .description { 74 | color: #8197a2; 75 | line-height: 20px; 76 | font-size: 12px; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/components/manage/ReadOnlyWhitelistAddresses.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ReadOnlyWhitelistAddresses } from './ReadOnlyWhitelistAddresses' 3 | import renderer from 'react-test-renderer' 4 | import Adapter from 'enzyme-adapter-react-15' 5 | import { configure, mount } from 'enzyme' 6 | 7 | const noAddressMessage = 'no addresses loaded' 8 | 9 | const whitelistsAddresses = [ 10 | { addr: "0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e", min: 1234, max: 50505, stored: true }, 11 | { addr: "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b", min: 1234, max: 50505, stored: true }, 12 | { addr: "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9", min: 1234, max: 50505, stored: true }, 13 | { addr: "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1", min: 1234, max: 50505, stored: true }, 14 | { addr: "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC", min: 1234, max: 50505, stored: true }, 15 | { addr: "0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E", min: 1234, max: 50505, stored: true }, 16 | { addr: "0xd03ea8624C8C5987235048901fB614fDcA89b117", min: 1234, max: 50505, stored: true }, 17 | { addr: "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d", min: 1234, max: 50505, stored: true }, 18 | { addr: "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", min: 1234, max: 50505, stored: true } 19 | ] 20 | 21 | configure({ adapter: new Adapter() }) 22 | 23 | describe('ManageForm', () => { 24 | it('should render the whitelist addresses', () => { 25 | expect(renderer.create( 26 | 27 | ).toJSON()).toMatchSnapshot() 28 | }) 29 | 30 | it(`should render "${noAddressMessage}" message if no whitelist available`, () => { 31 | const wrapper = mount() 32 | 33 | const message = wrapper.find('span') 34 | 35 | expect(message.text()).toBe(noAddressMessage) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /src/stores/TokenStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action, computed } from 'mobx'; 2 | import { VALIDATION_TYPES } from '../utils/constants' 3 | import autosave from './autosave' 4 | const { EMPTY, VALID, INVALID } = VALIDATION_TYPES; 5 | 6 | class TokenStore { 7 | 8 | @observable name; 9 | @observable ticker; 10 | @observable supply; 11 | @observable decimals; 12 | @observable validToken; 13 | @observable reservedTokensInput; 14 | @observable reservedTokens; 15 | 16 | constructor() { 17 | this.reset() 18 | autosave(this, 'TokenStore') 19 | } 20 | 21 | @action reset = () => { 22 | this.name = undefined 23 | this.ticker = undefined 24 | this.supply = 0 25 | this.decimals = undefined 26 | this.validToken = { 27 | 'name': EMPTY, 28 | 'ticker': EMPTY, 29 | 'decimals': EMPTY 30 | } 31 | this.reservedTokensInput = {} 32 | this.reservedTokens = [] 33 | } 34 | 35 | @action setProperty = (property, value) => { 36 | this[property] = value; 37 | } 38 | 39 | @action updateValidity = (property, validity) => { 40 | this.validToken[property] = validity 41 | } 42 | 43 | @action invalidateToken = () => { 44 | if (!this.validToken) { 45 | return; 46 | } 47 | 48 | Object.keys(this.validToken).forEach(key => { 49 | if (this.validToken[key] === EMPTY) { 50 | this.validToken[key] = INVALID; 51 | } 52 | }); 53 | } 54 | 55 | // Getters 56 | @computed get isTokenValid() { 57 | if (!this.validToken) { 58 | return; 59 | } 60 | 61 | const validKeys = Object.keys(this.validToken).filter((key) => { 62 | if (this.validToken[key] === VALID) { 63 | return true; 64 | } else { 65 | return false; 66 | } 67 | }); 68 | return validKeys.length === Object.keys(this.validToken).length 69 | } 70 | } 71 | 72 | export default TokenStore; 73 | -------------------------------------------------------------------------------- /src/components/Common/WhitelistItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../../assets/stylesheets/application.css'; 3 | import { inject, observer } from 'mobx-react' 4 | import classNames from 'classnames' 5 | import ReactTooltip from 'react-tooltip' 6 | 7 | @inject('tierStore') 8 | @observer 9 | export class WhitelistItem extends React.Component { 10 | removeItem () { 11 | const { tierStore, whitelistNum, crowdsaleNum } = this.props 12 | tierStore.removeWhitelistItem(whitelistNum, crowdsaleNum) 13 | } 14 | 15 | render () { 16 | const { addr, min, max, stored, duplicated, tierStore } = this.props 17 | 18 | return ( 19 |
24 |
25 | {addr} 26 | {min} 27 | {max} 28 |
29 |
30 | {!stored 31 | ? this.removeItem()} className="remove"> 32 | : null 33 | } 34 | {duplicated && !stored 35 | ? ! 36 | : null 37 | } 38 |
39 | 40 |

Address already loaded,

41 |

saving will overwrite old values

42 |
43 |
44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/stores/index.js: -------------------------------------------------------------------------------- 1 | import storage from 'store2' 2 | import ContractStore from './ContractStore'; 3 | import PricingStrategyStore from './PricingStrategyStore'; 4 | import ReservedTokenStore from './ReservedTokenStore'; 5 | import StepTwoValidationStore from './StepTwoValidationStore'; 6 | import TierStore from './TierStore'; 7 | import TokenStore from './TokenStore'; 8 | import Web3Store from './Web3Store'; 9 | import GeneralStore from './GeneralStore' 10 | import CrowdsalePageStore from './CrowdsalePageStore' 11 | import InvestStore from './InvestStore' 12 | import CrowdsaleStore from './CrowdsaleStore' 13 | import GasPriceStore from './GasPriceStore' 14 | import DeploymentStore from './DeploymentStore' 15 | import StatsStore from './StatsStore' 16 | 17 | // Clear local storage if there is no incomplete deployment 18 | if (storage.has('DeploymentStore') && storage.get('DeploymentStore').deploymentStep === null) { 19 | localStorage.clear() 20 | } 21 | 22 | const generalStore = new GeneralStore() 23 | const crowdsalePageStore = new CrowdsalePageStore() 24 | const contractStore = new ContractStore() 25 | const pricingStrategyStore = new PricingStrategyStore() 26 | const reservedTokenStore = new ReservedTokenStore() 27 | const stepTwoValidationStore = new StepTwoValidationStore() 28 | const tierStore = new TierStore() 29 | const tokenStore = new TokenStore() 30 | const web3Store = new Web3Store() 31 | const investStore = new InvestStore() 32 | const crowdsaleStore = new CrowdsaleStore() 33 | const gasPriceStore = new GasPriceStore() 34 | const deploymentStore = new DeploymentStore() 35 | const statsStore = new StatsStore() 36 | 37 | export { 38 | generalStore, 39 | crowdsalePageStore, 40 | contractStore, 41 | pricingStrategyStore, 42 | reservedTokenStore, 43 | stepTwoValidationStore, 44 | tierStore, 45 | tokenStore, 46 | web3Store, 47 | investStore, 48 | crowdsaleStore, 49 | gasPriceStore, 50 | deploymentStore, 51 | statsStore 52 | }; 53 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 25 | Token Wizard 26 | 27 | 28 | 31 |
32 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/home/crowdsale-modal.scss: -------------------------------------------------------------------------------- 1 | .crowdsale-modal { 2 | position: absolute; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | 7 | .modal { 8 | background: white; 9 | width: 45%; 10 | min-width: 830px; 11 | padding: 30px 20px 0; 12 | border-radius: 10px; 13 | position: relative; 14 | height: 80%; 15 | min-height: 480px; 16 | max-height: 615px; 17 | 18 | .title { 19 | margin-bottom: 20px; 20 | text-transform: uppercase; 21 | font-size: 24px; 22 | font-weight: bold; 23 | } 24 | 25 | .description { 26 | margin-bottom: 25px; 27 | color: #8197a2; 28 | line-height: 24px; 29 | font-size: 13px; 30 | } 31 | 32 | .close-button { 33 | i { 34 | &.icon { 35 | &:before { 36 | content: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEDETQvtZKRngAAAaJJREFUSMetlcFOwkAURV9NXLjURUNwBbToTlJ04ULDJ7jSjd+hbvkEl3yC/2L8CRODBklMQAwJHhdO8VHeQFu5SdN0+jq3b+6dOyIiAgRAD2i6ZymD9DvgBLjXLwLgmV98AlFZEjdfAkzdfE/pYI9FTIBWSYJzltEVoOk6yBKFBQn2DYIPoJoWRG5ijbFr3auR0qADzDLf94HKvNBdLYNoBMSWGRTBsdHBEKiZPweErgMNrxmAthI5xQDYy+OO0TozeER+Aeq5vA7Eno5CJfK3IXIll/3VWjcM142BW4PgT+QiO9ddRwZRFu9ekQsQWmbQBLuyCQA3HpKrTRFUjY2mNQr/m6ZnhsgYrmutSoZVBIlHg0uPvRuFjgkXFVOfyM4Mlr3jvPukY3TQB2qZOsveIyDJE9ezPDvZadAwlm7ZDEoDXxZV1mgX+7JuwQwekQdAfdUaq2RIjI4mQKQP/alxHuyVSIaJ4brDLRG5FpFtVf8qIu0gCIZFSIIgeBORUxH5UsM7InKR/sVj4bj2L1+kXPeQLejOD/3yEZTeD4C7dPwHuOyCT59BxPUAAAAASUVORK5CYII='); 37 | } 38 | } 39 | } 40 | position: absolute; 41 | top: -40px; 42 | right: -40px; 43 | z-index: 400000; 44 | padding: 15px; 45 | 46 | &:hover { 47 | cursor: pointer; 48 | } 49 | } 50 | } 51 | } 52 | 53 | .home { 54 | .crowdsale-modal { 55 | .modal { 56 | max-height: 360px; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/Common/ReservedTokensItem.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StaticRouter } from 'react-router' 3 | import ReservedTokensItem from './ReservedTokensItem' 4 | import Adapter from 'enzyme-adapter-react-15' 5 | import { configure, mount, shallow } from 'enzyme' 6 | 7 | configure({ adapter: new Adapter() }) 8 | 9 | const token = { 10 | addr: '0x1111111111111111111111111111111111111111', 11 | dim: 'percentage', 12 | val: '12.1234' 13 | } 14 | 15 | describe('ReservedTokensItem', () => { 16 | it(`should render the component with delete button enabled`, () => { 17 | const wrapper = mount(shallow( 18 | 26 | ).get(0)) 27 | 28 | expect(wrapper).toMatchSnapshot() 29 | expect(wrapper.find('.reserved-tokens-item-empty')).toHaveLength(1) 30 | }) 31 | 32 | it(`should render the component with no delete button`, () => { 33 | const wrapper = mount(shallow( 34 | 42 | ).get(0)) 43 | 44 | expect(wrapper).toMatchSnapshot() 45 | expect(wrapper.find('reserved-tokens-item-empty')).toHaveLength(0) 46 | }) 47 | 48 | it(`should receive the item index on remove button click`, () => { 49 | const onRemove = jest.fn() 50 | const wrapper = mount( 51 | 60 | ) 61 | 62 | wrapper.find('a').simulate('click') 63 | 64 | expect(onRemove).toHaveBeenCalledTimes(1) 65 | expect(onRemove).toHaveBeenLastCalledWith(0) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /src/components/manage/__snapshots__/FinalizeCrowdsaleStep.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`FinalizeCrowdsaleStep should render the component with active button 1`] = ` 4 |
7 |
10 |
13 | ! 14 |
15 |

18 | Finalize Crowdsale 19 |

20 |

23 | Finalize - Finalization is the last step of the crowdsale. You can make it only after the end of the last tier. After finalization, it's not possible to update tiers, buy tokens. All tokens will be movable, reserved tokens will be issued. 24 |

25 | 29 | 32 | Finalize Crowdsale 33 | 34 | 35 |
36 |
37 | `; 38 | 39 | exports[`FinalizeCrowdsaleStep should render the component with disabled button 1`] = ` 40 |
43 |
46 |
49 | ! 50 |
51 |

54 | Finalize Crowdsale 55 |

56 |

59 | Finalize - Finalization is the last step of the crowdsale. You can make it only after the end of the last tier. After finalization, it's not possible to update tiers, buy tokens. All tokens will be movable, reserved tokens will be issued. 60 |

61 | 65 | 68 | Finalize Crowdsale 69 | 70 | 71 |
72 |
73 | `; 74 | -------------------------------------------------------------------------------- /src/App.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Adapter from 'enzyme-adapter-react-15' 3 | import { configure, mount, render } from 'enzyme' 4 | import { MemoryRouter } from 'react-router' 5 | import renderer from 'react-test-renderer' 6 | import { Web3Provider } from './react-web3' 7 | import { deploymentStore } from './stores' 8 | 9 | import App from './App' 10 | 11 | configure({ adapter: new Adapter() }) 12 | 13 | describe('App Index', () => { 14 | const stores = { deploymentStore } 15 | 16 | beforeAll(() => { 17 | jest.useFakeTimers() 18 | }) 19 | 20 | afterEach(() => { 21 | jest.clearAllTimers() 22 | }) 23 | 24 | it(`should render app screen`, () => { 25 | // Given 26 | const component = renderer.create( 27 | 28 | 29 | 30 | ) 31 | 32 | // When 33 | const tree = component.toJSON() 34 | 35 | // Then 36 | expect(tree).toMatchSnapshot() 37 | }) 38 | 39 | it('renders without crashing', () => { 40 | // Given 41 | const wrapper = mount( 42 | 43 | 44 | 45 | ) 46 | 47 | // Given 48 | const tree = wrapper.html() 49 | 50 | // Then 51 | expect(tree).toMatchSnapshot() 52 | }) 53 | 54 | it('should render screen with render without throwing an error', () => { 55 | // When 56 | const wrapper = render( 57 | 58 | 59 | 60 | ) 61 | 62 | // Given 63 | const tree = wrapper.html() 64 | 65 | // Then 66 | expect(tree).toMatchSnapshot() 67 | }) 68 | 69 | it(`should find web3Provider`, () => { 70 | // Given 71 | const wrapper = mount() 72 | jest.runTimersToTime(2000) 73 | 74 | // When 75 | const web3ProviderWrapper = wrapper.find(Web3Provider) 76 | 77 | // Then 78 | expect(web3ProviderWrapper).toHaveLength(1) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /src/assets/stylesheets/application/steps/whitelist.scss: -------------------------------------------------------------------------------- 1 | .white-list { 2 | &-container { 3 | margin-bottom: 40px; 4 | } 5 | &-input-property { 6 | &-left { 7 | display: table-cell; 8 | vertical-align: top; 9 | padding-right: 5px; 10 | width: 42%; 11 | } 12 | 13 | &-middle { 14 | display: table-cell; 15 | vertical-align: top; 16 | padding-left: 5px; 17 | padding-right: 5px; 18 | width: 29%; 19 | } 20 | 21 | &-right { 22 | display: table-cell; 23 | vertical-align: top; 24 | padding-left: 5px; 25 | width: 29%; 26 | } 27 | } 28 | 29 | &-input-container { 30 | margin-bottom: 20px; 31 | display: table; 32 | } 33 | 34 | &-item { 35 | display: table-cell; 36 | height: 60px; 37 | line-height: 60px; 38 | 39 | &-left { 40 | padding-right: 5px; 41 | width: 42%; 42 | } 43 | 44 | &-middle { 45 | padding-left: 5px; 46 | padding-right: 5px; 47 | width: 29%; 48 | } 49 | 50 | &-right { 51 | padding-left: 5px; 52 | width: 29%; 53 | } 54 | 55 | &-empty { 56 | display: table-cell; 57 | width: 56px; 58 | } 59 | 60 | &-container { 61 | font-weight: bold; 62 | border-top: 1px solid #eee; 63 | display: table; 64 | width: 100%; 65 | 66 | &-last { 67 | border-bottom: 1px solid #eee; 68 | } 69 | 70 | &-inner { 71 | display: table; 72 | width: 100%; 73 | } 74 | 75 | &.to-be-removed { 76 | font-weight: normal; 77 | color: orange; 78 | text-decoration: line-through; 79 | } 80 | 81 | &.no-style { 82 | font-weight: normal; 83 | color: #555555; 84 | } 85 | 86 | &.duplicated { 87 | padding-bottom: 15px; 88 | border-top: 1px dashed orange; 89 | margin-top: -15px; 90 | 91 | span { 92 | line-height: 30px; 93 | height: 12px; 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /scripts/POAExtendedCrowdSale.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @title SampleCrowdsaleToken 3 | * @dev Very simple ERC20 Token that can be minted. 4 | * It is meant to be used in a crowdsale contract. 5 | */ 6 | contract SampleCrowdsaleToken is MintableToken { 7 | string public name; 8 | string public symbol; 9 | uint8 public decimals; 10 | uint256 public supply; 11 | 12 | function SampleCrowdsaleToken(string _name, string _symbol, uint8 _decimals, uint256 _supply) { 13 | name = _name; 14 | symbol = _symbol; 15 | decimals = _decimals; 16 | supply = _supply; 17 | } 18 | } 19 | 20 | /** 21 | * @title SampleCrowdsale 22 | * @dev This is an example of a fully fledged crowdsale. 23 | * The way to add new features to a base crowdsale is by multiple inheritance. 24 | * In this example we are providing following extensions: 25 | * CappedCrowdsale - sets a max boundary for raised funds 26 | * RefundableCrowdsale - set a min goal to be reached and returns funds if it's not met 27 | * 28 | * After adding multiple features it's good practice to run integration tests 29 | * to ensure that subcontracts works together as intended. 30 | */ 31 | contract SampleCrowdsale is Crowdsale { 32 | 33 | uint256 public supply; 34 | uint256 public investors; 35 | 36 | function SampleCrowdsale(uint256 _startBlock, uint256 _endBlock, uint256 _rate, address _wallet, uint256 _crowdsaleSupply, string _name, string _symbol, uint8 _decimals, uint256 _tokenSupply) 37 | Crowdsale(_startBlock, _endBlock, _rate, _wallet) 38 | { 39 | investors = 0; 40 | supply = _crowdsaleSupply; 41 | token = createTokenContract(_name, _symbol, _decimals, _tokenSupply); 42 | } 43 | 44 | function createTokenContract(string _name, string _symbol, uint8 _decimals, uint256 _supply) internal returns (MintableToken) { 45 | return new SampleCrowdsaleToken(_name, _symbol, _decimals, _supply); 46 | } 47 | 48 | function buySampleTokens(address _sender) payable { 49 | investors++; 50 | buyTokens(_sender); 51 | } 52 | 53 | function () payable { 54 | buySampleTokens(msg.sender); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/assets/stylesheets/application/base.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | p, h1, h2, h3, h4 { 8 | margin: 0; 9 | padding: 0; 10 | font-family: 'Open Sans', sans-serif; 11 | } 12 | 13 | html { 14 | height: 100%; 15 | } 16 | 17 | body { 18 | position: relative; 19 | width: 100%; 20 | min-width: 1000px; 21 | min-height: 100%; 22 | box-sizing: border-box; 23 | padding: $header-height 0 $footer-height; 24 | background-color: #fbfbfc; 25 | } 26 | 27 | .container { 28 | width: 960px; 29 | margin: 0 auto; 30 | box-sizing: border-box; 31 | } 32 | 33 | .hidden { 34 | overflow: hidden; 35 | } 36 | 37 | .notdisplayed { 38 | display: none; 39 | } 40 | 41 | .left { 42 | width: 46%; 43 | float: left; 44 | } 45 | 46 | .right { 47 | width: 46%; 48 | float: right; 49 | } 50 | 51 | .item-remove { 52 | @include image-2x('../images/delete@2x.png', 12px, 12px); 53 | background-image: url(../images/delete.png); 54 | background-repeat: no-repeat; 55 | display: block; 56 | width: 12px; 57 | height: 12px; 58 | cursor: pointer; 59 | float: right; 60 | } 61 | 62 | .copy { 63 | @include image-2x('../images/copy@2x.png', 12px, 12px); 64 | background-image: url(../images/copy.png); 65 | background-repeat: no-repeat; 66 | display: block; 67 | width: 12px; 68 | height: 12px; 69 | cursor: pointer; 70 | float: right; 71 | } 72 | 73 | .copy-field-container { 74 | display: table-cell; 75 | padding-left: 10px; 76 | vertical-align: top; 77 | padding-top: 30.5px; 78 | } 79 | 80 | .copy-area-container { 81 | display: table-cell; 82 | padding-left: 10px; 83 | } 84 | 85 | .display-container { 86 | display: table-cell; 87 | width: 100%; 88 | } 89 | .input-block-container { 90 | display: table; 91 | width: 100%; 92 | } 93 | .section-title { 94 | display: block; 95 | margin-bottom: 30px; 96 | text-transform: uppercase; 97 | font-size: 20px; 98 | font-weight: bold; 99 | } 100 | 101 | @media (max-height: $iframe-height) { 102 | body { 103 | padding-top: 0; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/utils/utils.spec.js: -------------------------------------------------------------------------------- 1 | import { countDecimalPlaces, acceptPositiveIntegerOnly } from './utils' 2 | 3 | describe('countDecimalPlaces', () => { 4 | [ 5 | { value: '1.123', expected: 3 }, 6 | { value: '1.12', expected: 2 }, 7 | { value: '1.', expected: 0 }, 8 | { value: '1', expected: 0 }, 9 | { value: '.123', expected: 3 }, 10 | { value: .123, expected: 3 }, 11 | { value: '1e-3', expected: 3 }, 12 | { value: '1e-2', expected: 2 }, 13 | { value: '1.2e-2', expected: 3 }, 14 | { value: '1.e-2', expected: 2 }, 15 | { value: '1.23123e2', expected: 3 }, 16 | { value: '123.123e+2', expected: 1 }, 17 | { value: 123.123e+2, expected: 1 }, 18 | { value: '.2e-2', expected: 3 }, 19 | { value: '1', expected: 0 }, 20 | { value: '123', expected: 0 }, 21 | { value: '0', expected: 0 }, 22 | { value: '-123', expected: 0 }, 23 | { value: 'abc', expected: 0 }, 24 | { value: 'e', expected: 0 }, 25 | { value: '', expected: 0 }, 26 | { value: null, expected: 0 }, 27 | { value: false, expected: 0 }, 28 | { value: undefined, expected: 0 } 29 | ].forEach(testCase => { 30 | it(`Should count decimals for ${testCase.value}`, () => { 31 | expect(countDecimalPlaces(testCase.value)).toBe(testCase.expected) 32 | }) 33 | }) 34 | }) 35 | 36 | 37 | describe('acceptPositiveIntegerOnly', () => { 38 | [ 39 | { value: '', expected: '' }, 40 | { value: 'a', expected: '' }, 41 | { value: function () {}, expected: '' }, 42 | { value: undefined, expected: '' }, 43 | { value: false, expected: '' }, 44 | { value: 'e', expected: '' }, 45 | { value: '.', expected: '' }, 46 | { value: 'as123', expected: '' }, 47 | { value: '-123', expected: '' }, 48 | { value: '123', expected: '123' }, 49 | { value: '12e1', expected: '12' }, 50 | { value: (22*2), expected: '44' }, 51 | { value: 35.3*2, expected: '70' }, 52 | ].forEach(testCase => { 53 | const action = testCase.expected === '' ? 'fail' : 'pass' 54 | 55 | it(`Should ${action} for '${testCase.value}'`, () => { 56 | expect(acceptPositiveIntegerOnly(testCase.value)).toBe(testCase.expected) 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right