diff --git a/App.js b/App.js index 66013ed..c1e6ccc 100644 --- a/App.js +++ b/App.js @@ -15,7 +15,7 @@ import { import { createAppContainer, createMaterialTopTabNavigator, createStackNavigator } from 'react-navigation'; import Apps, {Foam, Fork, Mintbase, Test} from './src/Apps' import CameraScreen from './src/AliceCore/Screens/Camera'; -import Profile from './src/AliceCore/Screens/Profile'; +import Tokens from './src/AliceCore/Screens/Tokens'; import MapboxGL from '@react-native-mapbox-gl/maps'; MapboxGL.setAccessToken('pk.eyJ1IjoibWFya3BlcmVpciIsImEiOiJjancwNDg4eWswNzk1NGJ0Z3V5OGtxZWltIn0.gZ7ev6fQETAFa4J9kao10w'); //TODO: change API key on release to TestFlight @@ -64,8 +64,8 @@ const AppTabNavigator = createMaterialTopTabNavigator({ ) } }, - Profile: { - screen: Profile, + Tokens: { + screen: Tokens, navigationOptions: { tabBarLabel: 'Settings', tabBarIcon: ({ focused }) => ( @@ -86,8 +86,8 @@ const AppTabNavigator = createMaterialTopTabNavigator({ } } }, { - initialRouteName: 'Profile', - order: ['Home', 'Apps', 'Profile', 'Activity'], + initialRouteName: 'Tokens', + order: ['Home', 'Apps', 'Tokens', 'Activity'], tabBarPosition: 'bottom', animationEnabled: true, tabBarOptions: { diff --git a/EmbeddedView.js b/EmbeddedView.js index 0a483ab..2f33e42 100644 --- a/EmbeddedView.js +++ b/EmbeddedView.js @@ -10,6 +10,7 @@ export default class SimpleView extends React.Component{ return ( {this.props.children} + TEST ); } diff --git a/ios b/ios index 4dd9a5a..fcfa1ee 160000 --- a/ios +++ b/ios @@ -1 +1 @@ -Subproject commit 4dd9a5a448778c375b023ff0b20c6059b1da84a3 +Subproject commit fcfa1eea2c59139d19ea3629617edc601c639395 diff --git a/src/AliceComponents/TransactionModal/CameraModal.js b/src/AliceComponents/TransactionModal/CameraModal.js new file mode 100644 index 0000000..bb11c3f --- /dev/null +++ b/src/AliceComponents/TransactionModal/CameraModal.js @@ -0,0 +1,113 @@ +'use strict'; + +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import {View, StyleSheet, TouchableOpacity, Text} from 'react-native' +import Modal from "react-native-modal"; +import Camera from "../../AliceSDK/Camera"; +import ReactNativeHaptic from 'react-native-haptic-feedback'; +import _ from "lodash"; +// const Web3 = require('web3'); +// const web3 = new Web3(); +// web3.setProvider(new web3.providers.HttpProvider('https://ropsten.infura.io/rqmgop6P5BDFqz6yfGla')); +// const privKey = 'cf06f0b35515af10b5dfef470e3a1e743470bf9033d06f198b4e829cb2e7ef05'; + +export default class CameraModal extends Component { + static propTypes = { + visible: PropTypes.bool, + modalControl: PropTypes.func + }; + + static defaultProps = { + screen: false + }; + + constructor(props) { + super(props); + this.camera = null; + this.state = { + exchangeRate: 0, + account: 'alpha', + visibleModal: null, + publicAddress: '', + txCount: 0, + balance: 0, + privKey: false, + pubKey: false, + mode: 'loading', + transaction: null, + result: null, + signedTx: null, + phoneUid: '', + showInput: false, + socketId: '', + cameraType: 'back', + flash: false, + }; + } + + // _signKey = async (msg, socketId) => { + // // ReactNativeHaptic.generate('selection'); + // const account = await web3.eth.accounts.privateKeyToAccount(privKey) + // .sign(msg); + // + // console.log('PHONE ID: ', this.state.phoneUid); + // fetch(`https://login.tenzorum.app/login/${socketId}/${this.state.phoneUid}/${msg}/${account.signature}`) + // .then((res) => { + // console.log('RES: ', res) + // }) + // }; + // + _onBarcodeRead = (read) => { + if (read) { + console.log('READ PUBLIC ADDRESS', read.data) + this.props.addressScan(read.data) + this.props.modalControl(); + // const dataArray = read.data.split('.'); + // TouchID.authenticate('verify user') + // .then(success => { + // this._signKey(dataArray[0], dataArray[1]); + // this.setState({socketId: dataArray[1]}) + // }) + // .catch(error => { + // console.log('ERROR: ', error); + // }); + } + }; + + render () { + return ( + + + + x + + + ) + } +} + +const styles = StyleSheet.create({ + modal: { + width: 250, + height: 250, + borderRadius: 15, + backgroundColor: 'white', + padding: 10, + alignItems: 'center', + justifyContent: 'center' + }, + rectangleContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'transparent' + } + }) + + diff --git a/src/AliceComponents/TransactionModal/Input.js b/src/AliceComponents/TransactionModal/Input.js new file mode 100644 index 0000000..278e8b9 --- /dev/null +++ b/src/AliceComponents/TransactionModal/Input.js @@ -0,0 +1,88 @@ +import React, { Component } from 'react'; +import { + AlertIOS, + AppRegistry, + Dimensions, + Image, + ScrollView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from 'react-native'; + + +let {height, width} = Dimensions.get('window'); + +export default class Input extends Component { + constructor() { + super(); + this.state = { + visibleModal: null + }; + } + + render() { + const {} = this.state; + const {onChangeText, _this, placeholder, keyboardType, autoCapitalize, value} = this.props; + return ( + + + + ) + } +} + + +const styles = StyleSheet.create({ + inputContainer: { + width: '100%', + height: 40, + borderRadius: 10, + backgroundColor: '#ccc', + }, + input: { + flex: 1, + backgroundColor: 'transparent', + // borderColor: '#ccc', + // borderWidth: 1, + // borderRadius: 10, + paddingLeft: 4, + overflow: 'hidden', + // shadowColor: 'rgba(0,0,0,0.5)', + // shadowRadius: 1, + // shadowOpacity: 1, + // shadowOffset: { + // width: 0, + // height: 0 + // }, + }, + inputStyle: { + marginLeft: 10, + backgroundColor: "transparent", + height: 35, + width: '100%', + padding: 5, + fontWeight: '900', + fontSize: 20, + color: '#333', + }, + topNavText: { + fontFamily: 'DIN Condensed', + color: 'white', + fontSize: 16, + }, +}); + diff --git a/src/AliceComponents/TransactionModal/QrModal.js b/src/AliceComponents/TransactionModal/QrModal.js new file mode 100644 index 0000000..7978a71 --- /dev/null +++ b/src/AliceComponents/TransactionModal/QrModal.js @@ -0,0 +1,56 @@ +'use strict'; + +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { + Clipboard, + StyleSheet, + Text, + TouchableOpacity +} from 'react-native' +import QRCode from 'react-native-qrcode-svg' +import Modal from 'react-native-modal' + +export default class QrModal extends Component { + static propTypes = { + value: PropTypes.string.isRequired, + isVisible: PropTypes.bool, + }; + + static defaultProps = { + screen: false + }; + + render () { + return ( + + Clipboard.setString(this.props.value)} style={styles.modal}> + + {this.props.value} + + + x + + + + ) + } +} + +const styles = StyleSheet.create({ + modal: { + width: 250, + height: 350, + borderRadius: 15, + backgroundColor: 'white', + padding: 10, + alignItems: 'center', + justifyContent: 'center' + }, + rectangleContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'transparent' + } +}) diff --git a/src/AliceComponents/TransactionModal/index.js b/src/AliceComponents/TransactionModal/index.js new file mode 100644 index 0000000..0f2edfc --- /dev/null +++ b/src/AliceComponents/TransactionModal/index.js @@ -0,0 +1,325 @@ +import React, { Component } from 'react'; +import { + Alert, + AppRegistry, + Clipboard, + StyleSheet, + Text, + View, + Image, + Dimensions, + KeyboardAvoidingView, + Keyboard, + ScrollView, + TouchableOpacity, + Platform, + TouchableWithoutFeedback, +} from 'react-native'; + +import Modal from 'react-native-modal'; +// import FeatherIcon from 'react-native-vector-icons/Feather' +// import MaterialCommIcon from "react-native-vector-icons/MaterialCommunityIcons"; +// import { RoundButton } from 'react-native-button-component'; +// import * as Animatable from 'react-native-animatable'; +// +// const Web3 = require('web3'); +// const web3 = new Web3(); +// web3.setProvider(new web3.providers.HttpProvider('https://ropsten.infura.io/')); + +import Input from './Input' +import {color} from "../../AliceUtils/themes"; +import {addressResolver, checkSubdomainOwner, newSubdomain} from "../../AliceUtils/ensFunctions"; + +// import QrModal from './QrModal'; +import CameraModal from './CameraModal'; +import {shadow, text} from "../../AliceUtils/themes"; +import {navigate} from "../../AliceUtils/navigationWrapper"; + +const personalWalletAddress = "0xf8894138aa4d7b54b7d49afa9d5600cdb5178721"; + +const emptyAddress = '0x0000000000000000000000000000000000000000'; + +const publicAddress = "0xb78197a43836e084bE4ff1F4c84d7557EA11F214"; + +// import { transferEtherNoReward, transferTokensNoReward, transferTokensWithTokenReward, transferEtherWithEtherReward } from 'tenzorum' +import {Wallet} from "../../AliceSDK"; +// import {getBalance, getTenzBalance} from "../../utils/ether"; + + +const cryptoCurrencies = [ + { name: 'Tenzorum', symbol: 'TENZ', abi: [], imageUrl: require('../../AliceAssets/localethereum.png'), type: "token", balance: 0, address: "0xB07C36074b8333B01e38A307df804FDc6c37e0eC", }, + { name: 'Ethereum', symbol: 'ETH', abi: [], imageUrl: require('../../AliceAssets/localethereum.png'), type: "cryptoCurrency", balance: '0', }, + { name: 'DAI', symbol: 'DAI', abi: [], imageUrl: require('../../AliceAssets/localethereum.png'), type: "token", balance: 52, }, + { name: 'FOAM', symbol: 'FOAM', abi: [], imageUrl: require('../../AliceAssets/localethereum.png'), type: "token", balance: 1000, }, + { name: 'Akropolis', symbol: 'AKR', abi: [], imageUrl: require('../../AliceAssets/localethereum.png'), type: "token", balance: 0, }, +]; + +let {height, width} = Dimensions.get('window'); + +type Props = {}; +export default class TransactionModal extends Component { + constructor(props){ + super(props); + this.state = { + buttonState: 'upload', + cryptoBalance: '', + ensDomain: '', + ensAvailable: false, + publicAddress: '', + ensMessage: 'Enter a public address or ENS username', + addressChecked: false, + cameraModalVisible: false, + qrModalVisible: false, + currentCrypto: { name: 'Select Currency' }, + tenzBalance: 2, + ethBalance: 0, + amount: '', + reward: '', + } + } + + handleTextRef = ref => this.text = ref; + + async componentDidMount() { + this.setState({publicAddress: await Wallet.getAddress()}); + Wallet.getBalance().then(bal => this.setState({ethBalance: bal})); + } + + _navigateToWebView = (uri) => { + uri = uri || '0x08b79a6a11624e666d21420e9a2bdcdbf9ecfb43b4537e346bf8b35530f0750e'; + this.setState({ buttonState: 'upload' }); + navigate('WebView', {uri: 'https://ropsten.etherscan.io/search?q='+uri}) + this.props.modalControl(); + }; + + // _sendTransaction = async () => { + // const {reward, amount, currentCrypto, publicAddress} = this.state; + // let response; + // Keyboard.dismiss(); + // try { + // if (currentCrypto.type === "token") { + // if (reward) { + // response = await transferTokensWithTokenReward(currentCrypto.address, web3.utils.toWei(amount, "ether"), publicAddress, web3.utils.toWei(reward, "ether")); + // console.log('PAYLOAD: ', response); + // } else { + // response = await transferTokensNoReward(currentCrypto.address, web3.utils.toWei(amount, "ether"), publicAddress); + // console.log('PAYLOAD: ', response); + // } + // } else if (currentCrypto.type === "cryptoCurrency") { + // if (reward) { + // response = await transferEtherWithEtherReward(web3.utils.toWei(amount, "ether"), publicAddress, web3.utils.toWei(reward, "ether")); + // console.log('PAYLOAD: ', response); + // } else { + // response = await transferEtherNoReward(web3.utils.toWei(amount, "ether"), publicAddress); + // console.log('PAYLOAD: ', response); + // } + // } else { + // console.log("set currency") + // } + // if (response.txHash) { + // this.setState({ buttonState: 'success' }) + // Alert.alert( + // 'Transaction Submitted', + // 'View on etherscan?', + // [ + // {text: 'View', onPress: () => this._navigateToWebView(response.txHash)}, + // {text: 'Close', onPress: () => this.props.modalControl(), style: 'cancel'}, + // ], + // { cancelable: false } + // ) + // } + // return response; + // + // } catch(e) { + // this.setState({ buttonState: 'upload' }) + // console.log("Unable to make token transfer with no reward") + // } + // }; + + _resolveAddress = async (ensUsername) => { + const {publicAddress} = this.state; + this.setState({addressChecked: true}); + if (ensUsername.length === 0) { + this.setState({ensAvailable: false, ensMessage: 'Enter a valid or unempty username'}); + } else if (true === true) { + if (ensUsername === emptyAddress) { + this.setState({ensAvailable: false, ensMessage: 'Invalid address'}); + } else if(ensUsername === publicAddress) { + this.setState({ensAvailable: true, ensMessage: "It's your domain!"}); + } else { + console.log(this.state.publicAddress) + this.setState({publicAddress: ensUsername}); + this.setState({ensAvailable: true, ensMessage: "Valid address: " + ensUsername}); + } + return ensUsername; + + } else { + this.setState({ensDomain: ensUsername}); + const {ensDomain} = this.state; + const addr = await addressResolver(ensUsername); + this.setState({publicAddress: addr}); + if (addr === emptyAddress) { + this.setState({ensAvailable: false, ensMessage: 'Invalid address'}); + } else if(addr === publicAddress) { + this.setState({ensAvailable: true, ensMessage: "It's your domain!"}); + } else { + this.setState({ensAvailable: true, ensMessage: "Valid address: " + this.state.publicAddress}); + } + return addr; + } + + }; + + _chooseBalance = (crypto) => { + switch(crypto) { + case "Tenzorum": + return this.state.tenzBalance; + case "Ethereum": + return this.state.ethBalance; + default: + return; + } + }; + + _addressScan = async (address) => { + const resolve = await this._resolveAddress(address); + if (resolve) this.setState({publicAddress: address, ensDomain: address}); + } + + render() { + const { ensDomain, ensAvailable, ensMessage, cameraModalVisible, qrModalVisible, reward, amount } = this.state; + const { isVisible } = this.props; + return ( + + + + + x + + + + + + {cryptoCurrencies.map((currency, key) => { + let amount = this._chooseBalance(currency.name); + const {imageUrl, balance} = currency; + let selectStyle = this.state.selected === key ? {backgroundColor: '#cbc7ef'} : {}; + return ( this.setState({currentCrypto: currency, selected: key})}> + {amount || balance} + {currency.symbol} + + ) + })} + + {this.state.currentCrypto.name} + + + + this.setState({cameraModalVisible: !cameraModalVisible})} style={styles.squareButton}> + + + + + + {ensMessage} + + + + this.setState({amount})} autoCapitalize="none" value={amount}/> + + + + this.setState({reward})} autoCapitalize="none" value={reward}/> + + + + + this.setState({cameraModalVisible: false})} addressScan={this._addressScan}/> + + ) + } +} + + +const styles = StyleSheet.create({ + mainContainer: { + backgroundColor: color.component, + borderRadius: 10, + height: 160, + padding: 20, + width: width - 40, + }, + closeButtonContainer: { + flexDirection: 'row', + alignItems: 'flex-end', + justifyContent: 'flex-end', + width: width-50, + height: 60, + }, + button: { + height: 40, + width: 40, + borderRadius: 10, + marginBottom: 10, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(0,0,0,0.5)' + }, + actionButtonIcon: { + fontSize: 20, + height: 22, + color: 'white', + }, + container: { + flex: 1, + padding: 20, + backgroundColor: '#d3e4ee', + }, + cryptoBox: { + width: 80, + alignItems: 'center', + justifyContent: 'space-around', + height: 120, + borderRadius: 15, + backgroundColor: 'white', + marginLeft: 10, + ...shadow + }, + header: { + height: 100, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + inputAndButton: { + flexDirection: 'row', + width: 265, + }, + sendButton: { + marginLeft: 10, + borderRadius: 10, + height: 40, + width: 80, + backgroundColor: '#a25cee', + alignItems: 'center', + justifyContent: 'center', + }, + squareButton: { + marginLeft: 10, + borderRadius: 10, + height: 40, + width: 40, + backgroundColor: '#3f69ee', + alignItems: 'center', + justifyContent: 'center', + ...shadow + }, + transactionBox: { + width: width - 40, + height: 410, + padding: 10, + borderRadius: 15, + backgroundColor: 'white', + ...shadow + }, +}); diff --git a/src/AliceCore/Screens/Activity.js b/src/AliceCore/Screens/Activity.js index aa4182b..134a3c9 100644 --- a/src/AliceCore/Screens/Activity.js +++ b/src/AliceCore/Screens/Activity.js @@ -31,54 +31,55 @@ export default class ActivityClass extends Component { } getTokenInfo = async () => { - // let activity; - // try { - // activity = await ThreeBoxActivity.get('0xA1b02d8c67b0FDCF4E379855868DeB470E169cfB'); - // const categorizedActivity = await addDataType(activity); - // let feed = categorizedActivity.internal - // .concat(categorizedActivity.txs) - // .concat(categorizedActivity.token); - // // if timestamp is undefined, give it the timestamp of the previous entry - // feed.map((item, i) => { - // const feedItem = item; - // if (!feedItem.timeStamp) { - // const deletedTime = parseInt(feed[i - 1].timeStamp, 10) + 1; - // feedItem.timeStamp = deletedTime.toString(); - // } - // return feedItem; - // }); - // - // // order feed chronologically - // feed.sort((a, b) => b.timeStamp - a.timeStamp); - // - // // order feed by address - // let feedByAddress = []; - // feed.forEach((item) => { - // // group feed by 3box or counterparty address activity - // if (feedByAddress.length > 0 && - // Object.keys(feedByAddress[feedByAddress.length - 1])[0] === othersAddress) { - // feedByAddress[feedByAddress.length - 1][othersAddress].push(item); - // } else if (feedByAddress.length > 0 && Object.keys(feedByAddress[feedByAddress.length - 1])[0] === 'threeBox' && !item.spaceName && (item.dataType === 'Public' || item.dataType === 'Private')) { - // feedByAddress[feedByAddress.length - 1].threeBox.push(item); - // } else if (feedByAddress.length > 0 && Object.keys(feedByAddress[feedByAddress.length - 1])[0] === item.spaceName) { - // feedByAddress[feedByAddress.length - 1][item.spaceName].push(item); - // } else if (item.spaceName) { - // feedByAddress.push({ - // [item.spaceName]: [item], - // }); - // } else if ((item.dataType === 'Public' || item.dataType === 'Private') && !item.spaceName) { - // feedByAddress.push({ - // threeBox: [item], - // }); - // } else { - // console.log('meh others address') - // } - // }); - // - // this.setState({activity, fetching: false, categorizedActivity, feed}); - // } catch(e) { - // console.log('ACTIVITY ERROR: ', e); - // } + let activity; + try { + activity = await ThreeBoxActivity.get('0xA1b02d8c67b0FDCF4E379855868DeB470E169cfB'); + console.log('ACTIVITY: ', activity) + const categorizedActivity = await addDataType(activity); + let feed = categorizedActivity.internal + .concat(categorizedActivity.txs) + .concat(categorizedActivity.token); + // if timestamp is undefined, give it the timestamp of the previous entry + feed.map((item, i) => { + const feedItem = item; + if (!feedItem.timeStamp) { + const deletedTime = parseInt(feed[i - 1].timeStamp, 10) + 1; + feedItem.timeStamp = deletedTime.toString(); + } + return feedItem; + }); + + // order feed chronologically + feed.sort((a, b) => b.timeStamp - a.timeStamp); + + // order feed by address + let feedByAddress = []; + feed.forEach((item) => { + // group feed by 3box or counterparty address activity + if (feedByAddress.length > 0 && + Object.keys(feedByAddress[feedByAddress.length - 1])[0] === othersAddress) { + feedByAddress[feedByAddress.length - 1][othersAddress].push(item); + } else if (feedByAddress.length > 0 && Object.keys(feedByAddress[feedByAddress.length - 1])[0] === 'threeBox' && !item.spaceName && (item.dataType === 'Public' || item.dataType === 'Private')) { + feedByAddress[feedByAddress.length - 1].threeBox.push(item); + } else if (feedByAddress.length > 0 && Object.keys(feedByAddress[feedByAddress.length - 1])[0] === item.spaceName) { + feedByAddress[feedByAddress.length - 1][item.spaceName].push(item); + } else if (item.spaceName) { + feedByAddress.push({ + [item.spaceName]: [item], + }); + } else if ((item.dataType === 'Public' || item.dataType === 'Private') && !item.spaceName) { + feedByAddress.push({ + threeBox: [item], + }); + } else { + console.log('meh others address') + } + }); + + this.setState({activity, fetching: false, categorizedActivity, feed}); + } catch(e) { + console.log('ACTIVITY ERROR: ', e); + } }; @@ -87,12 +88,13 @@ export default class ActivityClass extends Component { }; render() { + console.log('STATE: ', this.state) return ( - navigate('Profile')}> + navigate('Tokens')}> diff --git a/src/AliceCore/Screens/Dapps.js b/src/AliceCore/Screens/Dapps.js new file mode 100644 index 0000000..90e4c5d --- /dev/null +++ b/src/AliceCore/Screens/Dapps.js @@ -0,0 +1,190 @@ +/* + * This is the ExampleMaps Registrar for Alice + * Register your app by: + * 1. Creating a folder in the src/Apps directory which contains your React Native app + * 2. Exporting your app in the ExampleMaps Export Section + * 3. Adding your app to the list of apps in the Apps List Section +*/ + +import React, { Component } from 'react'; +import { + StyleSheet, Text, TouchableOpacity, Keyboard, TouchableWithoutFeedback, Image, View, Modal, Dimensions, WebView, +} from 'react-native'; +import Icon from '../../AliceComponents/IconComponent'; +import {navigate} from "../../AliceUtils/navigationWrapper"; +import AppIcon from "../../AliceComponents/AppIcon"; +import {Settings} from "../../AliceSDK/Web3"; + +import { AppRegistry } from './../../Apps'; +import ReactNativeHapticFeedback from "react-native-haptic-feedback"; + +const options = { + enableVibrateFallback: true, + ignoreAndroidSystemSettings: false +}; + +const WEBVIEW = 'WEBVIEW'; + +const { height, width } = Dimensions.get('window'); + +type Props = {}; +export default class AppsScreen extends Component { + static navigationOptions = ({ navigation }) => { + const { navigate } = navigation; + return { + tabBarIcon: ({ tintColor }) => , + }; + }; + + state = { + modalVisible: false, + }; + + openBrowser = () => { + ReactNativeHapticFeedback.trigger("impactLight", options); + Settings.openBrowser() + }; + + back = () => { + this.refs[WEBVIEW].goBack() + }; + forward = () => { + this.refs[WEBVIEW].goForward() + }; + reload = () => { + this.refs[WEBVIEW].reload() + }; + stopLoading = () => { + this.refs[WEBVIEW].stopLoading() + }; + + render() { + return ( + + <> + + navigate('Tokens')}> + + + + + + + + + {AppRegistry.map((app, i) => { + return () + }) + } + + + { + Alert.alert('Modal has been closed.'); + }}> + + console.log('message: ', e.nativeEvent.data)} + injectedJavaScript="window.onscroll=(e) => {window.postMessage('hello')};" + // injectedJavaScript={'(function(){return "Send me back!"}());'} + // injectedJavaScript="window.onscroll=function(e){window.postMessage(e)}';" + > + + + + {'<'} + + + {'>'} + + + C + + + X + + + + + + + ); + } +} + +const styles = StyleSheet.create({ + H1: { + fontWeight: 'bold', + color: 'black', + fontSize: 17, + }, + appIcon: { + alignItems: 'center', + height: 84, + margin: 10, + maxWidth: 84, + justifyContent: 'space-between', + }, + appsContainer: { + alignItems: 'center', + flexDirection: 'row', + flexWrap: 'wrap', + paddingBottom: 10, + paddingTop: 10, + width: width - 20, + + }, + appSquare: { + alignItems: 'center', + backgroundColor: '#43fd9c', + borderRadius: 32.5, + height: 65, + justifyContent: 'center', + width: 65, + shadowColor: '#212121', + shadowOffset: { + width: 0, + height: 3, + }, + shadowRadius: 10, + shadowOpacity: 0.1, + }, + appText: { + color: 'black', + fontSize: 10, + // fontFamily: 'Graphik', + }, + container: { + flex: 1, + padding: 20, + flexDirection: 'column', + justifyContent: 'flex-start', + alignItems: 'flex-start', + backgroundColor: 'white', + }, + headingText: { + color: 'black', + fontSize: 20, + // fontFamily: 'Graphik', + fontWeight: '500' + }, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 10, + }, + instructions: { + textAlign: 'center', + color: '#333333', + marginBottom: 5, + }, +}); diff --git a/src/AliceCore/Screens/Profile.js b/src/AliceCore/Screens/Tokens.js similarity index 89% rename from src/AliceCore/Screens/Profile.js rename to src/AliceCore/Screens/Tokens.js index bd56cea..4bcaeb7 100644 --- a/src/AliceCore/Screens/Profile.js +++ b/src/AliceCore/Screens/Tokens.js @@ -24,11 +24,12 @@ import QRCode from 'react-native-qrcode-svg'; import {Wallet} from '../../AliceSDK/Web3' import AppIcon from "../../AliceComponents/AppIcon"; import {AppRegistry} from "../../Apps"; +import TransactionModal from '../../AliceComponents/TransactionModal' //TODO: needs api key -export default class Profile extends Component { +export default class Tokens extends Component { constructor(props) { super(props); @@ -40,6 +41,7 @@ export default class Profile extends Component { nfts: [], profileModalVisible: false, tokenModalVisible: false, + transactionModalVisible: false, address: '' }; @@ -60,7 +62,7 @@ export default class Profile extends Component { onData(JSON.parse(this.responseText)); } }); - xhr.open("GET", "https://api.ethplorer.io/getAddressInfo/0xA1b02d8c67b0FDCF4E379855868DeB470E169cfB?apiKey=freekey"); + xhr.open("GET", "https://api.ethplorer.io/getAddressInfo/"+await Wallet.getAddress()+"?apiKey=freekey"); xhr.send(data); }; @@ -70,7 +72,8 @@ export default class Profile extends Component { }; openTokenModal = (tokenInfo, token) => { - this.setState({tokenModalVisible: !this.state.tokenModalVisible, tokenInfo, token}) + this.setState({transactionModalVisible: !this.state.transactionModalVisible, tokenInfo, token}) + // this.setState({transactionModalVisible: !this.state.transactionModalVisible, tokenModalVisible: !this.state.tokenModalVisible, tokenInfo, token}) }; closeTokenModal = () => { @@ -88,13 +91,14 @@ export default class Profile extends Component { onData(JSON.parse(this.responseText)); } }); - xhr.open("GET", "https://api.opensea.io/api/v1/assets?owner=0xA1b02d8c67b0FDCF4E379855868DeB470E169cfB"); + xhr.open("GET", "https://api.opensea.io/api/v1/assets?owner="+await Wallet.getAddress()); xhr.send(data); }; render() { - console.log('ADDRESS: ', this.state.address) + console.log('ADDRESS: ', this.state.address); + const { transactionModalVisible } = this.state; return ( {this.state.address} @@ -174,6 +178,7 @@ export default class Profile extends Component { + this.setState({transactionModalVisible: !transactionModalVisible})}/> ); } diff --git a/src/AliceSDK/Web3/index.js b/src/AliceSDK/Web3/index.js index fa35497..cb8552f 100644 --- a/src/AliceSDK/Web3/index.js +++ b/src/AliceSDK/Web3/index.js @@ -1,6 +1,6 @@ import { NativeModules, NativeEventEmitter } from "react-native"; import {ethers, Contract as EthersContract} from 'ethers'; -let infuraProvider = new ethers.providers.InfuraProvider('ropsten'); +let infuraProvider = new ethers.providers.InfuraProvider('mainnet'); // const getAddress = (cb) => NativeModules.WalletModule.getAddress(cb); @@ -29,6 +29,14 @@ const sendTransaction = async ({to, value, data}) => { } }; +const sendTransactionWithDapplet = async ({to, value, data}) => { + try { + return await NativeModules.WalletModule.sendTransactionWithDapplet(to, value, data); + } catch(e) { + return "Send transaction failed with error: " + e + } +}; + const signTransaction = async ({to, value, data}) => { try { return await NativeModules.WalletModule.signTransaction(to, value, data); @@ -39,18 +47,18 @@ const signTransaction = async ({to, value, data}) => { const signMessage = async (message) => { try { - return await NativeModules.WalletModule.signMessage(message); + return await NativeModules.WalletModule.signMessage(ethers.utils.formatBytes32String(message)); } catch(e) { return "Sign message failed with error: " + e } -} +}; const settingsPopUp = () => NativeModules.NativeVCModule.setting(); -const openBrowser = () => NativeModules.NativeVCModule.browser('foam.space'); +const openBrowser = (url) => url ? NativeModules.NativeVCModule.browser(url) : NativeModules.NativeVCModule.browser('duckduckgo.com'); const sendToken = () => { - + return "Coming Soon!" }; const write = async ({contractAddress, abi, functionName, parameters, value, data}) => { @@ -59,7 +67,6 @@ const write = async ({contractAddress, abi, functionName, parameters, value, dat } catch(e) { return "Write to contract failed with error: " + e } - }; const read = async ({contractAddress, abi, functionName, parameters}) => { @@ -69,12 +76,19 @@ const read = async ({contractAddress, abi, functionName, parameters}) => { } else if (parameters.length > 0) { return contract[functionName](...parameters); } +}; +const resolve = async (ensName) => { + if (ensName.slice(-4) === '.eth') { + try { + return await infuraProvider.resolveName(ensName); + } catch (e) { + return "ENS resolver error: " + e; + } + } }; const walletChangeEvent = () => { - console.log('Native Event Emitter: ', NativeEventEmitter) - console.log('Native Modules: ', NativeModules) return new NativeEventEmitter(NativeModules.CallRNModule); }; @@ -91,9 +105,14 @@ export const Wallet = { signMessage, sendToken, walletChangeEvent, + sendTransactionWithDapplet }; export const Contract = { write, read }; + +export const ENS = { + resolve +} diff --git a/src/AliceSDK/index.js b/src/AliceSDK/index.js index 8e5f3d5..a0d18e4 100644 --- a/src/AliceSDK/index.js +++ b/src/AliceSDK/index.js @@ -1,7 +1,11 @@ import Map from "@react-native-mapbox-gl/maps"; import {RNCamera as Camera} from 'react-native-camera'; +import {Wallet, Settings, Contract} from "./Web3"; export default { Map, Camera, + Wallet, + Settings, + Contract } diff --git a/src/AliceUtils/Styles/index.js b/src/AliceUtils/Styles/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/AliceUtils/ensFunctions.js b/src/AliceUtils/ensFunctions.js new file mode 100644 index 0000000..7ee76ad --- /dev/null +++ b/src/AliceUtils/ensFunctions.js @@ -0,0 +1,138 @@ +// import {Wallet} from "../AliceSDK/Web3"; +// // +// // const Web3 = require('web3'); +// // const web3 = new Web3(); +// // web3.setProvider(new web3.providers.HttpProvider('https://ropsten.infura.io/rqmgop6P5BDFqz6yfGla')); +// // const sha3 = require('sha3'); +// +// let factoryContract = null; +// const factoryAbi = [{"constant":true,"inputs":[],"name":"resolver","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_subdomain","type":"string"},{"name":"_domain","type":"string"},{"name":"_topdomain","type":"string"}],"name":"subdomainOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_registry","type":"address"}],"name":"updateRegistry","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_node","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"transferDomainOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_domain","type":"string"},{"name":"_topdomain","type":"string"}],"name":"domainOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_subdomain","type":"string"},{"name":"_domain","type":"string"},{"name":"_topdomain","type":"string"}],"name":"subdomainTarget","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"registry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"lockDomainOwnershipTransfers","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"transferContractOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_subdomain","type":"string"},{"name":"_domain","type":"string"},{"name":"_topdomain","type":"string"},{"name":"_owner","type":"address"},{"name":"_target","type":"address"}],"name":"newSubdomain","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"locked","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_resolver","type":"address"}],"name":"updateResolver","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_registry","type":"address"},{"name":"_resolver","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"creator","type":"address"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"subdomain","type":"string"},{"indexed":false,"name":"domain","type":"string"},{"indexed":false,"name":"topdomain","type":"string"}],"name":"SubdomainCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousRegistry","type":"address"},{"indexed":true,"name":"newRegistry","type":"address"}],"name":"RegistryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousResolver","type":"address"},{"indexed":true,"name":"newResolver","type":"address"}],"name":"ResolverUpdated","type":"event"},{"anonymous":false,"inputs":[],"name":"DomainTransfersLocked","type":"event"}] +// +// +// // Ropsten +// const factoryAddress = "0x62d6C93DF120FCa09a08258f3a644B5059aa12f0"; +// +// // Mainnet +// //factoryAddress: "0x21aa8d3eee8be2333ed180e9a5a8c0729c9b652c", +// +// factoryContract = new web3.eth.Contract(factoryAbi, factoryAddress); +// let currentAccount = getPubKey(); +// +// var isChecksumAddress = function (address) { +// // Check each case +// address = address.replace('0x',''); +// var addressHash = sha3(address.toLowerCase()); +// for (var i = 0; i < 40; i++ ) { +// console.log('sha checking') +// // the nth letter should be uppercase if the nth digit of casemap is 1 +// if ((parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) || (parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])) { +// return false; +// } +// } +// return true; +// }; +// +// export const addressResolver = async (input) => { +// if (input.substring(0,2) === "0x"){ +// if (!/^(0x)?[0-9a-f]{40}$/i.test(input)) { +// console.log("Invalid address"); +// return "0x0000000000000000000000000000000000000000"; +// } else if (/^(0x)?[0-9a-f]{40}$/.test(input) || /^(0x)?[0-9A-F]{40}$/.test(input)) { +// console.log("Valid address"); +// return input; +// } +// } else { +// return await checkSubdomainOwner(input, 'tenz-id'); +// } +// }; +// +// +// export const initWeb3 = () => { +// if(typeof web3 !== 'undefined') { +// // web3 = new Web3(web3.currentProvider); +// console.log('[x] web3 object initialized.'); +// // initContracts(); +// } else { +// //no web3 instance available show a popup +// } +// }; +// +// export const init = () => { +// console.log('[x] Initializing '); +// initWeb3(); +// }; +// +// export const loadAccount = () => { +// web3.eth.getAccounts(function(error, accounts) { +// if(error) { +// console.log("[x] Error loading accounts", error); +// } else { +// currentAccount = accounts[0]; +// console.log("[x] Using account", currentAccount); +// initActions(); +// } +// }); +// }; +// +// export const checkSubdomainOwner = async (subdomain, domain, topdomain) => { +// if (subdomain && domain) { +// try { +// return await factoryContract.methods.subdomainOwner(subdomain, domain, 'xyz').call(); +// } catch(e) { +// return "Error connecting to subdomain provider"; +// } +// } else { +// return "Enter both subdomain and domain" +// } +// }; +// +// export const newSubdomain = (subdomain, domain, owner, target) => { +// factoryContract.methods.newSubdomain( +// subdomain, domain, owner, target).send( +// { +// gas: 150000, +// from: currentAccount +// }, +// function(error, result){ +// if(error){ +// console.log('[x] Error during execution', error); +// } else { +// console.log('[x] Result', result); +// } +// } +// ) +// }; +// +// export const setSubdomain = async () => { +// const data = await factoryContract.methods.newSubdomain(subdomain, domain, owner, target).encodeABI(); +// const nonce = await web3.eth.getTransactionCount(currentAccount); +// const chainId = await web3.eth.net.getId(); +// +// const rawTx = { +// "nonce": nonce, +// "from": currentAccount, +// "to": "0xdb0a1cf7ec068fd48a3f5869bf4f60b62e4ecb5e", +// "value": "0x0", +// "gas": 40000, +// "gasPrice": 500000000000, // converts the gwei price to wei +// "chainId": 3, +// "data": web3.utils.toHex(data) +// }; +// +// const tx = new Tx(rawTx); +// tx.sign(account1.privateKey); +// +// const serializedTx = tx.serialize(); +// +// web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')) +// .on('transactionHash', (txHash) => { +// console.log('Tokens transferred:' , txHash); +// }) +// .on('confirmation', (conf, msg) => { +// //after account gets money +// if (conf === 0) { +// console.log('& Confirmed:' , conf); +// } +// }) +// } +// diff --git a/src/AliceUtils/themes.js b/src/AliceUtils/themes.js new file mode 100644 index 0000000..2af9a72 --- /dev/null +++ b/src/AliceUtils/themes.js @@ -0,0 +1,95 @@ +// @flow +import { Platform, Dimensions } from 'react-native'; + +// ============================== +// APP STYLE CONSTANTS +// ============================== + +// color +const color = { + textGrey: '#a7a1a0', + green: '#03c845', + darkGrey: '#eae9e2', + lightGrey: '#f8f7f2', + yellow: '#FFFF00', + magenta: '#FF00EC', + blue: '#17DAD9', + component: 'white', + bg: '#1D1D1D', + bg_text: '#FFFFFF', + bg_text_sec: '#B4B5B0', + bg_positive: '#72E762', + bg_warning: '#FAE265', + bg_alert: '#ED332B', + bg_text_positive: '#72E762', + card_bg: '#F9F9F9', + card_text: '#1A1A1A', + card_bg_text_sec: '#B4B5B0' +}; + +// font sizes +const fontSize = { + xsmall: 12, + small: 14, + default: 17, + large: 24, + xlarge: 32, +}; + + + +// text styles +const text = { + greySmall: { + fontFamily: 'DIN Condensed', + color: color.textGrey, + fontSize: 10, + }, + greyMedium: { + fontFamily: 'DIN Condensed', + color: color.textGrey, + }, + blackSmall: { + fontFamily: 'DIN Condensed', + color: 'black', + fontSize: 10, + }, + blackMedium: { + fontFamily: 'DIN Condensed', + color: 'black', + }, +}; + +// Component Specific +// ------------------------------ + +// navbar +const navbar = { + backgroundColor: 'white', + buttonColor: color.blue, + height: Platform.OS === 'ios' ? 64 : 44, + textColor: color.text, +}; + +// list header +const listheader = { + height: 34, +}; + +// next up +const nextup = { + height: Platform.OS === 'ios' ? 70 : 110, +}; + +const statusBarHeight = Platform.OS === 'ios' ? 20 : 24; +const talkPaneAndroidMinScrollAreaHeight = Dimensions.get('window').height - 48; + +export { + color, + fontSize, + text, + navbar, + nextup, + listheader, + talkPaneAndroidMinScrollAreaHeight, +}; diff --git a/src/Apps/Example/Screens/Home.js b/src/Apps/Example/Screens/Home.js index 029e1b2..b6d32eb 100644 --- a/src/Apps/Example/Screens/Home.js +++ b/src/Apps/Example/Screens/Home.js @@ -1,7 +1,6 @@ import React from "react"; -import {Image, Text, NativeModules, TouchableOpacity, View} from "react-native"; +import { Text, TouchableOpacity, View } from "react-native"; import {Wallet, Contract} from "../../../AliceSDK/Web3"; -import Modalize from '../Components/Modalize' import {FoodContractABI} from "../ABI"; import {NavigationBar} from "../../../AliceComponents/NavigationBar"; @@ -31,10 +30,6 @@ export default class ExampleHome extends React.Component { } - onClick = () => { - this.child.current.openModal(); - }; - getAddress = async () => { try { const address = await Wallet.getAddress(); @@ -69,6 +64,16 @@ export default class ExampleHome extends React.Component { } }; + openDapplet = async () => { + try { + const txHash = await Wallet.sendTransactionWithDapplet({to: '0xE115012aA32a46F53b09e0A71CD0afa0658Da55F', value: '0.01'}) + console.log('txHash: ', txHash); + this.setState({txHash}) + } catch(e) { + console.log(e); + } + }; + signTransaction = async () => { try { const signedTransaction = await Wallet.signTransaction({to: '0xE115012aA32a46F53b09e0A71CD0afa0658Da55F', value: '0.01', data: 'Hello'}); @@ -130,6 +135,10 @@ export default class ExampleHome extends React.Component { Send Transaction + TransactionHash: {this.state.txHash} + + Send Transaction with Dapplet + Signed Transaction: {this.state.signedTransaction} Sign Transaction @@ -151,16 +160,10 @@ export default class ExampleHome extends React.Component { Read From Contract Render Modal - - Pop Up - Get Balance: {this.state.balance} Get Balance - - - ); } diff --git a/src/Apps/index.js b/src/Apps/index.js index 69a705f..fab20a9 100644 --- a/src/Apps/index.js +++ b/src/Apps/index.js @@ -141,7 +141,7 @@ export default class AppsScreen extends Component { - navigate('Profile')}> + navigate('Tokens')}>