...
 
Commits (6)
......@@ -557,6 +557,7 @@
},
"wire":{
"amountMonth":"{{amount}}/month",
"noAddress":"This channel has not configured their {{type}} address yet",
"amountMonthDescription":"THIS POST CAN ONLY BE SEEN BY SUPPORTERS WHO WIRE {{amount}}/MONTH TO @{{name}}",
"weHaveReceivedYourTransaction":"We've received your transaction",
"pleaseTryUnlockingMessage":"Please try unlocking this post after it gets processed. We estimate it may take around 5 minutes.",
......
......@@ -7,6 +7,12 @@ class BlockchainWireService {
return await Web3Service.getContract('wire');
}
/**
* Create an onchain tokens wireß
* @param {string} receiver
* @param {string} tokensAmount
* @param {string} message
*/
async create(receiver, tokensAmount, message = '') {
const token = await BlockchainTokenService.getContract(),
wireAddress = (await this.getContract()).options.address;
......@@ -24,6 +30,21 @@ class BlockchainWireService {
return result.transactionHash;
}
/**
* Create a eth wire
* @param {string} receiver
* @param {string} tokensAmount
*/
async createEth(receiver, tokensAmount) {
const result = await Web3Service.sendEth(
receiver,
tokensAmount
);
return result.transactionHash;
}
}
export default new BlockchainWireService();
......@@ -126,6 +126,36 @@ class Web3Service {
throw new Error('E_CANCELLED');
}
}
/**
* Send ETH from the selected wallet to the destination address
* @param {string} to destination ETH address
* @param {number} amount eth amount
*/
async sendEth(to, amount) {
const toHex = this.web3.utils.toHex;
const baseOptions = await this.getTransactionOptions();
const privateKey = await BlockchainWalletService.unlock(baseOptions.from);
const nonce = await this.web3.eth.getTransactionCount(baseOptions.from);
const tx = {
nonce,
to,
from: baseOptions.from,
value: toHex( this.web3.utils.toWei(amount, 'ether') ),
gas: toHex(21000),
gasPrice: toHex( this.web3.utils.toWei('2', 'Gwei') ), // converts the gwei price to wei
}
const signedTx = sign(tx, privateKey);
return await new Promise((resolve, reject) => {
this.web3.eth.sendSignedTransaction(signedTx)
.once('transactionHash', hash => resolve({ transactionHash: hash }))
.once('error', e => reject(e));
});
}
}
export default new Web3Service();
......@@ -2,7 +2,6 @@ import React, { Component } from 'react';
import {
View,
Text,
TextInput,
Alert,
StyleSheet,
} from 'react-native';
......@@ -16,6 +15,8 @@ import Web3Service from '../../services/Web3Service';
import { ComponentsStyle } from '../../../styles/Components';
import i18n from '../../../common/services/i18n.service';
import TextInput from '../../../common/components/TextInput';
function addressExcerpt(address) {
return `0×${address.substr(2, 5)}...${address.substr(-5)}`;
}
......
......@@ -66,8 +66,21 @@ export default class BlockchainWalletModalScreen extends Component {
return;
}
let type;
switch(opts.currency) {
case 'tokens':
type = 'onchain';
break;
case 'eth':
type = 'eth';
break;
default:
throw new Error('BlockchainWalletModal: currency not supported '+ opts.currency);
}
payload = {
type: 'onchain',
type,
wallet: toJS(wallet)
}
}
......
......@@ -26,7 +26,7 @@ class StorageService {
return null;
}
value = await this._decryptIfNeeded(value);
value = await this._decryptIfNeeded(value, key);
return JSON.parse(value);
}
......@@ -36,7 +36,7 @@ class StorageService {
*
* @param {any} value
*/
async _decryptIfNeeded(value) {
async _decryptIfNeeded(value, key) {
if (value.startsWith(CRYPTO_AES_PREFIX)) {
const keychain = await AsyncStorage.getItem(`${STORAGE_KEY_KEYCHAIN_PREFIX}${key}`);
......
......@@ -22,26 +22,26 @@ export const CommonStyle = StyleSheet.create({
alignContent: 'center',
},
centered: {
alignContent: 'center',
alignItems: 'center',
alignSelf: 'center',
justifyContent: 'center',
alignContent: 'center',
alignSelf: 'center'
},
columnAlignCenter: {
flexDirection: 'column',
alignItems: 'center',
flexDirection: 'column',
},
columnAlignStart: {
flexDirection: 'column',
alignItems: 'flex-start',
flexDirection: 'column',
},
columnAlignEnd: {
flexDirection: 'column',
alignItems: 'flex-end',
flexDirection: 'column',
},
columnStretch: {
flexDirection: 'column',
alignItems: 'stretch',
flexDirection: 'column',
},
rowJustifyEnd: {
flexDirection: 'row',
......@@ -74,12 +74,11 @@ export const CommonStyle = StyleSheet.create({
fillFlex: {
flexGrow: 1,
},
// color
colorWhite: {
color: 'white'
color: '#FFFFFF'
},
colorBlack: {
color: 'black'
color: '#000000'
},
colorLight: {
color: colors.light
......@@ -158,6 +157,9 @@ export const CommonStyle = StyleSheet.create({
borderLight: {
borderColor: colors.light
},
borderLightGreyed: {
borderColor: colors.lightGreyed
},
borderSecondary: {
borderColor: colors.secondary
},
......
......@@ -70,7 +70,7 @@ export const ComponentsStyle = StyleSheet.create({
margin: 4,
padding: 4,
alignItems: 'center',
borderRadius: 15,
borderRadius: 20,
borderWidth: 1,
},
bluebutton: {
......
This diff is collapsed.
// @flow
import api from './../common/services/api.service';
import i18n from '../common/services/i18n.service';
import BlockchainWireService from '../blockchain/services/BlockchainWireService';
......@@ -13,9 +14,9 @@ class WireService {
* Unlock an activity
* @param {string} guid
*/
unlock(guid) {
unlock(guid: string): Promise<any> {
return api.get(`api/v1/wire/threshold/${guid}`)
.then((response) => {
.then((response: any): any => {
if (response.hasOwnProperty('activity')) {
return response.activity;
} else if (response.hasOwnProperty('entity')) {
......@@ -29,7 +30,7 @@ class WireService {
* Get overview
* @param {string} guid
*/
overview(guid) {
overview(guid: string): Promise<any> {
return api.get(`api/v1/wire/sums/overview/${guid}?merchant=1`);
}
......@@ -37,7 +38,7 @@ class WireService {
* Get user rewards
* @param {string} guid
*/
userRewards(guid) {
userRewards(guid: string): Promise<any>{
return api.get(`api/v1/wire/rewards/${guid}/entity`);
}
......@@ -45,14 +46,14 @@ class WireService {
* Get rewards
* @param {string} guid
*/
rewards(guid) {
rewards(guid: string): Promise<any>{
return api.get(`api/v1/wire/rewards/${guid}`)
.then(rewards => {
.then((rewards: any): any=> {
rewards = (rewards.wire_rewards) ? rewards.wire_rewards.rewards : null
if (rewards) {
// map types
for (let type in rewards) {
rewards[type] = rewards[type].map((reward) => {
rewards[type] = rewards[type].map((reward): any => {
reward.type = type;
return reward;
});
......@@ -66,7 +67,7 @@ class WireService {
* Send wire
* @param {object} opts
*/
async send(opts) {
async send(opts): Promise<any> {
const payload = await this.getTransactionPayloads(opts);
if (!payload) {
......@@ -75,10 +76,10 @@ class WireService {
return await api.post(`api/v1/wire/${opts.guid}`, {
payload,
method: 'tokens',
method: payload.method,
amount: opts.amount,
recurring: !!opts.recurring
}).then(result => {
}).then((result: any): any => {
result.payload = payload;
return result;
});
......@@ -88,8 +89,14 @@ class WireService {
* Get transaction payloads
* @param {object} opts
*/
async getTransactionPayloads(opts) {
const payload = await BlockchainWalletService.selectCurrent(i18n.t('wire.selectWalletMessage'), { signable: true, offchain: true, buyable: true, confirmTokenExchange: opts.amount });
async getTransactionPayloads(opts: Object): any {
let payload
if (opts.currency == 'tokens' || opts.currency == 'eth') {
payload = await BlockchainWalletService.selectCurrent(i18n.t('wire.selectWalletMessage'), { signable: true, offchain: opts.currency === 'tokens', buyable: opts.currency === 'tokens', confirmTokenExchange: opts.amount, currency: opts.currency });
}
if (!payload || payload.cancelled) {
return;
......@@ -128,6 +135,18 @@ class WireService {
receiver: opts.owner.eth_wallet,
txHash: await BlockchainWireService.create(opts.owner.eth_wallet, opts.amount)
};
case 'eth':
if (!opts.owner.eth_wallet) {
throw new Error(i18n.t('boosts.errorCantReceiveTokens'));
}
return {
method: payload.type,
address: payload.wallet.address,
receiver: opts.owner.eth_wallet,
txHash: await BlockchainWireService.createEth(opts.owner.eth_wallet, opts.amount)
};
}
throw new Error('Unknown type');
......
// @flow
import {
observable,
action
} from 'mobx'
import { Alert } from 'react-native';
import wireService from './WireService';
import currency from '../common/helpers/currency';
import i18n from '../common/services/i18n.service';
export type PayloadType =
| 'onchain'
| 'offchain'
| 'usd'
| 'eth'
| 'erc20'
| 'btc';
/**
* Wire store
*/
class WireStore {
@observable currency = 'tokens';
@observable amount = 1;
@observable sending = false;
@observable.shallow owner = null;
@observable recurring = false;
@observable showBtc = false;
@observable loaded = false;
@observable errors = [];
guid = null;
setGuid(guid) {
this.guid = guid;
@action
setShowBtc = (value: boolean) => {
this.showBtc = value;
}
@action
setCurrency(value) {
setCurrency = (value: string) => {
this.currency = value;
// only tokens and usd can be recurring
if (this.currency !== 'tokens' && this.currency !== 'usd') {
this.recurring = false;
}
this.validate();
}
@action
setAmount(val) {
setAmount(val: number) {
this.amount = val;
this.validate();
}
@action
setTier = (tier) => {
setTier = (tier: any) => {
this.amount = tier.amount;
if (tier.currency) this.currency = tier.currency;
if (tier.currency) {
this.setCurrency(tier.currency);
} else {
this.validate();
}
}
@action
setOwner(owner) {
setOwner(owner: any) {
this.owner = owner;
}
loadUser(guid) {
return wireService.userRewards(guid)
.then(owner => {
this.setOwner(owner);
return owner;
});
async loadUserRewards(): Promise<any> {
const owner = await wireService.userRewards(this.owner.guid);
const { merchant, eth_wallet, wire_rewards, sums } = owner;
if (this.owner) {
this.owner.merchant = merchant;
this.owner.eth_wallet = eth_wallet;
this.owner.wire_rewards = wire_rewards;
this.owner.sums = sums;
}
this.setLoaded(true);
return owner;
}
round(number, precision) {
@action
setLoaded(value: boolean) {
this.loaded = value;
}
round(number: number, precision: number): number {
const factor = Math.pow(10, precision);
const tempNumber = number * factor;
const roundedTempNumber = Math.round(tempNumber);
......@@ -64,16 +96,37 @@ class WireStore {
/**
* Get formated amount
*/
formatAmount(amount) {
return amount.toLocaleString('en-US') + ' tokens';
formatAmount(amount: number): string {
return amount.toLocaleString('en-US') + ' ' + this.currency;
}
/**
* Validate payment
*/
@action
validate() {
//TODO: implement wire validation
this.errors = [];
switch (this.currency) {
case 'btc':
if (this.owner && !this.owner.btc_address) {
this.errors.push(i18n.t('wire.noAddress', {type: 'Bitcoin'}));
}
break;
case 'eth':
if (this.owner && !this.owner.eth_wallet) {
this.errors.push(i18n.t('wire.noAddress', {type: 'ETH'}));
}
break;
}
if (this.amount <= 0) {
this.errors.push(i18n.t('boosts.errorAmountSholdbePositive'));
}
}
@action
setRecurring(recurring) {
setRecurring(recurring: boolean) {
this.recurring = !!recurring;
}
......@@ -85,13 +138,19 @@ class WireStore {
/**
* Confirm and Send wire
*/
async send() {
@action
async send(): Promise<any> {
if (this.sending) {
return;
}
let done;
// for btc we only show the btc component
if (this.currency === 'btc') {
return this.setShowBtc(true);
}
try {
this.sending = true;
......@@ -99,7 +158,8 @@ class WireStore {
amount: this.amount,
guid: this.guid,
owner: this.owner,
recurring: this.recurring
recurring: this.recurring,
currency: this.currency
});
this.stopSending();
......@@ -119,10 +179,13 @@ class WireStore {
@action
reset() {
this.amount = 1;
this.showBtc = false;
this.currency = 'tokens';
this.sending = false;
this.owner = null;
this.recurring = false;
this.guid = null;
this.loaded = false;
this.errors = [];
}
}
......
// @flow
import * as React from 'react'
import { View, Text, Linking } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { CommonStyle as CS } from '../../styles/Common';
import viewportPercentage from '../../common/helpers/viewportPercentage';
import Button from '../../common/components/Button';
import i18nService from '../../common/services/i18n.service';
type PropsType = {
address: string,
amount: number,
onCancel: ?Function
};
/**
* Btc Payment
*/
export default class BtcPayment extends React.PureComponent<PropsType> {
url = '';
/**
* Open bitcoin link
*/
openLink = () => {
Linking.openURL(this.url);
}
cancel = () => {
if (this.props.onCancel) {
this.props.onCancel();
}
}
/**
* Render
*/
render(): React.Node {
this.url = `bitcoin:${this.props.address}?amount=${this.props.amount}`;
return (
<View style={[CS.flexContainer, CS.marginTop3x]}>
<Text style={[CS.fontXL, CS.textCenter]}>Tap to send <Text style={CS.colorPrimary}>{ this.props.amount } BTC</Text> to</Text>
<Text style={[CS.colorPrimary, CS.fontL]} numberOfLines={1}>{ this.props.address }</Text>
<View style={CS.rowJustifyCenter}>
<Button
text={i18nService.t('goback')}
onPress={this.cancel}
containerStyle={CS.padding}
textStyle={CS.fontL}
/>
<Button
inverted
text={i18nService.t('send').toUpperCase()}
onPress={this.openLink}
containerStyle={CS.padding}
textStyle={CS.fontL}
/>
</View>
<Text style={[CS.fontXL, CS.textCenter, CS.marginTop4x]}>Or scan the following QR code</Text>
<View style={[CS.centered, CS.marginTop3x]}>
<QRCode
value={this.url}
size={viewportPercentage(70).value}
/>
</View>
</View>
);
}
}
\ No newline at end of file
// @flow
import * as React from 'react'
import Icon from 'react-native-vector-icons/FontAwesome5';
type PropsType = {
value: string
};
/**
* Payment method selector
*/
export default class PaymentMethodIcon extends React.PureComponent<PropsType> {
render(): React.Node {
const { value, ...other } = this.props;
let icon: string = '';
switch (value.toLowerCase()) {
case 'tokens':
icon = 'lightbulb';
break;
case 'usd':
icon = 'dollar-sign';
break;
case 'btc':
icon = 'bitcoin';
break;
case 'eth':
icon = 'ethereum';
break;
}
return <Icon name={icon} {...other} />
}
}
\ No newline at end of file
// @flow
import * as React from 'react'
import { Text, StyleSheet } from 'react-native';
import Menu, { MenuItem } from 'react-native-material-menu';
import featuresService from '../../common/services/features.service';
import testID from '../../common/helpers/testID';
import Colors from '../../styles/Colors';
import PaymentMethodIcon from './PaymentMethodIcon';
import { CommonStyle as CS } from '../../styles/Common';
type PropsType = {
button: React.Node,
value: string,
onSelect: Function
};
/**
* Payment method selector
*/
export default class PaymentMethodSelector extends React.PureComponent<PropsType> {
methods: Array<any>;
menuRef: ?React.ElementRef<Menu>;
/**
* @param {PropsType} props
*/
constructor(props: PropsType) {
super(props);
if (featuresService.has('wire-multi-currency')) {
this.methods = [
{label: 'Tokens', handle: (): any => this.onSelect('tokens')},
{label: 'USD', handle: (): any => this.onSelect('usd')},
{label: 'BTC', handle: (): any => this.onSelect('btc')},
{label: 'ETH', handle: (): any => this.onSelect('eth')},
];
} else {
this.methods = [{label: 'Tokens', handle: (): any => this.onSelect('tokens')}];
}
this.menuRef = React.createRef();
}
/**
* On method selected
* @param {*} method
*/
onSelect(method: any) {
if (this.props.onSelect) {
this.props.onSelect(method);
}
if (this.menuRef && this.menuRef.current) {
this.menuRef.current.hide();
}
}
/**
* Show menu
*/
show() {
if (this.menuRef && this.menuRef.current) {
this.menuRef.current.show();
}
}
/**
* Render
*/
render(): React.Node {
return (
<Menu ref={this.menuRef} button={this.props.button}>
{this.methods.map((method: any, i: number): MenuItem => (
<MenuItem
key={i}
onPress={method.handle}
textStyle={CS.fontXL}
{...testID(`PAYMENT METHOD ${method.label}`)}
>
<Text style={(method.label.toLowerCase() === this.props.value) ? styles.selected : null}><PaymentMethodIcon value={method.label} size={15} /> {method.label}</Text>
</MenuItem>
))}
</Menu>
);
}
}
const styles: any = StyleSheet.create({
selected: {
color: Colors.primary
}
})
\ No newline at end of file
import React, {PureComponent, Fragment} from 'react';
import { Text, Dimensions, View, StyleSheet } from 'react-native';
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import Carousel from 'react-native-snap-carousel';
import featuresService from '../../common/services/features.service';
import i18n from '../../common/services/i18n.service';
import ModalPicker from '../../common/components/ModalPicker';
import RewardsStateDecreaseView from '../../notifications/notification/view/RewardsStateDecreaseView';
import { CommonStyle as CS } from '../../styles/Common';
import viewportPercentage from '../../common/helpers/viewportPercentage';
......@@ -32,6 +29,10 @@ export default class SubscriptionTierCarousel extends PureComponent {
return amount > 1 ? 'Tokens' : 'Token';
case 'usd':
return 'USD';
case 'eth':
return 'ETH';
case 'btc':
return 'BTC';
}
}
......@@ -82,7 +83,7 @@ export default class SubscriptionTierCarousel extends PureComponent {
const amount = row.item.amount || this.props.amount;
const currency = row.item.currency || this.props.currency;
return (
<View key={`rewards${row.item.amount}`} style={[CS.rowJustifyCenter, CS.backgroundLightGreyed, CS.borderRadius5x, CS.shadow, CS.padding2x, CS.border, CS.borderGreyed]}>
<View key={`rewards${row.item.amount}`} style={[CS.rowJustifyCenter, CS.backgroundLightGreyed, CS.borderRadius5x, CS.padding2x, CS.border, CS.borderGreyed]}>
<View style={CS.columnAlignCenter}>
<Text style={[CS.fontXXL, CS.fontMedium, CS.colorDark]}>{amount} {this.getPluralizedCurrency(currency, row.item.amount)} / month</Text>
<Text numberOfLines={5} style={[CS.fontL, CS.fontHairline, CS.colorDark]}>{row.item.description}</Text>
......