├── src ├── contracts │ ├── Exchange.blocknumber.js │ ├── Exchange.address.js │ ├── Bridge.abi.js │ └── StableCoin.abi.js ├── planeta │ ├── contracts │ │ ├── CO2.address.js │ │ └── Air.json │ ├── PlantReceipt.js │ ├── cooldown.js │ ├── HandshakeButtons.js │ ├── FinalizeHandshake.js │ ├── MoreButtons.js │ ├── GlobalCO2.js │ ├── StartHandshake.js │ ├── plasma-utils.js │ ├── PlantTrees.js │ ├── TransferPassport.js │ └── transactionHandler.js ├── assets │ ├── dai.png │ ├── bity.png │ ├── pdai.png │ ├── pingu.gif │ ├── qrcode.png │ ├── rekt.gif │ ├── wyre.jpg │ ├── wyre.png │ ├── xdai.jpg │ ├── ethereum.png │ ├── ka-ching.mp3 │ ├── new-tag.png │ ├── papyrus.png │ ├── sendwyre.png │ ├── surprise.gif │ ├── handshake.gif │ ├── pollution.mp3 │ ├── burnerloader.gif │ ├── burnerwallet.png │ ├── customRPCHint.png │ ├── planeta-logo.gif │ ├── sad-trombone.mp3 │ ├── unreal-tournament-humiliation-sound.mp3 │ ├── unreal-tournament-monster-kill-sound.mp3 │ └── flame.svg ├── components │ ├── Ruler.js │ ├── InputInfo.js │ ├── Footer.js │ ├── StyledCard.js │ ├── Bottom.js │ ├── Receipt.js │ ├── Share.js │ ├── MainCard.js │ ├── ErrorReceipt.js │ ├── BurnWallet.js │ ├── NavCard.js │ ├── GoellarsBalance.js │ ├── Buttons.js │ ├── SimpleBalance.js │ ├── ShareLink.js │ ├── Balance.js │ ├── Loader.js │ ├── PassportReceipt.js │ ├── Passports │ │ ├── PassportView.js │ │ ├── styles.js │ │ └── index.js │ ├── Receive.js │ ├── Header.js │ ├── BityHistory.js │ ├── TxReceipt.js │ ├── RequestFunds.js │ └── RecentTransactions.js ├── index.js ├── i18n │ ├── locales │ │ ├── index.js │ │ ├── zh_TW.json │ │ ├── ja.json │ │ ├── he.json │ │ ├── es.json │ │ ├── ro.json │ │ ├── pt.json │ │ ├── ca.json │ │ ├── fr.json │ │ ├── ru.json │ │ ├── en.json │ │ └── de.json │ └── index.js ├── services │ ├── localStorage.js │ ├── ethgasstation.js │ ├── bity.js │ ├── plasma.js │ └── incogDetect.js └── theme.js ├── .editorconfig ├── twinkling.png ├── public ├── favicon.ico ├── whiteburn.png ├── icons │ ├── icon-72x72.png │ ├── icon-96x96.png │ ├── icon-144x144.png │ ├── icon-152x152.png │ └── icon-192x192.png ├── fonts │ └── arial_rounded_mt_bold.ttf ├── manifest.json ├── images │ └── flame.svg └── index.html ├── scripts ├── ci_deploy.sh ├── test.js ├── start.js ├── consolidate.js └── build.js ├── config ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── env.js └── webpackDevServer.config.js ├── DEPLOY.MD ├── .travis.yml ├── .gitignore ├── LICENSE ├── README.md └── package.json /src/contracts/Exchange.blocknumber.js: -------------------------------------------------------------------------------- 1 | module.exports = "6627956" 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.js] 2 | indent_style = space 3 | indent_size = 2 4 | -------------------------------------------------------------------------------- /twinkling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/twinkling.png -------------------------------------------------------------------------------- /src/contracts/Exchange.address.js: -------------------------------------------------------------------------------- 1 | module.exports = "0x2C4Bd064b998838076fa341A83d007FC2FA50957" 2 | -------------------------------------------------------------------------------- /src/planeta/contracts/CO2.address.js: -------------------------------------------------------------------------------- 1 | module.exports = "0xF64fFBC4A69631D327590f4151B79816a193a8c6"; 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/dai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/dai.png -------------------------------------------------------------------------------- /public/whiteburn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/public/whiteburn.png -------------------------------------------------------------------------------- /src/assets/bity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/bity.png -------------------------------------------------------------------------------- /src/assets/pdai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/pdai.png -------------------------------------------------------------------------------- /src/assets/pingu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/pingu.gif -------------------------------------------------------------------------------- /src/assets/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/qrcode.png -------------------------------------------------------------------------------- /src/assets/rekt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/rekt.gif -------------------------------------------------------------------------------- /src/assets/wyre.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/wyre.jpg -------------------------------------------------------------------------------- /src/assets/wyre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/wyre.png -------------------------------------------------------------------------------- /src/assets/xdai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/xdai.jpg -------------------------------------------------------------------------------- /src/assets/ethereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/ethereum.png -------------------------------------------------------------------------------- /src/assets/ka-ching.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/ka-ching.mp3 -------------------------------------------------------------------------------- /src/assets/new-tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/new-tag.png -------------------------------------------------------------------------------- /src/assets/papyrus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/papyrus.png -------------------------------------------------------------------------------- /src/assets/sendwyre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/sendwyre.png -------------------------------------------------------------------------------- /src/assets/surprise.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/surprise.gif -------------------------------------------------------------------------------- /src/assets/handshake.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/handshake.gif -------------------------------------------------------------------------------- /src/assets/pollution.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/pollution.mp3 -------------------------------------------------------------------------------- /public/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/public/icons/icon-72x72.png -------------------------------------------------------------------------------- /public/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/public/icons/icon-96x96.png -------------------------------------------------------------------------------- /src/assets/burnerloader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/burnerloader.gif -------------------------------------------------------------------------------- /src/assets/burnerwallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/burnerwallet.png -------------------------------------------------------------------------------- /src/assets/customRPCHint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/customRPCHint.png -------------------------------------------------------------------------------- /src/assets/planeta-logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/planeta-logo.gif -------------------------------------------------------------------------------- /src/assets/sad-trombone.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/sad-trombone.mp3 -------------------------------------------------------------------------------- /public/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/public/icons/icon-144x144.png -------------------------------------------------------------------------------- /public/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/public/icons/icon-152x152.png -------------------------------------------------------------------------------- /public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/fonts/arial_rounded_mt_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/public/fonts/arial_rounded_mt_bold.ttf -------------------------------------------------------------------------------- /src/assets/unreal-tournament-humiliation-sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/unreal-tournament-humiliation-sound.mp3 -------------------------------------------------------------------------------- /src/assets/unreal-tournament-monster-kill-sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/social-dist0rtion-protocol/planet-a/HEAD/src/assets/unreal-tournament-monster-kill-sound.mp3 -------------------------------------------------------------------------------- /src/components/Ruler.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Ruler = () => { 4 | return ( 5 |
6 | ) 7 | }; 8 | 9 | export default Ruler; 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.scss'; 4 | import App from './App'; 5 | import 'bootstrap/dist/css/bootstrap.min.css'; 6 | 7 | 8 | ReactDOM.render(, document.getElementById('root')); 9 | -------------------------------------------------------------------------------- /src/components/InputInfo.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | // TODO: Open an issue on Consensys/rimble-ui about this feature. 4 | export default styled.span` 5 | padding: 6px 0 0 0; 6 | color: ${props => props.color}; 7 | font-size: 0.7em; 8 | `; 9 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({alert, changeAlert}) => { 4 | return ( 5 |
changeAlert(null)}> 6 |
7 | {alert.message} 8 |
9 |
10 | ) 11 | }; 12 | -------------------------------------------------------------------------------- /scripts/ci_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "For now, we deploy manually to planeta.leap.rocks. Ask a SDP member to deploy!" 4 | #pip install --user awscli 5 | #npm run build 6 | #aws s3 sync ./build s3://$S3_BUCKET/ --acl public-read 7 | #aws configure set preview.cloudfront true 8 | #aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION --paths "/*" 9 | -------------------------------------------------------------------------------- /src/components/StyledCard.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import {Card} from 'rimble-ui'; 3 | 4 | const StyledCard = styled(Card).attrs(()=>({ 5 | my: 0, 6 | p: 3 7 | }))` 8 | background-color: #FAFAFA; 9 | border-radius: 4px; 10 | @media screen and (max-width: 480px){ 11 | border-radius: 0; 12 | } 13 | `; 14 | 15 | export default StyledCard; 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/en/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/i18n/locales/index.js: -------------------------------------------------------------------------------- 1 | import en from "./en.json"; 2 | import fr from "./fr.json"; 3 | import es from "./es.json"; 4 | import ca from "./ca.json"; 5 | import de from "./de.json"; 6 | import ro from "./ro.json"; 7 | import he from "./he.json"; 8 | import ru from "./ru.json"; 9 | import pt from "./pt.json"; 10 | import ja from "./ja.json"; 11 | 12 | 13 | export { en, fr, es, ca, de, ro, he, ru, pt, ja, }; 14 | -------------------------------------------------------------------------------- /src/contracts/Bridge.abi.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | "constant": false, 3 | "inputs": [ 4 | { 5 | "name": "_owner", 6 | "type": "address" 7 | }, 8 | { 9 | "name": "_amountOrTokenId", 10 | "type": "uint256" 11 | }, 12 | { 13 | "name": "_color", 14 | "type": "uint16" 15 | } 16 | ], 17 | "name": "deposit", 18 | "outputs": [], 19 | "payable": false, 20 | "stateMutability": "nonpayable", 21 | "type": "function" 22 | }] -------------------------------------------------------------------------------- /DEPLOY.MD: -------------------------------------------------------------------------------- 1 | # Deployment 2 | 3 | We're automatically deploying on every update to master using Travis-CI. Check 4 | the .travis.yml file for details. 5 | 6 | Manual deployment is still possible. For manual deployment, follow the 7 | instructions below: 8 | 9 | 1. Ask Kosta, Alberto or Tim for AWS S3 credentials. 10 | 1. Go to `~/.aws/credentials` and create a [named profile](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) called "leap" with the credentials you received. 11 | 1. `npm run deploy` will re-build, deploy to S3 and invalidate the Cloudfront Distribution. 12 | -------------------------------------------------------------------------------- /src/planeta/PlantReceipt.js: -------------------------------------------------------------------------------- 1 | // @format 2 | import React, { Component } from "react"; 3 | import { Flex, Box } from "rimble-ui"; 4 | import styled, { keyframes } from "styled-components"; 5 | import { Blockie } from "dapparatus"; 6 | 7 | const blockieSize = 10; 8 | 9 | export default class PlantReceipt extends Component { 10 | constructor(props) { 11 | super(props); 12 | } 13 | 14 | render() { 15 | const { 16 | receipt: { txHash, amount } 17 | } = this.props; 18 | 19 | return ( 20 |

21 | You locked {amount} Gigatonnes of CO₂ 22 |

23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/services/localStorage.js: -------------------------------------------------------------------------------- 1 | const getFieldName = (name, account) => (account ? `${account}${name}` : name); 2 | 3 | export const getStoredValue = (name, account) => 4 | localStorage.getItem(getFieldName(name, account)); 5 | 6 | export const storeValues = (params, account) => { 7 | const keys = Object.keys(params); 8 | const values = Object.values(params); 9 | keys.forEach((key, index) => { 10 | const value = values[index]; 11 | const name = getFieldName(key, account); 12 | localStorage.setItem(name, value); 13 | }); 14 | }; 15 | 16 | export const eraseStoredValue = (name, account) => 17 | localStorage.removeItem(getFieldName(name, account)); 18 | -------------------------------------------------------------------------------- /src/components/Bottom.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import i18n from '../i18n'; 3 | import { Icon } from 'rimble-ui' 4 | import { ActionButton } from '../components/Buttons' 5 | 6 | export default class Receive extends React.Component { 7 | render() { 8 | let {icon,text,action} = this.props 9 | 10 | if(!icon) icon = "times" 11 | if(!text) text = i18n.t('done') 12 | 13 | //icon = "fas fa-"+icon 14 | 15 | return ( 16 |
17 | {action()}}> 18 | 19 | {text} 20 | 21 |
22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '8.11' 5 | 6 | before_install: 7 | - pip install --user awscli 8 | - export PATH=$PATH:$HOME/.local/bin 9 | 10 | install: npm i 11 | 12 | script: 13 | - unset CI 14 | - npm run build 15 | 16 | cache: npm 17 | 18 | deploy: 19 | - provider: s3 20 | cache_control: "max-age=31536000" 21 | access_key_id: $AWS_ACCESS_KEY_ID 22 | secret_access_key: $AWS_SECRET_ACCESS_KEY 23 | bucket: $PROD_S3_BUCKET 24 | acl: public_read 25 | local_dir: build 26 | skip_cleanup: true 27 | region: 'eu-west-1' 28 | on: 29 | branch: master 30 | 31 | after_deploy: 32 | - aws configure set preview.cloudfront true 33 | - aws cloudfront create-invalidation --distribution-id $PROD_CLOUDFRONT_DISTRIBUTION --paths "/*" 34 | -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | import i18next from "i18next"; 2 | import LanguageDetector from "i18next-browser-languagedetector"; 3 | import { fr, en, es, ca, de, ro, he, ru, pt, ja } from "./locales"; 4 | 5 | const i18n = i18next; 6 | const options = { 7 | interpolation: { 8 | escapeValue: false // not needed for react!! 9 | }, 10 | 11 | debug: true, 12 | 13 | resources: { 14 | en: { 15 | common: en.en 16 | } 17 | }, 18 | 19 | fallbackLng: "en", 20 | 21 | ns: ["common"], 22 | 23 | defaultNS: "common", 24 | 25 | react: { 26 | wait: false, 27 | bindI18n: "languageChanged loaded", 28 | bindStore: "added removed", 29 | nsMode: "default" 30 | } 31 | }; 32 | 33 | i18next.use(LanguageDetector).init(options); 34 | i18next.changeLanguage(navigator.language, (err, t) => { 35 | if (err) return console.log("Something went wrong during loading"); 36 | }); 37 | 38 | export default i18n; 39 | -------------------------------------------------------------------------------- /src/components/Receipt.js: -------------------------------------------------------------------------------- 1 | // @format 2 | import React, { Component } from "react"; 3 | import TxReceipt from "./TxReceipt"; 4 | import TradeReceipt from "./TradeReceipt"; 5 | import PlantReceipt from "../planeta/PlantReceipt"; 6 | import PassportReceipt from "./PassportReceipt"; 7 | import ErrorReceipt from "./ErrorReceipt"; 8 | 9 | export default class Receipt extends Component { 10 | render() { 11 | const { 12 | receipt: { type } 13 | } = this.props; 14 | 15 | if (type === "trade") { 16 | return ; 17 | } else if (type === "error") { 18 | return ; 19 | } else if (type === "plant") { 20 | return ; 21 | } else if (type === "passport_transfer") { 22 | return ; 23 | } else { 24 | return ; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | const assetFilename = JSON.stringify(path.basename(filename)); 11 | 12 | if (filename.match(/\.svg$/)) { 13 | return `const React = require('react'); 14 | module.exports = { 15 | __esModule: true, 16 | default: ${assetFilename}, 17 | ReactComponent: React.forwardRef((props, ref) => ({ 18 | $$typeof: Symbol.for('react.element'), 19 | type: 'svg', 20 | ref: ref, 21 | key: null, 22 | props: Object.assign({}, props, { 23 | children: ${assetFilename} 24 | }) 25 | })), 26 | };`; 27 | } 28 | 29 | return `module.exports = ${assetFilename};`; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/Share.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CopyToClipboard } from "react-copy-to-clipboard"; 3 | import i18n from '../i18n'; 4 | import { 5 | Flex, 6 | Box, 7 | Input, 8 | QR as QRCode 9 | } from 'rimble-ui' 10 | 11 | export default class Receive extends React.Component { 12 | 13 | render() { 14 | let { 15 | changeAlert, 16 | url 17 | } = this.props 18 | 19 | return ( 20 |
21 | { 22 | changeAlert({type: 'success', message: i18n.t('share.copied')}) 23 | }}> 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/MainCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Flex, Icon, Box } from "rimble-ui"; 3 | import i18next from "i18next"; 4 | import { BorderButton } from "./Buttons"; 5 | 6 | export default ({ 7 | changeView, 8 | }) => { 9 | let sendButtons = ( 10 | 11 | 12 | 13 | changeView("receive")}> 14 | 15 | 16 | {i18next.t("main_card.receive")} 17 | 18 | 19 | 20 | 21 | changeView("send_to_address")}> 22 | 23 | 24 | {i18next.t("main_card.send")} 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | 32 | 33 | return sendButtons 34 | }; 35 | -------------------------------------------------------------------------------- /src/services/ethgasstation.js: -------------------------------------------------------------------------------- 1 | // @format 2 | import getConfig from "../config"; 3 | 4 | const CONFIG = getConfig(); 5 | const API = "https://ethgasstation.info/json/ethgasAPI.json"; 6 | 7 | function get() { 8 | return fetch(API, { 9 | mode: "cors", 10 | method: "get" 11 | }).then(r => r.json()); 12 | } 13 | 14 | // NOTE: Both price() and time() currently default to average. In the future, 15 | // these function could be adjusted to feature "fast" and "safe" too. 16 | // NOTE2: Both functions can throw and should be try-catch'ed. 17 | export async function price() { 18 | const { average } = await get(); 19 | if (average > 0 && average < 200) { 20 | const avg = average + average * CONFIG.ROOTCHAIN.GAS.BOOST_BY; 21 | return Math.round(avg * 100) / 1000; 22 | } 23 | return Promise.reject("Average out of range (0–200)"); 24 | } 25 | 26 | export async function time() { 27 | // NOTE: avgWait is returned from ethgasstation in minutes (double) 28 | const { avgWait } = await get(); 29 | // We convert minutes to milliseconds 30 | return avgWait * 60 * 1000; 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.env* 2 | *.pem 3 | .env 4 | clevis.json 5 | relayhttpprovider.env 6 | cloudfront.id.1 7 | cloudfront.id.2 8 | deploy.network 9 | aws.json 10 | backend/redisdata 11 | accounts.json 12 | **.DS_* 13 | *.log 14 | *.ens 15 | */*/*.abi 16 | */*/*.bytecode 17 | */*/*.address 18 | */*/*.run.xml 19 | */*/*.log.* 20 | */*/*.blockNumber 21 | */*.abi 22 | */*.bytecode 23 | */*.bib 24 | */*.address 25 | */*.run.xml 26 | */*.log.* 27 | */*.blockNumber 28 | */.clevis 29 | */*/.clevis 30 | */node_modules 31 | /node_modules 32 | public/reload.txt 33 | 34 | **/*.blockNumber 35 | **/*.head.address 36 | **/*.previous.address 37 | **/*.compiled 38 | **/*.bytecode 39 | **/*.abi 40 | **/*.address 41 | deploy.log 42 | public/reload.txt 43 | geth.log 44 | react.log 45 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 46 | 47 | # dependencies 48 | /node_modules 49 | 50 | # testing 51 | /coverage 52 | 53 | # production 54 | /build 55 | 56 | # misc 57 | .DS_Store 58 | .env.local 59 | .env.development.local 60 | .env.test.local 61 | .env.production.local 62 | 63 | npm-debug.log* 64 | yarn-debug.log* 65 | yarn-error.log* 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Austin Griffith 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 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Planet A", 3 | "name": "Planet A - A Tragedy of the CO2mmons", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "type": "image/x-icon", 8 | "sizes": "64x64 32x32 24x24 16x16" 9 | }, 10 | { 11 | "src": "/icons/icon-72x72.png", 12 | "type": "image/png", 13 | "sizes": "72x72" 14 | }, 15 | { 16 | "src": "/icons/icon-96x96.png", 17 | "type": "image/png", 18 | "sizes": "96x96" 19 | }, 20 | { 21 | "src": "/icons/icon-128x128.png", 22 | "type": "image/png", 23 | "sizes": "128x128" 24 | }, 25 | { 26 | "src": "/icons/icon-144x144.png", 27 | "type": "image/png", 28 | "sizes": "144x144" 29 | }, 30 | { 31 | "src": "/icons/icon-152x152.png", 32 | "type": "image/png", 33 | "sizes": "152x152" 34 | }, 35 | { 36 | "src": "/icons/icon-192x192.png", 37 | "type": "image/png", 38 | "sizes": "192x192" 39 | }, 40 | ], 41 | "start_url": "./index.html", 42 | "orientation": "portrait", 43 | "theme_color": "#0F1C24", 44 | "background_color": "#0F1C24" 45 | } 46 | -------------------------------------------------------------------------------- /src/components/ErrorReceipt.js: -------------------------------------------------------------------------------- 1 | // @format 2 | import React, { Component } from "react"; 3 | import { Flex, Box } from "rimble-ui"; 4 | 5 | export default class ErrorReceipt extends Component { 6 | render() { 7 | const { 8 | receipt: { message } 9 | } = this.props; 10 | 11 | return ( 12 |
13 |

20 | ❌ Err0r! 😭 21 |

22 | 23 | 24 | {/* Yeah this is not rimble-ui but I really had no time... */} 25 | 34 |
{message}
35 |
36 |
37 |
38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/planeta/cooldown.js: -------------------------------------------------------------------------------- 1 | import { getStoredValue, storeValues } from "../services/localStorage"; 2 | import getConfig from "../config"; 3 | 4 | // COOLDOWN is in seconds, here we convert it to millisecods 5 | // so we don't need to do extra conversions. 6 | const COOLDOWN = getConfig().PLANET_A.COOLDOWN * 1000; 7 | 8 | const getCooldownFor = passport => JSON.parse(getStoredValue("cooldown", passport) || "{}"); 9 | 10 | export function purge(myPassport) { 11 | const now = Date.now(); 12 | const cooldown = getCooldownFor(myPassport); 13 | for (let passport in cooldown) { 14 | if (now - cooldown[passport] > COOLDOWN) { 15 | delete cooldown[passport] 16 | } 17 | } 18 | return cooldown; 19 | } 20 | 21 | export function addHandshake(myPassport, theirPassport) { 22 | const now = Date.now(); 23 | const cooldown = purge(myPassport); 24 | cooldown[theirPassport] = now; 25 | storeValues({cooldown: JSON.stringify(cooldown)}, myPassport); 26 | } 27 | 28 | export function timeLeft(myPassport, theirPassport) { 29 | const now = Date.now(); 30 | const timestamp = getCooldownFor(myPassport)[theirPassport] || 0; 31 | return Math.max(COOLDOWN - (now - timestamp), 0); 32 | } 33 | -------------------------------------------------------------------------------- /src/components/BurnWallet.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Ruler from "./Ruler"; 3 | import i18n from '../i18n'; 4 | 5 | 6 | export default ({ mainStyle, burnWallet, goBack }) => { 7 | return ( 8 |
9 |
10 | {i18n.t('burn_wallet.burn_private_key_question')} 11 |
12 |
13 | {i18n.t('burn_wallet.disclaimer')} 14 |
15 |
16 | 17 |
18 | 19 |
20 | 23 |
24 | 25 |
26 | 29 |
30 |
31 |
32 | 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/NavCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Text, 4 | Link, 5 | Icon, 6 | Flex 7 | } from "rimble-ui"; 8 | 9 | export default ({title, titleLink, goBack, darkMode}) => { 10 | 11 | let titleDisplay = "" 12 | 13 | if (titleLink){ 14 | titleDisplay = ( 15 | 16 | {title} 17 | 18 | ) 19 | } else { 20 | titleDisplay = ( 21 | 22 | {title} 23 | 24 | ) 25 | } 26 | 27 | return ( 28 | 38 | {titleDisplay} 39 | {console.log("CLICKED");goBack()}} 48 | > 49 | 50 | 51 | 52 | ) 53 | }; 54 | -------------------------------------------------------------------------------- /src/components/GoellarsBalance.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Flex, Text } from "rimble-ui"; 3 | import styled from "styled-components"; 4 | import { getStoredValue } from "../services/localStorage"; 5 | 6 | const Container = styled(Flex).attrs(() => ({ 7 | flexDirection: "column", 8 | alignItems: "center", 9 | pb:3, 10 | mb:3, 11 | borderBottom: "1px solid #DFDFDF", 12 | width:"100%", 13 | justifyContent:"space-around" 14 | }))``; 15 | 16 | const Label = styled(Text).attrs(() => ({ 17 | fontSize: 2, 18 | color: "silver" 19 | }))``; 20 | 21 | const Value = styled(Text).attrs(() => ({ 22 | fontSize: 5, 23 | fontWeight: 4, 24 | color: "black" 25 | }))``; 26 | 27 | 28 | const GoellarsBalance = props => { 29 | const { balance } = props; 30 | const locale = getStoredValue("i18nextLng") || "en.en"; 31 | const formatter = new Intl.NumberFormat(locale, { 32 | style: "currency", 33 | currency: "PYG", // We use Paraguayan guaraní to display ₲ next to a value 34 | currencyDisplay: "symbol", 35 | maximumFractionDigits: 2 36 | }); 37 | const noBalance = isNaN(balance); 38 | const balanceValue = noBalance 39 | ? "--" 40 | : formatter.format(balance).replace('PYG', '₲'); 41 | return ( 42 | 43 | 44 | {balanceValue} 45 | 46 | ); 47 | }; 48 | 49 | export default GoellarsBalance; 50 | -------------------------------------------------------------------------------- /src/components/Buttons.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Button, OutlineButton } from "rimble-ui"; 3 | 4 | export const PrimaryButton = styled(Button)` 5 | cursor: pointer; 6 | color: var(--primary-btn-text-color); 7 | background-color: var(--primary-btn-bg-color) !important; 8 | border-bottom: 3px solid var(--primary-btn-border-color); 9 | &:hover { 10 | box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15); 11 | border: 1px solid transparent; 12 | } 13 | &:focus { 14 | outline: 0; 15 | } 16 | @media screen and (max-width: 480px){ 17 | padding-left: 0; 18 | padding-right: 0; 19 | } 20 | `; 21 | 22 | export const ActionButton = styled(Button)` 23 | cursor: pointer; 24 | background-color: #41545f !important; 25 | border: 1px solid #182933; 26 | color: #cad7de; 27 | & span { 28 | display: flex; 29 | align-items: center; 30 | } 31 | `; 32 | 33 | export const BorderButton = styled(OutlineButton)` 34 | cursor: pointer; 35 | color: var(--secondary-btn-text-color); 36 | border-bottom: 3px solid var(--secondary-btn-border-color); 37 | background-color: var(--secondary-btn-bg-color) !important; 38 | &:hover { 39 | color: var(--secondary-btn-text-color); 40 | border: 1px solid transparent; 41 | } 42 | &:focus { 43 | outline: 0; 44 | } 45 | @media screen and (max-width: 480px){ 46 | padding-left: 0; 47 | padding-right: 0; 48 | } 49 | `; 50 | -------------------------------------------------------------------------------- /src/planeta/HandshakeButtons.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Text, Heading } from "rimble-ui"; 3 | import { BorderButton } from "../components/Buttons"; 4 | import { choice } from "./utils"; 5 | 6 | const COLLABORATE_EMOJIS = ["🥰", "🌹", "🍻", "🥂"]; 7 | const DEFECT_EMOJIS = ["😈", "☠️", "🗡️", "💣"]; 8 | 9 | export default function HandshakeButtons({ handleStrategy }) { 10 | const collaborateEmoji = choice(COLLABORATE_EMOJIS); 11 | const defectEmoji = choice(DEFECT_EMOJIS); 12 | 13 | return ( 14 | <> 15 | Play fair ☺️ 16 | 17 | Earn Göllars by being fair to the environment and to 18 | your handshake partner. Hopefully, they are fair to you as well 🤞 19 | 20 | handleStrategy("collaborate")} 24 | > 25 | {collaborateEmoji} I want to be fair! {collaborateEmoji} 26 | 27 |
28 | Play greedy 🤑 29 | 30 | Wanna make extra money? Deceive your handshake partner{" "} 31 | Play smart to earn more Göllars. But watch out, you 32 | might get the same treatment 🤔 33 | 34 | handleStrategy("defect")}> 35 | {defectEmoji} I want to be greedy! {defectEmoji} 36 | 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/SimpleBalance.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { Text } from "rimble-ui"; 4 | 5 | // Here are all the classes that can be used to style the balance. 6 | // Some of them are not in use now, but are listed for clarity. 7 | 8 | const StyledBalance = styled.div` 9 | text-align: center; 10 | padding: 20px 0; 11 | 12 | color: var(--primary); 13 | 14 | .otherAssets { 15 | color: var(--dark-text); 16 | } 17 | 18 | span { 19 | font-size: 200%; 20 | } 21 | 22 | .integer { 23 | font-size: 400%; 24 | } 25 | 26 | .group { 27 | } 28 | 29 | .decimal { 30 | } 31 | 32 | .fraction { 33 | } 34 | 35 | .literal { 36 | } 37 | 38 | .currency { 39 | font-size: 100%; 40 | } 41 | `; 42 | 43 | export default ({ mainAmount, otherAmounts, currencyDisplay }) => { 44 | if (isNaN(mainAmount) || typeof mainAmount === "undefined") { 45 | mainAmount = 0.0; 46 | } 47 | 48 | const otherAssetsTotal = Object.values(otherAmounts).reduce( 49 | (acc, curr) => acc + parseInt(curr, 10), 50 | 0 51 | ); 52 | const parts = currencyDisplay(mainAmount, true); 53 | 54 | return ( 55 | <> 56 | 57 | {parts.map(({ type, value }) => ( 58 | {value} 59 | ))} 60 | {otherAssetsTotal > 0 && ( 61 | 62 | +{currencyDisplay(otherAssetsTotal)} in other assets 63 | 64 | )} 65 | 66 |
67 | 68 | ); 69 | }; 70 | -------------------------------------------------------------------------------- /src/components/ShareLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Ruler from "./Ruler"; 3 | import {CopyToClipboard} from 'react-copy-to-clipboard'; 4 | import QRCode from 'qrcode.react'; 5 | 6 | export default class ShareLink extends React.Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | copied: false 12 | } 13 | } 14 | render() { 15 | 16 | let port = window.location.port 17 | if(port && port!=="80"){ 18 | port=":"+port 19 | }else{ 20 | port="" 21 | } 22 | 23 | let url = window.location.protocol + "//" + window.location.hostname+port; 24 | let qrValue = url + "/" + this.props.sendLink + ";" + this.props.sendKey; 25 | let qrSize = Math.min(document.documentElement.clientWidth,512)-90 26 | 27 | return ( 28 |
29 | { 30 | this.props.changeAlert({type: 'success', message: 'Link copied to clipboard'}) 31 | }}> 32 |
33 |
34 | 35 |
36 | 37 |
38 |
39 | 40 |
41 | 42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/services/bity.js: -------------------------------------------------------------------------------- 1 | // @format 2 | 3 | const API = "https://exchange.api.bity.com/v2/"; 4 | 5 | export const placeOrder = (name, IBAN, amount, address) => { 6 | return fetch(API + "orders", { 7 | method: "POST", 8 | credentials: "include", 9 | body: JSON.stringify({ 10 | input: { 11 | amount, 12 | currency: "ETH", 13 | type: "crypto_address", 14 | crypto_address: address 15 | }, 16 | output: { 17 | currency: "EUR", 18 | type: "bank_account", 19 | iban: IBAN, 20 | owner: { 21 | name 22 | } 23 | } 24 | }), 25 | headers: { 26 | "Content-Type": "application/json", 27 | Accept: "application/json" 28 | } 29 | }); 30 | }; 31 | 32 | export const getOrder = id => { 33 | return fetch(`${API}orders/${id}`, { 34 | method: "GET", 35 | credentials: "include", 36 | headers: { 37 | "Content-Type": "application/json", 38 | Accept: "application/json" 39 | } 40 | }).then(res => res.json()); 41 | }; 42 | 43 | export const getOrders = () => { 44 | return fetch(`${API}orders`, { 45 | method: "GET", 46 | credentials: "include", 47 | headers: { 48 | "Content-Type": "application/json", 49 | Accept: "application/json" 50 | } 51 | }).then(res => res.json()); 52 | }; 53 | 54 | export const getEstimate = amount => { 55 | return fetch("https://exchange.api.bity.com/v2/orders/estimate", { 56 | method: "POST", 57 | credentials: "include", 58 | body: JSON.stringify({ 59 | input: { 60 | currency: "ETH", 61 | amount: amount 62 | }, 63 | output: { 64 | currency: "EUR" 65 | } 66 | }), 67 | headers: { 68 | "Content-Type": "application/json" 69 | } 70 | }).then(response => response.json()); 71 | }; 72 | -------------------------------------------------------------------------------- /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 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI, in coverage mode, explicitly adding `--no-watch`, 42 | // or explicitly running all tests 43 | if ( 44 | !process.env.CI && 45 | argv.indexOf('--coverage') === -1 && 46 | argv.indexOf('--no-watch') === -1 && 47 | argv.indexOf('--watchAll') === -1 48 | ) { 49 | // https://github.com/facebook/create-react-app/issues/5210 50 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 51 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 52 | } 53 | 54 | // Jest doesn't have this option so we'll remove it 55 | if (argv.indexOf('--no-watch') !== -1) { 56 | argv = argv.filter(arg => arg !== '--no-watch'); 57 | } 58 | 59 | 60 | jest.run(argv); 61 | -------------------------------------------------------------------------------- /src/planeta/FinalizeHandshake.js: -------------------------------------------------------------------------------- 1 | //@format 2 | import React from "react"; 3 | import { finalizeHandshake } from "./utils"; 4 | import HandshakeButtons from "./HandshakeButtons"; 5 | import { getStoredValue } from "../services/localStorage"; 6 | 7 | export default class FinalizeHandshake extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.handleStrategy = this.handleStrategy.bind(this); 11 | } 12 | 13 | async handleStrategy(strategy) { 14 | if (getStoredValue("expertMode") === "true") { 15 | strategy = strategy === "collaborate" ? "defect" : "collaborate"; 16 | } 17 | const { 18 | plasma, 19 | defaultPassport, 20 | scannerState, 21 | metaAccount, 22 | setReceipt, 23 | changeView 24 | } = this.props; 25 | changeView("loader"); 26 | 27 | let finalReceipt; 28 | try { 29 | finalReceipt = await finalizeHandshake( 30 | plasma, 31 | defaultPassport.unspent, 32 | scannerState.receipt, 33 | metaAccount.privateKey, 34 | strategy 35 | ); 36 | } catch (err) { 37 | // note: cooldown raises an error too, check planeta/utils.js 38 | setReceipt({ type: "error", message: err.toString() }); 39 | changeView("receipt"); 40 | return; 41 | } 42 | } 43 | 44 | componentDidMount() { 45 | const { goBack, changeAlert, defaultPassport: passport } = this.props; 46 | 47 | if (!passport) { 48 | // Sorry. 49 | goBack(); 50 | setTimeout( 51 | () => changeAlert({ type: "warning", message: "Select a passport" }), 52 | 100 53 | ); 54 | } 55 | } 56 | 57 | render() { 58 | const { defaultPassport: passport } = this.props; 59 | 60 | if (!passport) { 61 | return null; 62 | } 63 | 64 | return ; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/planeta/MoreButtons.js: -------------------------------------------------------------------------------- 1 | // @format 2 | import React from "react"; 3 | import { Flex, Icon, Box } from "rimble-ui"; 4 | import { PrimaryButton, BorderButton } from "../components/Buttons"; 5 | 6 | export default ({ changeView, passport, changeAlert }) => { 7 | const passportAlert = () => 8 | changeAlert({ type: "warning", message: "Select a passport" }); 9 | return ( 10 | 17 | 18 | { 22 | if (passport) { 23 | changeView("planet_a_handshake"); 24 | } else { 25 | passportAlert(); 26 | } 27 | }} 28 | > 29 | 30 | 31 | Handshake 32 | 33 | 34 | { 38 | if (passport) { 39 | changeView("planet_a_plant_trees"); 40 | } else { 41 | passportAlert(); 42 | } 43 | }} 44 | > 45 | 46 | 47 | Plant Trees 48 | 49 | 50 | 51 | 52 | { 56 | if (passport) { 57 | changeView("planet_a_transfer_passport"); 58 | } else { 59 | passportAlert(); 60 | } 61 | }} 62 | > 63 | 64 | 65 | Transfer Passport 66 | 67 | 68 | 69 | 70 | ); 71 | }; 72 | -------------------------------------------------------------------------------- /src/components/Balance.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Flex, Text, Image } from "rimble-ui"; 3 | import styled from 'styled-components' 4 | 5 | const Fiat = styled(Text).attrs(()=>({ 6 | fontSize: 4, 7 | fontWeight: 4 8 | }))``; 9 | 10 | const Token = styled(Text).attrs(()=>({ 11 | fontSize: 1, 12 | }))` 13 | color: var(--secondary-btn-text-color) 14 | `; 15 | 16 | const Amount = styled(Flex)` 17 | flex-direction: column; 18 | align-items: flex-end; 19 | `; 20 | 21 | const tokenDisplay = (amount, symbol = "", maximumFractionDigits = 2) => { 22 | const locale = localStorage.getItem('i18nextLng') 23 | const formatter = new Intl.NumberFormat(locale, { 24 | minimumFractionDigits: 2, 25 | maximumFractionDigits: maximumFractionDigits, 26 | }); 27 | return `${formatter.format(amount)} ${symbol}` 28 | }; 29 | 30 | const valuableTokens = ["ETH"] 31 | 32 | export default ({icon, text, amount, tokenAmount, currencyDisplay, symbol}) => { 33 | let opacity; 34 | let fiatValue; 35 | let tokenValue; 36 | const floatNumbers = valuableTokens.includes(text) ? 5 : 2 37 | 38 | if(isNaN(amount)){ 39 | opacity = 0.25; 40 | 41 | /* NOTE: Sometimes the exchangeRate to fiat wasn't loaded yet and hence 42 | * amount can become NaN. In this case, we simply pass 0 to currencyDisplay 43 | */ 44 | 45 | fiatValue = currencyDisplay(0); 46 | tokenValue = tokenDisplay(0); 47 | }else{ 48 | opacity = 1; 49 | fiatValue = currencyDisplay(amount); 50 | tokenValue = tokenDisplay(tokenAmount || amount, symbol || text, floatNumbers); 51 | } 52 | 53 | return ( 54 | 55 | 56 | 57 | 58 | {text} 59 | 60 | 61 | 62 | 63 | {fiatValue} 64 | {tokenValue} 65 | 66 | 67 | 68 | ) 69 | }; 70 | -------------------------------------------------------------------------------- /src/components/Loader.js: -------------------------------------------------------------------------------- 1 | // @format 2 | import React, { Component } from "react"; 3 | import { time } from "../services/ethgasstation"; 4 | import getConfig from "../config"; 5 | 6 | let interval; 7 | const CONFIG = getConfig(); 8 | const RATE = { 9 | PERCENT: 3, 10 | EACH: 250 11 | }; 12 | 13 | class Loader extends Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.state = { 18 | progress: 0, 19 | rate: { 20 | percent: RATE.PERCENT, 21 | each: RATE.EACH 22 | } 23 | }; 24 | 25 | this.progress = this.progress.bind(this); 26 | } 27 | 28 | async componentDidMount() { 29 | const { network } = this.props; 30 | 31 | let t; 32 | if (network === "ROOTCHAIN") { 33 | t = await time(); 34 | } else if (network === "SIDECHAIN") { 35 | t = CONFIG.SIDECHAIN.TIME_ESTIMATES.TX; //ms 36 | } else { 37 | // 8ms was Loader's default value. 38 | t = 8333; // ms 39 | } 40 | 41 | const rate = { 42 | // NOTE: We "round" the result of "each"'s calculation 43 | each: parseInt(t / 100 / RATE.PERCENT, 10), 44 | percent: RATE.PERCENT 45 | }; 46 | this.setState({ rate }, () => { 47 | interval = setInterval(this.progress, rate.each); 48 | }); 49 | } 50 | 51 | componentWillUnmount() { 52 | clearInterval(interval); 53 | } 54 | 55 | progress() { 56 | const { rate, progress } = this.state; 57 | 58 | let newProgress = progress + rate.percent; 59 | if (newProgress > 100) { 60 | newProgress = 100; 61 | clearInterval(interval); 62 | } 63 | this.setState({ progress: newProgress }); 64 | } 65 | 66 | render() { 67 | const { progress } = this.state; 68 | const { loaderImage } = this.props; 69 | 70 | return ( 71 |
72 | Burner Wallet Logo 77 |
78 |
82 |
83 |
84 | ); 85 | } 86 | } 87 | export default Loader; 88 | -------------------------------------------------------------------------------- /src/components/PassportReceipt.js: -------------------------------------------------------------------------------- 1 | // @format 2 | import React, { Component } from "react"; 3 | import { Flex, Box } from "rimble-ui"; 4 | import styled, { keyframes } from "styled-components"; 5 | import { Blockie } from "dapparatus"; 6 | import newtag from "../assets/new-tag.png"; 7 | import { 8 | Container, 9 | PassportCover, 10 | PassportLabel, 11 | PassportCountry, 12 | PassportName 13 | } from "./Passports/styles"; 14 | 15 | const blockieSize = 10; 16 | 17 | export default class PassportReceipt extends Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | 22 | render() { 23 | const { 24 | receipt: { from, to, passport } 25 | } = this.props; 26 | const name = passport.id.slice(0, 5); 27 | const { shortName } = passport.country; 28 | 29 | return ( 30 |
31 |

32 | 😭L0L you just gave your passp0rt away!!1😭 33 |

34 | 35 | 36 | 37 | {/* EXXXTREME 90ies CSS skills incoming */} 38 |
39 | You 40 |
41 | 42 | 43 | 44 | 45 | {shortName} 46 | Passport 47 | 48 | {name} 49 | 50 | 51 | 52 | 53 | 54 | {/* EXXXTREME 90ies CSS skills incoming */} 55 |
56 | The person that will commit crimes against humanity using your 57 | identity 😱 58 |
59 |
60 |
61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/planeta/GlobalCO2.js: -------------------------------------------------------------------------------- 1 | // @format 2 | import React, { Component } from "react"; 3 | import styled from "styled-components"; 4 | import { Heading, Flex, Text, Box } from "rimble-ui"; 5 | 6 | const Aligner = styled(Flex).attrs(() => ({ 7 | alignItems: "center", 8 | flexDirection: "row", 9 | mt: 2 10 | }))` 11 | color: #efcc11; 12 | text-align: center; 13 | font-family: "Saira", sans-serif; 14 | p { 15 | color: #a2852e; 16 | text-align: center; 17 | width: 100%; 18 | } 19 | h1 { 20 | flex: 1 1 0; 21 | text-align: right; 22 | margin: 0; 23 | margin-right: 16px; 24 | } 25 | `; 26 | 27 | const Label = styled(Text).attrs(() => ({ 28 | px: 3, 29 | fontWeight: "bold" 30 | }))` 31 | flex: 1.5 1.5 0; 32 | text-align: left; 33 | color: #cec6ff; 34 | margin: 0; 35 | `; 36 | 37 | const CO2Display = styled.h1` 38 | color: #efcc11; 39 | font-weight: bold; 40 | `; 41 | 42 | const NextDisplay = styled.h1` 43 | color: red; 44 | font-weight: bold; 45 | font-size: 24px; 46 | `; 47 | 48 | export default class GlobalCO2 extends Component { 49 | render() { 50 | const { value } = this.props; 51 | let toNext = 3420 - value; 52 | let message; 53 | if (toNext <= 0) { 54 | toNext = 4170 - value; 55 | message = "CO₂ left to meltdown"; 56 | } else { 57 | message = "CO₂ left to tipping point"; 58 | } 59 | 60 | if (value) { 61 | return ( 62 | 67 | 68 | 69 | {Math.round(value)} 70 | 71 | 72 | 73 | {Math.round(toNext)} 74 | 75 | 76 | 77 | (view Dashboard) 78 | 79 | 80 | values in Gigatons{" "} 81 | 82 | 83 | 84 | ); 85 | } else { 86 | return null; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /public/images/flame.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/flame.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Passports/PassportView.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Card, { Flex, Text } from "rimble-ui"; 3 | import { IconClose } from "./styles"; 4 | import PlanetAMoreButtons from "../../planeta/MoreButtons"; 5 | 6 | const Param = ({ label, value, color }) => { 7 | const locale = localStorage.getItem('i18nextLng') 8 | const formatter = new Intl.NumberFormat(locale, { 9 | minimumFractionDigits: 0, 10 | maximumFractionDigits: 2, 11 | }); 12 | return ( 13 | 14 | 15 | {label} 16 | 17 | 18 | {formatter.format(value)} 19 | 20 | 21 | ); 22 | }; 23 | 24 | const Citizen = props => { 25 | const { name, country } = props; 26 | return ( 27 | 28 | {`Citizen of the ${country}`} 29 | 30 | {name.length > 0 ? name : "Mr.Mysterious"} 31 | 32 | 33 | ); 34 | }; 35 | 36 | const PassportView = props => { 37 | const { passport, close } = props; 38 | const { changeView, changeAlert } = props; 39 | const { data } = passport; 40 | const country = passport.country.fullName; 41 | if (!passport) { 42 | return ( 43 | 44 | 45 | Border Control is checking your passport... 46 | 47 | 48 | ); 49 | } 50 | 51 | const { emitted, locked, name } = data; 52 | return ( 53 | 54 | 55 | 56 | 62 | 63 | 64 | 65 | 70 | 71 | ); 72 | }; 73 | 74 | export default PassportView; 75 | -------------------------------------------------------------------------------- /src/components/Receive.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-is-valid */ 2 | 3 | import React from 'react'; 4 | import {CopyToClipboard} from "react-copy-to-clipboard"; 5 | import RecentTransactions from './RecentTransactions'; 6 | import i18n from '../i18n'; 7 | import getConfig from "../config"; 8 | import { 9 | Flex, 10 | Box, 11 | PublicAddress, 12 | QR as QRCode 13 | } from 'rimble-ui' 14 | 15 | const CONFIG = getConfig(); 16 | 17 | export default class Receive extends React.Component { 18 | 19 | render() { 20 | let { 21 | view, 22 | buttonStyle, 23 | address, 24 | changeAlert, 25 | changeView, 26 | currencyDisplay, 27 | } = this.props 28 | 29 | return ( 30 |
31 |
32 | { 33 | changeAlert({type: 'success', message: i18n.t('receive.address_copied')}) 34 | }}> 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 49 | 50 | 61 |
62 | 69 |
70 | ) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Blockie } from "dapparatus"; 3 | import burnerloader from '../assets/burnerloader.gif'; 4 | import { Icon } from "rimble-ui"; 5 | export default ({openScanner, network, total, ens, address, changeView, view}) => { 6 | 7 | 8 | let sendButtonOpacity = 1.0 9 | if(view==="receive" || view==="send_badge"){ 10 | sendButtonOpacity = 0 11 | } 12 | 13 | 14 | 15 | let name = ens 16 | if(!name){ 17 | name = address.substring(2,8) 18 | } 19 | 20 | let moneyDisplay 21 | let blockieDisplay 22 | if(typeof total == "undefined" || Number.isNaN(total)){ 23 | moneyDisplay = ( 24 |
25 | Connecting.. 26 |
27 | ) 28 | blockieDisplay = ( 29 | 30 | ) 31 | }else{ 32 | /*moneyDisplay = ( 33 |
34 | {currencyDisplay(total)} 35 |
36 | )*/ 37 | moneyDisplay = ( 38 |
39 | {network} 40 |
41 | ) 42 | blockieDisplay = ( 43 | 46 | 47 | ) 48 | } 49 | 50 | let scanButtonStyle = { 51 | opacity:sendButtonOpacity, 52 | position:"fixed", 53 | zIndex:2, 54 | } 55 | 56 | if(view==="send_to_address"){ 57 | scanButtonStyle.position = "absolute" 58 | scanButtonStyle.right = -3 59 | scanButtonStyle.top = 217 60 | delete scanButtonStyle.bottom 61 | } 62 | 63 | let bottomRight = ( 64 |
65 | 72 |
73 | ) 74 | 75 | const mainOrExchange = view === "main" || view === "exchange"; 76 | const opacity =mainOrExchange ? 1 : 0.5; 77 | const blockieLink = mainOrExchange ? 'receive' : 'main'; 78 | let topLeft = ( 79 |
changeView(blockieLink)}> 80 | {blockieDisplay} 81 |
{name}
82 |
); 83 | 84 | 85 | let topRight = ( 86 |
87 | {moneyDisplay} 88 |
89 | ) 90 | 91 | 92 | return ( 93 |
94 | {topLeft} 95 | {topRight} 96 | {view === "main" ? bottomRight : null} 97 |
98 | ) 99 | }; 100 | -------------------------------------------------------------------------------- /src/planeta/StartHandshake.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { CopyToClipboard } from "react-copy-to-clipboard"; 3 | import i18n from "../i18n"; 4 | import { Flex, Box, Heading, QR as QRCode } from "rimble-ui"; 5 | import { startHandshake } from "./utils"; 6 | import HandshakeButtons from "./HandshakeButtons"; 7 | import { getStoredValue } from "../services/localStorage"; 8 | 9 | function renderReceipt(receipt, changeAlert) { 10 | const url = "/planeta/handshake/" + receipt; 11 | return ( 12 | <> 13 | 14 | Ask another person to scan this QRCode to handshake with you! 15 | 16 | 17 | { 20 | changeAlert({ 21 | type: "success", 22 | message: i18n.t("receive.address_copied") 23 | }); 24 | }} 25 | > 26 | 27 | 35 | 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | 43 | export default class Handshake extends React.Component { 44 | constructor(props) { 45 | super(props); 46 | this.state = {}; 47 | this.handleStrategy = this.handleStrategy.bind(this); 48 | } 49 | 50 | async handleStrategy(strategy) { 51 | if (getStoredValue("expertMode") === "true") { 52 | strategy = strategy === "collaborate" ? "defect" : "collaborate"; 53 | } 54 | const { metaAccount, web3, defaultPassport: passport } = this.props; 55 | const receipt = await startHandshake( 56 | web3, 57 | passport.unspent, 58 | metaAccount.privateKey, 59 | strategy 60 | ); 61 | this.setState({ receipt }); 62 | } 63 | 64 | render() { 65 | const { changeAlert, goBack } = this.props; 66 | const { receipt } = this.state; 67 | 68 | return ( 69 | 70 | {receipt ? ( 71 | renderReceipt(receipt, changeAlert) 72 | ) : ( 73 | 74 | )} 75 | 88 | 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/BityHistory.js: -------------------------------------------------------------------------------- 1 | // @format 2 | 3 | import React, { Component } from "react"; 4 | import i18n from "../i18n"; 5 | import { Flex, Box, Image, Icon } from "rimble-ui"; 6 | import { getOrder } from "../services/bity"; 7 | import Blockies from "react-blockies"; 8 | import bityLogo from "../assets/bity.png"; 9 | import Ruler from "./Ruler"; 10 | import Loader from "./Loader"; 11 | import burnerlogo from "../assets/burnerwallet.png"; 12 | 13 | export default class BityHistory extends Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = {}; 17 | } 18 | 19 | async componentDidMount() { 20 | const { orderId } = this.props; 21 | const order = await getOrder(orderId); 22 | this.setState({ order }); 23 | } 24 | 25 | render() { 26 | const { address, orderId } = this.props; 27 | const { order } = this.state; 28 | 29 | if (order) { 30 | return ( 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {order.output.amount} 41 | €* 42 | 43 | 44 | 45 | 46 | 47 | 54 | 55 | 56 | 57 |
{i18n.t("offramp.history.status_title")}
58 |
    59 | {order.timestamp_created && ( 60 |
  • ✓ {i18n.t("offramp.history.status.created")+orderId}
  • 61 | )} 62 | {order.timestamp_payment_received && ( 63 |
  • ✓ {i18n.t("offramp.history.status.received")}
  • 64 | )} 65 | {order.timestamp_executed && ( 66 |
  • ✓ {i18n.t("offramp.history.status.executed")}
  • 67 | )} 68 |
69 | 70 |

71 | *{i18n.t("offramp.history.disclaimer")} 72 | 77 | {i18n.t("offramp.history.click")} 78 | 79 | . 80 |

81 |
82 | ); 83 | } else { 84 | return ; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/components/Passports/styles.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Flex, Text, Icon } from "rimble-ui"; 3 | import styled, { keyframes, css } from "styled-components"; 4 | import passportPattern from '../../assets/papyrus.png'; 5 | 6 | export const Container = styled(Flex).attrs(() => ({ 7 | flexWrap: "wrap", 8 | alignItems: "center", 9 | justifyContent: "center", 10 | mb: 3, 11 | pt: 3, 12 | pb: 4, 13 | borderBottom: "1px solid #DFDFDF", 14 | width: "100%" 15 | }))` 16 | transition: transform 0.5s ease-in-out; 17 | cursor: pointer; 18 | `; 19 | 20 | const jump = keyframes` 21 | 5% { 22 | transform: scale3d(1.05, 1.1, 1); 23 | } 24 | 8% { 25 | transform: scale3d(1.05, 1.05, 1); 26 | } 27 | 20% { 28 | transform: scale3d(0.95, 0.95, 1); 29 | } 30 | 0%, 30%, 100% { 31 | transform: scale3d(1, 1, 1); 32 | } 33 | `; 34 | 35 | export const PassportCover = styled(Flex).attrs(({ shortName }) => ({ 36 | flexDirection: "column", 37 | bg: `passport${shortName}`, 38 | m:2, 39 | px: 3, 40 | py: 2, 41 | height: 4, 42 | width: '105px', 43 | justifyContent: "space-between" 44 | }))` 45 | border-radius: 4px 8px 8px 4px; 46 | box-shadow: inset -1px 0 rgba(255, 255, 255, 0.2), 47 | 0 1px 3px rgba(0, 0, 0, 0.2); 48 | position: relative; 49 | border: 2px solid; 50 | border-color: ${({theme, shortName}) => theme.colors[`passportBorder${shortName}`] || '#333'}; 51 | 52 | @supports (background-blend-mode: color-burn) { 53 | background-repeat: no-repeat; 54 | background-blend-mode: color-burn; 55 | /* Background made by Olivia Harmon, 56 | * https://www.toptal.com/designers/subtlepatterns/papyrus-pattern/ 57 | */ 58 | background-image: url(${passportPattern}); 59 | } 60 | 61 | animation: ${({ single }) => single ? css`${jump} 2.5s infinite linear` : 'none'}; 62 | `; 63 | 64 | export const PassportLabel = styled(Text).attrs(() => ({ 65 | color: "white", 66 | fontSize: 0, 67 | textAlign: "center" 68 | }))` 69 | text-transform: uppercase; 70 | `; 71 | 72 | export const PassportCountry = styled(Text).attrs(() => ({ 73 | color: "white", 74 | fontSize: 4, 75 | fontWeight: 4, 76 | textAlign: "center" 77 | }))``; 78 | 79 | export const PassportName = styled(Text).attrs(() => ({ 80 | color: "white", 81 | fontSize: 2, 82 | fontWeight: 2, 83 | textAlign: "center", 84 | opacity: 0.5 85 | }))``; 86 | 87 | const IconContainer = styled(Flex).attrs(() => ({ 88 | bg: "#eee", 89 | p: 1 90 | }))` 91 | position: absolute; 92 | cursor: pointer; 93 | top: 10px; 94 | right: 10px; 95 | border-radius: 30px; 96 | `; 97 | 98 | export const IconClose = ({ onClick }) => { 99 | return ( 100 | 101 | 102 | 103 | ); 104 | }; 105 | 106 | export const Loading = styled(Text).attrs(()=>({ 107 | fontSize: 2, 108 | color: "silver" 109 | }))``; 110 | -------------------------------------------------------------------------------- /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/facebook/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(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 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 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/components/TxReceipt.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-is-valid */ 2 | 3 | import React from 'react'; 4 | import { Blockie } from "dapparatus"; 5 | import i18n from '../i18n'; 6 | import bityLogo from '../assets/bity.png'; 7 | import { Image } from "rimble-ui"; 8 | 9 | const BockieSize = 12 10 | 11 | export default class Receive extends React.Component { 12 | 13 | componentDidMount(){ 14 | console.log("RECEIPT LOADED",this.props) 15 | if(this.props.receipt && this.props.receipt.daiposOrderId){ 16 | console.log("This was a daipos Order... ping their server for them...") 17 | // https://us-central1-daipos.cloudfunctions.net/transactionBuffer?orderId=0JFmycULnk9kAboK5ESg&txHash=0x8c831cd5cbc8786982817e43a0a77627ad0b12eaa92feff97fb3b7e91c263b1c&networkId=100 18 | let url = "https://us-central1-daipos.cloudfunctions.net/transactionBuffer?orderId="+this.props.receipt.daiposOrderId+"&txHash="+this.props.receipt.result.transactionHash+"&networkId=100" 19 | console.log("url:",url) 20 | fetch(url).then(r => r.json()).then((response)=>{ 21 | console.log("Finished hitting the Ching servers:",response) 22 | }) 23 | } 24 | console.log("CHECKING PARAMS:",this.props.receipt.params) 25 | if(this.props.receipt && this.props.receipt.params && this.props.receipt.params.callback){ 26 | console.log("Redirecting to ",this.props.receipt.params.callback,"with data:",this.props.receipt) 27 | let returnObject = { 28 | to: this.props.receipt.to, 29 | from: this.props.receipt.from, 30 | amount: this.props.receipt.amount, 31 | transactionHash: this.props.receipt.result.transactionHash, 32 | status: this.props.receipt.result.status, 33 | data: this.props.receipt.result.v, 34 | } 35 | console.log("returnObject",returnObject) 36 | setTimeout(()=>{ 37 | window.location = this.props.receipt.params.callback+"?receipt="+(encodeURI(JSON.stringify(returnObject))) 38 | },2500) 39 | } 40 | } 41 | render() { 42 | let {receipt, currencyDisplay} = this.props 43 | 44 | let message = "" 45 | 46 | let sendAmount = ( 47 |
48 | -{currencyDisplay(receipt.amount)}-> 49 |
50 | ) 51 | 52 | if(receipt.message){ 53 | message = ( 54 |
55 |
56 | {receipt.message} 57 |
58 |
59 | ) 60 | } 61 | 62 | return ( 63 |
64 |
65 |
66 |
67 | {!receipt.result.error && 68 | 69 | } 70 | {receipt.result.error && 71 | 72 | } 73 |
74 |
75 | 76 |
77 |
78 | 82 |
83 |
84 | {sendAmount} 85 |
86 |
87 | {receipt.to === "bity.com" ? 88 | : 89 | 93 | } 94 |
95 |
96 | {message} 97 | 98 |
99 | 106 |
107 | ) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/components/RequestFunds.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {CopyToClipboard} from "react-copy-to-clipboard"; 3 | import i18n from '../i18n'; 4 | import RecentTransactions from './RecentTransactions'; 5 | import { 6 | Flex, 7 | Box, 8 | Input, 9 | Field, 10 | QR as QRCode 11 | } from 'rimble-ui' 12 | import { PrimaryButton } from "./Buttons"; 13 | import { getStoredValue } from "../services/localStorage"; 14 | 15 | export default class RequestFunds extends React.Component { 16 | 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | message: "", 21 | amount: "", 22 | canRequest: false, 23 | requested: false 24 | } 25 | } 26 | 27 | updateState = (key, value) => { 28 | this.setState({ [key]: value },()=>{ 29 | this.setState({ canRequest: (this.state.amount > 0) }) 30 | }); 31 | }; 32 | 33 | request = () => { 34 | const { changeAlert } = this.props; 35 | const { amount, canRequest } = this.state; 36 | 37 | if(canRequest){ 38 | this.setState({ 39 | requested: true, 40 | amount 41 | }) 42 | }else{ 43 | changeAlert({type: 'warning', message: 'Please enter a valid amount'}) 44 | } 45 | }; 46 | 47 | render() { 48 | let { canRequest, message, amount, requested } = this.state; 49 | let { 50 | currencyDisplay, 51 | view, 52 | buttonStyle, 53 | address, 54 | changeView, 55 | } = this.props 56 | if(requested){ 57 | 58 | let url = window.location.protocol+"//"+window.location.hostname 59 | if(window.location.port&&window.location.port!==80&&window.location.port!==443){ 60 | url = url+":"+window.location.port 61 | } 62 | 63 | // TODO: Understand why these `replaceAll`s are used here. 64 | const encodedMessage = encodeURI(message).replaceAll("#","%23").replaceAll(";","%3B").replaceAll(":","%3A").replaceAll("/","%2F"); 65 | const currency = getStoredValue("currency", address); 66 | 67 | const qrValue = `${url}/${address};${amount};${encodedMessage};${currency}`; 68 | 69 | return ( 70 |
71 | { 72 | this.props.changeAlert({type: 'success', message: 'Request link copied to clipboard'}) 73 | }}> 74 |
75 |
76 | {/* NOTE: We don't need to convert here, as the user already put 77 | * in the amount in their local currency. 78 | */} 79 | {currencyDisplay(amount, false, false)} 80 |
81 | 82 |
83 | {message} 84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 |
96 | 107 |
108 | ) 109 | }else{ 110 | return ( 111 |
112 | 113 | this.updateState('amount', event.target.value)} 119 | /> 120 | 121 | 122 | 123 | this.updateState('message', event.target.value)} 129 | /> 130 | 131 | 132 | 138 | {i18n.t('request_funds.button')} 139 | 140 |
141 | ) 142 | } 143 | 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/planeta/transactionHandler.js: -------------------------------------------------------------------------------- 1 | import { Tx, Util } from "leap-core"; 2 | import JSBI from "jsbi"; 3 | import { BigInt } from "jsbi-utils"; 4 | import getConfig from "../config"; 5 | import { addHandshake } from "./cooldown"; 6 | 7 | const CONFIG = getConfig(); 8 | const GOELLAR_COLOR = 3; 9 | const BITMASK_CO2 = BigInt("0xffffffff"); 10 | const BITMASK_CO2_LOCKED = JSBI.leftShift(BITMASK_CO2, BigInt(32)); 11 | const EarthContractData = require("./contracts/Earth.json"); 12 | 13 | function parseHandshake(myAddress, inputs, outputs) { 14 | const theirAddress = inputs.filter( 15 | input => Util.isNST(input.color) && input.address !== myAddress 16 | )[0].address; 17 | 18 | // Filtering functions 19 | const isOwner = owner => ({ address }) => owner === address; 20 | const isMine = isOwner(myAddress); 21 | const isTheirs = isOwner(theirAddress); 22 | const isPassport = ({ color }) => Util.isNST(color); 23 | const isGoellar = ({ color }) => color === GOELLAR_COLOR; 24 | 25 | // Masking functions 26 | const mask = bitmask => data => JSBI.bitwiseAnd(BigInt(data), bitmask); 27 | const bitmaskCO2 = mask(BITMASK_CO2); 28 | const bitmaskCO2Locked = mask(BITMASK_CO2_LOCKED); 29 | 30 | // Defect detection 31 | const getDefectInInputs = address => { 32 | const defect = inputs.filter(isOwner(address)).filter(isGoellar); 33 | return defect.length > 0 ? BigInt(defect[0].value) : BigInt(0); 34 | }; 35 | const getDefectInPassport = (dataBefore, dataAfter) => 36 | JSBI.subtract(bitmaskCO2Locked(dataAfter), bitmaskCO2Locked(dataBefore)); 37 | const isDefect = (address, dataBefore, dataAfter) => 38 | JSBI.greaterThan( 39 | JSBI.add( 40 | getDefectInPassport(dataBefore, dataAfter), 41 | getDefectInInputs(address, inputs) 42 | ), 43 | BigInt(0) 44 | ); 45 | 46 | // Emissions and gains functions 47 | const getCO2 = (dataBefore, dataAfter) => 48 | JSBI.toNumber( 49 | JSBI.subtract(bitmaskCO2(dataAfter), bitmaskCO2(dataBefore)) 50 | ); 51 | const getGoellars = address => 52 | // FIXME: dividing to 1e14 to get an int, the dividing by 10000 to get the 53 | // float... 54 | JSBI.toNumber( 55 | JSBI.divide( 56 | JSBI.subtract( 57 | BigInt(outputs.filter(isOwner(address)).filter(isGoellar)[0].value), 58 | getDefectInInputs(address) 59 | ), 60 | BigInt("100000000000000") 61 | ) 62 | ) / 10000; 63 | 64 | const myPassport = BigInt( 65 | inputs.filter(isMine).filter(isPassport)[0].value 66 | ).toString(); 67 | const theirPassport = BigInt( 68 | inputs.filter(isTheirs).filter(isPassport)[0].value 69 | ).toString(); 70 | const myDataBefore = inputs.filter(isMine).filter(isPassport)[0].data; 71 | const myDataAfter = outputs.filter(isMine).filter(isPassport)[0].data; 72 | const theirDataBefore = inputs.filter(isTheirs).filter(isPassport)[0].data; 73 | const theirDataAfter = outputs.filter(isTheirs).filter(isPassport)[0].data; 74 | 75 | return { 76 | myAddress, 77 | myDefect: isDefect(myAddress, myDataBefore, myDataAfter), 78 | myGoellars: getGoellars(myAddress), 79 | myCO2: getCO2(myDataBefore, myDataAfter), 80 | myPassport, 81 | theirAddress, 82 | theirCO2: getCO2(theirDataBefore, theirDataAfter), 83 | theirDefect: isDefect(theirAddress, theirDataBefore, theirDataAfter), 84 | theirGoellars: getGoellars(theirAddress), 85 | theirPassport 86 | }; 87 | } 88 | 89 | export default async function process(plasma, passports, tx) { 90 | const myAddress = passports[0].unspent.output.address; 91 | const trade4ByteSignature = plasma.eth.abi 92 | .encodeFunctionSignature( 93 | EarthContractData.abi.filter(({ name }) => name === "trade")[0] 94 | ) 95 | .replace("0x", ""); 96 | if (tx.from.toLowerCase() !== EarthContractData.address) { 97 | return; 98 | } 99 | const plasmaTx = Tx.fromRaw(tx.raw); 100 | const outputs = plasmaTx.outputs; 101 | const isHandshake = 102 | plasmaTx.inputs.filter( 103 | ({ msgData }) => 104 | msgData && msgData.toString("hex").startsWith(trade4ByteSignature) 105 | ).length > 0; 106 | if (!isHandshake) { 107 | return; 108 | } 109 | const isMe = 110 | outputs.filter(({ address }) => address === myAddress).length > 0; 111 | if (!isMe) { 112 | return; 113 | } 114 | console.log("Transaction about me", tx); 115 | const inputs = await Promise.all( 116 | plasmaTx.inputs.map(({ prevout: { index, hash } }) => 117 | plasma.eth 118 | .getTransaction("0x" + hash.toString("hex")) 119 | .then(({ raw }) => Tx.fromRaw(raw).outputs[index]) 120 | ) 121 | ); 122 | const result = parseHandshake(myAddress, inputs, outputs); 123 | result.txHash = tx.hash; 124 | console.log("handshake values", result); 125 | console.log("handshake", `${CONFIG.SIDECHAIN.EXPLORER.URL}tx/${result.txHash}`); 126 | addHandshake(result.myPassport, result.theirPassport); 127 | return result; 128 | } 129 | -------------------------------------------------------------------------------- /scripts/consolidate.js: -------------------------------------------------------------------------------- 1 | const { bi, lessThan, add, subtract, multiply } = require("jsbi-utils"); 2 | const { bufferToHex, ripemd160, keccak256 } = require("ethereumjs-util"); 3 | const ethers = require("ethers"); 4 | const LeapProvider = require("leap-provider"); 5 | const { Tx, helpers, Input, Output } = require("leap-core"); 6 | 7 | const erc20ABI = require("../src/contracts/StableCoin.abi"); 8 | const earth = require("../src/planeta/contracts/Earth"); 9 | const air = require("../src/planeta/contracts/Air"); 10 | 11 | const { LeapEthers } = helpers; 12 | 13 | const CONFIG = { 14 | color: process.env.COLOR || 0, 15 | priv: process.env.PRIV_KEY 16 | }; 17 | 18 | const code = process.env.TYPE === 'air' ? air.code : earth.code; 19 | const scriptBuf = Buffer.from(code.replace("0x", ""), "hex"); 20 | const contractAddress = process.env.CONTRACT_ADDR || bufferToHex(ripemd160(scriptBuf)); 21 | 22 | const factor18 = bi("1000000000000000000"); 23 | 24 | const plasma = new LeapEthers( 25 | new LeapProvider(process.env.NODE_URL || "https://testnet-node.leapdao.org") 26 | ); 27 | 28 | const GAS_COST = process.env.TYPE === 'air'? bi(7007558) : bi(7001310); 29 | 30 | const threshold = { 31 | 1: bi(1), // 1 LEAP 32 | 2: bi(10000), // 20 CO2 33 | 3: bi(10) // 1 GOE 34 | }; 35 | 36 | const getMsgData = (tx, tokenAddr) => { 37 | const { v, r, s } = tx.getConditionSig(CONFIG.priv); 38 | const abi = earth.abi; 39 | const cons = abi.find(a => a.name === 'consolidate'); 40 | const methodSig = keccak256( 41 | `${cons.name}(${cons.inputs.map(i => i.type).join(",")})` 42 | ) 43 | .toString("hex") 44 | .substr(0, 8); 45 | const params = ethers.utils.defaultAbiCoder.encode(cons.inputs, [ 46 | tokenAddr, 47 | v, 48 | r, 49 | s 50 | ]); 51 | return `0x${methodSig}${params.substring(2)}`; 52 | }; 53 | 54 | const consolidate = async () => { 55 | const colors = await plasma.getColors(); 56 | const gasTokenAddress = colors[0]; 57 | const tokenAddr = colors[CONFIG.color]; 58 | 59 | console.log("Contract address:", contractAddress); 60 | console.log("GAS token:", gasTokenAddress, 0); 61 | console.log("Token to consolidate:", tokenAddr, CONFIG.color); 62 | 63 | const utxos = await plasma.getUnspent(contractAddress, CONFIG.color); 64 | console.log("UTXOs:", utxos.length); 65 | if (utxos.length <= 21) { 66 | console.log('Too little UTXOs to consolidate'); 67 | process.exit(0); 68 | } 69 | let dustUtxos = utxos.filter(u => 70 | lessThan( 71 | bi(u.output.value), 72 | multiply(threshold[CONFIG.color] || bi(1), factor18) 73 | ) 74 | ); 75 | dustUtxos = dustUtxos.slice(0, dustUtxos.length > 10 ? 10 : dustUtxos.length); 76 | if (utxos.length - dustUtxos.length < 20) { 77 | dustUtxos = dustUtxos.slice(0, utxos.length - 20); 78 | } 79 | console.log("UTXOs to consolidate:", dustUtxos.length); 80 | 81 | const gasToken = new ethers.Contract( 82 | gasTokenAddress, 83 | erc20ABI, 84 | plasma.provider 85 | ); 86 | const gasBalance = bi(await gasToken.balanceOf(contractAddress)); 87 | if (lessThan(gasBalance, GAS_COST)) { 88 | throw new Error( 89 | `Not enough gas for consolidationg. Need: ${GAS_COST}, balance: ${gasBalance}` 90 | ); 91 | } 92 | const gasUtxos = await plasma.getUnspent(contractAddress, 0); 93 | 94 | const tokenInputs = dustUtxos.map(u => { 95 | return new Input({ prevout: u.outpoint }); 96 | }); 97 | 98 | const inputs = [ 99 | new Input({ prevout: gasUtxos[0].outpoint, script: scriptBuf }), 100 | ...tokenInputs 101 | ]; 102 | 103 | const amount = dustUtxos.reduce((a, v) => add(a, bi(v.output.value)), bi(0)); 104 | 105 | const outputs = [ 106 | new Output(amount.toString(), contractAddress, CONFIG.color), 107 | new Output( 108 | subtract(bi(gasUtxos[0].output.value), GAS_COST).toString(), 109 | contractAddress, 110 | 0 111 | ) 112 | ]; 113 | 114 | let condition = Tx.spendCond(inputs, outputs); 115 | 116 | condition.inputs[0].setMsgData(getMsgData(condition, tokenAddr)); 117 | 118 | const rsp = await plasma.provider 119 | .send("checkSpendingCondition", [condition.hex()]) 120 | .then(rsp => { 121 | if (rsp.error) { 122 | // flip dat input 🎶 shuffle dat sighash 🎵 shake it, shake it 123 | condition.inputs = [ 124 | new Input({ prevout: gasUtxos[0].outpoint, script: scriptBuf }), 125 | ...[...tokenInputs.splice(1), ...tokenInputs] 126 | ]; 127 | condition.inputs[0].setMsgData(getMsgData(condition, tokenAddr)); 128 | return plasma.provider.send("checkSpendingCondition", [ 129 | condition.hex() 130 | ]); 131 | } 132 | return rsp; 133 | }); 134 | if (rsp.error) { 135 | console.error(rsp); 136 | console.log("🛑"); 137 | process.exit(1); 138 | return; 139 | } 140 | const { transactionHash } = await plasma.provider.sendTransaction(condition).then(tx => tx.wait()); 141 | console.log(`https://testnet.leapdao.org/explorer/tx/${transactionHash}`); 142 | console.log("✅"); 143 | process.exit(0); 144 | }; 145 | 146 | consolidate(); 147 | -------------------------------------------------------------------------------- /src/components/RecentTransactions.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Blockie } from "dapparatus"; 3 | import { Scaler } from "dapparatus"; 4 | import { Image } from "rimble-ui"; 5 | import bityLogo from "../assets/bity.png"; 6 | 7 | export default ({currencyDisplay, view, max, buttonStyle, vendorName, address, recentTxs, block, changeView}) => { 8 | let txns = [] 9 | let count=0 10 | if(!max) max=9999 11 | for(let r in recentTxs){ 12 | let thisValue = parseFloat(recentTxs[r].value) 13 | if(thisValue>0.0){ 14 | let dollarView = ( 15 | 16 | {currencyDisplay(recentTxs[r].value)} 17 | 18 | ) 19 | 20 | let toBlockie; 21 | if (recentTxs[r].to === "bity.com") { 22 | toBlockie = 23 | } else { 24 | toBlockie = ( 25 | 29 | ) 30 | } 31 | if(recentTxs[r].to===address && recentTxs[r].data) { 32 | let message = recentTxs[r].data 33 | let limit = 18 34 | if(message.length>limit){ 35 | message = message.substring(0,limit-3)+"..." 36 | } 37 | toBlockie = ( 38 | 39 | {message} 40 | 41 | ) 42 | } 43 | 44 | if(count++ 47 | ) 48 | 49 | let blockAge = block-recentTxs[r].blockNumber 50 | 51 | if(blockAge<=1&&recentTxs[r].to===address){ 52 | txns.push( 53 |
{ 54 | if(recentTxs[r].from===address){ 55 | changeView("account_"+recentTxs[r].to) 56 | } else if (recentTxs[r].to === "bity.com") { 57 | changeView("bity_"+recentTxs[r].orderId); 58 | }else{ 59 | changeView("account_"+recentTxs[r].from) 60 | } 61 | }}> 62 |
63 | 64 |
65 |
66 | 70 |
71 |
72 | 73 | {dollarView} 74 | 75 |
76 |
77 | {toBlockie} 78 |
79 |
80 | ) 81 | }else{ 82 | txns.push( 83 |
{ 84 | if (recentTxs[r].to === "bity.com") { 85 | changeView("bity_"+recentTxs[r].orderId); 86 | } else if(recentTxs[r].from===address ){ 87 | changeView("account_"+recentTxs[r].to) 88 | }else{ 89 | changeView("account_"+recentTxs[r].from) 90 | } 91 | }}> 92 |
93 | 97 |
98 |
99 | 100 | {dollarView} 101 | 102 |
103 |
104 | {toBlockie} 105 |
106 |
107 | 108 | {cleanTime((blockAge)*5)} ago 109 | 110 |
111 | 112 |
113 | ) 114 | } 115 | 116 | 117 | } 118 | 119 | } 120 | } 121 | if(txns.length>0){ 122 | return ( 123 |
124 | {txns} 125 |
126 | ) 127 | }else{ 128 | return ( 129 | 130 | ) 131 | } 132 | } 133 | 134 | let cleanTime = (s)=>{ 135 | if(s<60){ 136 | return s+"s" 137 | }else if(s/60<60){ 138 | return Math.round(s/6)/10+"m" 139 | }else { 140 | return Math.round((s/60/6)/24)/10+"d" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/contracts/StableCoin.abi.js: -------------------------------------------------------------------------------- 1 | module.exports = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"symbol_","type":"bytes32"}],"name":"DSToken","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"name_","type":"bytes32"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"stopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"push","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"move","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"start","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"guy","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"wad","type":"uint256"}],"name":"pull","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"}] -------------------------------------------------------------------------------- /src/planeta/contracts/Air.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "0x14b99e8037f2ac75df4a8981190acc77c831d7d7", 3 | "code": "0x608060405234801561001057600080fd5b50600436106100445760e060020a600035046309f257ef81146100495780630aef446d1461008c5780635ca740ab146100c8575b600080fd5b61008a600480360360a081101561005f57600080fd5b50803590602081013560ff169060408101359060608101359060800135600160a060020a0316610103565b005b61008a600480360360808110156100a257600080fd5b50803590600160a060020a03602082013581169160408101359160609091013516610277565b61008a600480360360808110156100de57600080fd5b50600160a060020a038135169060ff602082013516906040810135906060013561052a565b60008061011a3060a060020a8902178787876106c9565b909250905060018215151461016d576040805160e560020a62461bcd02815260206004820152600e6024820152609260020a6d1c9958dbdd995c8819985a5b195902604482015290519081900360640190fd5b600160a060020a038116730d56caf1ccb9eddf27423a1d0f8960554e7bc9d5146101dc576040805160e560020a62461bcd0281526020600482015260156024820152605b60020a740e6d2cedccae440c8decae640dcdee840dac2e8c6d02604482015290519081900360640190fd5b6040805160e060020a63a9059cbb028152600160a060020a038516600482015260248101899052905173f64ffbc4a69631d327590f4151b79816a193a8c69163a9059cbb9160448083019260209291908290030181600087803b15801561024257600080fd5b505af1158015610256573d6000803e3d6000fd5b505050506040513d602081101561026c57600080fd5b505050505050505050565b6000839050600081600160a060020a0316636352211e856040518263ffffffff1660e060020a0281526004018082815260200191505060206040518083038186803b1580156102c557600080fd5b505afa1580156102d9573d6000803e3d6000fd5b505050506040513d60208110156102ef57600080fd5b50516040805160e060020a6323b872dd028152600160a060020a0383166004820152306024820152604481018990529051919250731f89fb2199220a350287b162b9d0a330a2d2efad9182916323b872dd9160648083019260209291908290030181600087803b15801561036257600080fd5b505af1158015610376573d6000803e3d6000fd5b505050506040513d602081101561038c57600080fd5b50506040805160e060020a6337ebbc03028152600481018790529051600091600160a060020a038616916337ebbc0391602480820192602092909190829003018186803b1580156103dc57600080fd5b505afa1580156103f0573d6000803e3d6000fd5b505050506040513d602081101561040657600080fd5b50519050600160a060020a03841663a983d43f876104308466038d7ea4c6800060028e0204610706565b6040518363ffffffff1660e060020a0281526004018083815260200182815260200192505050600060405180830381600087803b15801561047057600080fd5b505af1158015610484573d6000803e3d6000fd5b50506040805160e060020a63a9059cbb028152600160a060020a038916600482015260028c026024820152905173f64ffbc4a69631d327590f4151b79816a193a8c6935083925063a9059cbb916044808201926020929091908290030181600087803b1580156104f357600080fd5b505af1158015610507573d6000803e3d6000fd5b505050506040513d602081101561051d57600080fd5b5050505050505050505050565b600080610539308686866106c9565b909250905060018215151461058c576040805160e560020a62461bcd02815260206004820152600e6024820152609260020a6d1c9958dbdd995c8819985a5b195902604482015290519081900360640190fd5b600160a060020a038116730d56caf1ccb9eddf27423a1d0f8960554e7bc9d5146105fb576040805160e560020a62461bcd0281526020600482015260156024820152605b60020a740e6d2cedccae440c8decae640dcdee840dac2e8c6d02604482015290519081900360640190fd5b6040805160e060020a6370a08231028152306004820181905291518892600160a060020a0384169263a9059cbb9284916370a08231916024808301926020929190829003018186803b15801561065057600080fd5b505afa158015610664573d6000803e3d6000fd5b505050506040513d602081101561067a57600080fd5b50516040805160e060020a63ffffffff8616028152600160a060020a03909316600484015260248301919091525160448083019260209291908290030181600087803b15801561024257600080fd5b60008060008060405188815287602082015286604082015285606082015260208160808360006001610bb8f1905190999098509650505050505050565b60008061071284610792565b905082810163ffffffff8083169082161161076c576040805160e560020a62461bcd02815260206004820152600f6024820152608860020a6e627566666572206f766572666c6f7702604482015290519081900360640190fd5b602060020a63ffffffff9091160267ffffffff0000000019909416939093179392505050565b602060020a90049056fea165627a7a72305820fec0008467ea06870ac7f4c941a1404cda122d3450a2847f3ed6ae04ab3b694e0029", 4 | "abi": [ 5 | { 6 | "constant": false, 7 | "inputs": [ 8 | { 9 | "name": "goellarAmount", 10 | "type": "uint256" 11 | }, 12 | { 13 | "name": "countryAddr", 14 | "type": "address" 15 | }, 16 | { 17 | "name": "passport", 18 | "type": "uint256" 19 | }, 20 | { 21 | "name": "earthAddr", 22 | "type": "address" 23 | } 24 | ], 25 | "name": "plantTree", 26 | "outputs": [], 27 | "payable": false, 28 | "stateMutability": "nonpayable", 29 | "type": "function" 30 | }, 31 | { 32 | "constant": false, 33 | "inputs": [ 34 | { 35 | "name": "token", 36 | "type": "address" 37 | }, 38 | { 39 | "name": "v", 40 | "type": "uint8" 41 | }, 42 | { 43 | "name": "r", 44 | "type": "bytes32" 45 | }, 46 | { 47 | "name": "s", 48 | "type": "bytes32" 49 | } 50 | ], 51 | "name": "consolidate", 52 | "outputs": [], 53 | "payable": false, 54 | "stateMutability": "nonpayable", 55 | "type": "function" 56 | }, 57 | { 58 | "constant": false, 59 | "inputs": [ 60 | { 61 | "name": "amount", 62 | "type": "uint256" 63 | }, 64 | { 65 | "name": "v", 66 | "type": "uint8" 67 | }, 68 | { 69 | "name": "r", 70 | "type": "bytes32" 71 | }, 72 | { 73 | "name": "s", 74 | "type": "bytes32" 75 | }, 76 | { 77 | "name": "earthAddr", 78 | "type": "address" 79 | } 80 | ], 81 | "name": "lockCO2", 82 | "outputs": [], 83 | "payable": false, 84 | "stateMutability": "nonpayable", 85 | "type": "function" 86 | } 87 | ] 88 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "burner-wallet", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@tammo/react-iban": "0.0.2", 7 | "base64url": "^3.0.1", 8 | "bootstrap": "^4.1.3", 9 | "dapparatus": "leapdao/dapparatus#5591152", 10 | "eth-crypto": "^1.3.2", 11 | "ethereum-mnemonic-privatekey-utils": "^1.0.5", 12 | "ethereumjs-util": "^6.0.0", 13 | "i18next": "^13.1.5", 14 | "i18next-browser-languagedetector": "^2.2.4", 15 | "iban": "0.0.12", 16 | "identity-obj-proxy": "3.0.0", 17 | "jsbi": "^3.1.0", 18 | "jsbi-utils": "^1.0.0", 19 | "leap-core": "^0.32.4", 20 | "leap-provider": "^1.0.0", 21 | "qrcode-reader": "^1.0.4", 22 | "qrcode.react": "^0.8.0", 23 | "query-string": "^6.3.0", 24 | "react": "^16.6.0", 25 | "react-app-polyfill": "^0.2.2", 26 | "react-blockies": "^1.4.0", 27 | "react-cookies": "^0.1.0", 28 | "react-copy-to-clipboard": "^5.0.1", 29 | "react-dev-utils": "^8.0.0", 30 | "react-dom": "^16.6.0", 31 | "react-dom-confetti": "^0.1.1", 32 | "react-emoji-render": "^0.5.0", 33 | "react-file-reader-input": "^2.0.0", 34 | "react-i18next": "^9.0.7", 35 | "react-linkify": "^0.2.2", 36 | "react-native-webview-messaging": "^1.2.3", 37 | "react-qr-reader": "^2.1.1", 38 | "react-scroll": "^1.7.10", 39 | "react-sound": "^1.2.0", 40 | "rimble-ui": "0.4.2", 41 | "styled-components": "^4.1.3", 42 | "web3": "1.0.0-beta.36" 43 | }, 44 | "scripts": { 45 | "start": "node scripts/start.js", 46 | "build": "node scripts/build.js", 47 | "test": "node scripts/test.js", 48 | "lint": "eslint src", 49 | "deploy": "echo 'Re-building, deploying build to S3 and invalidating Cloudfront distribution. To get deployment credentials, ask someone from the team.' && npm run build && AWS_PROFILE=leap aws s3 sync build/ s3://planeta.leap.rocks/ --acl public-read && AWS_PROFILE=leap aws cloudfront create-invalidation --distribution-id EMOOCRPZK4EIL --paths '/*'" 50 | }, 51 | "eslintConfig": { 52 | "extends": "react-app" 53 | }, 54 | "browserslist": [ 55 | ">0.2%", 56 | "not dead", 57 | "not ie <= 11", 58 | "not op_mini all" 59 | ], 60 | "devDependencies": { 61 | "@babel/core": "7.2.2", 62 | "@babel/plugin-transform-react-jsx": "^7.3.0", 63 | "@babel/plugin-transform-react-jsx-self": "^7.2.0", 64 | "@svgr/webpack": "4.1.0", 65 | "babel-core": "7.0.0-bridge.0", 66 | "babel-eslint": "9.0.0", 67 | "babel-jest": "23.6.0", 68 | "babel-loader": "8.0.5", 69 | "babel-plugin-named-asset-import": "^0.3.1", 70 | "babel-preset-react-app": "^7.0.2", 71 | "bfj": "6.1.1", 72 | "case-sensitive-paths-webpack-plugin": "2.2.0", 73 | "clevis": "github:austintgriffith/clevis#master", 74 | "cors": "^2.8.4", 75 | "css-loader": "1.0.0", 76 | "dotenv": "^6.2.0", 77 | "dotenv-expand": "4.2.0", 78 | "eslint": "5.12.0", 79 | "eslint-config-react-app": "^3.0.8", 80 | "eslint-loader": "2.1.1", 81 | "eslint-plugin-flowtype": "2.50.1", 82 | "eslint-plugin-import": "2.14.0", 83 | "eslint-plugin-jsx-a11y": "6.1.2", 84 | "eslint-plugin-react": "7.12.4", 85 | "express": "^4.16.4", 86 | "file-loader": "2.0.0", 87 | "fs": "0.0.1-security", 88 | "fs-extra": "7.0.1", 89 | "helmet": "^3.14.0", 90 | "html-webpack-plugin": "4.0.0-alpha.2", 91 | "jest": "23.6.0", 92 | "jest-pnp-resolver": "1.0.2", 93 | "jest-resolve": "23.6.0", 94 | "jest-watch-typeahead": "^0.2.1", 95 | "mini-css-extract-plugin": "0.5.0", 96 | "mocha": "^5.2.0", 97 | "node-sass": "^4.11.0", 98 | "openzeppelin-solidity": "2.0.0", 99 | "optimize-css-assets-webpack-plugin": "5.0.1", 100 | "path": "^0.12.7", 101 | "pnp-webpack-plugin": "1.2.1", 102 | "postcss-flexbugs-fixes": "4.1.0", 103 | "postcss-loader": "3.0.0", 104 | "postcss-preset-env": "6.5.0", 105 | "postcss-safe-parser": "4.0.1", 106 | "s3": "^4.4.0", 107 | "sass-loader": "7.1.0", 108 | "style-loader": "0.23.1", 109 | "terser-webpack-plugin": "1.2.2", 110 | "url-loader": "1.1.2", 111 | "webpack": "4.28.3", 112 | "webpack-dev-server": "3.1.14", 113 | "webpack-manifest-plugin": "2.0.4", 114 | "workbox-webpack-plugin": "3.6.3" 115 | }, 116 | "jest": { 117 | "collectCoverageFrom": [ 118 | "src/**/*.{js,jsx,ts,tsx}", 119 | "!src/**/*.d.ts" 120 | ], 121 | "resolver": "jest-pnp-resolver", 122 | "setupFiles": [ 123 | "react-app-polyfill/jsdom" 124 | ], 125 | "testMatch": [ 126 | "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", 127 | "/src/**/?(*.)(spec|test).{js,jsx,ts,tsx}" 128 | ], 129 | "testEnvironment": "jsdom", 130 | "testURL": "http://localhost", 131 | "transform": { 132 | "^.+\\.(js|jsx|ts|tsx)$": "/node_modules/babel-jest", 133 | "^.+\\.css$": "/config/jest/cssTransform.js", 134 | "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "/config/jest/fileTransform.js" 135 | }, 136 | "transformIgnorePatterns": [ 137 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$", 138 | "^.+\\.module\\.(css|sass|scss)$" 139 | ], 140 | "moduleNameMapper": { 141 | "^react-native$": "react-native-web", 142 | "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy" 143 | }, 144 | "moduleFileExtensions": [ 145 | "web.js", 146 | "js", 147 | "web.ts", 148 | "ts", 149 | "web.tsx", 150 | "tsx", 151 | "json", 152 | "web.jsx", 153 | "jsx", 154 | "node" 155 | ], 156 | "watchPlugins": [ 157 | "/Users/sunify/leapdao/burner-wallet/node_modules/jest-watch-typeahead/filename.js", 158 | "/Users/sunify/leapdao/burner-wallet/node_modules/jest-watch-typeahead/testname.js" 159 | ] 160 | }, 161 | "babel": { 162 | "presets": [ 163 | "react-app" 164 | ] 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /config/webpackDevServer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); 4 | const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware'); 5 | const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); 6 | const ignoredFiles = require('react-dev-utils/ignoredFiles'); 7 | const paths = require('./paths'); 8 | const fs = require('fs'); 9 | 10 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 11 | const host = process.env.HOST || '0.0.0.0'; 12 | 13 | module.exports = function(proxy, allowedHost) { 14 | return { 15 | // WebpackDevServer 2.4.3 introduced a security fix that prevents remote 16 | // websites from potentially accessing local content through DNS rebinding: 17 | // https://github.com/webpack/webpack-dev-server/issues/887 18 | // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a 19 | // However, it made several existing use cases such as development in cloud 20 | // environment or subdomains in development significantly more complicated: 21 | // https://github.com/facebook/create-react-app/issues/2271 22 | // https://github.com/facebook/create-react-app/issues/2233 23 | // While we're investigating better solutions, for now we will take a 24 | // compromise. Since our WDS configuration only serves files in the `public` 25 | // folder we won't consider accessing them a vulnerability. However, if you 26 | // use the `proxy` feature, it gets more dangerous because it can expose 27 | // remote code execution vulnerabilities in backends like Django and Rails. 28 | // So we will disable the host check normally, but enable it if you have 29 | // specified the `proxy` setting. Finally, we let you override it if you 30 | // really know what you're doing with a special environment variable. 31 | disableHostCheck: 32 | !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true', 33 | // Enable gzip compression of generated files. 34 | compress: true, 35 | // Silence WebpackDevServer's own logs since they're generally not useful. 36 | // It will still show compile warnings and errors with this setting. 37 | clientLogLevel: 'none', 38 | // By default WebpackDevServer serves physical files from current directory 39 | // in addition to all the virtual build products that it serves from memory. 40 | // This is confusing because those files won’t automatically be available in 41 | // production build folder unless we copy them. However, copying the whole 42 | // project directory is dangerous because we may expose sensitive files. 43 | // Instead, we establish a convention that only files in `public` directory 44 | // get served. Our build script will copy `public` into the `build` folder. 45 | // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: 46 | // 47 | // In JavaScript code, you can access it with `process.env.PUBLIC_URL`. 48 | // Note that we only recommend to use `public` folder as an escape hatch 49 | // for files like `favicon.ico`, `manifest.json`, and libraries that are 50 | // for some reason broken when imported through Webpack. If you just want to 51 | // use an image, put it in `src` and `import` it from JavaScript instead. 52 | contentBase: paths.appPublic, 53 | // By default files from `contentBase` will not trigger a page reload. 54 | watchContentBase: true, 55 | // Enable hot reloading server. It will provide /sockjs-node/ endpoint 56 | // for the WebpackDevServer client so it can learn when the files were 57 | // updated. The WebpackDevServer client is included as an entry point 58 | // in the Webpack development configuration. Note that only changes 59 | // to CSS are currently hot reloaded. JS changes will refresh the browser. 60 | hot: true, 61 | // It is important to tell WebpackDevServer to use the same "root" path 62 | // as we specified in the config. In development, we always serve from /. 63 | publicPath: '/', 64 | // WebpackDevServer is noisy by default so we emit custom message instead 65 | // by listening to the compiler events with `compiler.hooks[...].tap` calls above. 66 | quiet: true, 67 | // Reportedly, this avoids CPU overload on some systems. 68 | // https://github.com/facebook/create-react-app/issues/293 69 | // src/node_modules is not ignored to support absolute imports 70 | // https://github.com/facebook/create-react-app/issues/1065 71 | watchOptions: { 72 | ignored: ignoredFiles(paths.appSrc), 73 | }, 74 | // Enable HTTPS if the HTTPS environment variable is set to 'true' 75 | https: protocol === 'https', 76 | host, 77 | overlay: false, 78 | historyApiFallback: { 79 | // Paths with dots should still use the history fallback. 80 | // See https://github.com/facebook/create-react-app/issues/387. 81 | disableDotRule: true, 82 | }, 83 | public: allowedHost, 84 | proxy, 85 | before(app, server) { 86 | if (fs.existsSync(paths.proxySetup)) { 87 | // This registers user provided middleware for proxy reasons 88 | require(paths.proxySetup)(app); 89 | } 90 | 91 | // This lets us fetch source contents from webpack for the error overlay 92 | app.use(evalSourceMapMiddleware(server)); 93 | // This lets us open files from the runtime error overlay. 94 | app.use(errorOverlayMiddleware()); 95 | 96 | // This service worker file is effectively a 'no-op' that will reset any 97 | // previous service worker registered for the same host:port combination. 98 | // We do this in development to avoid hitting the production cache if 99 | // it used the same host and port. 100 | // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432 101 | app.use(noopServiceWorkerMiddleware()); 102 | }, 103 | }; 104 | }; 105 | -------------------------------------------------------------------------------- /src/i18n/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "en": { 3 | "app_name": "Burner", 4 | "about": "About", 5 | "code": "Code", 6 | "burn": "Burn PK", 7 | "show": "Show PK", 8 | "copy": "Copy PK", 9 | "create": "Create", 10 | "receive_title": "Receive", 11 | "receipt_title": "Receipt", 12 | "send_to_address_title": "Send to Address", 13 | "advance": "Advanced", 14 | "advance_title": "Advanced", 15 | "loading": "Loading...", 16 | "cancel": "Cancel", 17 | "done": "Done", 18 | "exchange_title": "Exchange", 19 | "amount": "Amount", 20 | "send": "Send", 21 | "withdraw": "Withdraw", 22 | "history_chat": "History", 23 | "request_funds_title": "Request funds", 24 | "request": "Request", 25 | "vendors": "Vendors", 26 | "main_card": { 27 | "receive": "Receive", 28 | "send": "Send", 29 | "share": "Share", 30 | "link": "Link", 31 | "vendors": "Vendors", 32 | "mint": "Register Movie" 33 | }, 34 | "send_with_link": { 35 | "error": "Please enter a valid amount" 36 | }, 37 | "share": { 38 | "copied": "URL copied to clipboard" 39 | }, 40 | "vendor": { 41 | "updating": "Updating", 42 | "open": "Open", 43 | "closed": "Closed", 44 | "add_product": "Add Product", 45 | "adding": "Adding" 46 | }, 47 | "withdraw_from_private": { 48 | "from_address": "Withdraw from Address", 49 | "amount": "Withdraw Amount", 50 | "withdraw": "Withdraw", 51 | "error": "Please enter a valid amount to withdraw" 52 | }, 53 | "send_to_address": { 54 | "to_address": "To Address", 55 | "send_amount": "Send Amount", 56 | "notice": "You can only send ", 57 | "error": "Please enter a valid address or ENS.", 58 | "currency_error": "Please update your burner-wallet to a newer version that supports currency conversion." 59 | }, 60 | "send_by_scan": { 61 | "try_again": "Please try again", 62 | "take_photo": "Take Photo", 63 | "capture": "Capture QR Code:" 64 | }, 65 | "more_buttons": { 66 | "request": "Request", 67 | "exchange": "Exchange" 68 | }, 69 | "receive": { 70 | "address_copied": "Address copied to clipboard" 71 | }, 72 | "history": { 73 | "metamask_error": "Encrypted messaging not available with MetaMask", 74 | "wave": "Wave" 75 | }, 76 | "exchange": { 77 | "withdrawal_explanation": "Only vendors can withdraw more than they deposited", 78 | "funds_transferred": "Funds Transferred!", 79 | "funds_bridged": "Funds Exchanged", 80 | "insufficient_funds": "Insufficient funds", 81 | "invalid_to_address": "Please enter a valid to address", 82 | "invalid_to_amount": "Please enter a valid to amount", 83 | "calculate_gas_price": "Calculating best gas price...", 84 | "go_to_etherscan": "go to etherscan?", 85 | "idk": "idk where to go from here? something that explains the bridge?", 86 | "fast_exit_swap": "Initializing currency swap in two transactions.", 87 | "fast_exit_patience": "Swap successfully initialized. You'll receive your DAI shortly." 88 | }, 89 | "request_funds": { 90 | "item_message": "Item or Message", 91 | "button": "Request", 92 | "amount": "Request Amount" 93 | }, 94 | "admin": { 95 | "add_admin": "Add Admin", 96 | "adding": "Adding", 97 | "add_vendor": "Add Vendor" 98 | }, 99 | "advanced": { 100 | "to_qr": "To QR" 101 | }, 102 | "balance_display": { 103 | "waiting_for_api": "Waiting for API", 104 | "no_currency_api": "No Currency API Data Available" 105 | }, 106 | "burn_wallet": { 107 | "burn_private_key_question": "Are you sure you want to burn this private key?", 108 | "disclaimer": "Don't do it! You will lose all funds!", 109 | "cancel": "No, Cancel", 110 | "burn": "Burn It!" 111 | }, 112 | "mint": { 113 | "title": "Register a Movie", 114 | "image_title": "Upload poster", 115 | "movie_title": "Upload movie", 116 | "rightholder": { 117 | "address": "Address of Rightholder", 118 | "name": "Name of Rightholder" 119 | }, 120 | "movie": { 121 | "name": "Name of the Movie" 122 | } 123 | }, 124 | "currency": { 125 | "label": "Display currency" 126 | }, 127 | "offramp": { 128 | "title": "Transfer to your Bank Account", 129 | "success": "Transfer successful! Check your bank account in the next 24 hours.", 130 | "required": "This field is required.", 131 | "country_not_supported": "This country is not yet supported by bity.com. Currently supported countries: Austria, Belgium, Bulgaria, Croatia, Cyprus, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Iceland, Ireland, Italy, Latvia, Liechtenstein, Lithuania, Luxembourg, Malta, Monaco, Netherlands, Norway, Poland, Portugal, Romania, San Marino, Spain, Slovakia, Slovenia, Sweden, Switzerland, UK and Northern Ireland.", 132 | "iban_incorrect": "The entered IBAN is incorrect.", 133 | "amount_too_small": "You can only cash out amounts greater than", 134 | "amount_too_big": "The amount you'd like to cash out exceeds your ether balance.", 135 | "account": "Your Bank Account", 136 | "form": { 137 | "owner": "Bank Account Owner", 138 | "amount": "Amount", 139 | "submit": "Cash out!" 140 | }, 141 | "history": { 142 | "title": "Order Status", 143 | "status_title": "Status", 144 | "disclaimer": "The amount of EURO that will show up in your bank account. Currently, guaranteeing bity.com's price rate is not possible. If you're interesting in helping to fix this, ", 145 | "click": "click here", 146 | "status": { 147 | "created": "Created your order with ID: ", 148 | "received": "Received your ether.", 149 | "executed": "Swapped your ether for EURO." 150 | } 151 | }, 152 | "errors": { 153 | "bity_connection": "There was a problem connecting to our exchange provider bity.com. Please try again later.", 154 | "ethgasstation_connection": "There was a problem getting the latest gas prices. Please try sending your transaction later." 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/i18n/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "de": { 3 | "app_name": "Burner", 4 | "about": "Über", 5 | "code": "Code", 6 | "burn": "PK verbrennen", 7 | "show": "PK anzeigen", 8 | "copy": "PK kopieren", 9 | "create": "Erstellen", 10 | "receive_title": "Empfangen", 11 | "receipt_title": "Beleg", 12 | "send_to_address_title": "An Adresse senden", 13 | "advance": "Erweitert", 14 | "advance_title": "Erweitert", 15 | "loading": "Lade...", 16 | "cancel": "Abbrechen", 17 | "done": "Fertig", 18 | "exchange_title": "Börse", 19 | "amount": "Betrag", 20 | "send": "Senden", 21 | "withdraw": "Abheben", 22 | "history_chat": "Verlauf", 23 | "request_funds_title": "Mittel anfordern", 24 | "request": "Anfrage", 25 | "vendors": "Verkäufer", 26 | "main_card": { 27 | "receive": "Empfangen", 28 | "send": "Senden", 29 | "share": "Teilen", 30 | "link": "Link", 31 | "vendors": "Verkäufer", 32 | "mint": "Film registrieren" 33 | }, 34 | "send_with_link": { 35 | "error": "Bitte einen güligen Betrag eingeben" 36 | }, 37 | "share": { 38 | "copied": "URL wurde in die Zwischenablage kopiert" 39 | }, 40 | "vendor": { 41 | "updating": "Aktualisiere", 42 | "open": "Offen", 43 | "closed": "Geschlossen", 44 | "add_product": "Produkt hinzufügen", 45 | "adding": "Füge hinzu" 46 | }, 47 | "withdraw_from_private": { 48 | "from_address": "Von Adresse abheben", 49 | "amount": "Abhebebetrag", 50 | "withdraw": "Abheben", 51 | "error": "Bitte einen gültigen Betrag für die Abhebung eingeben" 52 | }, 53 | "send_to_address": { 54 | "to_address": "An Adresse", 55 | "send_amount": "Betrag senden", 56 | "notice": "Du kannst nur folgenden Betrag senden: ", 57 | "error": "Bitte eine gültige Adresse oder ENS eingeben.", 58 | "currency_error": "Bitte aktualisiere deine burner-wallet zu einer neuen Version die Währungsumrechnung unterstützt." 59 | }, 60 | "send_by_scan": { 61 | "try_again": "Bitte versuchen Sie es erneut", 62 | "take_photo": "Foto machen", 63 | "capture": "QR-Code erfassen:" 64 | }, 65 | "more_buttons": { 66 | "request": "Anfrage", 67 | "exchange": "Börse" 68 | }, 69 | "receive": { 70 | "address_copied": "Adresse wurde in die Zwischenablage kopiert" 71 | }, 72 | "history": { 73 | "metamask_error": "Verschlüsselter Nachrichtenversand ist mit MetaMask nicht verfügbar", 74 | "wave": "Winken" 75 | }, 76 | "exchange": { 77 | "withdrawal_explanation": "Nur Verkäufer können mehr abheben, als sie eingezahlt haben", 78 | "funds_transferred": "Mittel übertragen!", 79 | "funds_bridged": "Mittel ausgetauscht", 80 | "insufficient_funds": "Nicht genug Mittel", 81 | "invalid_to_address": "Bitte eine gültige Adresse eingeben", 82 | "invalid_to_amount": "Bitte einen gültige Betrag eingeben", 83 | "calculate_gas_price": "Berechne den besten Gas-Preis...", 84 | "go_to_etherscan": "Auf Etherscan gehen?", 85 | "idk": "ich weiß nicht wie es hier weitergeht? vielleicht irgendetwas was die bridge erklärt?", 86 | "fast_exit_swap": "Dieser Währungswechsel wird in zwei Transaktionen stattfinden.", 87 | "fast_exit_patience": "Wechsel erfolgreich eingeleitet. Du wirst deinen DAI umgehend erhalten." 88 | }, 89 | "request_funds": { 90 | "item_message": "Gegenstand oder Nachricht", 91 | "button": "Anfragen", 92 | "amount": "Anzufragender Betrag" 93 | }, 94 | "admin": { 95 | "add_admin": "Admin hinzufügen", 96 | "adding": "Füge hinzu", 97 | "add_vendor": "Verkäufer hinzufügen" 98 | }, 99 | "advanced": { 100 | "to_qr": "Zu QR-Code" 101 | }, 102 | "balance_display": { 103 | "waiting_for_api": "Warte auf API", 104 | "no_currency_api": "Keine Währungs-API-Daten verfügbar" 105 | }, 106 | "burn_wallet": { 107 | "burn_private_key_question": "Sind Sie sich sicher, dass Sie diesen privaten Schlüssel verbrennen wollen?", 108 | "disclaimer": "Tun Sie das nicht! Sie werden all ihr Guthaben verlieren!", 109 | "cancel": "Nein, abbrechen", 110 | "burn": "Verbrenn ihn!" 111 | }, 112 | "mint": { 113 | "title": "Einen Film registrieren", 114 | "image_title": "Poster hochladen", 115 | "movie_title": "Film hochladen", 116 | "rightholder": { 117 | "address": "Adresse des Rechteinhabers", 118 | "name": "Name des Rechteinhabers" 119 | }, 120 | "movie": { 121 | "name": "Name des Films" 122 | } 123 | }, 124 | "offramp": { 125 | "title": "Zum Bankkonto überweisen", 126 | "success": "Überweisung erfolgreich. Überprüfen sie ihr Konto in den nächsten 24 Stunden.", 127 | "required": "Dieses Feld ist erforderlich.", 128 | "country_not_supported": "Dieses Land wird momentan noch nicht von Bity.com unterstützt. Unterstützte Länder sind: Österreich, Belgien, Bulgarien, Kroatien, Zypern, Tschechien, Dänemark, Estland, Finland, Frankreich, Deutschland, Griechenland, Ungarn, Island, Irland, Italien, Lettland, Liechtenstein, Litauen, Luxembourg, Malta, Monaco, Niederlande, Norwegen, Polen, Portugal, Rumänien, San Marino, Spanien, Slovakai, Slowenien, Schweden, Schweiz, Großbritannien", 129 | "iban_incorrect": "Die eingegebene IBAN ist inkorrekt.", 130 | "amount_too_small": "Sie können Beträge auszahlen die größer als", 131 | "amount_too_big": "Der auszuzahlende Betrag übersteigt ihren gesamten Ether Betrag.", 132 | "account": "Dein Bank Konto", 133 | "form": { 134 | "owner": "Kontoinhaber", 135 | "amount": "Wert", 136 | "submit": "Auszahlen!" 137 | }, 138 | "history": { 139 | "title": "Auftragsstatus", 140 | "status_title": "Status", 141 | "disclaimer": "Dieser Beitrag an EURO wird in wenigen Stunden auf dein Bankkonto überwiesen. Leider ist es im Moment nicht möglich eine Wechselrate (ETH/EUR) zu garantieren. Solltest du interessiert sein zu helfen, ", 142 | "click": "klicke hier", 143 | "status": { 144 | "created": "Auftrag erstellt mit ID: ", 145 | "received": "Ether erhalten.", 146 | "received": "Ether gegen EURO getauscht." 147 | } 148 | }, 149 | "errors": { 150 | "bity_connection": "Es gab ein Problem damit eine Verbindung zu unserer Partnerbörse bity.com herzustellen. Bitte versuche es später nochmal.", 151 | "ethgasstation_connection": "Es gab ein Problem damit die zuletzt aktuellen Gaspreise für das Ethereum Netzwerk zu bestimmen. Bitte versuche daher deine Transaktion zu einem späteren Zeitpunkt zu senden." 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /scripts/build.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 = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const path = require('path'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const fs = require('fs-extra'); 21 | const webpack = require('webpack'); 22 | const bfj = require('bfj'); 23 | const configFactory = require('../config/webpack.config'); 24 | const paths = require('../config/paths'); 25 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 26 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 27 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 28 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 29 | const printBuildError = require('react-dev-utils/printBuildError'); 30 | 31 | const measureFileSizesBeforeBuild = 32 | FileSizeReporter.measureFileSizesBeforeBuild; 33 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | 36 | // These sizes are pretty large. We'll warn for bundles exceeding them. 37 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 38 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 39 | 40 | const isInteractive = process.stdout.isTTY; 41 | 42 | // Warn and crash if required files are missing 43 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 44 | process.exit(1); 45 | } 46 | 47 | // Process CLI arguments 48 | const argv = process.argv.slice(2); 49 | const writeStatsJson = argv.indexOf('--stats') !== -1; 50 | 51 | // Generate configuration 52 | const config = configFactory('production'); 53 | 54 | // We require that you explicitly set browsers and do not fall back to 55 | // browserslist defaults. 56 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 57 | checkBrowsers(paths.appPath, isInteractive) 58 | .then(() => { 59 | // First, read the current file sizes in build directory. 60 | // This lets us display how much they changed later. 61 | return measureFileSizesBeforeBuild(paths.appBuild); 62 | }) 63 | .then(previousFileSizes => { 64 | // Remove all content but keep the directory so that 65 | // if you're in it, you don't end up in Trash 66 | fs.emptyDirSync(paths.appBuild); 67 | // Merge with the public folder 68 | copyPublicFolder(); 69 | // Start the webpack build 70 | return build(previousFileSizes); 71 | }) 72 | .then( 73 | ({ stats, previousFileSizes, warnings }) => { 74 | if (warnings.length) { 75 | console.log(chalk.yellow('Compiled with warnings.\n')); 76 | console.log(warnings.join('\n\n')); 77 | console.log( 78 | '\nSearch for the ' + 79 | chalk.underline(chalk.yellow('keywords')) + 80 | ' to learn more about each warning.' 81 | ); 82 | console.log( 83 | 'To ignore, add ' + 84 | chalk.cyan('// eslint-disable-next-line') + 85 | ' to the line before.\n' 86 | ); 87 | } else { 88 | console.log(chalk.green('Compiled successfully.\n')); 89 | } 90 | 91 | console.log('File sizes after gzip:\n'); 92 | printFileSizesAfterBuild( 93 | stats, 94 | previousFileSizes, 95 | paths.appBuild, 96 | WARN_AFTER_BUNDLE_GZIP_SIZE, 97 | WARN_AFTER_CHUNK_GZIP_SIZE 98 | ); 99 | console.log(); 100 | 101 | const appPackage = require(paths.appPackageJson); 102 | const publicUrl = paths.publicUrl; 103 | const publicPath = config.output.publicPath; 104 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 105 | printHostingInstructions( 106 | appPackage, 107 | publicUrl, 108 | publicPath, 109 | buildFolder, 110 | useYarn 111 | ); 112 | }, 113 | err => { 114 | console.log(chalk.red('Failed to compile.\n')); 115 | printBuildError(err); 116 | process.exit(1); 117 | } 118 | ) 119 | .catch(err => { 120 | if (err && err.message) { 121 | console.log(err.message); 122 | } 123 | process.exit(1); 124 | }); 125 | 126 | // Create the production build and print the deployment instructions. 127 | function build(previousFileSizes) { 128 | console.log('Creating an optimized production build...'); 129 | 130 | let compiler = webpack(config); 131 | return new Promise((resolve, reject) => { 132 | compiler.run((err, stats) => { 133 | let messages; 134 | if (err) { 135 | if (!err.message) { 136 | return reject(err); 137 | } 138 | messages = formatWebpackMessages({ 139 | errors: [err.message], 140 | warnings: [], 141 | }); 142 | } else { 143 | messages = formatWebpackMessages( 144 | stats.toJson({ all: false, warnings: true, errors: true }) 145 | ); 146 | } 147 | if (messages.errors.length) { 148 | // Only keep the first error. Others are often indicative 149 | // of the same problem, but confuse the reader with noise. 150 | if (messages.errors.length > 1) { 151 | messages.errors.length = 1; 152 | } 153 | return reject(new Error(messages.errors.join('\n\n'))); 154 | } 155 | if ( 156 | process.env.CI && 157 | (typeof process.env.CI !== 'string' || 158 | process.env.CI.toLowerCase() !== 'false') && 159 | messages.warnings.length 160 | ) { 161 | console.log( 162 | chalk.yellow( 163 | '\nTreating warnings as errors because process.env.CI = true.\n' + 164 | 'Most CI servers set it automatically.\n' 165 | ) 166 | ); 167 | return reject(new Error(messages.warnings.join('\n\n'))); 168 | } 169 | 170 | const resolveArgs = { 171 | stats, 172 | previousFileSizes, 173 | warnings: messages.warnings, 174 | }; 175 | if (writeStatsJson) { 176 | return bfj 177 | .write(paths.appBuild + '/bundle-stats.json', stats.toJson()) 178 | .then(() => resolve(resolveArgs)) 179 | .catch(error => reject(new Error(error))); 180 | } 181 | 182 | return resolve(resolveArgs); 183 | }); 184 | }); 185 | } 186 | 187 | function copyPublicFolder() { 188 | fs.copySync(paths.appPublic, paths.appBuild, { 189 | dereference: true, 190 | filter: file => file !== paths.appHtml, 191 | }); 192 | } 193 | --------------------------------------------------------------------------------