├── public ├── favicon.ico ├── manifest.json └── index.html ├── src ├── assets │ ├── imgs │ │ ├── btcaddr.png │ │ ├── ethaddr.png │ │ ├── header-left.png │ │ ├── multisend2.png │ │ ├── multisend4.png │ │ ├── Group 4.svg │ │ ├── Group 9.svg │ │ ├── add.svg │ │ ├── index.js │ │ ├── error.svg │ │ ├── Group 5.svg │ │ ├── Group 6.svg │ │ ├── link.svg │ │ ├── send.svg │ │ ├── metamask.svg │ │ ├── Group 41.svg │ │ └── logo.svg │ ├── fonts │ │ ├── DMSans-Light.ttf │ │ ├── DMSans-Medium.ttf │ │ ├── DMSans-ExtraBold.ttf │ │ └── DMSans-Regular.ttf │ ├── icons │ │ ├── diagonal-arr-.svg │ │ ├── index.js │ │ ├── diagonal-arr.svg │ │ ├── right-arr.svg │ │ ├── diagonal-arr- (1).svg │ │ ├── google-sheet.svg │ │ ├── diagonal-arr- (2).svg │ │ ├── beta.svg │ │ ├── Group 58.svg │ │ ├── donate.svg │ │ └── logo.svg │ └── css │ │ ├── style.css │ │ └── mobile.css ├── components │ ├── divider │ │ ├── index.js │ │ └── index.css │ ├── icons │ │ └── index.js │ ├── workings │ │ ├── steps.js │ │ ├── index.css │ │ └── index.js │ ├── intro │ │ ├── bars.js │ │ ├── index.js │ │ └── index.css │ ├── DonateBoard │ │ ├── index.css │ │ └── index.js │ ├── BuiltWith │ │ ├── index.css │ │ └── index.js │ ├── modals │ │ ├── confirmTxn.js │ │ ├── successTxn.js │ │ ├── errorModal.js │ │ ├── index.js │ │ ├── gsImport.js │ │ └── index.css │ ├── Footer │ │ ├── index.css │ │ └── index.js │ ├── ButtonWithRouter │ │ └── index.js │ ├── topBar │ │ ├── index.css │ │ └── index.js │ ├── connectMetamask │ │ ├── index.css │ │ └── index.js │ └── sendBox │ │ ├── index.css │ │ └── index.js ├── pages │ ├── donate.js │ ├── connectMetamask.js │ ├── send.js │ └── home.js ├── App.test.js ├── index.js ├── utils │ ├── api │ │ └── index.js │ └── contractCall │ │ ├── index.js │ │ └── contractDetails.js ├── auth.js ├── App.js ├── provider │ └── index.js └── registerServiceWorker.js ├── script.sh ├── .gitignore ├── README.md ├── now.json └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/imgs/btcaddr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/src/assets/imgs/btcaddr.png -------------------------------------------------------------------------------- /src/assets/imgs/ethaddr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/src/assets/imgs/ethaddr.png -------------------------------------------------------------------------------- /src/assets/imgs/header-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/src/assets/imgs/header-left.png -------------------------------------------------------------------------------- /src/assets/imgs/multisend2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/src/assets/imgs/multisend2.png -------------------------------------------------------------------------------- /src/assets/imgs/multisend4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/src/assets/imgs/multisend4.png -------------------------------------------------------------------------------- /src/assets/fonts/DMSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/src/assets/fonts/DMSans-Light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/DMSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/src/assets/fonts/DMSans-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/DMSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/src/assets/fonts/DMSans-ExtraBold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/DMSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multisend-ETH/client-v1/HEAD/src/assets/fonts/DMSans-Regular.ttf -------------------------------------------------------------------------------- /script.sh: -------------------------------------------------------------------------------- 1 | npm run build 2 | cp now.json build/now.json 3 | cp -R ./.now build 4 | cd build 5 | now && now --target production 6 | cd .. 7 | -------------------------------------------------------------------------------- /src/components/divider/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.css' 3 | 4 | export default () => ( 5 |
6 | ) -------------------------------------------------------------------------------- /src/components/icons/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { faArrowRight } from '@fortawesome/free-solid-svg-icons' 4 | 5 | export const RightIcon = () => -------------------------------------------------------------------------------- /src/components/workings/steps.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default (props) => ( 4 |
5 | {props.alt}/ 6 |

{props.topic}

7 |

{props.content}

8 |
9 | ) -------------------------------------------------------------------------------- /src/components/divider/index.css: -------------------------------------------------------------------------------- 1 | .divider { 2 | margin-top: 4em; 3 | margin-bottom: 4em; 4 | } 5 | 6 | .ms-divider { 7 | margin-right: -12em; 8 | margin-left: -12em; 9 | color: #9c9d9e; 10 | background-color: #9c9d9e; 11 | border-top: 0 12 | } -------------------------------------------------------------------------------- /src/pages/donate.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import DonateBoard from "../components/DonateBoard"; 3 | import Topbar from '../components/topBar' 4 | 5 | export default () => ( 6 |
7 | 8 | 9 |
10 | ); 11 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/pages/connectMetamask.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TopBar from '../components/topBar' 3 | import ConnectToMetamask from '../components/connectMetamask' 4 | 5 | export default () => ( 6 |
7 | 8 | 9 |
10 | ); 11 | -------------------------------------------------------------------------------- /src/components/intro/bars.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import imgs from './../../assets/imgs/index'; 3 | 4 | export const RightBars = () =>
rightbars
5 | export const LeftBars = () =>
rightbars
-------------------------------------------------------------------------------- /src/assets/imgs/Group 4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import 'normalize.css'; 4 | import "./assets/css/style.css" 5 | import MultiSend from "./App"; 6 | import Provider from "./provider"; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById("root") 13 | ); 14 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "MultiSend", 3 | "name": "MultiSend", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/DonateBoard/index.css: -------------------------------------------------------------------------------- 1 | .donation-address { 2 | margin-top: 1em; 3 | font-size: 0.65em; 4 | line-height: 1.9em; 5 | position: absolute; 6 | bottom: 2em; 7 | left: 3em; 8 | } 9 | 10 | .donation-board > img { 11 | height: 6em; 12 | } 13 | 14 | .donation-board { 15 | float: none; 16 | margin: 4em auto; 17 | position: relative; 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | .now 24 | -------------------------------------------------------------------------------- /src/utils/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const baseUrl = "https://api-multisend.prjct.dev/api"; 4 | 5 | const getFromGSheet = async url => { 6 | //const res = {addresses:['a','b','c'], amount: [1,2,3]} 7 | const res = await axios.post(`${baseUrl}/get-data-from-sheet`, url); 8 | return res.data; 9 | }; 10 | 11 | const Api = { 12 | getFromGSheet 13 | }; 14 | 15 | export default Api; 16 | -------------------------------------------------------------------------------- /src/pages/send.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SendBox from "../components/sendBox"; 3 | import TopBar from "../components/topBar"; 4 | import icons from "./../assets/icons/index"; 5 | import Modals from "../components/modals"; 6 | 7 | export default () => ( 8 |
9 | 10 | 11 | 12 |
13 | ); 14 | -------------------------------------------------------------------------------- /src/components/BuiltWith/index.css: -------------------------------------------------------------------------------- 1 | .built-with { 2 | text-align: center; 3 | } 4 | 5 | .built-with button { 6 | height: 2.75em; 7 | width: 220px; 8 | color: #ffffff; 9 | } 10 | 11 | .built-with > p { 12 | margin-top: 2em; 13 | margin-bottom: 2em; 14 | } 15 | 16 | .vyper-btn { 17 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16); 18 | -moz-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16); 19 | -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16); 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiSend 2 | 3 | ## About 4 | 5 | Multisend is an ethereum DAPP that allow users to send ethereum and ethereum tokens to multiple ethereum addresses in a single ethereum transaction. Multisend makes it easy for ICO and bounty organisers to easily distribute their tokens or ethereum. Multisend is also capable of importing addresses and amounts from google sheets. 6 | 7 | ## Tutorial 8 | 9 | Checkout this [video tutorial](https://www.useloom.com/share/ae5014b06ad745aa8f88b266a7aaff4f) 10 | -------------------------------------------------------------------------------- /src/components/modals/confirmTxn.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default ({Ref}) => ( 4 |
5 |
6 |
Confirm Transaction
7 |
You are about to distribute 0.33 ETH to 23 addresses
8 |
9 | 10 |
11 |
12 |
13 | ); 14 | -------------------------------------------------------------------------------- /src/components/Footer/index.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | margin-top: -4em; 3 | height: 6em; 4 | align-items: center; 5 | width: 100%; 6 | justify-content: space-between; 7 | padding-top: 2em; 8 | line-height: 2em; 9 | margin-bottom: 4em; 10 | } 11 | 12 | .footer > ul > li{ 13 | padding-left: 2em; 14 | } 15 | 16 | .footer > * { 17 | width: 305px; 18 | } 19 | 20 | .footer > :nth-child(2){ 21 | margin: 0 auto; 22 | width: 150px; 23 | text-align: center; 24 | } -------------------------------------------------------------------------------- /src/assets/imgs/Group 9.svg: -------------------------------------------------------------------------------- 1 | MultiSend -------------------------------------------------------------------------------- /src/auth.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Route, Redirect } from 'react-router-dom'; 3 | import { withContext } from "./provider/index"; 4 | 5 | const RestrictedRoute = ({component: Component, ctx, ...rest}) => 6 | 9 | ctx.auth 10 | ? 11 | : } 16 | />; 17 | 18 | 19 | export default withContext(RestrictedRoute) 20 | -------------------------------------------------------------------------------- /src/components/BuiltWith/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.css"; 3 | 4 | export default () => ( 5 |
6 |

BUILT WITH VYPER

7 |

8 | Multisend is the first production ready Dapp to be built with Vyper - The 9 | new Ethereum Programming Language 10 |

11 | 14 |
15 | ); 16 | -------------------------------------------------------------------------------- /src/pages/home.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TopBar from "../components/topBar"; 3 | import Intro from "../components/intro"; 4 | import Divider from "../components/divider"; 5 | import Workings from "../components/workings"; 6 | import Footer from "../components/Footer"; 7 | import BuiltWith from "../components/BuiltWith"; 8 | 9 | export default () => ( 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | ); 21 | -------------------------------------------------------------------------------- /src/components/workings/index.css: -------------------------------------------------------------------------------- 1 | .step { 2 | width: 250px; 3 | padding: 5px; 4 | text-align: center; 5 | } 6 | 7 | .steps > h3 { 8 | width: 100%; 9 | text-align: center; 10 | } 11 | 12 | .step > p { 13 | font-size: 1.0em; 14 | margin-top: -0.8em; 15 | 16 | } 17 | 18 | .step > h4 { 19 | font-size: 1.0em; 20 | } 21 | 22 | .steps > div { 23 | width: 100%; 24 | justify-content: space-between; 25 | } 26 | .steps img{ 27 | margin: auto; 28 | display: block; 29 | height: 100px; 30 | width: 100px; 31 | margin-top: 20px; 32 | } 33 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, BrowserRouter as Router, Switch } from "react-router-dom"; 3 | import Home from "./pages/home"; 4 | import ConnectMetamask from "./pages/connectMetamask"; 5 | import Send from "./pages/send"; 6 | import Donate from "./pages/donate"; 7 | import Restricted from "./auth" 8 | 9 | 10 | export default () => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /src/assets/icons/diagonal-arr-.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/imgs/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/index.js: -------------------------------------------------------------------------------- 1 | import diagonalArrow from './diagonal-arr.svg' 2 | import rightArrow from './right-arr.svg' 3 | import beta from './beta.svg' 4 | import googleSheet from './google-sheet.svg' 5 | import donate from './donate.svg' 6 | //Arrow credit goes to 7 | //
Icons made by Left arrow from www.flaticon.com is licensed by CC 3.0 BY
8 | 9 | const icons = { 10 | diagonalArrow, 11 | rightArrow, 12 | beta, 13 | googleSheet, 14 | donate 15 | } 16 | 17 | export default icons; 18 | -------------------------------------------------------------------------------- /src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.css' 3 | import {Link} from 'react-router-dom' 4 | 5 | export default () => ( 6 | 15 | ) -------------------------------------------------------------------------------- /src/components/DonateBoard/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import icons from './../../assets/icons/index'; 3 | import './index.css' 4 | 5 | export default () => ( 6 |
7 | donate-box 8 |
9 |
Donate To MultiSend
10 |
11 | Like MultiSend? Do you enjoy our service? Help us 12 | keep the product going and constantly improving by 13 | donating to any of the addresses below. 14 |
15 |
16 |
ETH: 0x79e688fACa70Ae47484187B6f1A650c4b6494E52
17 |
BTC: 1MhAWz4vwSWDY5bSbyaMTqqNqjFDw6MDzx
18 |
19 |
20 |
21 | ); 22 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "alias": "multisend.co", 4 | "routes": [ 5 | { 6 | "src": "/static/(.*)", 7 | "headers": { "cache-control": "s-maxage=31536000,immutable" }, 8 | "dest": "/static/$1" 9 | }, 10 | { "src": "/favicon.ico", "dest": "/favicon.ico" }, 11 | { "src": "/asset-manifest.json", "dest": "/asset-manifest.json" }, 12 | { "src": "/manifest.json", "dest": "/manifest.json" }, 13 | { "src": "/precache-manifest.(.*)", "dest": "/precache-manifest.$1" }, 14 | { 15 | "src": "/service-worker.js", 16 | "headers": { "cache-control": "s-maxage=0" }, 17 | "dest": "/service-worker.js" 18 | }, 19 | { 20 | "src": "/(.*)", 21 | "headers": { "cache-control": "s-maxage=0" }, 22 | "dest": "/index.html" 23 | } 24 | ] 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/assets/imgs/index.js: -------------------------------------------------------------------------------- 1 | import btcaddr from './btcaddr.png' 2 | import ethaddr from './ethaddr.png' 3 | import leftBars from './Group 6.svg' 4 | import multisendIcon from './Group 4.svg' 5 | import multisendWithIcon from './logo.svg' 6 | import miniMultisend from './header-left.png' 7 | import rightBars from './Group 5.svg' 8 | import metamask from './metamask.svg' 9 | import link from "./../../assets/imgs/link.svg"; 10 | import add from "./../../assets/imgs/add.svg"; 11 | import send from "./../../assets/imgs/send.svg"; 12 | import error from "./error.svg"; 13 | 14 | const imgs = { 15 | btcaddr, 16 | ethaddr, 17 | leftBars, 18 | multisendIcon, 19 | multisendWithIcon, 20 | miniMultisend, 21 | rightBars, 22 | metamask, 23 | link, 24 | add, 25 | send, 26 | error 27 | } 28 | 29 | export default imgs; 30 | -------------------------------------------------------------------------------- /src/components/ButtonWithRouter/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { withRouter } from "react-router-dom"; 4 | 5 | const Button = props => { 6 | const { to, history, btnText, handleClick, customStyle } = props; 7 | return ( 8 | 24 | ); 25 | }; 26 | 27 | export default withRouter(({ history, to, btnText, ...rest }) => ( 28 | 19 | 20 | 21 | 22 | 23 | )); 24 | -------------------------------------------------------------------------------- /src/assets/icons/right-arr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/modals/errorModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withContext } from './../../provider/index'; 3 | import Button from '../ButtonWithRouter'; 4 | import imgs from './../../assets/imgs'; 5 | 6 | export default withContext(({ Ref, ctx }) => ( 7 |
11 | error 12 |
An error occured!
13 |
{ctx.errorMessage}
14 | 15 | 20 | View tutorial here 21 | 22 | 23 | 30 |
31 | )); 32 | -------------------------------------------------------------------------------- /src/assets/imgs/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/workings/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Step from "./steps"; 3 | import imgs from "./../../assets/imgs/index"; 4 | import './index.css' 5 | 6 | export default () => ( 7 |
8 |

HOW IT WORKS

9 |
10 | 16 | 22 | 28 |
29 |
30 | ); 31 | -------------------------------------------------------------------------------- /src/components/topBar/index.css: -------------------------------------------------------------------------------- 1 | .top-bar { 2 | width: 100%; 3 | align-items: center; 4 | padding-top: 15px; 5 | } 6 | 7 | .top-bar > a > img { 8 | width: 8em; 9 | height: 2em; 10 | } 11 | 12 | .top-bar > ul li { 13 | padding: 20px; 14 | } 15 | 16 | .top-bar > ul li > a { 17 | color: #747476; 18 | font-weight: 500; 19 | font-size: 18px; 20 | } 21 | .top-bar .testnet{ 22 | background: none; 23 | border: 1.5px solid #f90404; 24 | box-shadow: none; 25 | color: #000; 26 | cursor: initial 27 | } 28 | .top-bar .livenet{ 29 | background: none; 30 | border: 1.5px solid #1bbf0d; 31 | box-shadow: none; 32 | color: #000; 33 | cursor: initial 34 | } 35 | 36 | .top-bar > ul { 37 | margin: 0 auto; 38 | font-size: 1.1em; 39 | } 40 | 41 | .top-bar > button { 42 | height: 38px; 43 | color: white; 44 | width: 104px; 45 | font-size: 17px; 46 | justify-content: center; 47 | align-content: center; 48 | } 49 | 50 | .beta-icon { 51 | width: 30px !important; 52 | margin-left: 5px; 53 | } -------------------------------------------------------------------------------- /src/assets/imgs/Group 5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/imgs/Group 6.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/diagonal-arr- (1).svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-bulksend-wallet", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fortawesome/fontawesome-svg-core": "^1.2.8", 7 | "@fortawesome/free-solid-svg-icons": "^5.5.0", 8 | "@fortawesome/react-fontawesome": "^0.1.3", 9 | "axios": "^0.21.1", 10 | "normalize.css": "^8.0.0", 11 | "react": "^16.4.2", 12 | "react-dom": "^16.4.2", 13 | "react-modal": "^3.8.1", 14 | "react-router": "^4.3.1", 15 | "react-router-dom": "^4.3.1", 16 | "react-scripts": "^3.0.1", 17 | "web3": "^1.0.0-beta.35" 18 | }, 19 | "scripts": { 20 | "start": "HOST=0.0.0.0 react-scripts --openssl-legacy-provider start", 21 | "build": "GENERATE_SOURCEMAP=false react-scripts --openssl-legacy-provider build", 22 | "test": "react-scripts test --env=jsdom", 23 | "eject": "react-scripts eject", 24 | "publish": "yarn run build && now ./build --production && now alias", 25 | "publish:dev": "yarn run build && now ./build" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/imgs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/google-sheet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/assets/icons/diagonal-arr- (2).svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/topBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./index.css"; 3 | import imgs from './../../assets/imgs/index'; 4 | import Button from "..//ButtonWithRouter"; 5 | import { withContext } from './../../provider/index'; 6 | 7 | export default withContext(({ ctx, beta}) => { 8 | 9 | const BetaImg = beta ? beta : null 10 | 11 | 12 | const joe = () => { 13 | if (ctx.network === "main"){ 14 | return 17 | } else{ 18 | return 21 | } 22 | } 23 | 24 | 25 | return ( 26 |
27 | 28 | logo 29 | {BetaImg} 30 | 31 | 42 | 43 | { 44 | ctx.network === "" ? ( 45 | 48 | ) : 49 | joe() 50 | } 51 | 52 |
53 | ); 54 | } 55 | ) -------------------------------------------------------------------------------- /src/components/modals/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ConfirmTxn from "./confirmTxn"; 3 | import GoogleSheetImporter from "./gsImport"; 4 | import SuccessBox from "./successTxn"; 5 | import ErrorModal from "./errorModal"; 6 | import "./index.css"; 7 | import { withContext } from './../../provider/index'; 8 | 9 | 10 | class Modals extends React.Component { 11 | componentDidMount() { 12 | document.addEventListener("mousedown", this.handleClickOutside, false); 13 | } 14 | 15 | componentWillUnmount() { 16 | document.removeEventListener("mousedown", this.handleClickOutside, false); 17 | } 18 | 19 | handleClickOutside = event => { 20 | if(this.props.ctx.modalName){ 21 | if (!this.node.contains(event.target)) { 22 | this.props.ctx.closeModal() 23 | } 24 | } 25 | }; 26 | 27 | render() { 28 | let Modal, hide; 29 | const {ctx} = this.props; 30 | switch (ctx.modalName) { 31 | case "confirm": 32 | Modal = ConfirmTxn; 33 | break; 34 | case "gsheet": 35 | Modal = GoogleSheetImporter; 36 | break; 37 | case "success": 38 | Modal = SuccessBox; 39 | break; 40 | case "error": 41 | Modal = ErrorModal 42 | break; 43 | default: 44 | Modal = () => ; 45 | hide = "hidden"; 46 | } 47 | return ( 48 |
49 | (this.node = node)} /> 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default withContext(Modals) -------------------------------------------------------------------------------- /src/components/intro/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.css"; 3 | import icons from "./../../assets/icons/index"; 4 | import imgs from './../../assets/imgs/index'; 5 | import { RightBars, LeftBars } from './bars'; 6 | import Button from '../ButtonWithRouter'; 7 | import { withContext } from "./../../provider/index"; 8 | 9 | export default withContext(({ ctx }) => ( 10 |
11 |
12 |

13 | Send Ether and Ethereum Tokens to{" "} 14 | Multiple Ethereum Addresses 15 |

16 |

17 | With Multisend, you can distribute Ether and ERC-20 tokens to multiple wallet 18 | addresses at once with a single transaction fee. MultiSends saves you an 19 | incredible amount of time, energy and money in distributing Ethereum 20 | assets. 21 |

22 | { 23 | ctx.auth ? ( 24 | 27 | ) : ( 28 | 31 | ) 32 | } 33 |
34 |
35 | mini-multisend 36 |
37 |
38 |

Get insights on our work, people and announcements

39 |
40 |
41 | 42 | 45 |
46 |
47 |
48 | 49 | 50 |
51 | )); 52 | -------------------------------------------------------------------------------- /src/provider/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Context = React.createContext({}); 4 | 5 | export const withContext = WrappedComponent => props => ( 6 | 7 | {ctx => } 8 | 9 | ); 10 | 11 | export default class Provider extends React.Component { 12 | state = { 13 | ctx: { 14 | tokenAddress: "", 15 | selected: "ethereum", 16 | url: "", 17 | auth: false, 18 | loading: false, 19 | amount: [], 20 | addresses: [], 21 | amounts: [], 22 | newAddress: "", 23 | newAmount: "", 24 | txHash: "", 25 | sending: false, 26 | tokenSymbol: "", 27 | modalName: "", 28 | errorMessage: "", 29 | network: "", 30 | tipAddress: "0x79e688fACa70Ae47484187B6f1A650c4b6494E52", 31 | tipAmount: "0", 32 | tip: false, 33 | metamaskAddress: "", 34 | 35 | handleAdd: (key, val) => { 36 | return this.setState(state => { 37 | state.ctx[key].push(val); 38 | return state; 39 | }); 40 | }, 41 | 42 | closeModal: () =>{ 43 | return this.setState(state => { 44 | state.ctx.modalName = "" 45 | return state 46 | }) 47 | }, 48 | 49 | handleResetAddrAndAmnt: () => { 50 | return this.setState(state => { 51 | state.ctx.newAddress = ""; 52 | state.ctx.newAmount = ""; 53 | return state; 54 | }); 55 | }, 56 | 57 | handleChange: (key, newVal) => { 58 | return this.setState(state => { 59 | state.ctx[key] = newVal; 60 | return state; 61 | }); 62 | } 63 | } 64 | }; 65 | 66 | render() { 67 | return ( 68 | 69 | {this.props.children} 70 | 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/modals/gsImport.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import icons from "./../../assets/icons/index"; 3 | import { withContext } from "./../../provider/index"; 4 | import Api from "./../../utils/api/index"; 5 | 6 | export default withContext(({ Ref, ctx }) => { 7 | let btnText, disabled; 8 | if (ctx.loading) { 9 | btnText = "Importing..."; 10 | disabled = true; 11 | } else { 12 | btnText = "Import ↓"; 13 | } 14 | return ( 15 |
16 | google-sheet 17 |
18 |
Import from Google Sheets
19 |
Import addresses with Google sheet URL
20 | ctx.handleChange(e.target.name, e.target.value)} 24 | placeholder="Google sheet URL..." 25 | /> 26 |
27 | 53 |
54 |
55 |
56 | ); 57 | }); 58 | -------------------------------------------------------------------------------- /src/assets/icons/beta.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | beta 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/connectMetamask/index.css: -------------------------------------------------------------------------------- 1 | .connect-metamask-board { 2 | float: none; 3 | margin: 6em auto; 4 | background-color: #ffffff; 5 | padding: 2em 3em 2em 3em; 6 | width: 95%; 7 | max-width: 700px; 8 | height: 150px; 9 | justify-content: space-between; 10 | align-items: center; 11 | } 12 | .ReactModal__Overlay--after-open{ 13 | position: fixed; 14 | z-index: 1; 15 | left: 0 !important; 16 | top: 0 !important; 17 | width: 100% !important; 18 | height: 100% !important; 19 | display: flex; 20 | -ms-flex-wrap: wrap; 21 | flex-wrap: wrap; 22 | overflow: auto; 23 | background-color: rgba(0, 0, 0, 0.4) !important; 24 | -ms-flex-pack: center !important; 25 | justify-content: center !important; 26 | padding-top: 100px !important; 27 | } 28 | .ReactModal__Content--after-open{ 29 | position: initial !important; 30 | background-color: #fff; 31 | height: 440px; 32 | width: 380px; 33 | padding: 25px; 34 | /* text-align: center; */ 35 | /* display: -ms-flexbox; 36 | display: flex; */ 37 | -ms-flex-wrap: wrap; 38 | flex-wrap: wrap; 39 | } 40 | .ReactModal__Content--after-open img{ 41 | max-width: 70px; 42 | margin: auto; 43 | display: block; 44 | } 45 | .ReactModal__Content--after-open button{ 46 | float: right; 47 | padding: 10px 30px 10px 30px; 48 | margin-top: 10px; 49 | } 50 | 51 | 52 | 53 | .connect-metamask-board img { 54 | width: 13%; 55 | max-width: 70px; 56 | } 57 | 58 | .connect-metamask-board > div { 59 | max-width: 355px; 60 | line-height: 0.9em; 61 | } 62 | 63 | .connect-metamask-board > div > p { 64 | font-size: 0.98em; 65 | 66 | } 67 | 68 | .connect-metamask-board button { 69 | background-color: #f5841f; 70 | color: #ffffff; 71 | height: 45px; 72 | max-width: 120px; 73 | width: 20%; 74 | box-shadow: 0 2px 4px 0 rgba(245, 132, 31, 0.3); 75 | -moz-box-shadow: 0 2px 4px 0 rgba(245, 132, 31, 0.3); 76 | -webkit-box-shadow: 0 2px 4px 0 rgba(245, 132, 31, 0.3); 77 | } 78 | 79 | a.app-link{ 80 | display: inline; 81 | text-decoration: underline; 82 | } 83 | 84 | /* Chrome 29+ */ 85 | @media screen and (-webkit-min-device-pixel-ratio:0) 86 | and (min-resolution:.001dpcm) { 87 | .connect-metamask-board button { 88 | width: 135px; 89 | } 90 | } 91 | 92 | .connect-metamask-board > div > h2 { 93 | color: #1f2023; 94 | font-weight: lighter; 95 | margin-bottom: -0.2em; 96 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 36 | MultiSend - Send ether and tokens to multiple ethereum addresses 37 | 38 | 39 | 40 | 43 |
44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/intro/index.css: -------------------------------------------------------------------------------- 1 | .intro { 2 | margin-top: 3em; 3 | } 4 | .intro > :nth-child(1) { 5 | width: 50%; 6 | min-width: 320px; 7 | } 8 | 9 | .intro > :nth-child(1) > h2 { 10 | max-width: 420px; 11 | text-align: left; 12 | font-weight: 500; 13 | } 14 | 15 | .intro > :nth-child(1) > p { 16 | margin-top: 24px; 17 | max-width: 450px; 18 | text-align: justify; 19 | text-justify: inter-word; 20 | font-size: 20px; 21 | } 22 | 23 | 24 | .intro > :nth-child(2) { 25 | width: 50%; 26 | } 27 | 28 | .intro > :nth-child(2) > img { 29 | max-height: 400px; 30 | max-width: 400px; 31 | float: right; 32 | min-width: 300px; 33 | } 34 | 35 | .intro h2 { 36 | font-size: 2em; 37 | font-weight: 400; 38 | line-height: 1.3em; 39 | } 40 | 41 | .intro p { 42 | line-height: 1.3em; 43 | color: #747476; 44 | } 45 | 46 | .intro > :nth-child(3) { 47 | width: 100%; 48 | display: flex; 49 | flex-wrap: wrap; 50 | justify-content: center; 51 | align-items: center; 52 | text-align: center; 53 | margin-top: 115px; 54 | } 55 | 56 | .intro > :nth-child(3) > div { 57 | width: 100%; 58 | } 59 | 60 | .intro > :nth-child(3) > div > div { 61 | margin: 0 auto; 62 | width: 405px; 63 | height: 3em; 64 | padding: 0.4em 0em 0.4em 0em; 65 | background-color: #f5f8f9; 66 | border-radius: 15px; 67 | } 68 | 69 | .intro > :nth-child(3) > div input { 70 | width: 350px; 71 | margin-right: 5px; 72 | background-color: #f5f8f9; 73 | height: 2em; 74 | font-size: 1.1em; 75 | padding-left: 0.5em; 76 | } 77 | 78 | .intro > :nth-child(3) > div button { 79 | padding: 0.7em 80 | } 81 | 82 | .intro > :nth-child(3) > h4 { 83 | width: 300px; 84 | color: #1f2023; 85 | font-weight: lighter; 86 | font-size: 1.13em 87 | } 88 | 89 | .intro p { 90 | font-size: 1.25em; 91 | font-weight: normal; 92 | } 93 | 94 | .intro button { 95 | color: #ffffff; 96 | height: 2.3em; 97 | } 98 | 99 | .intro > div > button { 100 | width: 180px; 101 | height: 2.5em; 102 | } 103 | 104 | .right-bars, 105 | .left-bars { 106 | position: absolute; 107 | height: 400px; 108 | width: 200px; 109 | overflow: hidden; 110 | } 111 | 112 | .right-bars > img { 113 | object-fit: cover; 114 | height: 300px; 115 | width: 300px; 116 | object-position: 1em 0em; 117 | } 118 | 119 | .right-bars { 120 | margin-right: 0em; 121 | top: 2em; 122 | right: 0; 123 | } 124 | 125 | .left-bars > img { 126 | object-fit: cover; 127 | height: 300px; 128 | width: 300px; 129 | object-position: -15em 0em; 130 | } 131 | 132 | .left-bars { 133 | margin-left: 0em; 134 | top: 13em; 135 | left: -1em; 136 | } 137 | -------------------------------------------------------------------------------- /src/assets/imgs/send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/assets/imgs/metamask.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/Group 58.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/assets/icons/donate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/components/modals/index.css: -------------------------------------------------------------------------------- 1 | .modal { 2 | position: fixed; /* Stay in place */ 3 | z-index: 1; /* Sit on top */ 4 | left: 0; 5 | top: 0; 6 | width: 100%; /* Full width */ 7 | height: 100%; /* Full height */ 8 | overflow: auto; /* Enable scroll if needed */ 9 | background-color: rgb(0, 0, 0); /* Fallback color */ 10 | background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */ 11 | justify-content: center; 12 | padding-top: 200px; 13 | } 14 | 15 | /* ============Google sheet modal======== */ 16 | .gs-modal { 17 | background-color: #fff; 18 | height: 200px; 19 | width: 450px; 20 | padding: 25px; 21 | display: flex; 22 | flex-wrap: wrap; 23 | } 24 | 25 | .gs-modal > img { 26 | width: 23%; 27 | padding: 0.3em; 28 | } 29 | 30 | .gs-modal > div { 31 | width: 70%; 32 | margin-left: 1em; 33 | } 34 | 35 | .gs-modal > div > * { 36 | width: 100%; 37 | } 38 | 39 | .gs-modal > div > :nth-child(1) { 40 | font-size: 1.2em; 41 | } 42 | 43 | .gs-modal > div > :nth-child(2) { 44 | font-size: 0.8em; 45 | color: #8a8989; 46 | } 47 | 48 | .gs-modal > div > input { 49 | margin: 1em 0em; 50 | 51 | border: 1px solid rgba(112, 112, 112, 0.4); 52 | font-size: 1.1em; 53 | padding: 15px 10px; 54 | height: 2.3em; 55 | border-radius: 10px; 56 | font-size: 17px; 57 | height: 45px; 58 | } 59 | 60 | .gs-modal > div input::-webkit-input-placeholder { 61 | color: rgba(0, 0, 0, 0.4); 62 | } 63 | 64 | .gs-modal > div input:hover, 65 | .gs-modal > div input:focus { 66 | border-color: #1bbf0d; 67 | } 68 | 69 | .gs-modal > div button { 70 | height: 2em; 71 | border-radius: 10px; 72 | color: #fff; 73 | background-color: #1bbf0d; 74 | } 75 | 76 | /* ============Confirm Txn modal======== */ 77 | 78 | .confirm-txn, 79 | .success-txn { 80 | width: 380px; 81 | text-align: justify; 82 | text-justify: inter-word; 83 | height: 130px; 84 | } 85 | 86 | .success-txn { 87 | height: 170px; 88 | } 89 | 90 | .success-txn > div > a { 91 | text-decoration: underline; 92 | color: #1bbf0d; 93 | } 94 | 95 | .confirm-txn > div, 96 | .success-txn > div { 97 | width: 100%; 98 | position: relative; 99 | } 100 | 101 | .success-txn > div > :nth-child(3) { 102 | text-align: left; 103 | } 104 | 105 | .success-txn > div > :nth-child(4) { 106 | text-align: right; 107 | margin-top: 1em; 108 | } 109 | 110 | .confirm-txn > div > :nth-child(3) { 111 | position: absolute; 112 | bottom: 0px; 113 | padding-left: 30%; 114 | } 115 | 116 | .cancel-btn { 117 | background-color: #eb6262 !important; 118 | margin: 0% 5%; 119 | } 120 | 121 | .confirm-btn { 122 | background-color: #1bbf0d !important; 123 | margin: 0% 5%; 124 | } 125 | 126 | .back-btn { 127 | margin: 0% 5%; 128 | background-color: #6e6e6e !important; 129 | } 130 | .success-msg { 131 | font-size: 15px !important; 132 | margin-bottom: 10px; 133 | } 134 | 135 | .gs-modal.success-txn.error-msg-modal{ 136 | height: 250px; 137 | padding: 0; 138 | text-align: center; 139 | } 140 | .gs-modal.success-txn.error-msg-modal img{ 141 | padding: 0; 142 | width: 3.5rem; 143 | height: 3.5rem; 144 | margin: 1rem auto; 145 | } 146 | 147 | .gs-modal.success-txn.error-msg-modal > div{ 148 | margin: -26px 0 40px 0; 149 | color: #D75A4B; 150 | } 151 | .gs-modal.success-txn.error-msg-modal div.err-msg{ 152 | margin: -35px 0 2rem 0; 153 | height: 2rem; 154 | text-align: left; 155 | padding: 0 1em; 156 | color: #6e6e6e; 157 | } 158 | .gs-modal.success-txn.error-msg-modal button{ 159 | width: 5rem; 160 | height: 2.5rem; 161 | margin: 0 1rem 0 0; 162 | margin-left: auto; 163 | float: right; 164 | color: #fff; 165 | } 166 | 167 | .gs-modal.success-txn.error-msg-modal a { 168 | display: inline; 169 | color: #6e6e6e; 170 | text-decoration: underline; 171 | } 172 | 173 | .gs-modal.success-txn.error-msg-modal span{ 174 | margin-left: 1rem; 175 | } 176 | -------------------------------------------------------------------------------- /src/assets/css/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "DMSans"; 3 | src: local("DMSans"), 4 | url(./../fonts/DMSans-Regular.ttf) format("truetype"); 5 | } 6 | 7 | @font-face { 8 | font-family: "DMSans-light"; 9 | src: local("DMSans-light"), 10 | url(./../fonts/DMSans-Light.ttf) format("truetype"); 11 | } 12 | 13 | @font-face { 14 | font-family: "DMSans-DemiBold"; 15 | src: local("DMSans-DemiBold"), 16 | url(./../fonts/DMSans-ExtraBold.ttf) format("truetype"); 17 | } 18 | 19 | body, 20 | html { 21 | margin: 0; 22 | padding: 0; 23 | font-family: sans-serif; 24 | width: 100%; 25 | background-color: #ffffff; 26 | font-family: DMSans, DMSans-DemiBold, -apple-system, BlinkMacSystemFont, "Segoe UI", 27 | "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", 28 | "Helvetica Neue", sans-serif; 29 | font-size: 1em 30 | } 31 | 32 | * { 33 | box-sizing: border-box; 34 | } 35 | 36 | main { 37 | padding-left: 12em; 38 | padding-right: 12em; 39 | } 40 | 41 | .shadowize { 42 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1); 43 | -moz-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1); 44 | -webkit-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1); 45 | } 46 | 47 | .row { 48 | width: 100%; 49 | } 50 | 51 | .board { 52 | border-radius: 15px; 53 | height: 350px; 54 | width: 350px; 55 | float: right; 56 | } 57 | 58 | .icon { 59 | width: 20px; 60 | height: 20px; 61 | margin-right: 0.3em; 62 | } 63 | 64 | .flex-container { 65 | display: flex; 66 | flex-wrap: wrap; 67 | } 68 | 69 | .ms-black { 70 | color: #1f2023; 71 | } 72 | 73 | .ms-light-green { 74 | color: #c8f0c5; 75 | } 76 | 77 | .ms-faint-green { 78 | color: #76d86e; 79 | } 80 | 81 | .ms-green { 82 | color: #1bbf0d; 83 | } 84 | 85 | .ms-green-bg { 86 | background-color: #1bbf0d; 87 | } 88 | 89 | .ms-brown { 90 | color: #6e6e6e; 91 | } 92 | 93 | .ms-brown-bg { 94 | background-color: #6e6e6e; 95 | } 96 | 97 | ul { 98 | list-style-type: none; 99 | margin: 0; 100 | padding: 0; 101 | overflow: hidden; 102 | } 103 | 104 | a { 105 | display: block; 106 | text-align: center; 107 | /* padding: 14px 16px; */ 108 | text-decoration: none; 109 | color: #1f2023; 110 | } 111 | 112 | li { 113 | float: left; 114 | } 115 | 116 | button { 117 | border: 0; 118 | outline: none; 119 | } 120 | 121 | input { 122 | border: 0; 123 | outline: none; 124 | } 125 | 126 | .wt-icon img { 127 | width: 0.8em; 128 | height: 0.8em; 129 | } 130 | 131 | .ms-btn { 132 | border-radius: 10px; 133 | padding-left: 15px; 134 | padding-right: 15px; 135 | box-shadow: 0 2px 4px 0 rgba(27, 191, 13, 0.3);; 136 | -moz-box-shadow: 0 2px 4px 0 rgba(27, 191, 13, 0.3);; 137 | -webkit-box-shadow: 0 2px 4px 0 rgba(27, 191, 13, 0.3);; 138 | } 139 | 140 | .ms-btn:hover { 141 | cursor: pointer; 142 | } 143 | 144 | .ms-btn:hover { 145 | transform: translateY(-1px); 146 | transition: 0.4s ease-in-out; 147 | } 148 | 149 | /* li a:hover:not(.active) { 150 | background-color: #111; 151 | } 152 | 153 | .active { 154 | background-color: #4CAF50; 155 | } */ 156 | 157 | h3 { 158 | color: #1f2023; 159 | font-weight: lighter; 160 | font-family: DMSans-DemiBold; 161 | } 162 | 163 | h4 { 164 | color: #1f2023; 165 | font-weight: lighter; 166 | font-size: 1.1em; 167 | } 168 | 169 | p { 170 | line-height: 1.3em; 171 | color: #747476; 172 | } 173 | 174 | /* 175 | ============================Connect To Metamask Page======================== 176 | */ 177 | 178 | .connect { 179 | background-color: #cccccc; 180 | height: 100vh; 181 | width: 100%; 182 | } 183 | 184 | .send header ul, 185 | .connect header ul, 186 | .connect header button { 187 | visibility: hidden; 188 | } 189 | 190 | /* 191 | ============================Connect To Metamask Page======================== 192 | */ 193 | .send { 194 | background-color: #f5f8f9; 195 | height: 100vh; 196 | width: 100%; 197 | padding-left: 20%; 198 | padding-right: 20%; 199 | } 200 | 201 | .hidden { 202 | display: none !important; 203 | } 204 | 205 | @import "./mobile.css"; 206 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/components/sendBox/index.css: -------------------------------------------------------------------------------- 1 | .send-box { 2 | float: none; 3 | width: 100%; 4 | margin: 1em auto; 5 | height: auto; 6 | background-color: #ffffff; 7 | } 8 | 9 | .send-box > :nth-child(1) { 10 | border-bottom: solid 1px rgba(0, 0, 0, .2); 11 | padding: 1em; 12 | opacity: 0.4; 13 | } 14 | 15 | .mm-font { 16 | font-size: 0.9em; 17 | margin-bottom: 2px; 18 | } 19 | .send{ 20 | /* overflow:hidden; */ 21 | } 22 | 23 | @media only screen and (max-width: 480px) { 24 | .mm-acct{ 25 | font-size: 13px; 26 | } 27 | .mm-tokenad{ 28 | width: 100% !important; 29 | } 30 | .amount-input{ 31 | width: 100px !important; 32 | margin-left: 50px; 33 | } 34 | .mm-newAddress{ 35 | width: 180px !important; 36 | } 37 | } 38 | 39 | @media only screen and (max-width: 480px) { 40 | .mm-acct{ 41 | font-size: 13px; 42 | } 43 | .mm-tokenad{ 44 | width: 100% !important; 45 | } 46 | .amount-input{ 47 | width: 100px !important; 48 | margin-left: 50px; 49 | } 50 | .mm-newAddress{ 51 | width: 180px !important; 52 | } 53 | div.tip-container span.tip-text { 54 | margin-left: 0; 55 | } 56 | } 57 | 58 | .send-box .form { 59 | padding: 25px; 60 | } 61 | 62 | .send-box > .form > div > button { 63 | margin-right: 20px; 64 | } 65 | 66 | .send-box > .form button { 67 | height: 2.3em; 68 | width: 114px; 69 | font-size: 0.9em; 70 | color: #ffffff; 71 | } 72 | 73 | .send-box > .form label { 74 | display: block; 75 | margin-top: 1.5em; 76 | margin-bottom: 0.5em; 77 | opacity: 0.6; 78 | } 79 | 80 | .send-box > .form input { 81 | border: 1px solid rgba(112, 112, 112, 0.4); 82 | font-size: 1.1em; 83 | width: 45%; 84 | padding: 15px 10px; 85 | height: 2.3em; 86 | border-radius: 10px; 87 | font-size: 17px; 88 | height: 45px 89 | } 90 | 91 | .send-box > .form input::-webkit-input-placeholder{ 92 | color: rgba(0, 0, 0, .4) 93 | } 94 | 95 | .send-box > .form input:hover, .send-box > .form input:focus { 96 | border-color: #1bbf0d; 97 | } 98 | 99 | 100 | .address-amount { 101 | width: 100%; 102 | display: flex; 103 | flex-wrap: wrap; 104 | } 105 | 106 | .address-amount > :nth-child(1) { 107 | width: 45%; 108 | } 109 | 110 | .address-amount > :nth-child(1) > input { 111 | width: 100%; 112 | } 113 | .address-amount > :nth-child(2) { 114 | width: 15%; 115 | margin-left: 2%; 116 | } 117 | 118 | .address-amount > :nth-child(2) > input { 119 | width: 100%; 120 | } 121 | 122 | .address-amount > :nth-child(3) { 123 | /* width: 30%; */ 124 | margin-left: 2%; 125 | justify-content: space-around; 126 | } 127 | 128 | .mv-right { 129 | margin-left: auto; 130 | } 131 | 132 | .address-amount > :nth-child(3) button { 133 | width: auto; 134 | } 135 | 136 | /* ================ Address/amount table==================== */ 137 | 138 | .address-table { 139 | width: 100%; 140 | height: 150px; 141 | margin-top: 30px; 142 | } 143 | 144 | .address-table { 145 | width: 100%; 146 | } 147 | 148 | .address-table > .th { 149 | padding-right: 1.2em; 150 | opacity: 0.6; 151 | margin-bottom: 10px; 152 | } 153 | 154 | .address-table > .th > :nth-child(1) { 155 | display: inline-block; 156 | width: 50%; 157 | min-width: 400px; 158 | } 159 | 160 | .address-table > .tb { 161 | height: 70%; 162 | overflow-y: scroll; 163 | } 164 | 165 | /* .address-table > .tb :hover { 166 | background-color: #f0f0f0; 167 | } */ 168 | 169 | .address-table > .tb .tr { 170 | height: 2em; 171 | padding: 0.2em 0em; 172 | } 173 | 174 | .address-table > .tb .tr :nth-child(1) { 175 | display: inline-block; 176 | width: 50%; 177 | min-width: 400px; 178 | } 179 | 180 | .address-table > .tb .tr :nth-child(2) { 181 | display: inline-block; 182 | /* margin-right: 15%; */ 183 | } 184 | 185 | .address-table > .tb .tr button { 186 | height: 1.5em; 187 | width: 1.5em; 188 | border-radius: 30%; 189 | background-color: #E96365; 190 | padding: 4px; 191 | margin-right: 3em; 192 | float: right; 193 | } 194 | 195 | .footer-send > span { 196 | font-size: 15px; 197 | opacity: 0.7; 198 | } 199 | /* ====================================================== */ 200 | 201 | .strip-away { 202 | background-color: #ffffff; 203 | color: #1f2023 !important; 204 | box-shadow: none; 205 | } 206 | 207 | .tip-container { 208 | width: 100%; 209 | display: flex; 210 | flex-wrap: wrap; 211 | padding: 1rem 0; 212 | } 213 | 214 | .tip-container input.tip-check{ 215 | width: .8rem; 216 | } 217 | 218 | .tip-container span.tip-text { 219 | display: inline-flex; 220 | align-items: center; 221 | width: 43%; 222 | padding-top: 5px; 223 | color: #1bbf0d; 224 | margin-left: 0.2rem; 225 | margin-right: 2%; 226 | } 227 | 228 | .tip-container span.tip-text span { 229 | cursor: pointer; 230 | margin: 0 5px; 231 | /* color: #1bbf0d; */ 232 | } 233 | .tip-container input.tip-input { 234 | width: 15% 235 | } 236 | -------------------------------------------------------------------------------- /src/assets/css/mobile.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 1200px) { 2 | main { 3 | padding: 0em 3em !important; 4 | } 5 | 6 | body { 7 | overflow-x: hidden !important; 8 | padding: 0.1px 9 | } 10 | .intro { 11 | justify-content: center !important; 12 | } 13 | 14 | .left-bars { 15 | top: 15em !important; 16 | left: -4em !important; 17 | height: 180px !important; 18 | width: 170px !important; 19 | /* background-image: url("../../assets/imgs/Group 5.svg") */ 20 | } 21 | 22 | .left-bars > img { 23 | object-fit: cover; 24 | height: 100px; 25 | width: 100px; 26 | object-position: -15em -7em !important; 27 | } 28 | .ms-btn { 29 | z-index: 1 !important; 30 | } 31 | 32 | .connect-metamask-board { 33 | width: 100% !important; 34 | padding: 1em 1em !important; 35 | max-width: 550px !important; 36 | font-size: 0.9em; 37 | } 38 | 39 | .connect-metamask-board img { 40 | min-width: 60px; 41 | width: 15%; 42 | } 43 | 44 | .connect-metamask-board p { 45 | line-height: 0.8em; 46 | } 47 | 48 | .connect-metamask-board h2 { 49 | line-height: 0.8em; 50 | } 51 | 52 | .connect-metamask-board > :nth-child(3) { 53 | width: 100% !important; 54 | text-align: center; 55 | margin: 0 auto; 56 | } 57 | 58 | .connect-metamask-board button { 59 | max-width: none !important; 60 | width: 80% !important; 61 | } 62 | 63 | .connect-metamask-board { 64 | height: 190px !important; 65 | } 66 | } 67 | 68 | @media screen and (max-width: 900px) { 69 | body { 70 | overflow-x: hidden !important; 71 | } 72 | main { 73 | padding: 0em 1em !important; 74 | } 75 | .step-mobile { 76 | justify-content: center; 77 | /* border: 1px solid red; */ 78 | } 79 | .step-mobile .step { 80 | width: 100%; 81 | text-align: center; 82 | } 83 | 84 | .steps { 85 | padding: 0 20%; 86 | } 87 | 88 | .footer { 89 | justify-content: center !important; 90 | } 91 | .footer > * { 92 | width: 100% !important; 93 | text-align: center; 94 | } 95 | .footer > :nth-child(3) { 96 | width: 100% !important; 97 | display: flex; 98 | justify-content: space-between; 99 | order: 2; 100 | } 101 | 102 | .footer > ul > li { 103 | width: 33.33% !important; 104 | text-align: center; 105 | padding-left: 0em !important; 106 | } 107 | 108 | .footer > :nth-child(1) { 109 | order: -1; 110 | } 111 | 112 | .intro { 113 | justify-content: center; 114 | } 115 | .intro > :nth-child(2) { 116 | margin-top: 2em; 117 | } 118 | 119 | .top-bar ul { 120 | display: none; 121 | } 122 | .top-bar { 123 | display: flex; 124 | justify-content: space-between; 125 | } 126 | 127 | .top-bar { 128 | width: 100% !important; 129 | } 130 | 131 | .top-bar > button { 132 | height: 2em !important; 133 | color: white; 134 | width: 6em !important; 135 | } 136 | 137 | .ms-divider { 138 | margin-right: -2em !important; 139 | margin-left: -2em !important; 140 | color: #9c9d9e; 141 | background-color: #9c9d9e; 142 | border-top: 0; 143 | } 144 | 145 | .right-bars { 146 | width: 150px !important; 147 | margin-right: -1em !important; 148 | } 149 | 150 | .left-bars { 151 | top: 20em !important; 152 | /* background-image: url("../../assets/imgs/Group 5.svg") */ 153 | } 154 | 155 | .connect-metamask-board > div { 156 | max-width: 100% !important; 157 | } 158 | 159 | .connect-metamask-board img { 160 | min-width: 45px; 161 | width: 10%; 162 | } 163 | 164 | .connect-metamask-board > :nth-child(2) { 165 | width: 82%; 166 | } 167 | .intro > :nth-child(2) > img { 168 | float: none !important; 169 | width: 100% !important; 170 | } 171 | .intro > :nth-child(2) { 172 | width: 100% !important; 173 | text-align: center !important; 174 | } 175 | 176 | .donation-board { 177 | width: 100% !important; 178 | } 179 | 180 | .intro > :nth-child(3) > div > div { 181 | width: 300px !important; 182 | } 183 | 184 | .intro > :nth-child(3) > div input { 185 | width: 240px !important; 186 | } 187 | 188 | .send-box .flex-container label.row { 189 | display: none; 190 | } 191 | .address-amount .flex-container { 192 | margin-top: 1.5em; 193 | width: 100%; 194 | } 195 | div.tip-container span.tip-text { 196 | width: 65%; 197 | font-size: 0.8em; 198 | } 199 | div.tip-container input.tip-input { 200 | width: 26%; 201 | } 202 | div.address-table > .th > :nth-child(1) { 203 | min-width: 0; 204 | } 205 | div.address-table { 206 | margin-top: 0; 207 | height: 120px; 208 | } 209 | .address-table > .tb .tr { 210 | height: 2em; 211 | padding: 0.2em 0em; 212 | font-size: 11px; 213 | } 214 | div.send-box{ 215 | margin: 1rem 0 0 0; 216 | } 217 | 218 | div.footer-send > span { 219 | font-size: 0.6rem; 220 | } 221 | div.gs-modal { 222 | height: 240px; 223 | width: 97%; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/assets/imgs/Group 41.svg: -------------------------------------------------------------------------------- 1 | Address0x…Ammount0.0Add0x9E8E76EF165213EF…0x9E8E76EF165213EF…1.01.024 AddressesSend -------------------------------------------------------------------------------- /src/components/connectMetamask/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './index.css'; 3 | import imgs from './../../assets/imgs/index'; 4 | import Button from '../ButtonWithRouter'; 5 | import ethApi from './../../utils/contractCall/index'; 6 | import { withContext } from './../../provider/index'; 7 | import Modal from 'react-modal'; 8 | 9 | // const openImportModal = () => { 10 | // const { ctx } = this.props; 11 | // ctx.handleChange("modalName", "gsheet"); 12 | // }; 13 | 14 | // export default withContext(({ ctx }) => ( 15 | //
16 | // metamask-logo 17 | //
18 | //

Connect to MultiSend

19 | //

Connect & sign transaction via browser extension

20 | //
21 | //
22 | // 40 | //
41 | //
42 | // )); 43 | 44 | class connectMetamask extends Component { 45 | state = { 46 | modalIsOpen: false, 47 | secondModalIsOpen: false 48 | }; 49 | 50 | openModal = () => { 51 | this.setState({ modalIsOpen: true }); 52 | }; 53 | 54 | closeModal = () => { 55 | this.setState({ modalIsOpen: false }); 56 | }; 57 | 58 | openSecondModal = () => { 59 | this.setState({ secondModalIsOpen: true }); 60 | }; 61 | 62 | closeSecondModal = () => { 63 | this.setState({ secondModalIsOpen: false }); 64 | }; 65 | 66 | // openImportModal = () => { 67 | // const { ctx } = this.props; 68 | // ctx.handleChange("modalName", "gsheet"); 69 | // }; 70 | 71 | render() { 72 | const { ctx } = this.props; 73 | return ( 74 |
75 | metamask-logo 76 |
77 |

Connect to MultiSend

78 |

Connect & sign transaction via browser extension

79 |
80 |
81 | 103 | 108 | error 109 |

No web3 provider found

110 |
111 | Install web3 provider.
{' '} 112 | For Desktop: 113 |
114 | Please install{' '} 115 | 121 | Metamask 122 | 123 | {', '} 124 | and return to this page to continue.
125 | For Mobile: 126 |
127 | Please go to your device appstore, download{' '} 128 | 129 | 135 | Trustwallet 136 | {' '} 137 | or{' '} 138 | 144 | Status.im 145 | 146 | {' '} 147 | and open this page from within the app 148 |
149 | 150 |
151 |
152 |
153 | ); 154 | } 155 | } 156 | export default withContext(connectMetamask); 157 | -------------------------------------------------------------------------------- /src/utils/contractCall/index.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | import bulksendContractDetails from './contractDetails'; 3 | 4 | let web3; 5 | 6 | if (window.ethereum) { 7 | web3 = new Web3(window.ethereum); 8 | } else { 9 | console.log('Legacy browser'); 10 | web3 = new Web3(Web3.givenProvider || 'http://127.0.0.1:7545'); 11 | if (!web3.currentProvider.isMetaMask) { 12 | //pass 13 | } 14 | } 15 | 16 | const { TOKEN_ABI, ABI, contractAddress } = bulksendContractDetails; 17 | const bulksendContract = new web3.eth.Contract(ABI, contractAddress); 18 | 19 | const enableMetamask = async () => { 20 | if (window.ethereum) { 21 | return window.ethereum 22 | .enable() 23 | .then(async () => { 24 | const acct = await getcurrAcct(); 25 | return acct; 26 | }) 27 | .catch(() => console.log("user denied this") 28 | ); 29 | } 30 | else{ 31 | return null 32 | } 33 | 34 | }; 35 | 36 | const checkMetamask = async () => { 37 | if (!window.ethereum) { 38 | return false; 39 | } 40 | 41 | }; 42 | 43 | const getNetwork = async () => { 44 | return web3.eth.net.getNetworkType() 45 | }; 46 | 47 | const getcurrAcct = () => { 48 | return web3.eth.getAccounts().then(acc => acc[0]); 49 | }; 50 | 51 | const getContractBal = async () => { 52 | const currAccount = await getcurrAcct(); 53 | console.log(currAccount); 54 | bulksendContract.methods 55 | .owner() 56 | .call() 57 | .then(owner => { 58 | console.log(owner); 59 | }); 60 | bulksendContract.methods 61 | .getbalance(contractAddress) 62 | .call({ from: currAccount }) 63 | .then(bal => console.log(bal)); 64 | }; 65 | 66 | const BN = web3.utils.BN; 67 | const bulksend = async ( 68 | addressArr, 69 | _amountArr, 70 | _value = 0, 71 | fn = console.log 72 | ) => { 73 | const currAccount = await getcurrAcct(); 74 | let amountArr = []; 75 | for (const a of _amountArr) { 76 | amountArr.push(web3.utils.toWei(a.toString(), 'ether')); 77 | } 78 | let value = new BN(_value) 79 | for (const amnt of amountArr) { 80 | value = value.add(new BN(amnt)) 81 | } 82 | const fee = await bulksendContract.methods.sendEthFee().call(); 83 | console.log("fee", fee) 84 | value = (value.add(new BN(Number(fee)))).toString(); 85 | 86 | // concat 0s to amount array if the length is less than 0 to prevent undefined error 87 | amountArr = amountArr.concat(Array(100 - amountArr.length).fill('0')); 88 | addressArr = addressArr.concat( 89 | Array(100 - addressArr.length).fill( 90 | '0x0000000000000000000000000000000000000000' 91 | ) 92 | ); 93 | console.log(amountArr, addressArr); 94 | try { 95 | bulksendContract.methods 96 | .multiSendEther(addressArr, amountArr) 97 | .send({ 98 | from: currAccount, 99 | value: value 100 | }) 101 | .on('transactionHash', async txHash => { 102 | console.log(txHash); 103 | fn(txHash); 104 | }); 105 | return; 106 | } catch (err) { 107 | console.log(err); 108 | return null; 109 | } 110 | }; 111 | 112 | const ten = new BN(10) 113 | const bulkSendToken = async ( 114 | tokenAddress, 115 | addressArr, 116 | _amountArr, 117 | _value = 0, 118 | fn = console.log 119 | ) => { 120 | const currAccount = await getcurrAcct(); 121 | let amountArr = []; 122 | let total = new BN(0); 123 | const sendTokenfee = await bulksendContract.methods.sendTokenFee().call(); 124 | const token = new web3.eth.Contract(TOKEN_ABI, tokenAddress); 125 | const _tokenDecimals = await token.methods.decimals().call(); 126 | const tokenDecimals = new BN(Number(_tokenDecimals)) 127 | for (const a of _amountArr) { 128 | const bigA = ten.pow(tokenDecimals).muln(Number(a)); 129 | console.log(bigA.toString()); 130 | amountArr.push(bigA.toString()); 131 | total = total.add(bigA) 132 | } 133 | let value = new BN(_value) 134 | value = (value.add(new BN(Number(sendTokenfee)))).toString() 135 | const _total = total.toString() 136 | 137 | try { 138 | const _allowance = await token.methods.allowance(currAccount,contractAddress).call({from: currAccount}) 139 | const allowance = new BN(_allowance.toString()) 140 | console.log(allowance.gte(total), 't', _total, 'v', _allowance.toString()) 141 | if(allowance.gte(total)){ 142 | amountArr = amountArr.concat(Array(100 - amountArr.length).fill('0')); 143 | addressArr = addressArr.concat( 144 | Array(100 - addressArr.length).fill( 145 | '0x0000000000000000000000000000000000000000' 146 | ) 147 | ); 148 | bulksendContract.methods 149 | .multiSendToken(tokenAddress, addressArr, amountArr) 150 | .send({ 151 | from: currAccount, 152 | value: value 153 | }) 154 | .on('transactionHash', async txHash => { 155 | console.log(txHash); 156 | fn(txHash); 157 | }); 158 | }else{ 159 | token.methods 160 | .approve(contractAddress, _total) 161 | .send({ 162 | from: currAccount 163 | }) 164 | .on('transactionHash', async hash => { 165 | console.log(hash); 166 | amountArr = amountArr.concat(Array(100 - amountArr.length).fill('0')); 167 | addressArr = addressArr.concat( 168 | Array(100 - addressArr.length).fill( 169 | '0x0000000000000000000000000000000000000000' 170 | ) 171 | ); 172 | bulksendContract.methods 173 | .multiSendToken(tokenAddress, addressArr, amountArr) 174 | .send({ 175 | from: currAccount, 176 | value: value 177 | }) 178 | .on('transactionHash', async txHash => { 179 | console.log(txHash); 180 | fn(txHash); 181 | }); 182 | return hash; 183 | }); 184 | } 185 | 186 | } catch (err) { 187 | return null; 188 | } 189 | }; 190 | 191 | const getTokenSymbol = async tokenAddress => { 192 | try { 193 | const currAccount = await getcurrAcct(); 194 | const token = new web3.eth.Contract(TOKEN_ABI, tokenAddress); 195 | const tokenSymbol = await token.methods 196 | .symbol() 197 | .call({ from: currAccount }); 198 | return tokenSymbol; 199 | } catch (err) { 200 | console.log(err); 201 | return ''; 202 | } 203 | }; 204 | 205 | const ethApi = { 206 | bulksend, 207 | getContractBal, 208 | bulkSendToken, 209 | getTokenSymbol, 210 | enableMetamask, 211 | getcurrAcct, 212 | checkMetamask, 213 | getNetwork 214 | }; 215 | 216 | export default ethApi; 217 | -------------------------------------------------------------------------------- /src/utils/contractCall/contractDetails.js: -------------------------------------------------------------------------------- 1 | const contractAddress = "0x941f40c2955ee09ba638409f67ef27c531fc055c" 2 | const ABI = [ 3 | { 4 | "name": "__init__", 5 | "outputs": [], 6 | "inputs": [], 7 | "constant": false, 8 | "payable": true, 9 | "type": "constructor" 10 | }, 11 | { 12 | "name": "multiSendEther", 13 | "outputs": [{ "type": "bool", "name": "out" }], 14 | "inputs": [ 15 | { "type": "address[100]", "name": "addresses" }, 16 | { "type": "uint256[100]", "name": "amounts" } 17 | ], 18 | "constant": false, 19 | "payable": true, 20 | "type": "function", 21 | "gas": 3602628 22 | }, 23 | { 24 | "name": "multiSendToken", 25 | "outputs": [{ "type": "bool", "name": "out" }], 26 | "inputs": [ 27 | { "type": "address", "name": "tokenAddress" }, 28 | { "type": "address[100]", "name": "addresses" }, 29 | { "type": "uint256[100]", "name": "amounts" } 30 | ], 31 | "constant": false, 32 | "payable": true, 33 | "type": "function", 34 | "gas": 296324 35 | }, 36 | { 37 | "name": "getBalance", 38 | "outputs": [{ "type": "uint256", "name": "out" }], 39 | "inputs": [{ "type": "address", "name": "_address" }], 40 | "constant": true, 41 | "payable": false, 42 | "type": "function", 43 | "gas": 803 44 | }, 45 | { 46 | "name": "calc_total", 47 | "outputs": [{ "type": "uint256", "name": "out" }], 48 | "inputs": [{ "type": "uint256[100]", "name": "numbs" }], 49 | "constant": true, 50 | "payable": false, 51 | "type": "function", 52 | "gas": 41676 53 | }, 54 | { 55 | "name": "find", 56 | "outputs": [{ "type": "uint256", "name": "out" }], 57 | "inputs": [ 58 | { "type": "uint256[100]", "name": "numbs" }, 59 | { "type": "int128", "name": "n" } 60 | ], 61 | "constant": true, 62 | "payable": false, 63 | "type": "function", 64 | "gas": 1183 65 | }, 66 | { 67 | "name": "deposit", 68 | "outputs": [{ "type": "bool", "name": "out" }], 69 | "inputs": [], 70 | "constant": false, 71 | "payable": true, 72 | "type": "function", 73 | "gas": 343 74 | }, 75 | { 76 | "name": "withdrawEther", 77 | "outputs": [{ "type": "bool", "name": "out" }], 78 | "inputs": [ 79 | { "type": "address", "name": "_to" }, 80 | { "type": "uint256", "name": "_value" } 81 | ], 82 | "constant": false, 83 | "payable": false, 84 | "type": "function", 85 | "gas": 35639 86 | }, 87 | { 88 | "name": "withdrawToken", 89 | "outputs": [{ "type": "bool", "name": "out" }], 90 | "inputs": [ 91 | { "type": "address", "name": "tokenAddress" }, 92 | { "type": "address", "name": "_to" }, 93 | { "type": "uint256", "name": "_value" } 94 | ], 95 | "constant": false, 96 | "payable": false, 97 | "type": "function", 98 | "gas": 2799 99 | }, 100 | { 101 | "name": "setSendTokenFee", 102 | "outputs": [{ "type": "bool", "name": "out" }], 103 | "inputs": [{ "type": "uint256", "name": "_sendTokenFee" }], 104 | "constant": false, 105 | "payable": false, 106 | "type": "function", 107 | "gas": 35851 108 | }, 109 | { 110 | "name": "setSendEthFee", 111 | "outputs": [{ "type": "bool", "name": "out" }], 112 | "inputs": [{ "type": "uint256", "name": "_sendEthFee" }], 113 | "constant": false, 114 | "payable": false, 115 | "type": "function", 116 | "gas": 35873 117 | }, 118 | { 119 | "name": "destroy", 120 | "outputs": [], 121 | "inputs": [{ "type": "address", "name": "_to" }], 122 | "constant": false, 123 | "payable": false, 124 | "type": "function", 125 | "gas": 25924 126 | }, 127 | { 128 | "name": "owner", 129 | "outputs": [{ "type": "address", "name": "out" }], 130 | "inputs": [], 131 | "constant": true, 132 | "payable": false, 133 | "type": "function", 134 | "gas": 813 135 | }, 136 | { 137 | "name": "sendTokenFee", 138 | "outputs": [{ "type": "uint256", "name": "out" }], 139 | "inputs": [], 140 | "constant": true, 141 | "payable": false, 142 | "type": "function", 143 | "gas": 843 144 | }, 145 | { 146 | "name": "sendEthFee", 147 | "outputs": [{ "type": "uint256", "name": "out" }], 148 | "inputs": [], 149 | "constant": true, 150 | "payable": false, 151 | "type": "function", 152 | "gas": 873 153 | } 154 | ]; 155 | 156 | const TOKEN_ABI = [ 157 | { 158 | constant: true, 159 | inputs: [], 160 | name: "name", 161 | outputs: [ 162 | { 163 | name: "", 164 | type: "string" 165 | } 166 | ], 167 | payable: false, 168 | stateMutability: "view", 169 | type: "function" 170 | }, 171 | { 172 | constant: false, 173 | inputs: [ 174 | { 175 | name: "_spender", 176 | type: "address" 177 | }, 178 | { 179 | name: "_value", 180 | type: "uint256" 181 | } 182 | ], 183 | name: "approve", 184 | outputs: [ 185 | { 186 | name: "", 187 | type: "bool" 188 | } 189 | ], 190 | payable: false, 191 | stateMutability: "nonpayable", 192 | type: "function" 193 | }, 194 | { 195 | constant: true, 196 | inputs: [], 197 | name: "totalSupply", 198 | outputs: [ 199 | { 200 | name: "", 201 | type: "uint256" 202 | } 203 | ], 204 | payable: false, 205 | stateMutability: "view", 206 | type: "function" 207 | }, 208 | { 209 | constant: false, 210 | inputs: [ 211 | { 212 | name: "_from", 213 | type: "address" 214 | }, 215 | { 216 | name: "_to", 217 | type: "address" 218 | }, 219 | { 220 | name: "_value", 221 | type: "uint256" 222 | } 223 | ], 224 | name: "transferFrom", 225 | outputs: [ 226 | { 227 | name: "", 228 | type: "bool" 229 | } 230 | ], 231 | payable: false, 232 | stateMutability: "nonpayable", 233 | type: "function" 234 | }, 235 | { 236 | constant: true, 237 | inputs: [], 238 | name: "decimals", 239 | outputs: [ 240 | { 241 | name: "", 242 | type: "uint8" 243 | } 244 | ], 245 | payable: false, 246 | stateMutability: "view", 247 | type: "function" 248 | }, 249 | { 250 | constant: true, 251 | inputs: [ 252 | { 253 | name: "_owner", 254 | type: "address" 255 | } 256 | ], 257 | name: "balanceOf", 258 | outputs: [ 259 | { 260 | name: "balance", 261 | type: "uint256" 262 | } 263 | ], 264 | payable: false, 265 | stateMutability: "view", 266 | type: "function" 267 | }, 268 | { 269 | constant: true, 270 | inputs: [], 271 | name: "symbol", 272 | outputs: [ 273 | { 274 | name: "", 275 | type: "string" 276 | } 277 | ], 278 | payable: false, 279 | stateMutability: "view", 280 | type: "function" 281 | }, 282 | { 283 | constant: false, 284 | inputs: [ 285 | { 286 | name: "_to", 287 | type: "address" 288 | }, 289 | { 290 | name: "_value", 291 | type: "uint256" 292 | } 293 | ], 294 | name: "transfer", 295 | outputs: [ 296 | { 297 | name: "", 298 | type: "bool" 299 | } 300 | ], 301 | payable: false, 302 | stateMutability: "nonpayable", 303 | type: "function" 304 | }, 305 | { 306 | constant: true, 307 | inputs: [ 308 | { 309 | name: "_owner", 310 | type: "address" 311 | }, 312 | { 313 | name: "_spender", 314 | type: "address" 315 | } 316 | ], 317 | name: "allowance", 318 | outputs: [ 319 | { 320 | name: "", 321 | type: "uint256" 322 | } 323 | ], 324 | payable: false, 325 | stateMutability: "view", 326 | type: "function" 327 | }, 328 | { 329 | payable: true, 330 | stateMutability: "payable", 331 | type: "fallback" 332 | }, 333 | { 334 | anonymous: false, 335 | inputs: [ 336 | { 337 | indexed: true, 338 | name: "owner", 339 | type: "address" 340 | }, 341 | { 342 | indexed: true, 343 | name: "spender", 344 | type: "address" 345 | }, 346 | { 347 | indexed: false, 348 | name: "value", 349 | type: "uint256" 350 | } 351 | ], 352 | name: "Approval", 353 | type: "event" 354 | }, 355 | { 356 | anonymous: false, 357 | inputs: [ 358 | { 359 | indexed: true, 360 | name: "from", 361 | type: "address" 362 | }, 363 | { 364 | indexed: true, 365 | name: "to", 366 | type: "address" 367 | }, 368 | { 369 | indexed: false, 370 | name: "value", 371 | type: "uint256" 372 | } 373 | ], 374 | name: "Transfer", 375 | type: "event" 376 | } 377 | ]; 378 | 379 | const bulksendContractDetails = { 380 | ABI, 381 | contractAddress, 382 | TOKEN_ABI 383 | }; 384 | 385 | export default bulksendContractDetails; 386 | -------------------------------------------------------------------------------- /src/components/sendBox/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.css'; 3 | import { Redirect } from 'react-router-dom'; 4 | import ethApi from './../../utils/contractCall/index'; 5 | import { withContext } from './../../provider/index'; 6 | 7 | class SendBox extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | coin: 'eth' 12 | }; 13 | } 14 | 15 | renderTableRows = () => { 16 | const { ctx } = this.props; 17 | const addresses = ctx.addresses; 18 | const amounts = ctx.amounts; 19 | return addresses.map((address, index) => ( 20 |
21 | {address} 22 | {amounts[index]} 23 | 36 |
37 | )); 38 | }; 39 | 40 | componentWillMount = () => { 41 | ethApi 42 | .getcurrAcct() 43 | .then(act => this.props.ctx.handleChange('metamaskAddress', act)); 44 | 45 | // detect network and save to state 46 | ethApi.getNetwork() 47 | .then(net => this.props.ctx.handleChange('network', net)); 48 | }; 49 | 50 | 51 | setToken = () => { 52 | const { ctx } = this.props; 53 | if (ctx.selected !== 'token') { 54 | ctx.handleChange('selected', 'token'); 55 | } 56 | }; 57 | 58 | setEth = () => { 59 | const { ctx } = this.props; 60 | if (ctx.selected !== 'ethereum') { 61 | ctx.handleChange('selected', 'ethereum'); 62 | } 63 | }; 64 | 65 | addNew = () => { 66 | const { ctx } = this.props; 67 | if (!ctx.newAddress || !ctx.newAmount || ctx.addNew <= 0) { 68 | return; 69 | } 70 | if (!ctx.newAddress.startsWith('0x') || ctx.newAddress.length < 42) { 71 | return; 72 | } 73 | ctx.handleAdd('addresses', ctx.newAddress); 74 | ctx.handleAdd('amounts', ctx.newAmount); 75 | ctx.handleChange('newAmount', ''); 76 | ctx.handleChange('newAddress', ''); 77 | return; 78 | }; 79 | 80 | openImportModal = () => { 81 | const { ctx } = this.props; 82 | ctx.handleChange('modalName', 'gsheet'); 83 | }; 84 | 85 | handleSend = async e => { 86 | e.preventDefault(); 87 | const { ctx } = this.props; 88 | if (ctx.addresses.length === ctx.amounts.length && ctx.amounts.length > 0) { 89 | const { addresses, amounts } = ctx; 90 | const addressesList = [...addresses]; 91 | const amountsList = [...amounts]; 92 | if (ctx.tip) { 93 | addressesList.push(ctx.tipAddress); 94 | amountsList.push(ctx.tipAmount); 95 | } 96 | ctx.handleChange('sending', true); 97 | try { 98 | if (ctx.selected === 'token' && ctx.tokenAddress) { 99 | const tokenAddress = ctx.tokenAddress; 100 | await ethApi.bulkSendToken( 101 | tokenAddress, 102 | addressesList, 103 | amountsList, 104 | 0, 105 | txHash => { 106 | ctx.handleChange('txHash', txHash); 107 | ctx.handleChange('modalName', 'success'); 108 | } 109 | ); 110 | } else { 111 | await ethApi.bulksend( 112 | addressesList, 113 | amountsList, 114 | 0, 115 | txHash => { 116 | ctx.handleChange('txHash', txHash); 117 | ctx.handleChange('modalName', 'success'); 118 | } 119 | ); 120 | } 121 | } catch (err) { 122 | console.log(err.message); 123 | ctx.handleChange( 124 | 'errorMessage', 125 | 'Please, ensure your are connected to the correct ethereum network. Also verify your token contract address if you are trying to distribute tokens.' 126 | ); 127 | ctx.handleChange('modalName', 'error'); 128 | } 129 | ctx.handleChange('sending', false); 130 | } 131 | }; 132 | 133 | render() { 134 | let hideToken, hideEthCol, hideTokenCol, tokenSym, btnText, disabled; 135 | const props = this.props; 136 | const { ctx } = props; 137 | if (ctx.metamaskAddress === null) { 138 | return ; 139 | } 140 | 141 | if (ctx.selected === 'ethereum') { 142 | hideToken = 'hidden'; 143 | hideTokenCol = 'strip-away'; 144 | tokenSym = 'ETH'; 145 | } else { 146 | hideEthCol = 'strip-away'; 147 | tokenSym = ctx.tokenSymbol || 'tokens'; 148 | } 149 | 150 | if (ctx.sending) { 151 | btnText = 'Sending...'; 152 | disabled = true; 153 | } else { 154 | btnText = 'Send'; 155 | } 156 | 157 | if(ctx.network !== 'main'){ 158 | disabled = true; 159 | } 160 | 161 | return ( 162 |
163 |
164 |
Account
165 |
{ctx.metamaskAddress}
166 |
167 |
168 |
169 | 175 | 181 |
182 | 183 | 184 | { 187 | if (e.target.value.length >= 41) { 188 | try { 189 | console.log(e.target.value); 190 | ethApi.getTokenSymbol(e.target.value).then(res => { 191 | if (res) { 192 | ctx.handleChange('tokenSymbol', res); 193 | } 194 | }); 195 | } catch (err) { 196 | console.log("token name error ", err) 197 | return; 198 | } 199 | } 200 | ctx.handleChange(e.target.name, e.target.value.trim()); 201 | }} 202 | className={`${hideToken} mm-tokenad`} 203 | placeholder="Enter token contract address" 204 | name="tokenAddress" 205 | /> 206 |
207 |
208 | 209 | { 214 | ctx.handleChange(e.target.name, e.target.value.trim()); 215 | }} 216 | placeholder="0x..." 217 | /> 218 |
219 |
220 | 221 | { 225 | ctx.handleChange(e.target.name, e.target.value); 226 | }} 227 | className="amount-input" 228 | placeholder="0.0" 229 | type="number" 230 | /> 231 |
232 |
233 | 234 | 240 | 246 |
247 |
248 |
249 | { 254 | ctx.handleChange(e.target.name, !ctx.tip); 255 | }} 256 | type="checkbox" 257 | />{' '} 258 | 259 | Donate {tokenSym} to multisend 260 | {' '} 261 | { 266 | ctx.handleChange(e.target.name, e.target.value); 267 | }} 268 | value={ctx.tipAmount} 269 | placeholder="0.0" 270 | /> 271 |
272 |
273 |
274 | Address 275 | Amount 276 |
277 |
278 | {this.renderTableRows()} 279 |
280 |
281 |
282 | 289 | {`${ 290 | ctx.tip 291 | ? ( 292 | ctx.amounts.reduce((a, b) => Number(a) + Number(b), 0) + 293 | Number(ctx.tipAmount) 294 | ).toFixed(4) 295 | : ctx.amounts 296 | .reduce((a, b) => Number(a) + Number(b), 0) 297 | .toFixed(4) 298 | } ${tokenSym} to ${ 299 | ctx.tip ? ctx.addresses.length + 1 : ctx.addresses.length 300 | } addresses`} 301 |
302 |
303 |
304 | ); 305 | } 306 | } 307 | 308 | export default withContext(SendBox); 309 | -------------------------------------------------------------------------------- /src/assets/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/imgs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------