├── .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 |
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 |
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 |
{props.label}
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 |
8 |
14 | {item.label}
15 |
16 | ))
17 |
18 | return (
19 |
20 |
{props.title}
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 |
10 | Rate
11 |
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 |
10 | Decimals
11 |
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 |
10 | Wallet Address
11 |
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 |
10 | Some Title
11 |
12 |
15 |
18 |
24 |
27 | Item 1
28 |
29 |
30 |
33 |
39 |
42 | Item 2
43 |
44 |
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 |
{props.title}
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 |
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 |
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 |
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 |
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 |
27 | You need to enable JavaScript to run this app.
28 |
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 |
27 | You need to enable JavaScript to run this app.
28 |
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 |
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 |
29 | You need to enable JavaScript to run this app.
30 |
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