mirror of
https://github.com/urbit/shrub.git
synced 2024-12-20 09:21:42 +03:00
Convert components, remove old store
This commit is contained in:
parent
69c123c22e
commit
f269be8ba9
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Root } from './js/components/root.js';
|
import Root from './js/components/root.js';
|
||||||
import { api } from './js/api.js';
|
import { api } from './js/api.js';
|
||||||
import { subscription } from "./js/subscription.js";
|
import { SettingsProvider } from './js/hooks/useSettings';
|
||||||
|
|
||||||
import './css/indigo-static.css';
|
import './css/indigo-static.css';
|
||||||
import './css/fonts.css';
|
import './css/fonts.css';
|
||||||
@ -13,11 +13,13 @@ import './css/custom.css';
|
|||||||
const channel = new window.channel();
|
const channel = new window.channel();
|
||||||
api.setChannel(window.ship, channel);
|
api.setChannel(window.ship, channel);
|
||||||
|
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.accept()
|
module.hot.accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render((
|
ReactDOM.render(
|
||||||
<Root channel={channel}/>
|
<SettingsProvider channel={channel}>
|
||||||
), document.querySelectorAll("#root")[0]);
|
<Root />
|
||||||
|
</SettingsProvider>,
|
||||||
|
document.querySelectorAll('#root')[0]
|
||||||
|
);
|
||||||
|
@ -1,170 +1,140 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Row, Text, Button, Col } from '@tlon/indigo-react';
|
import { Row, Text, Button, Col } from '@tlon/indigo-react';
|
||||||
import Send from './send.js';
|
import Send from './send.js';
|
||||||
import CurrencyPicker from './currencyPicker.js';
|
import CurrencyPicker from './currencyPicker.js';
|
||||||
import { satsToCurrency } from '../../lib/util.js';
|
import { satsToCurrency } from '../../lib/util.js';
|
||||||
import { store } from '../../store.js';
|
import { useSettings } from '../../hooks/useSettings.js';
|
||||||
|
import { api } from '../../api';
|
||||||
|
|
||||||
export default class Balance extends Component {
|
const Balance = () => {
|
||||||
constructor(props) {
|
const {
|
||||||
super(props);
|
address,
|
||||||
|
confirmedBalance: sats,
|
||||||
|
unconfirmedBalance: unconfirmedSats,
|
||||||
|
denomination,
|
||||||
|
currencyRates,
|
||||||
|
setPsbt,
|
||||||
|
setFee,
|
||||||
|
setError,
|
||||||
|
} = useSettings();
|
||||||
|
const [sending, setSending] = useState(false);
|
||||||
|
const [copiedButton, setCopiedButton] = useState(false);
|
||||||
|
const [copiedString, setCopiedString] = useState(false);
|
||||||
|
|
||||||
this.state = {
|
const copyAddress = (arg) => {
|
||||||
sending: false,
|
|
||||||
copiedButton: false,
|
|
||||||
copiedString: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.copyAddress = this.copyAddress.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
copyAddress(arg) {
|
|
||||||
let address = this.props.state.address;
|
|
||||||
navigator.clipboard.writeText(address);
|
navigator.clipboard.writeText(address);
|
||||||
this.props.api.btcWalletCommand({ 'gen-new-address': null });
|
api.btcWalletCommand({ 'gen-new-address': null });
|
||||||
|
|
||||||
if (arg === 'button') {
|
if (arg === 'button') {
|
||||||
this.setState({ copiedButton: true });
|
setCopiedButton(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setState({ copiedButton: false });
|
setCopiedButton(false);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else if (arg === 'string') {
|
} else if (arg === 'string') {
|
||||||
this.setState({ copiedString: true });
|
setCopiedString(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setState({ copiedString: false });
|
setCopiedString(false);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
const unconfirmedString = unconfirmedSats ? ` (${unconfirmedSats}) ` : '';
|
||||||
const sats = this.props.state.confirmedBalance || 0;
|
|
||||||
const unconfirmedSats = this.props.state.unconfirmedBalance;
|
|
||||||
|
|
||||||
const unconfirmedString = unconfirmedSats ? ` (${unconfirmedSats}) ` : '';
|
const value = satsToCurrency(sats, denomination, currencyRates);
|
||||||
|
const sendDisabled = sats === 0;
|
||||||
|
const addressText =
|
||||||
|
address === null ? '' : address.slice(0, 6) + '...' + address.slice(-6);
|
||||||
|
|
||||||
const denomination = this.props.state.denomination;
|
const conversion = currencyRates[denomination]?.last;
|
||||||
const value = satsToCurrency(
|
|
||||||
sats,
|
|
||||||
denomination,
|
|
||||||
this.props.state.currencyRates
|
|
||||||
);
|
|
||||||
const sendDisabled = sats === 0;
|
|
||||||
const addressText =
|
|
||||||
this.props.state.address === null
|
|
||||||
? ''
|
|
||||||
: this.props.state.address.slice(0, 6) +
|
|
||||||
'...' +
|
|
||||||
this.props.state.address.slice(-6);
|
|
||||||
|
|
||||||
const conversion = this.props.state.currencyRates[denomination].last;
|
return (
|
||||||
|
<>
|
||||||
return (
|
{sending ? (
|
||||||
<>
|
<Send
|
||||||
{this.state.sending ? (
|
value={value}
|
||||||
<Send
|
conversion={conversion}
|
||||||
state={this.props.state}
|
stopSending={() => {
|
||||||
api={this.props.api}
|
setSending(false);
|
||||||
psbt={this.props.state.psbt}
|
setPsbt('');
|
||||||
fee={this.props.state.fee}
|
setFee(0);
|
||||||
currencyRates={this.props.state.currencyRates}
|
setError('');
|
||||||
shipWallets={this.props.state.shipWallets}
|
}}
|
||||||
value={value}
|
/>
|
||||||
denomination={denomination}
|
) : (
|
||||||
sats={sats}
|
<Col
|
||||||
conversion={conversion}
|
height="400px"
|
||||||
network={this.props.network}
|
width="100%"
|
||||||
error={this.props.state.error}
|
backgroundColor="white"
|
||||||
stopSending={() => {
|
borderRadius="48px"
|
||||||
this.setState({ sending: false });
|
justifyContent="space-between"
|
||||||
store.handleEvent({
|
mb={5}
|
||||||
data: { psbt: '', fee: 0, error: '', 'broadcast-fail': null },
|
p={5}
|
||||||
});
|
>
|
||||||
}}
|
<Row justifyContent="space-between">
|
||||||
/>
|
<Text color="orange" fontSize={1}>
|
||||||
) : (
|
Balance
|
||||||
<Col
|
</Text>
|
||||||
height="400px"
|
<Text
|
||||||
width="100%"
|
color="lightGray"
|
||||||
backgroundColor="white"
|
fontSize="14px"
|
||||||
borderRadius="48px"
|
mono
|
||||||
justifyContent="space-between"
|
style={{ cursor: 'pointer' }}
|
||||||
mb={5}
|
onClick={() => copyAddress('string')}
|
||||||
p={5}
|
>
|
||||||
>
|
{copiedString ? 'copied' : addressText}
|
||||||
<Row justifyContent="space-between">
|
</Text>
|
||||||
<Text color="orange" fontSize={1}>
|
<CurrencyPicker />
|
||||||
Balance
|
</Row>
|
||||||
</Text>
|
<Col justifyContent="center" alignItems="center">
|
||||||
<Text
|
<Text
|
||||||
color="lightGray"
|
fontSize="40px"
|
||||||
fontSize="14px"
|
color="orange"
|
||||||
mono
|
style={{ whiteSpace: 'nowrap' }}
|
||||||
style={{ cursor: 'pointer' }}
|
>
|
||||||
onClick={() => {
|
{value}
|
||||||
this.copyAddress('string');
|
</Text>
|
||||||
}}
|
<Text
|
||||||
>
|
fontSize={1}
|
||||||
{this.state.copiedString ? 'copied' : addressText}
|
color="orange"
|
||||||
</Text>
|
>{`${sats}${unconfirmedString} sats`}</Text>
|
||||||
<CurrencyPicker
|
|
||||||
api={this.props.api}
|
|
||||||
denomination={denomination}
|
|
||||||
currencies={this.props.state.currencyRates}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
<Col justifyContent="center" alignItems="center">
|
|
||||||
<Text
|
|
||||||
fontSize="40px"
|
|
||||||
color="orange"
|
|
||||||
style={{ whiteSpace: 'nowrap' }}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
fontSize={1}
|
|
||||||
color="orange"
|
|
||||||
>{`${sats}${unconfirmedString} sats`}</Text>
|
|
||||||
</Col>
|
|
||||||
<Row flexDirection="row-reverse">
|
|
||||||
<Button
|
|
||||||
disabled={sendDisabled}
|
|
||||||
fontSize={1}
|
|
||||||
fontWeight="bold"
|
|
||||||
color={sendDisabled ? 'lighterGray' : 'white'}
|
|
||||||
backgroundColor={sendDisabled ? 'veryLightGray' : 'orange'}
|
|
||||||
style={{ cursor: sendDisabled ? 'default' : 'pointer' }}
|
|
||||||
borderColor="none"
|
|
||||||
borderRadius="24px"
|
|
||||||
height="48px"
|
|
||||||
onClick={() => this.setState({ sending: true })}
|
|
||||||
>
|
|
||||||
Send
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
mr={3}
|
|
||||||
disabled={this.state.copiedButton}
|
|
||||||
fontSize={1}
|
|
||||||
fontWeight="bold"
|
|
||||||
color={this.state.copiedButton ? 'green' : 'orange'}
|
|
||||||
backgroundColor={
|
|
||||||
this.state.copiedButton ? 'veryLightGreen' : 'midOrange'
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
cursor: this.state.copiedButton ? 'default' : 'pointer',
|
|
||||||
}}
|
|
||||||
borderColor="none"
|
|
||||||
borderRadius="24px"
|
|
||||||
height="48px"
|
|
||||||
onClick={() => {
|
|
||||||
this.copyAddress('button');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.state.copiedButton ? 'Address Copied!' : 'Copy Address'}
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
<Row flexDirection="row-reverse">
|
||||||
</>
|
<Button
|
||||||
);
|
disabled={sendDisabled}
|
||||||
}
|
fontSize={1}
|
||||||
}
|
fontWeight="bold"
|
||||||
|
color={sendDisabled ? 'lighterGray' : 'white'}
|
||||||
|
backgroundColor={sendDisabled ? 'veryLightGray' : 'orange'}
|
||||||
|
style={{ cursor: sendDisabled ? 'default' : 'pointer' }}
|
||||||
|
borderColor="none"
|
||||||
|
borderRadius="24px"
|
||||||
|
height="48px"
|
||||||
|
onClick={() => setSending(true)}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mr={3}
|
||||||
|
disabled={copiedButton}
|
||||||
|
fontSize={1}
|
||||||
|
fontWeight="bold"
|
||||||
|
color={copiedButton ? 'green' : 'orange'}
|
||||||
|
backgroundColor={copiedButton ? 'veryLightGreen' : 'midOrange'}
|
||||||
|
style={{
|
||||||
|
cursor: copiedButton ? 'default' : 'pointer',
|
||||||
|
}}
|
||||||
|
borderColor="none"
|
||||||
|
borderRadius="24px"
|
||||||
|
height="48px"
|
||||||
|
onClick={() => copyAddress('button')}
|
||||||
|
>
|
||||||
|
{copiedButton ? 'Address Copied!' : 'Copy Address'}
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Balance;
|
||||||
|
@ -1,72 +1,49 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Box, LoadingSpinner, Col } from '@tlon/indigo-react';
|
||||||
Box,
|
import { Switch, Route } from 'react-router-dom';
|
||||||
Icon,
|
import Balance from './balance.js';
|
||||||
Row,
|
|
||||||
Text,
|
|
||||||
LoadingSpinner,
|
|
||||||
Col,
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
import {
|
|
||||||
Switch,
|
|
||||||
Route,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import Balance from './balance.js';
|
|
||||||
import Transactions from './transactions.js';
|
import Transactions from './transactions.js';
|
||||||
import Warning from './warning.js';
|
import Warning from './warning.js';
|
||||||
import Header from './header.js';
|
import Header from './header.js';
|
||||||
import Settings from './settings.js';
|
import Settings from './settings.js';
|
||||||
|
import { useSettings } from '../../hooks/useSettings.js';
|
||||||
|
|
||||||
export default class Body extends Component {
|
const Body = () => {
|
||||||
constructor(props) {
|
const { loaded, showWarning: warning } = useSettings();
|
||||||
super(props);
|
const cardWidth = window.innerWidth <= 475 ? '350px' : '400px';
|
||||||
}
|
return !loaded ? (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<LoadingSpinner
|
||||||
|
width={7}
|
||||||
|
height={7}
|
||||||
|
background="midOrange"
|
||||||
|
foreground="orange"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Switch>
|
||||||
|
<Route path="/~btc/settings">
|
||||||
|
<Col display="flex" flexDirection="column" width={cardWidth}>
|
||||||
|
<Header settings={true} />
|
||||||
|
<Settings />
|
||||||
|
</Col>
|
||||||
|
</Route>
|
||||||
|
<Route path="/~btc">
|
||||||
|
<Col display="flex" flexDirection="column" width={cardWidth}>
|
||||||
|
<Header settings={false} />
|
||||||
|
{!warning ? null : <Warning />}
|
||||||
|
<Balance />
|
||||||
|
<Transactions />
|
||||||
|
</Col>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
export default Body;
|
||||||
|
|
||||||
const cardWidth = window.innerWidth <= 475 ? '350px' : '400px'
|
|
||||||
|
|
||||||
if (!this.props.loaded) {
|
|
||||||
return (
|
|
||||||
<Box display="flex" width="100%" height="100%" alignItems="center" justifyContent="center">
|
|
||||||
<LoadingSpinner
|
|
||||||
width={7}
|
|
||||||
height={7}
|
|
||||||
background="midOrange"
|
|
||||||
foreground="orange"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Switch>
|
|
||||||
<Route path="/~btc/settings">
|
|
||||||
<Col
|
|
||||||
display='flex'
|
|
||||||
flexDirection='column'
|
|
||||||
width={cardWidth}
|
|
||||||
>
|
|
||||||
<Header settings={true} state={this.props.state}/>
|
|
||||||
<Settings state={this.props.state}
|
|
||||||
api={this.props.api}
|
|
||||||
network={this.props.network}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Route>
|
|
||||||
<Route path="/~btc">
|
|
||||||
<Col
|
|
||||||
display='flex'
|
|
||||||
flexDirection='column'
|
|
||||||
width={cardWidth}
|
|
||||||
>
|
|
||||||
<Header settings={false} state={this.props.state}/>
|
|
||||||
{ (!this.props.warning) ? null : <Warning api={this.props.api}/>}
|
|
||||||
<Balance api={this.props.api} state={this.props.state} network={this.props.network}/>
|
|
||||||
<Transactions state={this.props.state} network={this.props.network}/>
|
|
||||||
</Col>
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Icon,
|
Icon,
|
||||||
@ -9,231 +9,203 @@ import {
|
|||||||
Col,
|
Col,
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
} from '@tlon/indigo-react';
|
} from '@tlon/indigo-react';
|
||||||
|
|
||||||
import { Sigil } from './sigil.js';
|
import { Sigil } from './sigil.js';
|
||||||
|
|
||||||
import * as bitcoin from 'bitcoinjs-lib';
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
import * as kg from 'urbit-key-generation';
|
|
||||||
import { isValidPatp } from 'urbit-ob';
|
import { isValidPatp } from 'urbit-ob';
|
||||||
|
import Sent from './sent.js';
|
||||||
import Sent from './sent.js'
|
import Error from './error.js';
|
||||||
import Error from './error.js'
|
|
||||||
|
|
||||||
import { satsToCurrency } from '../../lib/util.js';
|
import { satsToCurrency } from '../../lib/util.js';
|
||||||
|
import { useSettings } from '../../hooks/useSettings.js';
|
||||||
|
import { api } from '../../api';
|
||||||
|
|
||||||
export default class BridgeInvoice extends Component {
|
const BridgeInvoice = ({ payee, stopSending, satsAmount }) => {
|
||||||
constructor(props) {
|
const { error, currencyRates, fee, broadcastSuccess, denomination, psbt } =
|
||||||
super(props);
|
useSettings();
|
||||||
|
const [txHex, setTxHex] = useState('');
|
||||||
|
const [ready, setReady] = useState(false);
|
||||||
|
const [localError, setLocalError] = useState('');
|
||||||
|
const [broadcasting, setBroadcasting] = useState(false);
|
||||||
|
const invoiceRef = useRef();
|
||||||
|
|
||||||
this.state = {
|
useEffect(() => {
|
||||||
txHex: '',
|
if (broadcasting && localError !== '') {
|
||||||
ready: false,
|
setBroadcasting(false);
|
||||||
error: this.props.state.error,
|
|
||||||
broadcasting: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.checkTxHex = this.checkTxHex.bind(this);
|
|
||||||
this.broadCastTx = this.broadCastTx.bind(this);
|
|
||||||
this.sendBitcoin = this.sendBitcoin.bind(this);
|
|
||||||
this.clickDismiss = this.clickDismiss.bind(this);
|
|
||||||
this.setInvoiceRef = this.setInvoiceRef.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
broadCastTx(hex) {
|
|
||||||
let command = {
|
|
||||||
'broadcast-tx': hex
|
|
||||||
}
|
}
|
||||||
return this.props.api.btcWalletCommand(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
window.open('https://bridge.urbit.org/?kind=btc&utx=' + this.props.psbt);
|
|
||||||
document.addEventListener("click", this.clickDismiss);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount(){
|
|
||||||
document.removeEventListener("click", this.clickDismiss);
|
|
||||||
}
|
|
||||||
|
|
||||||
setInvoiceRef(n){
|
|
||||||
this.invoiceRef = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
clickDismiss(e){
|
|
||||||
if (this.invoiceRef && !(this.invoiceRef.contains(e.target))){
|
|
||||||
this.props.stopSending();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps){
|
|
||||||
if (this.state.broadcasting) {
|
|
||||||
if (this.state.error !== '') {
|
|
||||||
this.setState({broadcasting: false});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevProps.state.error !== this.props.state.error) {
|
|
||||||
this.setState({error: this.props.state.error});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendBitcoin(hex) {
|
|
||||||
try {
|
|
||||||
bitcoin.Transaction.fromHex(hex)
|
|
||||||
this.broadCastTx(hex)
|
|
||||||
this.setState({broadcasting: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
catch(e) {
|
|
||||||
this.setState({error: 'invalid-signed', broadcasting: false});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTxHex(e){
|
|
||||||
let txHex = e.target.value;
|
|
||||||
let ready = (txHex.length > 0);
|
|
||||||
let error = '';
|
|
||||||
this.setState({txHex, ready, error});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { stopSending, payee, denomination, satsAmount, psbt, currencyRates, fee } = this.props;
|
|
||||||
const { error, txHex } = this.state;
|
|
||||||
|
|
||||||
let inputColor = 'black';
|
|
||||||
let inputBg = 'white';
|
|
||||||
let inputBorder = 'lightGray';
|
|
||||||
|
|
||||||
if (error !== '') {
|
if (error !== '') {
|
||||||
inputColor = 'red';
|
setLocalError(error);
|
||||||
inputBg = 'veryLightRed';
|
|
||||||
inputBorder = 'red';
|
|
||||||
}
|
}
|
||||||
|
}, [error, broadcasting, setBroadcasting]);
|
||||||
|
|
||||||
const isShip = isValidPatp(payee);
|
useEffect(() => {
|
||||||
|
window.open('https://bridge.urbit.org/?kind=btc&utx=' + psbt);
|
||||||
|
});
|
||||||
|
|
||||||
const icon = (isShip)
|
const broadCastTx = (hex) => {
|
||||||
? <Sigil ship={payee} size={24} color="black" classes={''} icon padding={5}/>
|
let command = {
|
||||||
: <Box backgroundColor="lighterGray"
|
'broadcast-tx': hex,
|
||||||
width="24px"
|
};
|
||||||
height="24px"
|
return api.btcWalletCommand(command);
|
||||||
textAlign="center"
|
};
|
||||||
alignItems="center"
|
|
||||||
borderRadius="2px"
|
|
||||||
p={1}
|
|
||||||
><Icon icon="Bitcoin" color="gray"/></Box>;
|
|
||||||
|
|
||||||
return (
|
const sendBitcoin = (hex) => {
|
||||||
<>
|
try {
|
||||||
{ this.props.state.broadcastSuccess ?
|
bitcoin.Transaction.fromHex(hex);
|
||||||
<Sent
|
broadCastTx(hex);
|
||||||
payee={payee}
|
setBroadcasting(true);
|
||||||
stopSending={stopSending}
|
} catch (e) {
|
||||||
denomination={denomination}
|
setLocalError('invalid-signed');
|
||||||
currencyRates={currencyRates}
|
setBroadcasting(false);
|
||||||
satsAmount={satsAmount}
|
}
|
||||||
/> :
|
};
|
||||||
|
|
||||||
|
const checkTxHex = (e) => {
|
||||||
|
setTxHex(e.target.value);
|
||||||
|
setReady(txHex.length > 0);
|
||||||
|
setLocalError('');
|
||||||
|
};
|
||||||
|
|
||||||
|
let inputColor = 'black';
|
||||||
|
let inputBg = 'white';
|
||||||
|
let inputBorder = 'lightGray';
|
||||||
|
|
||||||
|
if (localError !== '') {
|
||||||
|
inputColor = 'red';
|
||||||
|
inputBg = 'veryLightRed';
|
||||||
|
inputBorder = 'red';
|
||||||
|
}
|
||||||
|
|
||||||
|
const isShip = isValidPatp(payee);
|
||||||
|
|
||||||
|
const icon = isShip ? (
|
||||||
|
<Sigil ship={payee} size={24} color="black" classes={''} icon padding={5} />
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
backgroundColor="lighterGray"
|
||||||
|
width="24px"
|
||||||
|
height="24px"
|
||||||
|
textAlign="center"
|
||||||
|
alignItems="center"
|
||||||
|
borderRadius="2px"
|
||||||
|
p={1}
|
||||||
|
>
|
||||||
|
<Icon icon="Bitcoin" color="gray" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{broadcastSuccess ? (
|
||||||
|
<Sent payee={payee} stopSending={stopSending} satsAmount={satsAmount} />
|
||||||
|
) : (
|
||||||
|
<Col
|
||||||
|
ref={invoiceRef}
|
||||||
|
width="100%"
|
||||||
|
backgroundColor="white"
|
||||||
|
borderRadius="48px"
|
||||||
|
mb={5}
|
||||||
|
p={5}
|
||||||
|
>
|
||||||
<Col
|
<Col
|
||||||
ref={this.setInvoiceRef}
|
|
||||||
width='100%'
|
|
||||||
backgroundColor='white'
|
|
||||||
borderRadius='48px'
|
|
||||||
mb={5}
|
|
||||||
p={5}
|
p={5}
|
||||||
|
mt={4}
|
||||||
|
backgroundColor="veryLightGreen"
|
||||||
|
borderRadius="24px"
|
||||||
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Col
|
<Row>
|
||||||
p={5}
|
<Text color="green" fontSize="40px">
|
||||||
mt={4}
|
{satsToCurrency(satsAmount, denomination, currencyRates)}
|
||||||
backgroundColor='veryLightGreen'
|
|
||||||
borderRadius='24px'
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Row>
|
|
||||||
<Text
|
|
||||||
color='green'
|
|
||||||
fontSize='40px'
|
|
||||||
>{satsToCurrency(satsAmount, denomination, currencyRates)}</Text>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Text
|
|
||||||
fontWeight="bold"
|
|
||||||
fontSize='16px'
|
|
||||||
color='midGreen'
|
|
||||||
>{`${satsAmount} sats`}</Text>
|
|
||||||
</Row>
|
|
||||||
<Row mt={2}>
|
|
||||||
<Text
|
|
||||||
fontSize='14px'
|
|
||||||
color='midGreen'
|
|
||||||
>{`Fee: ${satsToCurrency(fee, denomination, currencyRates)} (${fee} sats)`}</Text>
|
|
||||||
</Row>
|
|
||||||
<Row mt={4} >
|
|
||||||
<Text fontSize='16px' fontWeight="bold" color="gray">You are paying</Text>
|
|
||||||
</Row>
|
|
||||||
<Row mt={2} alignItems="center">
|
|
||||||
{icon}
|
|
||||||
<Text ml={2}
|
|
||||||
mono
|
|
||||||
color="gray"
|
|
||||||
fontSize='14px'
|
|
||||||
style={{'display': 'block', 'overflow-wrap': 'anywhere'}}
|
|
||||||
>{payee}</Text>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
<Box mt={3}>
|
|
||||||
<Text fontSize='14px' fontWeight='500'>
|
|
||||||
Bridge signed transaction
|
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Row>
|
||||||
<Box mt={1} mb={2}>
|
<Row>
|
||||||
<Text gray fontSize='14px'>
|
<Text
|
||||||
Copy the signed transaction from Bridge
|
fontWeight="bold"
|
||||||
|
fontSize="16px"
|
||||||
|
color="midGreen"
|
||||||
|
>{`${satsAmount} sats`}</Text>
|
||||||
|
</Row>
|
||||||
|
<Row mt={2}>
|
||||||
|
<Text fontSize="14px" color="midGreen">{`Fee: ${satsToCurrency(
|
||||||
|
fee,
|
||||||
|
denomination,
|
||||||
|
currencyRates
|
||||||
|
)} (${fee} sats)`}</Text>
|
||||||
|
</Row>
|
||||||
|
<Row mt={4}>
|
||||||
|
<Text fontSize="16px" fontWeight="bold" color="gray">
|
||||||
|
You are paying
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
<Row mt={2} alignItems="center">
|
||||||
|
{icon}
|
||||||
|
<Text
|
||||||
|
ml={2}
|
||||||
|
mono
|
||||||
|
color="gray"
|
||||||
|
fontSize="14px"
|
||||||
|
style={{ display: 'block', 'overflow-wrap': 'anywhere' }}
|
||||||
|
>
|
||||||
|
{payee}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
|
||||||
<Input
|
|
||||||
value={this.state.txHex}
|
|
||||||
fontSize='14px'
|
|
||||||
placeholder='010000000001019e478cc370323ac539097...'
|
|
||||||
autoCapitalize='none'
|
|
||||||
autoCorrect='off'
|
|
||||||
color={inputColor}
|
|
||||||
backgroundColor={inputBg}
|
|
||||||
borderColor={inputBorder}
|
|
||||||
style={{'line-height': '4'}}
|
|
||||||
onChange={this.checkTxHex}
|
|
||||||
/>
|
|
||||||
{ (error !== '') &&
|
|
||||||
<Row>
|
|
||||||
<Error
|
|
||||||
error={error}
|
|
||||||
fontSize='14px'
|
|
||||||
mt={2}/>
|
|
||||||
</Row>
|
|
||||||
}
|
|
||||||
<Row
|
|
||||||
flexDirection='row-reverse'
|
|
||||||
mt={4}
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
primary
|
|
||||||
children='Send BTC'
|
|
||||||
mr={3}
|
|
||||||
fontSize={1}
|
|
||||||
borderRadius='24px'
|
|
||||||
border='none'
|
|
||||||
height='48px'
|
|
||||||
onClick={() => this.sendBitcoin(txHex)}
|
|
||||||
disabled={!this.state.ready || error || this.state.broadcasting}
|
|
||||||
color={(this.state.ready && !error && !this.state.broadcasting) ? "white" : "lighterGray"}
|
|
||||||
backgroundColor={(this.state.ready && !error && !this.state.broadcasting) ? "green" : "veryLightGray"}
|
|
||||||
style={{cursor: (this.state.ready && !error) ? "pointer" : "default"}}
|
|
||||||
/>
|
|
||||||
{this.state.broadcasting ? <LoadingSpinner mr={3}/> : null}
|
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
}
|
<Box mt={3}>
|
||||||
</>
|
<Text fontSize="14px" fontWeight="500">
|
||||||
);
|
Bridge signed transaction
|
||||||
}
|
</Text>
|
||||||
}
|
</Box>
|
||||||
|
<Box mt={1} mb={2}>
|
||||||
|
<Text gray fontSize="14px">
|
||||||
|
Copy the signed transaction from Bridge
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Input
|
||||||
|
value={txHex}
|
||||||
|
fontSize="14px"
|
||||||
|
placeholder="010000000001019e478cc370323ac539097..."
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect="off"
|
||||||
|
color={inputColor}
|
||||||
|
backgroundColor={inputBg}
|
||||||
|
borderColor={inputBorder}
|
||||||
|
style={{ 'line-height': '4' }}
|
||||||
|
onChange={(e) => checkTxHex(e)}
|
||||||
|
/>
|
||||||
|
{localError !== '' && (
|
||||||
|
<Row>
|
||||||
|
<Error error={localError} fontSize="14px" mt={2} />
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
<Row flexDirection="row-reverse" mt={4} alignItems="center">
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
mr={3}
|
||||||
|
fontSize={1}
|
||||||
|
borderRadius="24px"
|
||||||
|
border="none"
|
||||||
|
height="48px"
|
||||||
|
onClick={() => sendBitcoin(txHex)}
|
||||||
|
disabled={!ready || localError || broadcasting}
|
||||||
|
color={
|
||||||
|
ready && !localError && !broadcasting ? 'white' : 'lighterGray'
|
||||||
|
}
|
||||||
|
backgroundColor={
|
||||||
|
ready && !localError && !broadcasting
|
||||||
|
? 'green'
|
||||||
|
: 'veryLightGray'
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
cursor: ready && !localError ? 'pointer' : 'default',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send BTC
|
||||||
|
</Button>
|
||||||
|
{broadcasting ? <LoadingSpinner mr={3} /> : null}
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BridgeInvoice;
|
||||||
|
@ -1,50 +1,37 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Icon, Row, Text } from '@tlon/indigo-react';
|
||||||
Box,
|
import { api } from '../../api';
|
||||||
Icon,
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
Row,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
LoadingSpinner,
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import { satsToCurrency } from '../../lib/util.js'
|
const CurrencyPicker = () => {
|
||||||
import { store } from '../../store';
|
const { denomination, currencyRates } = useSettings();
|
||||||
|
const switchCurrency = () => {
|
||||||
export default class CurrencyPicker extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.switchCurrency = this.switchCurrency.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
switchCurrency(){
|
|
||||||
let newCurrency;
|
let newCurrency;
|
||||||
if (this.props.denomination === 'BTC') {
|
if (denomination === 'BTC') {
|
||||||
if (this.props.currencies['USD']) {
|
if (currencyRates['USD']) {
|
||||||
newCurrency = "USD";
|
newCurrency = 'USD';
|
||||||
}
|
}
|
||||||
} else if (this.props.denomination === 'USD') {
|
} else if (denomination === 'USD') {
|
||||||
newCurrency = "BTC";
|
newCurrency = 'BTC';
|
||||||
}
|
}
|
||||||
let setCurrency = {
|
let setCurrency = {
|
||||||
"put-entry": {
|
'put-entry': {
|
||||||
value: newCurrency,
|
value: newCurrency,
|
||||||
"entry-key": "currency",
|
'entry-key': 'currency',
|
||||||
"bucket-key": "btc-wallet",
|
'bucket-key': 'btc-wallet',
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
this.props.api.settingsEvent(setCurrency);
|
api.settingsEvent(setCurrency);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row style={{ cursor: 'pointer' }} onClick={() => switchCurrency()}>
|
||||||
|
<Icon icon="ChevronDouble" color="orange" pt="2px" pr={1} />
|
||||||
|
<Text color="orange" fontSize={1}>
|
||||||
|
{denomination}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
export default CurrencyPicker;
|
||||||
return (
|
|
||||||
<Row style={{cursor: "pointer"}} onClick={this.switchCurrency}>
|
|
||||||
<Icon icon="ChevronDouble" color="orange" pt="2px" pr={1} />
|
|
||||||
<Text color="orange" fontSize={1}>{this.props.denomination}</Text>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { Text } from '@tlon/indigo-react';
|
import { Text } from '@tlon/indigo-react';
|
||||||
|
|
||||||
const errorToString = (error) => {
|
const errorToString = (error) => {
|
||||||
@ -26,16 +26,12 @@ const errorToString = (error) => {
|
|||||||
if (error === 'invalid-signed') {
|
if (error === 'invalid-signed') {
|
||||||
return 'Invalid signed bitcoin transaction';
|
return 'Invalid signed bitcoin transaction';
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function Error(props) {
|
const Error = ({ error, ...rest }) => (
|
||||||
const error = errorToString(props.error);
|
<Text color="red" {...rest}>
|
||||||
|
{errorToString(error)}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
return(
|
export default Error;
|
||||||
<Text
|
|
||||||
color='red'
|
|
||||||
{...props}>
|
|
||||||
{error}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -1,99 +1,99 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Icon,
|
|
||||||
Row,
|
|
||||||
Text,
|
Text,
|
||||||
Button,
|
|
||||||
Col,
|
Col,
|
||||||
StatelessRadioButtonField as RadioButton,
|
StatelessRadioButtonField as RadioButton,
|
||||||
Label,
|
Label,
|
||||||
} from '@tlon/indigo-react';
|
} from '@tlon/indigo-react';
|
||||||
|
|
||||||
|
const feeLevels = {
|
||||||
|
low: 'low',
|
||||||
|
mid: 'mid',
|
||||||
|
high: 'high',
|
||||||
|
};
|
||||||
|
|
||||||
export default class FeePicker extends Component {
|
const FeePicker = ({ feeChoices, feeSelect, feeDismiss }) => {
|
||||||
constructor(props) {
|
const [feeSelected, setFeeSelected] = useState(feeLevels.mid);
|
||||||
super(props);
|
const [modalElement, setModalElement] = useState();
|
||||||
|
const modalRef = useRef();
|
||||||
|
|
||||||
this.state = {
|
// const clickDismiss = (e) => {
|
||||||
selected: 'mid'
|
// console.log(modalElement, e);
|
||||||
}
|
// // if (modalRef && !modalRef.contains(e.target)) {
|
||||||
|
// // feeDismiss();
|
||||||
|
// // }
|
||||||
|
// };
|
||||||
|
|
||||||
this.select = this.select.bind(this);
|
const select = (which) => {
|
||||||
this.clickDismiss = this.clickDismiss.bind(this);
|
setFeeSelected(which);
|
||||||
this.setModalRef = this.setModalRef.bind(this);
|
feeSelect(which);
|
||||||
}
|
feeDismiss();
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
// useEffect(() => {
|
||||||
document.addEventListener("click", this.clickDismiss);
|
// document.addEventListener('click', (e) => clickDismiss(e));
|
||||||
}
|
// setModalElement(modalRef.current);
|
||||||
|
// console.log(modalRef.current);
|
||||||
|
// return () => document.addEventListener('click', clickDismiss);
|
||||||
|
// }, []);
|
||||||
|
|
||||||
componentWillUnount() {
|
return (
|
||||||
document.removeEventListener("click", this.clickDismiss);
|
<Box
|
||||||
}
|
// ref={modalRef}
|
||||||
|
// onClick={() => feeDismiss()}
|
||||||
|
position="absolute"
|
||||||
|
p={4}
|
||||||
|
border="1px solid green"
|
||||||
|
zIndex={10}
|
||||||
|
backgroundColor="white"
|
||||||
|
borderRadius={3}
|
||||||
|
>
|
||||||
|
<Text fontSize={1} color="black" fontWeight="bold" mb={4}>
|
||||||
|
Transaction Speed
|
||||||
|
</Text>
|
||||||
|
<Col mt={4}>
|
||||||
|
<RadioButton
|
||||||
|
name="feeRadio"
|
||||||
|
selected={feeSelected === feeLevels.low}
|
||||||
|
p="2"
|
||||||
|
onChange={() => {
|
||||||
|
select('low');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label fontSize="14px">
|
||||||
|
Slow: {feeChoices.low[1]} sats/vbyte ~{feeChoices.low[0]}m
|
||||||
|
</Label>
|
||||||
|
</RadioButton>
|
||||||
|
|
||||||
setModalRef(n) {
|
<RadioButton
|
||||||
this.modalRef = n;
|
name="feeRadio"
|
||||||
}
|
selected={feeSelected === feeLevels.mid}
|
||||||
|
p="2"
|
||||||
|
onChange={() => {
|
||||||
|
select('mid');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label fontSize="14px">
|
||||||
|
Normal: {feeChoices.mid[1]} sats/vbyte ~{feeChoices.mid[0]}m
|
||||||
|
</Label>
|
||||||
|
</RadioButton>
|
||||||
|
|
||||||
clickDismiss(e) {
|
<RadioButton
|
||||||
if (this.modalRef && !(this.modalRef.contains(e.target))){
|
name="feeRadio"
|
||||||
this.props.feeDismiss();
|
selected={feeSelected === feeLevels.high}
|
||||||
}
|
p="2"
|
||||||
}
|
onChange={() => {
|
||||||
|
select('high');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label fontSize="14px">
|
||||||
|
Fast: {feeChoices.high[1]} sats/vbyte ~{feeChoices.high[0]}m
|
||||||
|
</Label>
|
||||||
|
</RadioButton>
|
||||||
|
</Col>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
select(which) {
|
export default FeePicker;
|
||||||
this.setState({selected: which});
|
|
||||||
this.props.feeSelect(which);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
ref={this.setModalRef}
|
|
||||||
position="absolute" p={4}
|
|
||||||
border="1px solid green" zIndex={10}
|
|
||||||
backgroundColor="white" borderRadius={3}
|
|
||||||
>
|
|
||||||
<Text fontSize={1} color="black" fontWeight="bold" mb={4}>
|
|
||||||
Transaction Speed
|
|
||||||
</Text>
|
|
||||||
<Col mt={4}>
|
|
||||||
<RadioButton
|
|
||||||
name="feeRadio"
|
|
||||||
selected={this.state.selected === 'low'}
|
|
||||||
p="2"
|
|
||||||
onChange={() => {
|
|
||||||
this.select('low');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Label fontSize="14px">Slow: {this.props.feeChoices.low[1]} sats/vbyte ~{this.props.feeChoices.low[0]}m</Label>
|
|
||||||
</RadioButton>
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
name="feeRadio"
|
|
||||||
selected={this.state.selected === 'mid'}
|
|
||||||
p="2"
|
|
||||||
onChange={() => {
|
|
||||||
this.select('mid');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Label fontSize="14px">Normal: {this.props.feeChoices.mid[1]} sats/vbyte ~{this.props.feeChoices.mid[0]}m</Label>
|
|
||||||
</RadioButton>
|
|
||||||
|
|
||||||
<RadioButton
|
|
||||||
name="feeRadio"
|
|
||||||
selected={this.state.selected === 'high'}
|
|
||||||
p="2"
|
|
||||||
onChange={() => {
|
|
||||||
this.select('high');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Label fontSize="14px">Fast: {this.props.feeChoices.high[1]} sats/vbyte ~{this.props.feeChoices.high[0]}m</Label>
|
|
||||||
</RadioButton>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,78 +1,81 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Box, Icon, Row, Text } from '@tlon/indigo-react';
|
||||||
Box,
|
|
||||||
Icon,
|
|
||||||
Row,
|
|
||||||
Text,
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
|
|
||||||
export default class Header extends Component {
|
const Header = ({ settings }) => {
|
||||||
constructor(props) {
|
const { provider } = useSettings();
|
||||||
super(props);
|
let icon = settings ? 'X' : 'Adjust';
|
||||||
|
let iconColor = settings ? 'black' : 'orange';
|
||||||
|
let iconLink = settings ? '/~btc' : '/~btc/settings';
|
||||||
|
|
||||||
|
let connection = null;
|
||||||
|
let badge = null;
|
||||||
|
if (!(provider && provider.connected)) {
|
||||||
|
connection = (
|
||||||
|
<Text fontSize={1} color="red" fontWeight="bold" mr={3}>
|
||||||
|
Provider Offline
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!settings) {
|
||||||
|
badge = (
|
||||||
|
<Box
|
||||||
|
borderRadius="50%"
|
||||||
|
width="8px"
|
||||||
|
height="8px"
|
||||||
|
backgroundColor="red"
|
||||||
|
position="absolute"
|
||||||
|
top="0px"
|
||||||
|
right="0px"
|
||||||
|
></Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
render() {
|
<Row
|
||||||
let icon = this.props.settings ? "X" : "Adjust";
|
height={8}
|
||||||
let iconColor = this.props.settings ? "black" : "orange";
|
width="100%"
|
||||||
let iconLink = this.props.settings ? "/~btc" : "/~btc/settings";
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
let connection = null;
|
pt={5}
|
||||||
let badge = null;
|
pb={5}
|
||||||
if (!(this.props.state.provider && this.props.state.provider.connected)) {
|
>
|
||||||
connection =
|
<Row alignItems="center" justifyContent="center">
|
||||||
<Text fontSize={1} color="red" fontWeight="bold" mr={3}>
|
<Box
|
||||||
Provider Offline
|
backgroundColor="orange"
|
||||||
|
borderRadius={4}
|
||||||
|
mr="12px"
|
||||||
|
width={5}
|
||||||
|
height={5}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Icon icon="Bitcoin" width={4} p={1} height={4} color="white" />
|
||||||
|
</Box>
|
||||||
|
<Text fontSize={2} fontWeight="bold" color="orange">
|
||||||
|
Bitcoin
|
||||||
</Text>
|
</Text>
|
||||||
|
</Row>
|
||||||
if (!this.props.settings) {
|
<Row alignItems="center">
|
||||||
badge = <Box borderRadius="50%" width="8px" height="8px" backgroundColor="red" position="absolute" top="0px" right="0px"></Box>
|
{connection}
|
||||||
|
<Link to={iconLink}>
|
||||||
}
|
<Box
|
||||||
}
|
backgroundColor="white"
|
||||||
|
borderRadius={4}
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row
|
|
||||||
height={8}
|
|
||||||
width='100%'
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
pt={5}
|
|
||||||
pb={5}
|
|
||||||
>
|
|
||||||
<Row alignItems="center" justifyContent="center">
|
|
||||||
<Box backgroundColor="orange"
|
|
||||||
borderRadius={4} mr="12px"
|
|
||||||
width={5}
|
width={5}
|
||||||
height={5}
|
height={5}
|
||||||
alignItems="center"
|
p={2}
|
||||||
justifyContent="center"
|
position="relative"
|
||||||
>
|
>
|
||||||
<Icon icon="Bitcoin" width={4} p={1} height={4} color="white"/>
|
{badge}
|
||||||
|
<Icon icon={icon} color={iconColor} />
|
||||||
</Box>
|
</Box>
|
||||||
<Text fontSize={2} fontWeight="bold" color="orange">
|
</Link>
|
||||||
Bitcoin
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
<Row alignItems="center">
|
|
||||||
{connection}
|
|
||||||
<Link to={iconLink}>
|
|
||||||
<Box backgroundColor="white"
|
|
||||||
borderRadius={4}
|
|
||||||
width={5}
|
|
||||||
height={5}
|
|
||||||
p={2}
|
|
||||||
position="relative"
|
|
||||||
>
|
|
||||||
{badge}
|
|
||||||
<Icon icon={icon} color={iconColor} />
|
|
||||||
</Box>
|
|
||||||
</Link>
|
|
||||||
</Row>
|
|
||||||
</Row>
|
</Row>
|
||||||
);
|
</Row>
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Icon,
|
Icon,
|
||||||
@ -9,18 +9,15 @@ import {
|
|||||||
Col,
|
Col,
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
} from '@tlon/indigo-react';
|
} from '@tlon/indigo-react';
|
||||||
|
import { Sigil } from './sigil.js';
|
||||||
import { Sigil } from './sigil.js'
|
|
||||||
|
|
||||||
import * as bitcoin from 'bitcoinjs-lib';
|
import * as bitcoin from 'bitcoinjs-lib';
|
||||||
import * as kg from 'urbit-key-generation';
|
import * as kg from 'urbit-key-generation';
|
||||||
import * as bip39 from 'bip39';
|
import Sent from './sent.js';
|
||||||
|
|
||||||
import Sent from './sent.js'
|
|
||||||
import { patp2dec, isValidPatq, isValidPatp } from 'urbit-ob';
|
import { patp2dec, isValidPatq, isValidPatp } from 'urbit-ob';
|
||||||
|
|
||||||
import { satsToCurrency } from '../../lib/util.js';
|
import { satsToCurrency } from '../../lib/util.js';
|
||||||
import Error from './error.js';
|
import Error from './error.js';
|
||||||
|
import { useSettings } from '../../hooks/useSettings.js';
|
||||||
|
import { api } from '../../api';
|
||||||
|
|
||||||
const BITCOIN_MAINNET_INFO = {
|
const BITCOIN_MAINNET_INFO = {
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
@ -46,246 +43,234 @@ const BITCOIN_TESTNET_INFO = {
|
|||||||
wif: 0xef,
|
wif: 0xef,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Invoice extends Component {
|
const Invoice = ({ stopSending, payee, satsAmount }) => {
|
||||||
constructor(props) {
|
const {
|
||||||
super(props);
|
error,
|
||||||
|
currencyRates,
|
||||||
|
psbt,
|
||||||
|
fee,
|
||||||
|
broadcastSuccess,
|
||||||
|
network,
|
||||||
|
denomination,
|
||||||
|
} = useSettings();
|
||||||
|
const [masterTicket, setMasterTicket] = useState('');
|
||||||
|
const [ready, setReady] = useState(false);
|
||||||
|
const [localError, setLocalError] = useState(error);
|
||||||
|
const [broadcasting, setBroadcasting] = useState(false);
|
||||||
|
const invoiceRef = useRef();
|
||||||
|
|
||||||
this.state = {
|
useEffect(() => {
|
||||||
masterTicket: '',
|
if (broadcasting && localError !== '') {
|
||||||
ready: false,
|
setBroadcasting(false);
|
||||||
error: this.props.state.error,
|
|
||||||
sent: false,
|
|
||||||
broadcasting: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.checkTicket = this.checkTicket.bind(this);
|
|
||||||
this.broadCastTx = this.broadCastTx.bind(this);
|
|
||||||
this.sendBitcoin = this.sendBitcoin.bind(this);
|
|
||||||
this.clickDismiss = this.clickDismiss.bind(this);
|
|
||||||
this.setInvoiceRef = this.setInvoiceRef.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(){
|
|
||||||
document.addEventListener("click", this.clickDismiss);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnMount(){
|
|
||||||
document.removeEventListener("click", this.clickDismiss);
|
|
||||||
}
|
|
||||||
|
|
||||||
setInvoiceRef(n){
|
|
||||||
this.invoiceRef = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
clickDismiss(e){
|
|
||||||
if (this.invoiceRef && !(this.invoiceRef.contains(e.target))) {
|
|
||||||
this.props.stopSending();
|
|
||||||
}
|
}
|
||||||
}
|
}, [error, broadcasting, setBroadcasting]);
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
const clickDismiss = (e) => {
|
||||||
if (this.state.broadcasting) {
|
if (invoiceRef && !invoiceRef.contains(e.target)) {
|
||||||
if (this.state.error !== '') {
|
stopSending();
|
||||||
this.setState({broadcasting: false});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
broadCastTx(psbtHex) {
|
useEffect(() => {
|
||||||
|
document.addEventListener('click', clickDismiss);
|
||||||
|
return () => document.removeEventListener('click', clickDismiss);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const broadCastTx = (psbtHex) => {
|
||||||
let command = {
|
let command = {
|
||||||
'broadcast-tx': psbtHex
|
'broadcast-tx': psbtHex,
|
||||||
}
|
};
|
||||||
return this.props.api.btcWalletCommand(command)
|
return api.btcWalletCommand(command);
|
||||||
}
|
};
|
||||||
|
|
||||||
sendBitcoin(ticket, psbt) {
|
const sendBitcoin = (ticket, psbt) => {
|
||||||
const newPsbt = bitcoin.Psbt.fromBase64(psbt);
|
const newPsbt = bitcoin.Psbt.fromBase64(psbt);
|
||||||
this.setState({broadcasting: true});
|
setBroadcasting(true);
|
||||||
kg.generateWallet({ ticket, ship: parseInt(patp2dec('~' + window.ship)) })
|
kg.generateWallet({
|
||||||
.then(urbitWallet => {
|
ticket,
|
||||||
const { xpub } = this.props.network === 'testnet'
|
ship: parseInt(patp2dec('~' + window.ship)),
|
||||||
? urbitWallet.bitcoinTestnet.keys
|
}).then((urbitWallet) => {
|
||||||
: urbitWallet.bitcoinMainnet.keys;
|
// const { xpub } =
|
||||||
|
// network === 'testnet'
|
||||||
|
// ? urbitWallet.bitcoinTestnet.keys
|
||||||
|
// : urbitWallet.bitcoinMainnet.keys;
|
||||||
|
|
||||||
const { xprv: zprv } = urbitWallet.bitcoinMainnet.keys;
|
const { xprv: zprv } = urbitWallet.bitcoinMainnet.keys;
|
||||||
const { xprv: vprv } = urbitWallet.bitcoinTestnet.keys;
|
const { xprv: vprv } = urbitWallet.bitcoinTestnet.keys;
|
||||||
|
|
||||||
const isTestnet = (this.props.network === 'testnet');
|
const isTestnet = network === 'testnet';
|
||||||
const derivationPrefix = isTestnet ? "m/84'/1'/0'/" : "m/84'/0'/0'/";
|
const derivationPrefix = isTestnet ? "m/84'/1'/0'/" : "m/84'/0'/0'/";
|
||||||
|
|
||||||
const btcWallet = (isTestnet)
|
const btcWallet = isTestnet
|
||||||
? bitcoin.bip32.fromBase58(vprv, BITCOIN_TESTNET_INFO)
|
? bitcoin.bip32.fromBase58(vprv, BITCOIN_TESTNET_INFO)
|
||||||
: bitcoin.bip32.fromBase58(zprv, BITCOIN_MAINNET_INFO);
|
: bitcoin.bip32.fromBase58(zprv, BITCOIN_MAINNET_INFO);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const hex = newPsbt.data.inputs
|
const hex = newPsbt.data.inputs
|
||||||
.reduce((psbt, input, idx) => {
|
.reduce((psbt, input, idx) => {
|
||||||
// removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0
|
// removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0
|
||||||
const path = input.bip32Derivation[0].path
|
const path = input.bip32Derivation[0].path
|
||||||
.split(derivationPrefix)
|
.split(derivationPrefix)
|
||||||
.join('');
|
.join('');
|
||||||
const prv = btcWallet.derivePath(path).privateKey;
|
const prv = btcWallet.derivePath(path).privateKey;
|
||||||
return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv));
|
return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv));
|
||||||
}, newPsbt)
|
}, newPsbt)
|
||||||
.finalizeAllInputs()
|
.finalizeAllInputs()
|
||||||
.extractTransaction()
|
.extractTransaction()
|
||||||
.toHex();
|
.toHex();
|
||||||
|
|
||||||
this.broadCastTx(hex);
|
broadCastTx(hex);
|
||||||
}
|
} catch (e) {
|
||||||
catch(e) {
|
setLocalError('invalid-master-ticket');
|
||||||
this.setState({error: 'invalid-master-ticket', broadcasting: false});
|
setBroadcasting(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
}
|
const checkTicket = (e) => {
|
||||||
|
|
||||||
|
|
||||||
checkTicket(e){
|
|
||||||
// TODO: port over bridge ticket validation logic
|
// TODO: port over bridge ticket validation logic
|
||||||
let masterTicket = e.target.value;
|
setMasterTicket(e.target.value);
|
||||||
let ready = isValidPatq(masterTicket);
|
setReady(isValidPatq(e.target.value));
|
||||||
let error = (ready) ? '' : 'invalid-master-ticket';
|
setLocalError(isValidPatq(e.target.value) ? '' : 'invalid-master-ticket');
|
||||||
this.setState({masterTicket, ready, error});
|
};
|
||||||
|
|
||||||
|
let inputColor = 'black';
|
||||||
|
let inputBg = 'white';
|
||||||
|
let inputBorder = 'lightGray';
|
||||||
|
|
||||||
|
if (error !== '') {
|
||||||
|
inputColor = 'red';
|
||||||
|
inputBg = 'veryLightRed';
|
||||||
|
inputBorder = 'red';
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
const isShip = isValidPatp(payee);
|
||||||
const broadcastSuccess = this.props.state.broadcastSuccess;
|
|
||||||
const { stopSending, payee, denomination, satsAmount, psbt, currencyRates, fee } = this.props;
|
|
||||||
const { sent, error } = this.state;
|
|
||||||
|
|
||||||
let inputColor = 'black';
|
const icon = isShip ? (
|
||||||
let inputBg = 'white';
|
<Sigil ship={payee} size={24} color="black" classes={''} icon padding={5} />
|
||||||
let inputBorder = 'lightGray';
|
) : (
|
||||||
|
<Box
|
||||||
|
backgroundColor="lighterGray"
|
||||||
|
width="24px"
|
||||||
|
height="24px"
|
||||||
|
textAlign="center"
|
||||||
|
alignItems="center"
|
||||||
|
borderRadius="2px"
|
||||||
|
p={1}
|
||||||
|
>
|
||||||
|
<Icon icon="Bitcoin" color="gray" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
if (error !== '') {
|
return (
|
||||||
inputColor = 'red';
|
<>
|
||||||
inputBg = 'veryLightRed';
|
{broadcastSuccess ? (
|
||||||
inputBorder = 'red';
|
<Sent payee={payee} stopSending={stopSending} satsAmount={satsAmount} />
|
||||||
}
|
) : (
|
||||||
|
<Col
|
||||||
const isShip = isValidPatp(payee);
|
ref={invoiceRef}
|
||||||
|
width="100%"
|
||||||
const icon = (isShip)
|
backgroundColor="white"
|
||||||
? <Sigil ship={payee} size={24} color="black" classes={''} icon padding={5}/>
|
borderRadius="48px"
|
||||||
: <Box backgroundColor="lighterGray"
|
mb={5}
|
||||||
width="24px"
|
p={5}
|
||||||
height="24px"
|
onClick={() => stopSending()}
|
||||||
textAlign="center"
|
>
|
||||||
alignItems="center"
|
|
||||||
borderRadius="2px"
|
|
||||||
p={1}
|
|
||||||
><Icon icon="Bitcoin" color="gray"/></Box>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{ broadcastSuccess ?
|
|
||||||
<Sent
|
|
||||||
payee={payee}
|
|
||||||
stopSending={stopSending}
|
|
||||||
denomination={denomination}
|
|
||||||
currencyRates={currencyRates}
|
|
||||||
satsAmount={satsAmount}
|
|
||||||
/> :
|
|
||||||
<Col
|
<Col
|
||||||
ref={this.setInvoiceRef}
|
|
||||||
width='100%'
|
|
||||||
backgroundColor='white'
|
|
||||||
borderRadius='48px'
|
|
||||||
mb={5}
|
|
||||||
p={5}
|
p={5}
|
||||||
|
mt={4}
|
||||||
|
backgroundColor="veryLightGreen"
|
||||||
|
borderRadius="24px"
|
||||||
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Col
|
<Row>
|
||||||
p={5}
|
<Text color="green" fontSize="40px">
|
||||||
mt={4}
|
{satsToCurrency(satsAmount, denomination, currencyRates)}
|
||||||
backgroundColor='veryLightGreen'
|
|
||||||
borderRadius='24px'
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Row>
|
|
||||||
<Text
|
|
||||||
color='green'
|
|
||||||
fontSize='40px'
|
|
||||||
>{satsToCurrency(satsAmount, denomination, currencyRates)}</Text>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Text
|
|
||||||
fontWeight="bold"
|
|
||||||
fontSize='16px'
|
|
||||||
color='midGreen'
|
|
||||||
>{`${satsAmount} sats`}</Text>
|
|
||||||
</Row>
|
|
||||||
<Row mt={2}>
|
|
||||||
<Text
|
|
||||||
fontSize='14px'
|
|
||||||
color='midGreen'
|
|
||||||
>{`Fee: ${satsToCurrency(fee, denomination, currencyRates)} (${fee} sats)`}</Text>
|
|
||||||
</Row>
|
|
||||||
<Row mt={4} >
|
|
||||||
<Text fontSize='16px' fontWeight="bold" color="gray">You are paying</Text>
|
|
||||||
</Row>
|
|
||||||
<Row mt={2} alignItems="center">
|
|
||||||
{icon}
|
|
||||||
<Text ml={2}
|
|
||||||
mono
|
|
||||||
color="gray"
|
|
||||||
fontSize='14px'
|
|
||||||
style={{'display': 'block', 'overflow-wrap': 'anywhere'}}
|
|
||||||
>{payee}</Text>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
<Row mt={3} mb={2} alignItems="center">
|
|
||||||
<Text gray fontSize={1} fontWeight='600' mr={4}>
|
|
||||||
Ticket
|
|
||||||
</Text>
|
</Text>
|
||||||
<Input
|
|
||||||
value={this.state.masterTicket}
|
|
||||||
fontSize="14px"
|
|
||||||
type="password"
|
|
||||||
name="masterTicket"
|
|
||||||
obscure={value => value.replace(/[^~-]+/g, '••••••')}
|
|
||||||
placeholder="••••••-••••••-••••••-••••••"
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect="off"
|
|
||||||
color={inputColor}
|
|
||||||
backgroundColor={inputBg}
|
|
||||||
borderColor={inputBorder}
|
|
||||||
onChange={this.checkTicket}
|
|
||||||
/>
|
|
||||||
</Row>
|
</Row>
|
||||||
{(error !== '') &&
|
<Row>
|
||||||
<Row>
|
<Text
|
||||||
<Error
|
fontWeight="bold"
|
||||||
fontSize='14px'
|
fontSize="16px"
|
||||||
color='red'
|
color="midGreen"
|
||||||
error={error}
|
>{`${satsAmount} sats`}</Text>
|
||||||
mt={2}/>
|
</Row>
|
||||||
</Row>
|
<Row mt={2}>
|
||||||
}
|
<Text fontSize="14px" color="midGreen">{`Fee: ${satsToCurrency(
|
||||||
<Row
|
fee,
|
||||||
flexDirection='row-reverse'
|
denomination,
|
||||||
mt={4}
|
currencyRates
|
||||||
alignItems="center"
|
)} (${fee} sats)`}</Text>
|
||||||
>
|
</Row>
|
||||||
<Button
|
<Row mt={4}>
|
||||||
primary
|
<Text fontSize="16px" fontWeight="bold" color="gray">
|
||||||
children='Send BTC'
|
You are paying
|
||||||
mr={3}
|
</Text>
|
||||||
fontSize={1}
|
</Row>
|
||||||
border="none"
|
<Row mt={2} alignItems="center">
|
||||||
borderRadius='24px'
|
{icon}
|
||||||
color={(this.state.ready && !error && !this.state.broadcasting) ? "white" : "lighterGray"}
|
<Text
|
||||||
backgroundColor={(this.state.ready && !error && !this.state.broadcasting) ? "green" : "veryLightGray"}
|
ml={2}
|
||||||
height='48px'
|
mono
|
||||||
onClick={() => this.sendBitcoin(this.state.masterTicket, psbt)}
|
color="gray"
|
||||||
disabled={!this.state.ready || error || this.state.broadcasting}
|
fontSize="14px"
|
||||||
style={{cursor: (this.state.ready && !error && !this.state.broadcasting) ? "pointer" : "default"}}
|
style={{ display: 'block', 'overflow-wrap': 'anywhere' }}
|
||||||
/>
|
>
|
||||||
{ (this.state.broadcasting) ? <LoadingSpinner mr={3}/> : null}
|
{payee}
|
||||||
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
}
|
<Row mt={3} mb={2} alignItems="center">
|
||||||
</>
|
<Text gray fontSize={1} fontWeight="600" mr={4}>
|
||||||
);
|
Ticket
|
||||||
}
|
</Text>
|
||||||
}
|
<Input
|
||||||
|
value={masterTicket}
|
||||||
|
fontSize="14px"
|
||||||
|
type="password"
|
||||||
|
name="masterTicket"
|
||||||
|
obscure={(value) => value.replace(/[^~-]+/g, '••••••')}
|
||||||
|
placeholder="••••••-••••••-••••••-••••••"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect="off"
|
||||||
|
color={inputColor}
|
||||||
|
backgroundColor={inputBg}
|
||||||
|
borderColor={inputBorder}
|
||||||
|
onChange={() => checkTicket()}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
{error !== '' && (
|
||||||
|
<Row>
|
||||||
|
<Error fontSize="14px" color="red" error={error} mt={2} />
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
<Row flexDirection="row-reverse" mt={4} alignItems="center">
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
mr={3}
|
||||||
|
fontSize={1}
|
||||||
|
border="none"
|
||||||
|
borderRadius="24px"
|
||||||
|
color={ready && !error && !broadcasting ? 'white' : 'lighterGray'}
|
||||||
|
backgroundColor={
|
||||||
|
ready && !error && !broadcasting ? 'green' : 'veryLightGray'
|
||||||
|
}
|
||||||
|
height="48px"
|
||||||
|
onClick={() => sendBitcoin(masterTicket, psbt)}
|
||||||
|
disabled={!ready || error || broadcasting}
|
||||||
|
style={{
|
||||||
|
cursor:
|
||||||
|
ready && !error && !broadcasting ? 'pointer' : 'default',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send BTC
|
||||||
|
</Button>
|
||||||
|
{broadcasting ? <LoadingSpinner mr={3} /> : null}
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Invoice;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
@ -8,159 +8,157 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
} from '@tlon/indigo-react';
|
} from '@tlon/indigo-react';
|
||||||
|
|
||||||
import { isValidPatp } from 'urbit-ob';
|
import { isValidPatp } from 'urbit-ob';
|
||||||
|
import { api } from '../../api';
|
||||||
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
|
|
||||||
export default class ProviderModal extends Component {
|
const providerStatuses = {
|
||||||
constructor(props) {
|
checking: 'checking',
|
||||||
super(props);
|
failed: 'failed',
|
||||||
|
ready: 'ready',
|
||||||
|
initial: '',
|
||||||
|
};
|
||||||
|
|
||||||
this.state = {
|
const ProviderModal = () => {
|
||||||
potentialProvider: null,
|
const { providerPerms } = useSettings();
|
||||||
checkingProvider: false,
|
const [providerStatus, setProviderStatus] = useState(
|
||||||
providerFailed: false,
|
providerStatuses.initial
|
||||||
ready: false,
|
);
|
||||||
provider: null,
|
const [potentialProvider, setPotentialProvider] = useState(null);
|
||||||
connecting: false,
|
const [provider, setProvider] = useState(null);
|
||||||
};
|
const [connecting, setConnecting] = useState(false);
|
||||||
|
|
||||||
this.checkProvider = this.checkProvider.bind(this);
|
const checkProvider = (e) => {
|
||||||
this.submitProvider = this.submitProvider.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkProvider(e) {
|
|
||||||
// TODO: loading states
|
// TODO: loading states
|
||||||
let provider = e.target.value;
|
setProviderStatus(providerStatuses.initial);
|
||||||
let ready = false;
|
let givenProvider = e.target.value;
|
||||||
let checkingProvider = false;
|
if (isValidPatp(givenProvider)) {
|
||||||
let potentialProvider = this.state.potentialProvider;
|
|
||||||
|
|
||||||
if (isValidPatp(provider)) {
|
|
||||||
let command = {
|
let command = {
|
||||||
'check-provider': provider,
|
'check-provider': givenProvider,
|
||||||
};
|
};
|
||||||
potentialProvider = provider;
|
setPotentialProvider(givenProvider);
|
||||||
checkingProvider = true;
|
setProviderStatus(providerStatuses.checking);
|
||||||
this.props.api.btcWalletCommand(command);
|
api.btcWalletCommand(command);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setState({ providerFailed: true, checkingProvider: false });
|
setProviderStatus(providerStatuses.failed);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
this.setState({ provider, ready, checkingProvider, potentialProvider });
|
setProvider(givenProvider);
|
||||||
}
|
};
|
||||||
|
|
||||||
componentDidUpdate() {
|
const submitProvider = () => {
|
||||||
if (!this.state.ready) {
|
if (providerStatus === providerStatuses.ready) {
|
||||||
if (this.props.providerPerms[this.state.provider]) {
|
let command = {
|
||||||
this.setState({
|
'set-provider': provider,
|
||||||
ready: true,
|
};
|
||||||
checkingProvider: false,
|
api.btcWalletCommand(command);
|
||||||
providerFailed: false,
|
setConnecting(true);
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (providerStatus !== providerStatuses.ready) {
|
||||||
|
if (providerPerms.provider === provider && providerPerms.permitted) {
|
||||||
|
setProviderStatus(providerStatuses.ready);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}, [providerStatus, providerPerms, provider, setProviderStatus]);
|
||||||
|
|
||||||
submitProvider() {
|
let workingNode = null;
|
||||||
if (this.state.ready) {
|
let workingColor = null;
|
||||||
let command = {
|
let workingBg = null;
|
||||||
'set-provider': this.state.provider,
|
if (providerStatus === providerStatuses.ready) {
|
||||||
};
|
workingColor = 'green';
|
||||||
this.props.api.btcWalletCommand(command);
|
workingBg = 'veryLightGreen';
|
||||||
this.setState({ connecting: true });
|
workingNode = (
|
||||||
}
|
<Box mt={3}>
|
||||||
}
|
<Text fontSize="14px" color="green">
|
||||||
|
{provider} is a working provider node
|
||||||
render() {
|
</Text>
|
||||||
let workingNode = null;
|
</Box>
|
||||||
let workingColor = null;
|
);
|
||||||
let workingBg = null;
|
} else if (providerStatus === providerStatuses.failed) {
|
||||||
if (this.state.ready) {
|
workingColor = 'red';
|
||||||
workingColor = 'green';
|
workingBg = 'veryLightRed';
|
||||||
workingBg = 'veryLightGreen';
|
workingNode = (
|
||||||
workingNode = (
|
<Box mt={3}>
|
||||||
<Box mt={3}>
|
<Text fontSize="14px" color="red">
|
||||||
<Text fontSize="14px" color="green">
|
{potentialProvider} is not a working provider node
|
||||||
{this.state.provider} is a working provider node
|
</Text>
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
} else if (this.state.providerFailed) {
|
|
||||||
workingColor = 'red';
|
|
||||||
workingBg = 'veryLightRed';
|
|
||||||
workingNode = (
|
|
||||||
<Box mt={3}>
|
|
||||||
<Text fontSize="14px" color="red">
|
|
||||||
{this.state.potentialProvider} is not a working provider node
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box width="100%" height="100%" padding={3}>
|
|
||||||
<Row>
|
|
||||||
<Icon icon="Bitcoin" mr={2} />
|
|
||||||
<Text fontSize="14px" fontWeight="bold">
|
|
||||||
Step 1 of 2: Set up Bitcoin Provider Node
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
<Box mt={3}>
|
|
||||||
<Text fontSize="14px" fontWeight="regular" color="gray">
|
|
||||||
In order to perform Bitcoin transaction in Landscape, you'll
|
|
||||||
need to set a provider node. A provider node is an urbit which
|
|
||||||
maintains a synced Bitcoin ledger.
|
|
||||||
<a
|
|
||||||
fontSize="14px"
|
|
||||||
target="_blank"
|
|
||||||
href="https://urbit.org/bitcoin-wallet"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{' '}
|
|
||||||
Learn More
|
|
||||||
</a>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box mt={3} mb={2}>
|
|
||||||
<Text fontSize="14px" fontWeight="500">
|
|
||||||
Provider Node
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Row alignItems="center">
|
|
||||||
<StatelessTextInput
|
|
||||||
mr={2}
|
|
||||||
width="256px"
|
|
||||||
fontSize="14px"
|
|
||||||
type="text"
|
|
||||||
name="masterTicket"
|
|
||||||
placeholder="e.g. ~zod"
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect="off"
|
|
||||||
mono
|
|
||||||
backgroundColor={workingBg}
|
|
||||||
color={workingColor}
|
|
||||||
borderColor={workingColor}
|
|
||||||
onChange={this.checkProvider}
|
|
||||||
/>
|
|
||||||
{this.state.checkingProvider ? <LoadingSpinner /> : null}
|
|
||||||
</Row>
|
|
||||||
{workingNode}
|
|
||||||
<Row alignItems="center" mt={3}>
|
|
||||||
<Button
|
|
||||||
mr={2}
|
|
||||||
primary
|
|
||||||
disabled={!this.state.ready}
|
|
||||||
fontSize="14px"
|
|
||||||
style={{ cursor: this.state.ready ? 'pointer' : 'default' }}
|
|
||||||
onClick={() => {
|
|
||||||
this.submitProvider(this.state.provider);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Set Peer Node
|
|
||||||
</Button>
|
|
||||||
{this.state.connecting ? <LoadingSpinner /> : null}
|
|
||||||
</Row>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<Box width="100%" height="100%" padding={3}>
|
||||||
|
<Row>
|
||||||
|
<Icon icon="Bitcoin" mr={2} />
|
||||||
|
<Text fontSize="14px" fontWeight="bold">
|
||||||
|
Step 1 of 2: Set up Bitcoin Provider Node
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
<Box mt={3}>
|
||||||
|
<Text fontSize="14px" fontWeight="regular" color="gray">
|
||||||
|
In order to perform Bitcoin transaction in Landscape, you'll need
|
||||||
|
to set a provider node. A provider node is an urbit which maintains a
|
||||||
|
synced Bitcoin ledger.
|
||||||
|
<a
|
||||||
|
fontSize="14px"
|
||||||
|
target="_blank"
|
||||||
|
href="https://urbit.org/bitcoin-wallet"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
Learn More
|
||||||
|
</a>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box mt={3} mb={2}>
|
||||||
|
<Text fontSize="14px" fontWeight="500">
|
||||||
|
Provider Node
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Row alignItems="center">
|
||||||
|
<StatelessTextInput
|
||||||
|
mr={2}
|
||||||
|
width="256px"
|
||||||
|
fontSize="14px"
|
||||||
|
type="text"
|
||||||
|
name="masterTicket"
|
||||||
|
placeholder="e.g. ~zod"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect="off"
|
||||||
|
mono
|
||||||
|
backgroundColor={workingBg}
|
||||||
|
color={workingColor}
|
||||||
|
borderColor={workingColor}
|
||||||
|
onChange={(e) => checkProvider(e)}
|
||||||
|
/>
|
||||||
|
{providerStatus === providerStatuses.checking ? (
|
||||||
|
<LoadingSpinner />
|
||||||
|
) : null}
|
||||||
|
</Row>
|
||||||
|
{workingNode}
|
||||||
|
<Row alignItems="center" mt={3}>
|
||||||
|
<Button
|
||||||
|
mr={2}
|
||||||
|
primary
|
||||||
|
disabled={providerStatus !== providerStatuses.ready}
|
||||||
|
fontSize="14px"
|
||||||
|
style={{
|
||||||
|
cursor:
|
||||||
|
providerStatus === providerStatuses.ready ? 'pointer' : 'default',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
submitProvider(provider);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Set Peer Node
|
||||||
|
</Button>
|
||||||
|
{connecting ? <LoadingSpinner /> : null}
|
||||||
|
</Row>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProviderModal;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Icon,
|
Icon,
|
||||||
@ -8,450 +8,443 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Col,
|
Col,
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
StatelessRadioButtonField as RadioButton,
|
|
||||||
} from '@tlon/indigo-react';
|
} from '@tlon/indigo-react';
|
||||||
|
import Invoice from './invoice.js';
|
||||||
import Invoice from './invoice.js'
|
import BridgeInvoice from './bridgeInvoice.js';
|
||||||
import BridgeInvoice from './bridgeInvoice.js'
|
import FeePicker from './feePicker.js';
|
||||||
import FeePicker from './feePicker.js'
|
import Error from './error.js';
|
||||||
import Error from './error.js'
|
import Signer from './signer.js';
|
||||||
import Signer from './signer.js'
|
|
||||||
|
|
||||||
import { validate } from 'bitcoin-address-validation';
|
import { validate } from 'bitcoin-address-validation';
|
||||||
|
|
||||||
import * as ob from 'urbit-ob';
|
import * as ob from 'urbit-ob';
|
||||||
|
import { useSettings } from '../../hooks/useSettings.js';
|
||||||
|
import { api } from '../../api';
|
||||||
|
|
||||||
export default class Send extends Component {
|
const focusFields = {
|
||||||
constructor(props) {
|
empty: '',
|
||||||
super(props);
|
payee: 'payee',
|
||||||
|
currency: 'currency',
|
||||||
|
sats: 'sats',
|
||||||
|
note: 'note',
|
||||||
|
};
|
||||||
|
|
||||||
this.state = {
|
const Send = ({ stopSending, value, conversion }) => {
|
||||||
signing: false,
|
const { error, setError, network, psbt, denomination, shipWallets } =
|
||||||
denomAmount: '0.00',
|
useSettings();
|
||||||
satsAmount: '0',
|
const [signing, setSigning] = useState(false);
|
||||||
payee: '',
|
const [denomAmount, setDenomAmount] = useState('0.00');
|
||||||
checkingPatp: false,
|
const [satsAmount, setSatsAmount] = useState('0');
|
||||||
payeeType: '',
|
const [payee, setPayee] = useState('');
|
||||||
ready: false,
|
const [checkingPatp, setCheckingPatp] = useState(false);
|
||||||
validPayee: false,
|
const [payeeType, setPayeeType] = useState('');
|
||||||
focusPayee: true,
|
const [ready, setReady] = useState(false);
|
||||||
focusCurrency: false,
|
const [validPayee, setValidPayee] = useState(false);
|
||||||
focusSats: false,
|
const [focusedField, setFocusedField] = useState(focusFields.empty);
|
||||||
focusNote: false,
|
const [feeChoices, setFeeChoices] = useState({
|
||||||
submitting: false,
|
low: [10, 1],
|
||||||
feeChoices: {
|
mid: [10, 1],
|
||||||
low: [10, 1],
|
high: [10, 1],
|
||||||
mid: [10, 1],
|
});
|
||||||
high: [10, 1],
|
const [feeValue, setFeeValue] = useState('mid');
|
||||||
},
|
const [showModal, setShowModal] = useState(false);
|
||||||
feeValue: "mid",
|
const [note, setNote] = useState('');
|
||||||
showModal: false,
|
const [choosingSignMethod, setChoosingSignMethod] = useState(false);
|
||||||
note: '',
|
const [signMethod, setSignMethod] = useState('bridge');
|
||||||
choosingSignMethod: false,
|
|
||||||
signMethod: 'bridge',
|
|
||||||
};
|
|
||||||
|
|
||||||
this.initPayment = this.initPayment.bind(this);
|
const feeDismiss = () => {
|
||||||
this.checkPayee = this.checkPayee.bind(this);
|
setShowModal(false);
|
||||||
this.feeSelect = this.feeSelect.bind(this);
|
};
|
||||||
this.feeDismiss = this.feeDismiss.bind(this);
|
|
||||||
this.toggleSignMethod = this.toggleSignMethod.bind(this);
|
|
||||||
this.setSignMethod = this.setSignMethod.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
feeDismiss() {
|
const feeSelect = (which) => {
|
||||||
this.setState({showModal: false});
|
setFeeValue(which);
|
||||||
}
|
};
|
||||||
|
|
||||||
feeSelect(which) {
|
const handleSetSignMethod = (signMethod) => {
|
||||||
this.setState({feeValue: which});
|
setSignMethod(signMethod);
|
||||||
}
|
setChoosingSignMethod(false);
|
||||||
|
};
|
||||||
componentDidMount(){
|
|
||||||
if (this.props.network === 'bitcoin'){
|
|
||||||
let url = "https://bitcoiner.live/api/fees/estimates/latest";
|
|
||||||
fetch(url).then(res => res.json()).then(n => {
|
|
||||||
let estimates = Object.keys(n.estimates);
|
|
||||||
let mid = Math.floor(estimates.length/2)
|
|
||||||
let high = estimates.length - 1;
|
|
||||||
this.setState({
|
|
||||||
feeChoices: {
|
|
||||||
high: [30, n.estimates[30]["sat_per_vbyte"]],
|
|
||||||
mid: [180, n.estimates[180]["sat_per_vbyte"]],
|
|
||||||
low: [360, n.estimates[360]["sat_per_vbyte"]],
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setSignMethod(signMethod) {
|
|
||||||
this.setState({signMethod, choosingSignMethod: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
checkPayee(e){
|
|
||||||
store.handleEvent({data: {error: ''}});
|
|
||||||
|
|
||||||
let payee = e.target.value;
|
|
||||||
let isPatp = ob.isValidPatp(payee);
|
|
||||||
let isAddress = validate(payee);
|
|
||||||
|
|
||||||
|
const checkPayee = (e) => {
|
||||||
|
console.log('checkPayee', { e });
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
let payeeReceived = e.target.value;
|
||||||
|
let isPatp = ob.isValidPatp(payeeReceived);
|
||||||
|
let isAddress = validate(payeeReceived);
|
||||||
|
console.log({ payeeReceived, isPatp, isAddress });
|
||||||
if (isPatp) {
|
if (isPatp) {
|
||||||
let command = {'check-payee': payee}
|
console.log('isPatp', isPatp);
|
||||||
this.props.api.btcWalletCommand(command)
|
let command = { 'check-payee': payeeReceived };
|
||||||
|
api.btcWalletCommand(command);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setState({checkingPatp: false});
|
setCheckingPatp(false);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
this.setState({
|
setCheckingPatp(true);
|
||||||
checkingPatp: true,
|
setPayeeType('ship');
|
||||||
payeeType: 'ship',
|
setPayee(payeeReceived);
|
||||||
payee,
|
|
||||||
});
|
|
||||||
} else if (isAddress) {
|
} else if (isAddress) {
|
||||||
this.setState({
|
setPayee(payeeReceived);
|
||||||
payee,
|
setReady(true);
|
||||||
ready: true,
|
setCheckingPatp(false);
|
||||||
checkingPatp: false,
|
setPayeeType('address');
|
||||||
payeeType: 'address',
|
setValidPayee(true);
|
||||||
validPayee: true,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
setPayee(payeeReceived);
|
||||||
payee,
|
setReady(false);
|
||||||
ready: false,
|
setCheckingPatp(false);
|
||||||
checkingPatp: false,
|
setPayeeType('');
|
||||||
payeeType: '',
|
setValidPayee(false);
|
||||||
validPayee: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
const toggleSignMethod = () => {
|
||||||
if ((prevProps.error !== this.props.error) &&
|
setChoosingSignMethod(!choosingSignMethod);
|
||||||
(this.props.error !== '') && (this.props.error !== 'broadcast-fail')) {
|
};
|
||||||
this.setState({signing: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.state.ready && this.state.checkingPatp) {
|
const initPayment = () => {
|
||||||
if (this.props.shipWallets[this.state.payee.slice(1)]) {
|
if (payeeType === 'ship') {
|
||||||
this.setState({ready: true, checkingPatp: false, validPayee: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSignMethod(toggle) {
|
|
||||||
this.setState({choosingSignMethod: !toggle});
|
|
||||||
}
|
|
||||||
|
|
||||||
initPayment() {
|
|
||||||
if (this.state.payeeType === 'ship') {
|
|
||||||
let command = {
|
let command = {
|
||||||
'init-payment': {
|
'init-payment': {
|
||||||
'payee': this.state.payee,
|
payee,
|
||||||
'value': parseInt(this.state.satsAmount),
|
value: parseInt(satsAmount),
|
||||||
'feyb': this.state.feeChoices[this.state.feeValue][1],
|
feyb: feeChoices[feeValue][1],
|
||||||
'note': (this.state.note || null),
|
note: note || null,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
this.props.api.btcWalletCommand(command).then(res => this.setState({signing: true}));
|
|
||||||
} else if (this.state.payeeType === 'address') {
|
api.btcWalletCommand(command).then(() => setSigning(true));
|
||||||
|
} else if (payeeType === 'address') {
|
||||||
let command = {
|
let command = {
|
||||||
'init-payment-external': {
|
'init-payment-external': {
|
||||||
'address': this.state.payee,
|
address: payee,
|
||||||
'value': parseInt(this.state.satsAmount),
|
value: parseInt(satsAmount),
|
||||||
'feyb': 1,
|
feyb: 1,
|
||||||
'note': (this.state.note || null),
|
note: note || null,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
this.props.api.btcWalletCommand(command).then(res => this.setState({signing: true}));
|
api.btcWalletCommand(command).then(() => setSigning(true));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (network === 'bitcoin') {
|
||||||
|
let url = 'https://bitcoiner.live/api/fees/estimates/latest';
|
||||||
|
fetch(url)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((n) => {
|
||||||
|
// let estimates = Object.keys(n.estimates);
|
||||||
|
// let mid = Math.floor(estimates.length / 2);
|
||||||
|
// let high = estimates.length - 1;
|
||||||
|
setFeeChoices({
|
||||||
|
high: [30, n.estimates[30]['sat_per_vbyte']],
|
||||||
|
mid: [180, n.estimates[180]['sat_per_vbyte']],
|
||||||
|
low: [360, n.estimates[360]['sat_per_vbyte']],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ready && checkingPatp) {
|
||||||
|
console.log({ ready, checkingPatp, shipWallets, payee });
|
||||||
|
if (shipWallets.payee === payee.slice(1) && shipWallets.hasWallet) {
|
||||||
|
console.log('good');
|
||||||
|
setReady(true);
|
||||||
|
setCheckingPatp(false);
|
||||||
|
setValidPayee(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [ready, checkingPatp, shipWallets]);
|
||||||
|
|
||||||
|
let payeeColor = 'black';
|
||||||
|
let payeeBg = 'white';
|
||||||
|
let payeeBorder = 'lightGray';
|
||||||
|
if (error) {
|
||||||
|
payeeColor = 'red';
|
||||||
|
payeeBorder = 'red';
|
||||||
|
payeeBg = 'veryLightRed';
|
||||||
|
} else if (focusedField === focusFields.payee && validPayee) {
|
||||||
|
payeeColor = 'green';
|
||||||
|
payeeBorder = 'green';
|
||||||
|
payeeBg = 'veryLightGreen';
|
||||||
|
} else if (!focusedField === focusFields.payee && validPayee) {
|
||||||
|
payeeColor = 'blue';
|
||||||
|
payeeBorder = 'white';
|
||||||
|
payeeBg = 'white';
|
||||||
|
} else if (focusedField !== focusFields.payee && !validPayee) {
|
||||||
|
payeeColor = 'red';
|
||||||
|
payeeBorder = 'red';
|
||||||
|
payeeBg = 'veryLightRed';
|
||||||
|
} else if (
|
||||||
|
focusedField === 'payee' &&
|
||||||
|
!validPayee &&
|
||||||
|
!checkingPatp &&
|
||||||
|
payeeType === 'ship'
|
||||||
|
) {
|
||||||
|
payeeColor = 'red';
|
||||||
|
payeeBorder = 'red';
|
||||||
|
payeeBg = 'veryLightRed';
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
const signReady = ready && parseInt(satsAmount) > 0 && !signing;
|
||||||
let payeeColor = "black";
|
|
||||||
let payeeBg = "white";
|
|
||||||
let payeeBorder = "lightGray";
|
|
||||||
if (this.props.error) {
|
|
||||||
payeeColor="red";
|
|
||||||
payeeBorder = "red";
|
|
||||||
payeeBg="veryLightRed";
|
|
||||||
} else if (this.state.focusPayee && this.state.validPayee) {
|
|
||||||
payeeColor = "green";
|
|
||||||
payeeBorder = "green";
|
|
||||||
payeeBg = "veryLightGreen";
|
|
||||||
} else if (!this.state.focusPayee && this.state.validPayee){
|
|
||||||
payeeColor="blue";
|
|
||||||
payeeBorder = "white";
|
|
||||||
payeeBg = "white";
|
|
||||||
} else if (!this.state.focusPayee && !this.state.validPayee) {
|
|
||||||
payeeColor="red";
|
|
||||||
payeeBorder = "red";
|
|
||||||
payeeBg="veryLightRed";
|
|
||||||
} else if (this.state.focusPayee &&
|
|
||||||
!this.state.validPayee &&
|
|
||||||
!this.state.checkingPatp &&
|
|
||||||
this.state.payeeType === 'ship'){
|
|
||||||
payeeColor="red";
|
|
||||||
payeeBorder = "red";
|
|
||||||
payeeBg="veryLightRed";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let invoice = null;
|
||||||
const { api, value, conversion, stopSending, denomination, psbt, currencyRates, error, network, fee } = this.props;
|
if (signMethod === 'masterTicket') {
|
||||||
const { denomAmount, satsAmount, signing, payee, choosingSignMethod, signMethod } = this.state;
|
invoice = (
|
||||||
|
<Invoice
|
||||||
const signReady = (this.state.ready && (parseInt(this.state.satsAmount) > 0)) && !signing;
|
stopSending={stopSending}
|
||||||
|
payee={payee}
|
||||||
let invoice = null;
|
satsAmount={satsAmount}
|
||||||
if (signMethod === 'masterTicket') {
|
/>
|
||||||
invoice =
|
);
|
||||||
<Invoice
|
} else if (signMethod === 'bridge') {
|
||||||
network={network}
|
invoice = (
|
||||||
api={api}
|
<BridgeInvoice
|
||||||
psbt={psbt}
|
stopSending={stopSending}
|
||||||
fee={fee}
|
payee={payee}
|
||||||
currencyRates={currencyRates}
|
satsAmount={satsAmount}
|
||||||
stopSending={stopSending}
|
/>
|
||||||
payee={payee}
|
|
||||||
denomination={denomination}
|
|
||||||
satsAmount={satsAmount}
|
|
||||||
state={this.props.state}
|
|
||||||
/>
|
|
||||||
} else if (signMethod === 'bridge') {
|
|
||||||
invoice =
|
|
||||||
<BridgeInvoice
|
|
||||||
state={this.props.state}
|
|
||||||
api={api}
|
|
||||||
psbt={psbt}
|
|
||||||
fee={fee}
|
|
||||||
currencyRates={currencyRates}
|
|
||||||
stopSending={stopSending}
|
|
||||||
payee={payee}
|
|
||||||
denomination={denomination}
|
|
||||||
satsAmount={satsAmount}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{ (signing && psbt) ? invoice :
|
|
||||||
<Col
|
|
||||||
width='100%'
|
|
||||||
backgroundColor='white'
|
|
||||||
borderRadius='48px'
|
|
||||||
mb={5}
|
|
||||||
p={5}
|
|
||||||
>
|
|
||||||
<Col width="100%">
|
|
||||||
<Row
|
|
||||||
justifyContent='space-between'
|
|
||||||
alignItems='center'
|
|
||||||
>
|
|
||||||
<Text highlight color='blue' fontSize={1}>Send BTC</Text>
|
|
||||||
<Text highlight color='blue' fontSize={1}>{value}</Text>
|
|
||||||
<Icon
|
|
||||||
icon='X'
|
|
||||||
cursor='pointer'
|
|
||||||
onClick={() => stopSending()}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
<Row
|
|
||||||
alignItems='center'
|
|
||||||
mt={6}
|
|
||||||
justifyContent='space-between'>
|
|
||||||
<Row justifyContent="space-between" width='calc(40% - 30px)' alignItems="center">
|
|
||||||
<Text gray fontSize={1} fontWeight='600'>To</Text>
|
|
||||||
{this.state.checkingPatp ?
|
|
||||||
<LoadingSpinner background="midOrange" foreground="orange"/> : null
|
|
||||||
}
|
|
||||||
</Row>
|
|
||||||
<Input
|
|
||||||
autoFocus
|
|
||||||
onFocus={() => {this.setState({focusPayee: true})}}
|
|
||||||
onBlur={() => {this.setState({focusPayee: false})}}
|
|
||||||
color={payeeColor}
|
|
||||||
backgroundColor={payeeBg}
|
|
||||||
borderColor={payeeBorder}
|
|
||||||
ml={2}
|
|
||||||
flexGrow="1"
|
|
||||||
fontSize='14px'
|
|
||||||
placeholder='~sampel-palnet or BTC address'
|
|
||||||
value={payee}
|
|
||||||
fontFamily="mono"
|
|
||||||
disabled={signing}
|
|
||||||
onChange={this.checkPayee}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
{error &&
|
|
||||||
<Row
|
|
||||||
alignItems='center'
|
|
||||||
justifyContent='space-between'>
|
|
||||||
{/* yes this is a hack */}
|
|
||||||
<Box width='calc(40% - 30px)'/>
|
|
||||||
<Error
|
|
||||||
error={error}
|
|
||||||
fontSize='14px'
|
|
||||||
ml={2}
|
|
||||||
mt={2}
|
|
||||||
width='100%' />
|
|
||||||
</Row>
|
|
||||||
}
|
|
||||||
<Row
|
|
||||||
alignItems='center'
|
|
||||||
mt={4}
|
|
||||||
justifyContent='space-between'>
|
|
||||||
<Text
|
|
||||||
gray
|
|
||||||
fontSize={1}
|
|
||||||
fontWeight='600'
|
|
||||||
width="40%"
|
|
||||||
>Amount</Text>
|
|
||||||
<Input
|
|
||||||
onFocus={() => {this.setState({focusCurrency: true})}}
|
|
||||||
onBlur={() => {this.setState({focusCurrency: false})}}
|
|
||||||
fontSize='14px'
|
|
||||||
width='100%'
|
|
||||||
type='number'
|
|
||||||
borderColor={this.state.focusCurrency ? "lightGray" : "none"}
|
|
||||||
disabled={signing}
|
|
||||||
value={denomAmount}
|
|
||||||
onChange={e => {
|
|
||||||
this.setState({
|
|
||||||
denomAmount: e.target.value,
|
|
||||||
satsAmount: Math.round(parseFloat(e.target.value) / conversion * 100000000)
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Text color="lighterGray" fontSize={1} ml={3}>{denomination}</Text>
|
|
||||||
</Row>
|
|
||||||
<Row
|
|
||||||
alignItems='center'
|
|
||||||
mt={2}
|
|
||||||
justifyContent='space-between'>
|
|
||||||
{/* yes this is a hack */}
|
|
||||||
<Box width='40%'/>
|
|
||||||
<Input
|
|
||||||
onFocus={() => {this.setState({focusSats: true})}}
|
|
||||||
onBlur={() => {this.setState({focusSats: false})}}
|
|
||||||
fontSize='14px'
|
|
||||||
width='100%'
|
|
||||||
type='number'
|
|
||||||
borderColor={this.state.focusSats ? "lightGray" : "none"}
|
|
||||||
disabled={signing}
|
|
||||||
value={satsAmount}
|
|
||||||
onChange={e => {
|
|
||||||
this.setState({
|
|
||||||
denomAmount: parseFloat(e.target.value) * (conversion / 100000000),
|
|
||||||
satsAmount: e.target.value
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Text color="lightGray" fontSize={1} ml={3}>sats</Text>
|
|
||||||
</Row>
|
|
||||||
<Row mt={4} width="100%" justifyContent="space-between">
|
|
||||||
<Text
|
|
||||||
gray
|
|
||||||
fontSize={1}
|
|
||||||
fontWeight='600'
|
|
||||||
width="40%"
|
|
||||||
>Fee</Text>
|
|
||||||
<Row alignItems="center">
|
|
||||||
<Text mr={2} color="lightGray" fontSize="14px">
|
|
||||||
{this.state.feeChoices[this.state.feeValue][1]} sats/vbyte
|
|
||||||
</Text>
|
|
||||||
<Icon icon="ChevronSouth"
|
|
||||||
fontSize="14px"
|
|
||||||
color="lightGray"
|
|
||||||
onClick={() => {if (!this.state.showModal) this.setState({showModal: true}); }}
|
|
||||||
cursor="pointer"/>
|
|
||||||
</Row>
|
|
||||||
</Row>
|
|
||||||
<Col alignItems="center">
|
|
||||||
{!this.state.showModal ? null :
|
|
||||||
<FeePicker
|
|
||||||
feeChoices={this.state.feeChoices}
|
|
||||||
feeSelect={this.feeSelect}
|
|
||||||
feeDismiss={this.feeDismiss}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Col>
|
|
||||||
<Row mt={4} width="100%"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems='center'
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
gray
|
|
||||||
fontSize={1}
|
|
||||||
fontWeight='600'
|
|
||||||
width="40%"
|
|
||||||
>Note</Text>
|
|
||||||
<Input
|
|
||||||
onFocus={() => {this.setState({focusNote: true})}}
|
|
||||||
onBlur={() => {this.setState({focusNote: false})}}
|
|
||||||
fontSize='14px'
|
|
||||||
width='100%'
|
|
||||||
placeholder="What's this for?"
|
|
||||||
type='text'
|
|
||||||
borderColor={this.state.focusNote ? "lightGray" : "none"}
|
|
||||||
disabled={signing}
|
|
||||||
value={this.state.note}
|
|
||||||
onChange={e => {
|
|
||||||
this.setState({
|
|
||||||
note: e.target.value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
<Row
|
|
||||||
flexDirection='row-reverse'
|
|
||||||
alignItems="center"
|
|
||||||
mt={4}
|
|
||||||
>
|
|
||||||
<Signer
|
|
||||||
signReady={signReady}
|
|
||||||
choosingSignMethod={choosingSignMethod}
|
|
||||||
signMethod={signMethod}
|
|
||||||
setSignMethod={this.setSignMethod}
|
|
||||||
initPayment={this.initPayment} />
|
|
||||||
{ (!(signing && !error)) ? null :
|
|
||||||
<LoadingSpinner mr={2} background="midOrange" foreground="orange"/>
|
|
||||||
}
|
|
||||||
<Button
|
|
||||||
width='48px'
|
|
||||||
children={
|
|
||||||
<Icon
|
|
||||||
icon={choosingSignMethod ? 'X' : 'Ellipsis'}
|
|
||||||
color={signReady ? 'blue' : 'lighterGray'}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fontSize={1}
|
|
||||||
fontWeight='bold'
|
|
||||||
borderRadius='24px'
|
|
||||||
mr={2}
|
|
||||||
height='48px'
|
|
||||||
onClick={() => this.toggleSignMethod(choosingSignMethod)}
|
|
||||||
color={signReady ? 'white' : 'lighterGray'}
|
|
||||||
backgroundColor={signReady ? 'rgba(33, 157, 255, 0.2)' : 'veryLightGray'}
|
|
||||||
disabled={!signReady}
|
|
||||||
border='none'
|
|
||||||
style={{cursor: signReady ? 'pointer' : 'default'}} />
|
|
||||||
</Row>
|
|
||||||
{signMethod === 'masterTicket' &&
|
|
||||||
<Row
|
|
||||||
mt={4}
|
|
||||||
alignItems='center'
|
|
||||||
>
|
|
||||||
<Icon icon='Info' color='yellow' height={4} width={4}/>
|
|
||||||
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>
|
|
||||||
We recommend that you sign transactions using Bridge to protect your master ticket.
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
}
|
|
||||||
</Col>
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{signing && psbt ? (
|
||||||
|
invoice
|
||||||
|
) : (
|
||||||
|
<Col
|
||||||
|
width="100%"
|
||||||
|
backgroundColor="white"
|
||||||
|
borderRadius="48px"
|
||||||
|
mb={5}
|
||||||
|
p={5}
|
||||||
|
>
|
||||||
|
<Col width="100%">
|
||||||
|
<Row justifyContent="space-between" alignItems="center">
|
||||||
|
<Text highlight color="blue" fontSize={1}>
|
||||||
|
Send BTC
|
||||||
|
</Text>
|
||||||
|
<Text highlight color="blue" fontSize={1}>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
<Icon icon="X" cursor="pointer" onClick={() => stopSending()} />
|
||||||
|
</Row>
|
||||||
|
<Row alignItems="center" mt={6} justifyContent="space-between">
|
||||||
|
<Row
|
||||||
|
justifyContent="space-between"
|
||||||
|
width="calc(40% - 30px)"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text gray fontSize={1} fontWeight="600">
|
||||||
|
To
|
||||||
|
</Text>
|
||||||
|
{checkingPatp ? (
|
||||||
|
<LoadingSpinner background="midOrange" foreground="orange" />
|
||||||
|
) : null}
|
||||||
|
</Row>
|
||||||
|
<Input
|
||||||
|
// autoFocus
|
||||||
|
onFocus={() => {
|
||||||
|
setFocusedField(focusFields.payee);
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setFocusedField(focusFields.empty);
|
||||||
|
}}
|
||||||
|
color={payeeColor}
|
||||||
|
backgroundColor={payeeBg}
|
||||||
|
borderColor={payeeBorder}
|
||||||
|
ml={2}
|
||||||
|
flexGrow="1"
|
||||||
|
fontSize="14px"
|
||||||
|
placeholder="~sampel-palnet or BTC address"
|
||||||
|
value={payee}
|
||||||
|
fontFamily="mono"
|
||||||
|
disabled={signing}
|
||||||
|
onChange={(e) => checkPayee(e)}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
{error && (
|
||||||
|
<Row alignItems="center" justifyContent="space-between">
|
||||||
|
{/* yes this is a hack */}
|
||||||
|
<Box width="calc(40% - 30px)" />
|
||||||
|
<Error
|
||||||
|
error={error}
|
||||||
|
fontSize="14px"
|
||||||
|
ml={2}
|
||||||
|
mt={2}
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
<Row alignItems="center" mt={4} justifyContent="space-between">
|
||||||
|
<Text gray fontSize={1} fontWeight="600" width="40%">
|
||||||
|
Amount
|
||||||
|
</Text>
|
||||||
|
<Input
|
||||||
|
onFocus={() => {
|
||||||
|
setFocusedField(focusFields.currency);
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setFocusedField(focusFields.empty);
|
||||||
|
}}
|
||||||
|
fontSize="14px"
|
||||||
|
width="100%"
|
||||||
|
type="number"
|
||||||
|
borderColor={
|
||||||
|
focusedField === focusFields.currency ? 'lightGray' : 'none'
|
||||||
|
}
|
||||||
|
disabled={signing}
|
||||||
|
value={denomAmount}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDenomAmount(e.target.value);
|
||||||
|
setSatsAmount(
|
||||||
|
Math.round(
|
||||||
|
(parseFloat(e.target.value) / conversion) * 100000000
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text color="lighterGray" fontSize={1} ml={3}>
|
||||||
|
{denomination}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
<Row alignItems="center" mt={2} justifyContent="space-between">
|
||||||
|
{/* yes this is a hack */}
|
||||||
|
<Box width="40%" />
|
||||||
|
<Input
|
||||||
|
onFocus={() => {
|
||||||
|
setFocusedField(focusFields.sats);
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setFocusedField(focusFields.empty);
|
||||||
|
}}
|
||||||
|
fontSize="14px"
|
||||||
|
width="100%"
|
||||||
|
type="number"
|
||||||
|
borderColor={
|
||||||
|
focusedField === focusFields.sats ? 'lightGray' : 'none'
|
||||||
|
}
|
||||||
|
disabled={signing}
|
||||||
|
value={satsAmount}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDenomAmount(
|
||||||
|
parseFloat(e.target.value) * (conversion / 100000000)
|
||||||
|
);
|
||||||
|
setSatsAmount(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text color="lightGray" fontSize={1} ml={3}>
|
||||||
|
sats
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
<Row mt={4} width="100%" justifyContent="space-between">
|
||||||
|
<Text gray fontSize={1} fontWeight="600" width="40%">
|
||||||
|
Fee
|
||||||
|
</Text>
|
||||||
|
<Row alignItems="center">
|
||||||
|
<Text mr={2} color="lightGray" fontSize="14px">
|
||||||
|
{feeChoices[feeValue][1]} sats/vbyte
|
||||||
|
</Text>
|
||||||
|
<Icon
|
||||||
|
icon="ChevronSouth"
|
||||||
|
fontSize="14px"
|
||||||
|
color="lightGray"
|
||||||
|
onClick={() => {
|
||||||
|
if (!showModal) setShowModal(true);
|
||||||
|
}}
|
||||||
|
cursor="pointer"
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
<Col alignItems="center">
|
||||||
|
{!showModal ? null : (
|
||||||
|
<FeePicker
|
||||||
|
feeChoices={feeChoices}
|
||||||
|
feeSelect={feeSelect}
|
||||||
|
feeDismiss={feeDismiss}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
<Row
|
||||||
|
mt={4}
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text gray fontSize={1} fontWeight="600" width="40%">
|
||||||
|
Note
|
||||||
|
</Text>
|
||||||
|
<Input
|
||||||
|
onFocus={() => {
|
||||||
|
setFocusedField(focusFields.note);
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setFocusedField(focusFields.empty);
|
||||||
|
}}
|
||||||
|
fontSize="14px"
|
||||||
|
width="100%"
|
||||||
|
placeholder="What's this for?"
|
||||||
|
type="text"
|
||||||
|
borderColor={
|
||||||
|
focusedField === focusFields.note ? 'lightGray' : 'none'
|
||||||
|
}
|
||||||
|
disabled={signing}
|
||||||
|
value={note}
|
||||||
|
onChange={(e) => {
|
||||||
|
setNote(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Row flexDirection="row-reverse" alignItems="center" mt={4}>
|
||||||
|
<Signer
|
||||||
|
signReady={signReady}
|
||||||
|
choosingSignMethod={choosingSignMethod}
|
||||||
|
signMethod={signMethod}
|
||||||
|
setSignMethod={handleSetSignMethod}
|
||||||
|
initPayment={initPayment}
|
||||||
|
/>
|
||||||
|
{!(signing && !error) ? null : (
|
||||||
|
<LoadingSpinner
|
||||||
|
mr={2}
|
||||||
|
background="midOrange"
|
||||||
|
foreground="orange"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
width="48px"
|
||||||
|
fontSize={1}
|
||||||
|
fontWeight="bold"
|
||||||
|
borderRadius="24px"
|
||||||
|
mr={2}
|
||||||
|
height="48px"
|
||||||
|
onClick={() => toggleSignMethod(choosingSignMethod)}
|
||||||
|
color={signReady ? 'white' : 'lighterGray'}
|
||||||
|
backgroundColor={
|
||||||
|
signReady ? 'rgba(33, 157, 255, 0.2)' : 'veryLightGray'
|
||||||
|
}
|
||||||
|
disabled={!signReady}
|
||||||
|
border="none"
|
||||||
|
style={{ cursor: signReady ? 'pointer' : 'default' }}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon={choosingSignMethod ? 'X' : 'Ellipsis'}
|
||||||
|
color={signReady ? 'blue' : 'lighterGray'}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
{signMethod === 'masterTicket' && (
|
||||||
|
<Row mt={4} alignItems="center">
|
||||||
|
<Icon icon="Info" color="yellow" height={4} width={4} />
|
||||||
|
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>
|
||||||
|
We recommend that you sign transactions using Bridge to protect
|
||||||
|
your master ticket.
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Send;
|
||||||
|
@ -1,59 +1,36 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Icon, Row, Col, Center, Text } from '@tlon/indigo-react';
|
||||||
Box,
|
|
||||||
Icon,
|
|
||||||
StatelessTextInput as Input,
|
|
||||||
Row,
|
|
||||||
Center,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
|
|
||||||
import { satsToCurrency } from '../../lib/util.js';
|
import { satsToCurrency } from '../../lib/util.js';
|
||||||
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
|
|
||||||
export default function Sent(props) {
|
const Sent = ({ payee, stopSending, satsAmount }) => {
|
||||||
const { payee, denomination, satsAmount, stopSending, currencyRates } = props;
|
const { denomination, currencyRates } = useSettings();
|
||||||
return (
|
return (
|
||||||
<Col
|
<Col
|
||||||
height='400px'
|
height="400px"
|
||||||
width='100%'
|
width="100%"
|
||||||
backgroundColor='orange'
|
backgroundColor="orange"
|
||||||
borderRadius='48px'
|
borderRadius="48px"
|
||||||
mb={5}
|
mb={5}
|
||||||
p={5}
|
p={5}
|
||||||
>
|
>
|
||||||
<Row
|
<Row flexDirection="row-reverse">
|
||||||
flexDirection='row-reverse'
|
<Icon color="white" icon="X" cursor="pointer" onClick={stopSending} />
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
color='white'
|
|
||||||
icon='X'
|
|
||||||
cursor='pointer'
|
|
||||||
onClick={stopSending}
|
|
||||||
/>
|
|
||||||
</Row>
|
</Row>
|
||||||
<Center>
|
<Center>
|
||||||
<Text
|
<Text
|
||||||
style={{'display': 'block', 'overflow-wrap': 'anywhere'}}
|
style={{ display: 'block', 'overflow-wrap': 'anywhere' }}
|
||||||
color='white'>{`You sent BTC to ${payee}`}</Text>
|
color="white"
|
||||||
|
>{`You sent BTC to ${payee}`}</Text>
|
||||||
</Center>
|
</Center>
|
||||||
<Center
|
<Center flexDirection="column" flex="1 1 auto">
|
||||||
flexDirection='column'
|
<Text color="white" fontSize="40px">
|
||||||
flex='1 1 auto'
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
color='white'
|
|
||||||
fontSize='40px'
|
|
||||||
>
|
|
||||||
{satsToCurrency(satsAmount, denomination, currencyRates)}
|
{satsToCurrency(satsAmount, denomination, currencyRates)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text color="white">{`${satsAmount} sats`}</Text>
|
||||||
color='white'
|
|
||||||
>
|
|
||||||
{`${satsAmount} sats`}
|
|
||||||
</Text>
|
|
||||||
</Center>
|
</Center>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Sent;
|
||||||
|
@ -1,121 +1,114 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Row, Text, Button, Col } from '@tlon/indigo-react';
|
||||||
Box,
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
Icon,
|
import { api } from '../../api';
|
||||||
Row,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
|
|
||||||
export default class Settings extends Component {
|
const Settings = () => {
|
||||||
constructor(props) {
|
const { wallet, provider } = useSettings();
|
||||||
super(props);
|
|
||||||
this.changeProvider = this.changeProvider.bind(this);
|
|
||||||
this.replaceWallet = this.replaceWallet.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
changeProvider(){
|
const changeProvider = () => {
|
||||||
this.props.api.btcWalletCommand({'set-provider': null});
|
api.btcWalletCommand({ 'set-provider': null });
|
||||||
}
|
};
|
||||||
|
|
||||||
replaceWallet(){
|
const replaceWallet = () => {
|
||||||
this.props.api.btcWalletCommand({
|
api.btcWalletCommand({
|
||||||
'delete-wallet': this.props.state.wallet,
|
'delete-wallet': wallet,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
let connColor = 'red';
|
||||||
let connColor = "red";
|
let connBackground = 'veryLightRed';
|
||||||
let connBackground = "veryLightRed";
|
let conn = 'Offline';
|
||||||
let conn = 'Offline'
|
let host = '';
|
||||||
let host = '';
|
if (provider) {
|
||||||
if (this.props.state.provider){
|
if (provider.connected) conn = 'Connected';
|
||||||
if (this.props.state.provider.connected) conn = 'Connected';
|
if (provider.host) host = provider.host;
|
||||||
if (this.props.state.provider.host) host = this.props.state.provider.host;
|
if (provider.connected && provider.host) {
|
||||||
if (this.props.state.provider.connected && this.props.state.provider.host) {
|
connColor = 'orange';
|
||||||
connColor = "orange";
|
connBackground = 'lightOrange';
|
||||||
connBackground = "lightOrange";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<Col
|
|
||||||
display="flex"
|
|
||||||
width="100%"
|
|
||||||
p={5}
|
|
||||||
mb={5}
|
|
||||||
borderRadius="48px"
|
|
||||||
backgroundColor="white"
|
|
||||||
>
|
|
||||||
<Row mb="12px">
|
|
||||||
<Text fontSize={1} fontWeight="bold" color="black">
|
|
||||||
XPub Derivation
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
<Row borderRadius="12px"
|
|
||||||
backgroundColor="veryLightGray"
|
|
||||||
py={5}
|
|
||||||
px="36px"
|
|
||||||
mb="12px"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="space-between"
|
|
||||||
>
|
|
||||||
<Text mono
|
|
||||||
fontSize={1}
|
|
||||||
style={{wordBreak: "break-all"}}
|
|
||||||
color="gray"
|
|
||||||
>
|
|
||||||
{this.props.state.wallet}
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
<Row width="100%" mb={5}>
|
|
||||||
<Button children="Replace Wallet"
|
|
||||||
width="100%"
|
|
||||||
fontSize={1}
|
|
||||||
fontWeight="bold"
|
|
||||||
backgroundColor="gray"
|
|
||||||
color="white"
|
|
||||||
borderColor="none"
|
|
||||||
borderRadius="12px"
|
|
||||||
p={4}
|
|
||||||
onClick={this.replaceWallet}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
<Row mb="12px">
|
|
||||||
<Text fontSize={1} fontWeight="bold" color="black">
|
|
||||||
BTC Node Provider
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
<Col mb="12px"
|
|
||||||
py={5}
|
|
||||||
px="36px"
|
|
||||||
borderRadius="12px"
|
|
||||||
backgroundColor={connBackground}
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="space-between"
|
|
||||||
>
|
|
||||||
<Text fontSize={1} color={connColor} mono>
|
|
||||||
~{host}
|
|
||||||
</Text>
|
|
||||||
<Text fontSize={0} color={connColor}>
|
|
||||||
{conn}
|
|
||||||
</Text>
|
|
||||||
</Col>
|
|
||||||
<Row width="100%">
|
|
||||||
<Button children="Change Provider"
|
|
||||||
width="100%"
|
|
||||||
fontSize={1}
|
|
||||||
fontWeight="bold"
|
|
||||||
backgroundColor="orange"
|
|
||||||
color="white"
|
|
||||||
borderColor="none"
|
|
||||||
borderRadius="12px"
|
|
||||||
p={4}
|
|
||||||
onClick={this.changeProvider}
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<Col
|
||||||
|
display="flex"
|
||||||
|
width="100%"
|
||||||
|
p={5}
|
||||||
|
mb={5}
|
||||||
|
borderRadius="48px"
|
||||||
|
backgroundColor="white"
|
||||||
|
>
|
||||||
|
<Row mb="12px">
|
||||||
|
<Text fontSize={1} fontWeight="bold" color="black">
|
||||||
|
XPub Derivation
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
<Row
|
||||||
|
borderRadius="12px"
|
||||||
|
backgroundColor="veryLightGray"
|
||||||
|
py={5}
|
||||||
|
px="36px"
|
||||||
|
mb="12px"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<Text mono fontSize={1} style={{ wordBreak: 'break-all' }} color="gray">
|
||||||
|
{wallet}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
<Row width="100%" mb={5}>
|
||||||
|
<Button
|
||||||
|
width="100%"
|
||||||
|
fontSize={1}
|
||||||
|
fontWeight="bold"
|
||||||
|
backgroundColor="gray"
|
||||||
|
color="white"
|
||||||
|
borderColor="none"
|
||||||
|
borderRadius="12px"
|
||||||
|
p={4}
|
||||||
|
onClick={() => replaceWallet()}
|
||||||
|
>
|
||||||
|
Replace Wallet
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
<Row mb="12px">
|
||||||
|
<Text fontSize={1} fontWeight="bold" color="black">
|
||||||
|
BTC Node Provider
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
<Col
|
||||||
|
mb="12px"
|
||||||
|
py={5}
|
||||||
|
px="36px"
|
||||||
|
borderRadius="12px"
|
||||||
|
backgroundColor={connBackground}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<Text fontSize={1} color={connColor} mono>
|
||||||
|
~{host}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize={0} color={connColor}>
|
||||||
|
{conn}
|
||||||
|
</Text>
|
||||||
|
</Col>
|
||||||
|
<Row width="100%">
|
||||||
|
<Button
|
||||||
|
width="100%"
|
||||||
|
fontSize={1}
|
||||||
|
fontWeight="bold"
|
||||||
|
backgroundColor="orange"
|
||||||
|
color="white"
|
||||||
|
borderColor="none"
|
||||||
|
borderRadius="12px"
|
||||||
|
p={4}
|
||||||
|
onClick={() => changeProvider()}
|
||||||
|
>
|
||||||
|
Change Provider
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Settings;
|
||||||
|
@ -1,52 +1,55 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
|
import { Box, Button } from '@tlon/indigo-react';
|
||||||
|
|
||||||
import {
|
const Signer = ({
|
||||||
Box,
|
signReady,
|
||||||
Button,
|
initPayment,
|
||||||
} from '@tlon/indigo-react';
|
choosingSignMethod,
|
||||||
|
signMethod,
|
||||||
export default function Signer(props) {
|
setSignMethod,
|
||||||
const { signReady, initPayment, choosingSignMethod, signMethod, setSignMethod } = props;
|
}) => {
|
||||||
|
return choosingSignMethod ? (
|
||||||
return (
|
<Box borderRadius="24px" backgroundColor="rgba(33, 157, 255, 0.2)">
|
||||||
choosingSignMethod ?
|
|
||||||
<Box
|
|
||||||
borderRadius='24px'
|
|
||||||
backgroundColor='rgba(33, 157, 255, 0.2)'
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
border='none'
|
border="none"
|
||||||
backgroundColor='transparent'
|
backgroundColor="transparent"
|
||||||
fontWeight='bold'
|
fontWeight="bold"
|
||||||
cursor='pointer'
|
cursor="pointer"
|
||||||
color={(signMethod === 'masterTicket') ? 'blue' : 'lightBlue'}
|
color={signMethod === 'masterTicket' ? 'blue' : 'lightBlue'}
|
||||||
height='48px'
|
height="48px"
|
||||||
onClick={() => setSignMethod('masterTicket')}
|
onClick={() => setSignMethod('masterTicket')}
|
||||||
children='Sign with Master Ticket' />
|
>
|
||||||
|
Sign with Master Ticket
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
border='none'
|
border="none"
|
||||||
backgroundColor='transparent'
|
backgroundColor="transparent"
|
||||||
fontWeight='bold'
|
fontWeight="bold"
|
||||||
cursor='pointer'
|
cursor="pointer"
|
||||||
color={(signMethod === 'bridge') ? 'blue' : 'lightBlue'}
|
color={signMethod === 'bridge' ? 'blue' : 'lightBlue'}
|
||||||
height='48px'
|
height="48px"
|
||||||
onClick={() => setSignMethod('bridge')}
|
onClick={() => setSignMethod('bridge')}
|
||||||
children='Sign with Bridge' />
|
>
|
||||||
|
Sign with Bridge
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
:
|
) : (
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
children={signMethod === 'bridge' ? 'Sign with Bridge' : 'Sign with Master Ticket'}
|
|
||||||
fontSize={1}
|
fontSize={1}
|
||||||
fontWeight='bold'
|
fontWeight="bold"
|
||||||
borderRadius='24px'
|
borderRadius="24px"
|
||||||
height='48px'
|
height="48px"
|
||||||
onClick={initPayment}
|
onClick={initPayment}
|
||||||
color={signReady ? 'white' : 'lighterGray'}
|
color={signReady ? 'white' : 'lighterGray'}
|
||||||
backgroundColor={signReady ? 'blue' : 'veryLightGray'}
|
backgroundColor={signReady ? 'blue' : 'veryLightGray'}
|
||||||
disabled={!signReady}
|
disabled={!signReady}
|
||||||
border='none'
|
border="none"
|
||||||
style={{cursor: signReady ? 'pointer' : 'default'}}
|
style={{ cursor: signReady ? 'pointer' : 'default' }}
|
||||||
/>
|
>
|
||||||
)
|
{signMethod === 'bridge' ? 'Sign with Bridge' : 'Sign with Master Ticket'}
|
||||||
}
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Signer;
|
||||||
|
@ -1,52 +1,44 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { Box } from '@tlon/indigo-react';
|
import { Box } from '@tlon/indigo-react';
|
||||||
|
import WalletModal from './walletModal.js';
|
||||||
|
import ProviderModal from './providerModal.js';
|
||||||
|
import { useSettings } from '../../hooks/useSettings.js';
|
||||||
|
|
||||||
import WalletModal from './walletModal.js'
|
const StartupModal = () => {
|
||||||
import ProviderModal from './providerModal.js'
|
const { wallet, provider } = useSettings();
|
||||||
|
let modal = null;
|
||||||
|
|
||||||
|
if (wallet && provider) {
|
||||||
export default class StartupModal extends Component {
|
return null;
|
||||||
constructor(props) {
|
} else if (!provider) {
|
||||||
super(props);
|
modal = <ProviderModal />;
|
||||||
|
} else if (!wallet) {
|
||||||
|
modal = <WalletModal />;
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
render() {
|
backgroundColor="scales.black20"
|
||||||
let modal = null;
|
left="0px"
|
||||||
|
top="0px"
|
||||||
if (this.props.state.wallet && this.props.state.provider) {
|
width="100%"
|
||||||
return null;
|
height="100%"
|
||||||
} else if (!this.props.state.provider){
|
position="fixed"
|
||||||
modal =
|
display="flex"
|
||||||
<ProviderModal
|
zIndex={10}
|
||||||
api={this.props.api}
|
justifyContent="center"
|
||||||
providerPerms={this.props.state.providerPerms}
|
alignItems="center"
|
||||||
/>
|
>
|
||||||
} else if (!this.props.state.wallet){
|
|
||||||
modal = <WalletModal api={this.props.api} network={this.props.network}/>
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box
|
<Box
|
||||||
backgroundColor="scales.black20"
|
|
||||||
left="0px"
|
|
||||||
top="0px"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
position="fixed"
|
|
||||||
display="flex"
|
display="flex"
|
||||||
zIndex={10}
|
flexDirection="column"
|
||||||
justifyContent="center"
|
width="400px"
|
||||||
alignItems="center"
|
backgroundColor="white"
|
||||||
|
borderRadius={3}
|
||||||
>
|
>
|
||||||
<Box display="flex"
|
{modal}
|
||||||
flexDirection="column"
|
|
||||||
width='400px'
|
|
||||||
backgroundColor="white"
|
|
||||||
borderRadius={3}
|
|
||||||
>
|
|
||||||
{modal}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
</Box>
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default StartupModal;
|
||||||
|
@ -1,105 +1,96 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Box, Row, Text, Col } from '@tlon/indigo-react';
|
||||||
Box,
|
|
||||||
Icon,
|
|
||||||
Row,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
LoadingSpinner,
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import TxAction from './tx-action.js';
|
||||||
|
import TxCounterparty from './tx-counterparty.js';
|
||||||
|
import { satsToCurrency } from '../../lib/util.js';
|
||||||
|
import { useSettings } from '../../hooks/useSettings.js';
|
||||||
|
|
||||||
import { Sigil } from './sigil.js'
|
const Transaction = ({ tx }) => {
|
||||||
import TxAction from './tx-action.js'
|
const { denomination, currencyRates } = useSettings();
|
||||||
import TxCounterparty from './tx-counterparty.js'
|
const pending = !tx.recvd;
|
||||||
import { satsToCurrency } from '../../lib/util.js'
|
|
||||||
|
|
||||||
export default class Transaction extends Component {
|
let weSent = _.find(tx.inputs, (input) => {
|
||||||
constructor(props) {
|
return input.ship === window.ship;
|
||||||
super(props);
|
});
|
||||||
}
|
let weRecv = tx.outputs.every((output) => {
|
||||||
|
return output.ship === window.ship;
|
||||||
|
});
|
||||||
|
|
||||||
render() {
|
let action = weRecv ? 'recv' : weSent ? 'sent' : 'recv';
|
||||||
const pending = (!this.props.tx.recvd);
|
|
||||||
|
|
||||||
let weSent = _.find(this.props.tx.inputs, (input) => {
|
let counterShip = null;
|
||||||
return (input.ship === window.ship);
|
let counterAddress = null;
|
||||||
|
let value;
|
||||||
|
let sign;
|
||||||
|
|
||||||
|
if (action === 'sent') {
|
||||||
|
let counter = _.find(tx.outputs, (output) => {
|
||||||
|
return output.ship !== window.ship;
|
||||||
});
|
});
|
||||||
let weRecv = this.props.tx.outputs.every((output) => {
|
counterShip = _.get(counter, 'ship', null);
|
||||||
return (output.ship === window.ship)
|
counterAddress = _.get(counter, 'val.address', null);
|
||||||
});
|
value = _.get(counter, 'val.value', null);
|
||||||
|
sign = '-';
|
||||||
let action =
|
} else if (action === 'recv') {
|
||||||
(weRecv) ? "recv" :
|
value = _.reduce(
|
||||||
(weSent) ? "sent" : "recv";
|
tx.outputs,
|
||||||
|
(sum, output) => {
|
||||||
let counterShip = null;
|
|
||||||
let counterAddress = null;
|
|
||||||
let value;
|
|
||||||
let sign;
|
|
||||||
|
|
||||||
if (action === "sent") {
|
|
||||||
let counter = _.find(this.props.tx.outputs, (output) => {
|
|
||||||
return (output.ship !== window.ship);
|
|
||||||
});
|
|
||||||
counterShip = _.get(counter, 'ship', null);
|
|
||||||
counterAddress = _.get(counter, 'val.address', null);
|
|
||||||
value = _.get(counter, 'val.value', null);
|
|
||||||
sign = '-'
|
|
||||||
}
|
|
||||||
else if (action === "recv") {
|
|
||||||
value = _.reduce(this.props.tx.outputs, (sum, output) => {
|
|
||||||
if (output.ship === window.ship) {
|
if (output.ship === window.ship) {
|
||||||
return sum + output.val.value;
|
return sum + output.val.value;
|
||||||
} else {
|
} else {
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
}, 0);
|
},
|
||||||
|
0
|
||||||
|
|
||||||
if (weSent && weRecv) {
|
|
||||||
counterAddress = _.get(_.find(this.props.tx.inputs, (input) => {
|
|
||||||
return (input.ship === window.ship);
|
|
||||||
}), 'val.address', null);
|
|
||||||
} else {
|
|
||||||
let counter = _.find(this.props.tx.inputs, (input) => {
|
|
||||||
return (input.ship !== window.ship);
|
|
||||||
});
|
|
||||||
counterShip = _.get(counter, 'ship', null);
|
|
||||||
counterAddress = _.get(counter, 'val.address', null);
|
|
||||||
}
|
|
||||||
sign = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
let currencyValue = sign + satsToCurrency(value, this.props.denom, this.props.rates);
|
|
||||||
|
|
||||||
const failure = Boolean(this.props.tx.failure);
|
|
||||||
if (failure) action = "fail";
|
|
||||||
|
|
||||||
const txid = this.props.tx.txid.dat.slice(2).replaceAll('.','');
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col
|
|
||||||
width='100%'
|
|
||||||
backgroundColor="white"
|
|
||||||
justifyContent="space-between"
|
|
||||||
mb="16px"
|
|
||||||
>
|
|
||||||
<Row justifyContent="space-between" alignItems="center">
|
|
||||||
<TxAction action={action} pending={pending} txid={txid} network={this.props.network}/>
|
|
||||||
<Text fontSize="14px" alignItems="center" color="gray">
|
|
||||||
{sign}{value} sats
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
<Box ml="11px" borderLeft="2px solid black" height="4px">
|
|
||||||
</Box>
|
|
||||||
<Row justifyContent="space-between" alignItems="center">
|
|
||||||
<TxCounterparty address={counterAddress} ship={counterShip}/>
|
|
||||||
<Text fontSize="14px">{currencyValue}</Text>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (weSent && weRecv) {
|
||||||
|
counterAddress = _.get(
|
||||||
|
_.find(tx.inputs, (input) => {
|
||||||
|
return input.ship === window.ship;
|
||||||
|
}),
|
||||||
|
'val.address',
|
||||||
|
null
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let counter = _.find(tx.inputs, (input) => {
|
||||||
|
return input.ship !== window.ship;
|
||||||
|
});
|
||||||
|
counterShip = _.get(counter, 'ship', null);
|
||||||
|
counterAddress = _.get(counter, 'val.address', null);
|
||||||
|
}
|
||||||
|
sign = '';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
let currencyValue = sign + satsToCurrency(value, denomination, currencyRates);
|
||||||
|
|
||||||
|
const failure = Boolean(tx.failure);
|
||||||
|
if (failure) action = 'fail';
|
||||||
|
|
||||||
|
const txid = tx.txid.dat.slice(2).replaceAll('.', '');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col
|
||||||
|
width="100%"
|
||||||
|
backgroundColor="white"
|
||||||
|
justifyContent="space-between"
|
||||||
|
mb="16px"
|
||||||
|
>
|
||||||
|
<Row justifyContent="space-between" alignItems="center">
|
||||||
|
<TxAction action={action} pending={pending} txid={txid} />
|
||||||
|
<Text fontSize="14px" alignItems="center" color="gray">
|
||||||
|
{sign}
|
||||||
|
{value} sats
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
<Box ml="11px" borderLeft="2px solid black" height="4px"></Box>
|
||||||
|
<Row justifyContent="space-between" alignItems="center">
|
||||||
|
<TxCounterparty address={counterAddress} ship={counterShip} />
|
||||||
|
<Text fontSize="14px">{currencyValue}</Text>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Transaction;
|
||||||
|
@ -1,62 +1,43 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import {
|
import { Box, Text, Col } from '@tlon/indigo-react';
|
||||||
Box,
|
|
||||||
Icon,
|
|
||||||
Row,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
|
|
||||||
import Transaction from './transaction.js';
|
import Transaction from './transaction.js';
|
||||||
|
import { useSettings } from '../../hooks/useSettings.js';
|
||||||
|
|
||||||
|
const Transactions = () => {
|
||||||
export default class Transactions extends Component {
|
const { history } = useSettings();
|
||||||
constructor(props) {
|
if (!history || history.length <= 0) {
|
||||||
super(props);
|
return (
|
||||||
|
<Box
|
||||||
|
alignItems="center"
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
height="340px"
|
||||||
|
width="100%"
|
||||||
|
p={5}
|
||||||
|
mb={5}
|
||||||
|
borderRadius="48px"
|
||||||
|
backgroundColor="white"
|
||||||
|
>
|
||||||
|
<Text color="gray" fontSize={2} fontWeight="bold">
|
||||||
|
No Transactions Yet
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Col
|
||||||
|
width="100%"
|
||||||
|
backgroundColor="white"
|
||||||
|
borderRadius="48px"
|
||||||
|
mb={5}
|
||||||
|
p={5}
|
||||||
|
>
|
||||||
|
{history.map((tx, i) => {
|
||||||
|
return <Transaction tx={tx} key={i} />;
|
||||||
|
})}
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Transactions;
|
||||||
render() {
|
|
||||||
if (!this.props.state.history || this.props.state.history.length <= 0) {
|
|
||||||
return (
|
|
||||||
<Box alignItems="center"
|
|
||||||
display="flex"
|
|
||||||
justifyContent="center"
|
|
||||||
height="340px"
|
|
||||||
width="100%"
|
|
||||||
p={5}
|
|
||||||
mb={5}
|
|
||||||
borderRadius="48px"
|
|
||||||
backgroundColor="white"
|
|
||||||
>
|
|
||||||
<Text color="gray" fontSize={2} fontWeight="bold">No Transactions Yet</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Col
|
|
||||||
width='100%'
|
|
||||||
backgroundColor="white"
|
|
||||||
borderRadius="48px"
|
|
||||||
mb={5}
|
|
||||||
p={5}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
this.props.state.history.map((tx, i) => {
|
|
||||||
return(
|
|
||||||
<Transaction
|
|
||||||
tx={tx}
|
|
||||||
key={i}
|
|
||||||
denom={this.props.state.denomination}
|
|
||||||
rates={this.props.state.currencyRates}
|
|
||||||
network={this.props.network}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,74 +1,72 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { Box, Icon, Row, Text, LoadingSpinner } from '@tlon/indigo-react';
|
import { Box, Icon, Row, Text, LoadingSpinner } from '@tlon/indigo-react';
|
||||||
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
|
|
||||||
export default class TxAction extends Component {
|
const TxAction = ({ action, pending, txid }) => {
|
||||||
constructor(props) {
|
const { network } = useSettings();
|
||||||
super(props);
|
const leftIcon =
|
||||||
}
|
action === 'sent'
|
||||||
|
? 'ArrowSouth'
|
||||||
|
: action === 'recv'
|
||||||
|
? 'ArrowNorth'
|
||||||
|
: action === 'fail'
|
||||||
|
? 'X'
|
||||||
|
: 'NullIcon';
|
||||||
|
|
||||||
render() {
|
const actionColor =
|
||||||
const leftIcon =
|
action === 'sent'
|
||||||
this.props.action === 'sent'
|
? 'sentBlue'
|
||||||
? 'ArrowSouth'
|
: action === 'recv'
|
||||||
: this.props.action === 'recv'
|
? 'recvGreen'
|
||||||
? 'ArrowNorth'
|
: action === 'fail'
|
||||||
: this.props.action === 'fail'
|
? 'gray'
|
||||||
? 'X'
|
: 'red';
|
||||||
: 'NullIcon';
|
|
||||||
|
|
||||||
const actionColor =
|
const actionText =
|
||||||
this.props.action === 'sent'
|
action === 'sent' && !pending
|
||||||
? 'sentBlue'
|
? 'Sent BTC'
|
||||||
: this.props.action === 'recv'
|
: action === 'sent' && pending
|
||||||
? 'recvGreen'
|
? 'Sending BTC'
|
||||||
: this.props.action === 'fail'
|
: action === 'recv' && !pending
|
||||||
? 'gray'
|
? 'Received BTC'
|
||||||
: 'red';
|
: action === 'recv' && pending
|
||||||
|
? 'Receiving BTC'
|
||||||
|
: action === 'fail'
|
||||||
|
? 'Failed'
|
||||||
|
: 'error';
|
||||||
|
|
||||||
const actionText =
|
const pendingSpinner = !pending ? null : (
|
||||||
this.props.action === 'sent' && !this.props.pending
|
<LoadingSpinner background="midOrange" foreground="orange" />
|
||||||
? 'Sent BTC'
|
);
|
||||||
: this.props.action === 'sent' && this.props.pending
|
|
||||||
? 'Sending BTC'
|
|
||||||
: this.props.action === 'recv' && !this.props.pending
|
|
||||||
? 'Received BTC'
|
|
||||||
: this.props.action === 'recv' && this.props.pending
|
|
||||||
? 'Receiving BTC'
|
|
||||||
: this.props.action === 'fail'
|
|
||||||
? 'Failed'
|
|
||||||
: 'error';
|
|
||||||
|
|
||||||
const pending = !this.props.pending ? null : (
|
const url =
|
||||||
<LoadingSpinner background="midOrange" foreground="orange" />
|
network === 'testnet'
|
||||||
);
|
? `http://blockstream.info/testnet/tx/${txid}`
|
||||||
|
: `http://blockstream.info/tx/${txid}`;
|
||||||
|
|
||||||
const url =
|
return (
|
||||||
this.props.network === 'testnet'
|
<Row alignItems="center">
|
||||||
? `http://blockstream.info/testnet/tx/${this.props.txid}`
|
<Box
|
||||||
: `http://blockstream.info/tx/${this.props.txid}`;
|
backgroundColor={actionColor}
|
||||||
|
width="24px"
|
||||||
|
height="24px"
|
||||||
|
textAlign="center"
|
||||||
|
alignItems="center"
|
||||||
|
borderRadius="2px"
|
||||||
|
mr={2}
|
||||||
|
p={1}
|
||||||
|
>
|
||||||
|
<Icon icon={leftIcon} color="white" />
|
||||||
|
</Box>
|
||||||
|
<Text color={actionColor} fontSize="14px">
|
||||||
|
{actionText}
|
||||||
|
</Text>
|
||||||
|
<a href={url} target="_blank" rel="noreferrer">
|
||||||
|
<Icon color={actionColor} icon="ArrowNorthEast" ml={1} mr={2} />
|
||||||
|
</a>
|
||||||
|
{pendingSpinner}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
export default TxAction;
|
||||||
<Row alignItems="center">
|
|
||||||
<Box
|
|
||||||
backgroundColor={actionColor}
|
|
||||||
width="24px"
|
|
||||||
height="24px"
|
|
||||||
textAlign="center"
|
|
||||||
alignItems="center"
|
|
||||||
borderRadius="2px"
|
|
||||||
mr={2}
|
|
||||||
p={1}
|
|
||||||
>
|
|
||||||
<Icon icon={leftIcon} color="white" />
|
|
||||||
</Box>
|
|
||||||
<Text color={actionColor} fontSize="14px">
|
|
||||||
{actionText}
|
|
||||||
</Text>
|
|
||||||
<a href={url} target="_blank" rel="noreferrer">
|
|
||||||
<Icon color={actionColor} icon="ArrowNorthEast" ml={1} mr={2} />
|
|
||||||
</a>
|
|
||||||
{pending}
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,53 +1,36 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Box, Icon, Row, Text } from '@tlon/indigo-react';
|
||||||
Box,
|
import { Sigil } from './sigil.js';
|
||||||
Icon,
|
|
||||||
Row,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
|
|
||||||
import { Sigil } from './sigil.js'
|
const TxCounterparty = ({ ship, address }) => {
|
||||||
import TxAction from './tx-action.js'
|
const icon = ship ? (
|
||||||
|
<Sigil ship={ship} size={24} color="black" classes={''} icon padding={5} />
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
backgroundColor="lighterGray"
|
||||||
|
width="24px"
|
||||||
|
height="24px"
|
||||||
|
textAlign="center"
|
||||||
|
alignItems="center"
|
||||||
|
borderRadius="2px"
|
||||||
|
p={1}
|
||||||
|
>
|
||||||
|
<Icon icon="Bitcoin" color="gray" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
const addressText = !address
|
||||||
|
? ''
|
||||||
|
: address.slice(0, 6) + '...' + address.slice(-6);
|
||||||
|
const text = ship ? `~${ship}` : addressText;
|
||||||
|
|
||||||
export default class TxCounterparty extends Component {
|
return (
|
||||||
constructor(props) {
|
<Row alignItems="center">
|
||||||
super(props);
|
{icon}
|
||||||
}
|
<Text ml={2} mono fontSize="14px" color="gray">
|
||||||
|
{text}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TxCounterparty;
|
||||||
render() {
|
|
||||||
const icon = (this.props.ship)
|
|
||||||
? <Sigil
|
|
||||||
ship={this.props.ship}
|
|
||||||
size={24}
|
|
||||||
color="black"
|
|
||||||
classes={''}
|
|
||||||
icon
|
|
||||||
padding={5}
|
|
||||||
/>
|
|
||||||
: <Box backgroundColor="lighterGray"
|
|
||||||
width="24px"
|
|
||||||
height="24px"
|
|
||||||
textAlign="center"
|
|
||||||
alignItems="center"
|
|
||||||
borderRadius="2px"
|
|
||||||
p={1}
|
|
||||||
>
|
|
||||||
<Icon icon="Bitcoin" color="gray"/>
|
|
||||||
</Box>
|
|
||||||
const addressText = (!this.props.address) ? '' :
|
|
||||||
this.props.address.slice(0, 6) + '...' +
|
|
||||||
this.props.address.slice(-6);
|
|
||||||
const text = (this.props.ship) ?
|
|
||||||
`~${this.props.ship}` : addressText;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row alignItems="center">
|
|
||||||
{icon}
|
|
||||||
<Text ml={2} mono fontSize="14px" color="gray">{text}</Text>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
@ -6,247 +6,251 @@ import {
|
|||||||
StatelessTextInput,
|
StatelessTextInput,
|
||||||
Icon,
|
Icon,
|
||||||
Row,
|
Row,
|
||||||
Input,
|
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
} from '@tlon/indigo-react';
|
} from '@tlon/indigo-react';
|
||||||
|
|
||||||
import { patp2dec, isValidPatq } from 'urbit-ob';
|
import { patp2dec, isValidPatq } from 'urbit-ob';
|
||||||
|
import * as kg from 'urbit-key-generation';
|
||||||
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
|
import { api } from '../../api';
|
||||||
|
|
||||||
const kg = require('urbit-key-generation');
|
const WalletModal = () => {
|
||||||
const bitcoin = require('bitcoinjs-lib');
|
const { network } = useSettings();
|
||||||
const bs58check = require('bs58check')
|
const [mode, setMode] = useState('xpub');
|
||||||
import { Buffer } from 'buffer';
|
const [masterTicket, setMasterTicket] = useState('');
|
||||||
|
const [confirmedMasterTicket, setConfirmedMasterTicket] = useState('');
|
||||||
|
const [xpub, setXpub] = useState('');
|
||||||
|
const [readyToSubmit, setReadyToSubmit] = useState(false);
|
||||||
|
const [processingSubmission, setProcessingSubmission] = useState(false);
|
||||||
|
const [confirmingMasterTicket, setConfirmingMasterTicket] = useState(false);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
export default class WalletModal extends Component {
|
const checkTicket = ({
|
||||||
constructor(props) {
|
event: {
|
||||||
super(props);
|
target: { value },
|
||||||
|
},
|
||||||
this.state = {
|
}) => {
|
||||||
mode: 'xpub',
|
|
||||||
masterTicket: '',
|
|
||||||
confirmedMasterTicket: '',
|
|
||||||
xpub: '',
|
|
||||||
readyToSubmit: false,
|
|
||||||
processingSubmission: false,
|
|
||||||
confirmingMasterTicket: false,
|
|
||||||
error: false,
|
|
||||||
}
|
|
||||||
this.checkTicket = this.checkTicket.bind(this);
|
|
||||||
this.checkXPub = this.checkXPub.bind(this);
|
|
||||||
this.submitMasterTicket = this.submitMasterTicket.bind(this);
|
|
||||||
this.submitXPub = this.submitXPub.bind(this);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTicket(e){
|
|
||||||
// TODO: port over bridge ticket validation logic
|
// TODO: port over bridge ticket validation logic
|
||||||
if (this.state.confirmingMasterTicket) {
|
if (confirmingMasterTicket) {
|
||||||
let confirmedMasterTicket = e.target.value;
|
setConfirmedMasterTicket(value);
|
||||||
let readyToSubmit = isValidPatq(confirmedMasterTicket);
|
setReadyToSubmit(isValidPatq(value));
|
||||||
this.setState({confirmedMasterTicket, readyToSubmit});
|
|
||||||
} else {
|
} else {
|
||||||
let masterTicket = e.target.value;
|
setMasterTicket(value);
|
||||||
let readyToSubmit = isValidPatq(masterTicket);
|
setReadyToSubmit(isValidPatq(value));
|
||||||
this.setState({masterTicket, readyToSubmit});
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
checkXPub(e){
|
const checkXPub = ({
|
||||||
let xpub = e.target.value;
|
event: {
|
||||||
let readyToSubmit = (xpub.length > 0);
|
target: { value: xpubGiven },
|
||||||
this.setState({xpub, readyToSubmit});
|
},
|
||||||
}
|
}) => {
|
||||||
|
setXpub(xpubGiven);
|
||||||
|
setReadyToSubmit(xpubGiven.length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
submitMasterTicket(ticket){
|
const submitXPub = (givenXpub) => {
|
||||||
this.setState({processingSubmission: true});
|
|
||||||
kg.generateWallet({ ticket, ship: parseInt(patp2dec('~' + window.ship)) })
|
|
||||||
.then(urbitWallet => {
|
|
||||||
const { xpub } = this.props.network === 'testnet'
|
|
||||||
? urbitWallet.bitcoinTestnet.keys :
|
|
||||||
urbitWallet.bitcoinMainnet.keys
|
|
||||||
|
|
||||||
this.submitXPub(xpub);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
submitXPub(xpub){
|
|
||||||
const command = {
|
const command = {
|
||||||
"add-wallet": {
|
'add-wallet': {
|
||||||
"xpub": xpub,
|
xpub: givenXpub,
|
||||||
"fprint": [4, 0],
|
fprint: [4, 0],
|
||||||
"scan-to": null,
|
'scan-to': null,
|
||||||
"max-gap": 8,
|
'max-gap': 8,
|
||||||
"confs": 1
|
confs: 1,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
api.btcWalletCommand(command);
|
api.btcWalletCommand(command);
|
||||||
this.setState({processingSubmission: true});
|
setProcessingSubmission(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
const submitMasterTicket = (ticket) => {
|
||||||
const buttonDisabled = (!this.state.readyToSubmit || this.state.processingSubmission );
|
setProcessingSubmission(true);
|
||||||
const inputDisabled = this.state.processingSubmission;
|
kg.generateWallet({
|
||||||
const processingSpinner = (!this.state.processingSubmission) ? null :
|
ticket,
|
||||||
<LoadingSpinner/>
|
ship: parseInt(patp2dec('~' + window.ship)),
|
||||||
|
}).then((urbitWallet) => {
|
||||||
|
const { xpub: xpubFromWallet } =
|
||||||
|
network === 'testnet'
|
||||||
|
? urbitWallet.bitcoinTestnet.keys
|
||||||
|
: urbitWallet.bitcoinMainnet.keys;
|
||||||
|
|
||||||
if (this.state.mode === 'masterTicket'){
|
submitXPub(xpubFromWallet);
|
||||||
return (
|
});
|
||||||
<Box
|
};
|
||||||
width="100%"
|
|
||||||
height="100%"
|
const buttonDisabled = !readyToSubmit || processingSubmission;
|
||||||
padding={3}
|
const inputDisabled = processingSubmission;
|
||||||
>
|
// const processingSpinner = !processingSubmission ? null : <LoadingSpinner />;
|
||||||
<Row>
|
|
||||||
<Icon icon="Bitcoin" mr={2}/>
|
if (mode === 'masterTicket') {
|
||||||
<Text fontSize="14px" fontWeight="bold">
|
return (
|
||||||
Step 2 of 2: Import your extended public key
|
<Box width="100%" height="100%" padding={3}>
|
||||||
</Text>
|
<Row>
|
||||||
</Row>
|
<Icon icon="Bitcoin" mr={2} />
|
||||||
<Row
|
<Text fontSize="14px" fontWeight="bold">
|
||||||
mt={3}
|
Step 2 of 2: Import your extended public key
|
||||||
alignItems='center'
|
</Text>
|
||||||
>
|
</Row>
|
||||||
<Icon icon='Info' color='yellow' height={4} width={4}/>
|
<Row mt={3} alignItems="center">
|
||||||
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>
|
<Icon icon="Info" color="yellow" height={4} width={4} />
|
||||||
We recommend that you import your wallet using Bridge to protect your master ticket.
|
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>
|
||||||
</Text>
|
We recommend that you import your wallet using Bridge to protect
|
||||||
</Row>
|
your master ticket.
|
||||||
<Box
|
</Text>
|
||||||
display='flex'
|
</Row>
|
||||||
alignItems='center'
|
<Box display="flex" alignItems="center" mt={3} mb={2}>
|
||||||
mt={3}
|
{confirmingMasterTicket && (
|
||||||
mb={2}>
|
<Icon
|
||||||
{this.state.confirmingMasterTicket &&
|
icon="ArrowWest"
|
||||||
<Icon
|
cursor="pointer"
|
||||||
icon='ArrowWest'
|
|
||||||
cursor='pointer'
|
|
||||||
onClick={() => this.setState({
|
|
||||||
confirmingMasterTicket: false,
|
|
||||||
masterTicket: '',
|
|
||||||
confirmedMasterTicket: '',
|
|
||||||
error: false
|
|
||||||
})}/>}
|
|
||||||
<Text fontSize="14px" fontWeight="500">
|
|
||||||
{this.state.confirmingMasterTicket ? 'Confirm Master Ticket' : 'Master Ticket'}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Row alignItems="center">
|
|
||||||
<StatelessTextInput
|
|
||||||
mr={2}
|
|
||||||
width="256px"
|
|
||||||
value={this.state.confirmingMasterTicket ? this.state.confirmedMasterTicket : this.state.masterTicket}
|
|
||||||
disabled={inputDisabled}
|
|
||||||
fontSize="14px"
|
|
||||||
type="password"
|
|
||||||
name="masterTicket"
|
|
||||||
obscure={value => value.replace(/[^~-]+/g, '••••••')}
|
|
||||||
placeholder="••••••-••••••-••••••-••••••"
|
|
||||||
autoCapitalize="none"
|
|
||||||
autoCorrect="off"
|
|
||||||
onChange={this.checkTicket}
|
|
||||||
/>
|
|
||||||
{(!inputDisabled) ? null : <LoadingSpinner/>}
|
|
||||||
</Row>
|
|
||||||
{this.state.error &&
|
|
||||||
<Row mt={2}>
|
|
||||||
<Text
|
|
||||||
fontSize='14px'
|
|
||||||
color='red'>
|
|
||||||
Master tickets do not match
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
}
|
|
||||||
<Row mt={3}>
|
|
||||||
<Button
|
|
||||||
primary
|
|
||||||
color="black"
|
|
||||||
backgroundColor="veryLightGray"
|
|
||||||
borderColor="veryLightGray"
|
|
||||||
children="Cancel"
|
|
||||||
fontSize="14px"
|
|
||||||
mr={2}
|
|
||||||
style={{cursor: "pointer"}}
|
|
||||||
onClick={() => {this.setState({mode: 'xpub', masterTicket: '', xpub: '', readyToSubmit: false})}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
primary
|
|
||||||
disabled={buttonDisabled}
|
|
||||||
children="Next Step"
|
|
||||||
fontSize="14px"
|
|
||||||
style={{cursor: buttonDisabled ? "default" : "pointer"}}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!this.state.confirmingMasterTicket) {
|
setConfirmingMasterTicket(false);
|
||||||
this.setState({confirmingMasterTicket: true});
|
setMasterTicket('');
|
||||||
} else {
|
setConfirmedMasterTicket('');
|
||||||
if (this.state.masterTicket === this.state.confirmedMasterTicket) {
|
setError(false);
|
||||||
this.setState({error: false});
|
|
||||||
this.submitMasterTicket(this.state.masterTicket);
|
|
||||||
} else {
|
|
||||||
this.setState({error: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Row>
|
)}
|
||||||
|
<Text fontSize="14px" fontWeight="500">
|
||||||
|
{confirmingMasterTicket ? 'Confirm Master Ticket' : 'Master Ticket'}
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
<Row alignItems="center">
|
||||||
} else if (this.state.mode === 'xpub') {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
padding={3}
|
|
||||||
>
|
|
||||||
<Row>
|
|
||||||
<Icon icon="Bitcoin" mr={2}/>
|
|
||||||
<Text fontSize="14px" fontWeight="bold">
|
|
||||||
Step 2 of 2: Import your extended public key
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
<Box mt={3}>
|
|
||||||
<Text fontSize="14px" fontWeight="regular" color="gray">
|
|
||||||
Visit <a href='https://bridge.urbit.org/?kind=xpub' target='_blank' style={{color: 'black'}} >bridge.urbit.org</a> to obtain your key
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box mt={3} mb={2}>
|
|
||||||
<Text fontSize="14px" fontWeight="500">
|
|
||||||
Extended Public Key (XPub)
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<StatelessTextInput
|
<StatelessTextInput
|
||||||
value={this.state.xpub}
|
mr={2}
|
||||||
|
width="256px"
|
||||||
|
value={
|
||||||
|
confirmingMasterTicket ? confirmedMasterTicket : masterTicket
|
||||||
|
}
|
||||||
disabled={inputDisabled}
|
disabled={inputDisabled}
|
||||||
fontSize="14px"
|
fontSize="14px"
|
||||||
type="password"
|
type="password"
|
||||||
name="xpub"
|
name="masterTicket"
|
||||||
|
obscure={(value) => value.replace(/[^~-]+/g, '••••••')}
|
||||||
|
placeholder="••••••-••••••-••••••-••••••"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
onChange={this.checkXPub}
|
onChange={(e) => checkTicket(e)}
|
||||||
/>
|
/>
|
||||||
<Box mt={3} mb={3}>
|
{!inputDisabled ? null : <LoadingSpinner />}
|
||||||
<Text fontSize="14px" fontWeight="regular"
|
</Row>
|
||||||
color={(inputDisabled) ? "lighterGray" : "gray"}
|
{error && (
|
||||||
style={{cursor: (inputDisabled) ? "default" : "pointer"}}
|
<Row mt={2}>
|
||||||
onClick={() => {
|
<Text fontSize="14px" color="red">
|
||||||
if (inputDisabled) return;
|
Master tickets do not match
|
||||||
this.setState({mode: 'masterTicket', xpub: '', masterTicket: '', readyToSubmit: false})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Import using master ticket ->
|
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Row>
|
||||||
|
)}
|
||||||
|
<Row mt={3}>
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
mt={3}
|
color="black"
|
||||||
disabled={buttonDisabled}
|
backgroundColor="veryLightGray"
|
||||||
children="Next Step"
|
borderColor="veryLightGray"
|
||||||
fontSize="14px"
|
fontSize="14px"
|
||||||
style={{cursor: this.state.ready ? "pointer" : "default"}}
|
mr={2}
|
||||||
onClick={() => { this.submitXPub(this.state.xpub) }}
|
style={{ cursor: 'pointer' }}
|
||||||
/>
|
onClick={() => {
|
||||||
|
setMode('xpub');
|
||||||
|
setMasterTicket('');
|
||||||
|
setXpub('');
|
||||||
|
setReadyToSubmit(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
disabled={buttonDisabled}
|
||||||
|
fontSize="14px"
|
||||||
|
style={{ cursor: buttonDisabled ? 'default' : 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
if (!confirmingMasterTicket) {
|
||||||
|
setConfirmingMasterTicket(true);
|
||||||
|
} else {
|
||||||
|
if (masterTicket === confirmedMasterTicket) {
|
||||||
|
setError(false);
|
||||||
|
submitMasterTicket(masterTicket);
|
||||||
|
} else {
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Next Step
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
} else if (mode === 'xpub') {
|
||||||
|
return (
|
||||||
|
<Box width="100%" height="100%" padding={3}>
|
||||||
|
<Row>
|
||||||
|
<Icon icon="Bitcoin" mr={2} />
|
||||||
|
<Text fontSize="14px" fontWeight="bold">
|
||||||
|
Step 2 of 2: Import your extended public key
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
<Box mt={3}>
|
||||||
|
<Text fontSize="14px" fontWeight="regular" color="gray">
|
||||||
|
Visit{' '}
|
||||||
|
<a
|
||||||
|
rel="noreferrer"
|
||||||
|
href="https://bridge.urbit.org/?kind=xpub"
|
||||||
|
target="_blank"
|
||||||
|
style={{ color: 'black' }}
|
||||||
|
>
|
||||||
|
bridge.urbit.org
|
||||||
|
</a>{' '}
|
||||||
|
to obtain your key
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
<Box mt={3} mb={2}>
|
||||||
}
|
<Text fontSize="14px" fontWeight="500">
|
||||||
|
Extended Public Key (XPub)
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<StatelessTextInput
|
||||||
|
value={xpub}
|
||||||
|
disabled={inputDisabled}
|
||||||
|
fontSize="14px"
|
||||||
|
type="password"
|
||||||
|
name="xpub"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoCorrect="off"
|
||||||
|
onChange={(e) => checkXPub(e)}
|
||||||
|
/>
|
||||||
|
<Box mt={3} mb={3}>
|
||||||
|
<Text
|
||||||
|
fontSize="14px"
|
||||||
|
fontWeight="regular"
|
||||||
|
color={inputDisabled ? 'lighterGray' : 'gray'}
|
||||||
|
style={{ cursor: inputDisabled ? 'default' : 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
if (inputDisabled) return;
|
||||||
|
setMode('masterTicket');
|
||||||
|
setXpub('');
|
||||||
|
setMasterTicket('');
|
||||||
|
setReadyToSubmit(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Import using master ticket ->
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
primary
|
||||||
|
mt={3}
|
||||||
|
disabled={buttonDisabled}
|
||||||
|
fontSize="14px"
|
||||||
|
style={{ cursor: readyToSubmit ? 'pointer' : 'default' }}
|
||||||
|
onClick={() => {
|
||||||
|
submitXPub(xpub);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Next Step
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default WalletModal;
|
||||||
|
@ -1,79 +1,72 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Box, Text, Button, Col, Anchor } from '@tlon/indigo-react';
|
||||||
Box,
|
import { api } from '../../api';
|
||||||
Icon,
|
import { useSettings } from '../../hooks/useSettings';
|
||||||
Row,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
Anchor,
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
|
|
||||||
import { store } from '../../store'
|
const Warning = () => {
|
||||||
|
const { setWarning } = useSettings();
|
||||||
|
const understand = () => {
|
||||||
|
setWarning(false);
|
||||||
export default class Warning extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.understand = this.understand.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
understand(){
|
|
||||||
store.handleEvent({ data: { bucket: { warning: false}}});
|
|
||||||
let removeWarning = {
|
let removeWarning = {
|
||||||
"put-entry": {
|
'put-entry': {
|
||||||
value: false,
|
value: false,
|
||||||
"entry-key": "warning",
|
'entry-key': 'warning',
|
||||||
"bucket-key": "btc-wallet",
|
'bucket-key': 'btc-wallet',
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
this.props.api.settingsEvent(removeWarning);
|
api.settingsEvent(removeWarning);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
return (
|
<Box
|
||||||
<Box
|
backgroundColor="red"
|
||||||
backgroundColor="red"
|
color="white"
|
||||||
color="white"
|
borderRadius="32px"
|
||||||
borderRadius="32px"
|
justifyContent="space-between"
|
||||||
justifyContent="space-between"
|
width="100%"
|
||||||
width='100%'
|
p={5}
|
||||||
p={5}
|
mb={5}
|
||||||
mb={5}
|
>
|
||||||
|
<Col>
|
||||||
|
<Text color="white" fontWeight="bold" fontSize={1}>
|
||||||
|
Warning!
|
||||||
|
</Text>
|
||||||
|
<br />
|
||||||
|
<Text color="white" fontWeight="bold" fontSize={1}>
|
||||||
|
Be safe while using this wallet, and be sure to store responsible
|
||||||
|
amounts of BTC.
|
||||||
|
</Text>
|
||||||
|
<Text color="white" fontWeight="bold" fontSize={1}>
|
||||||
|
Always ensure that the checksum of the wallet matches that of the
|
||||||
|
wallet's repo.
|
||||||
|
</Text>
|
||||||
|
<br />
|
||||||
|
<Anchor href="https://urbit.org/bitcoin-wallet" target="_blank">
|
||||||
|
<Text
|
||||||
|
color="white"
|
||||||
|
fontWeight="bold"
|
||||||
|
fontSize={1}
|
||||||
|
style={{ textDecoration: 'underline' }}
|
||||||
|
>
|
||||||
|
Learn more on urbit.org
|
||||||
|
</Text>
|
||||||
|
</Anchor>
|
||||||
|
</Col>
|
||||||
|
<Button
|
||||||
|
backgroundColor="white"
|
||||||
|
fontSize={1}
|
||||||
|
mt={5}
|
||||||
|
color="red"
|
||||||
|
fontWeight="bold"
|
||||||
|
borderRadius="24px"
|
||||||
|
p="24px"
|
||||||
|
borderColor="none"
|
||||||
|
onClick={() => understand()}
|
||||||
>
|
>
|
||||||
<Col>
|
I understand
|
||||||
<Text color="white" fontWeight='bold' fontSize={1}>
|
</Button>
|
||||||
Warning!
|
</Box>
|
||||||
</Text>
|
);
|
||||||
<br/>
|
};
|
||||||
<Text color="white" fontWeight='bold' fontSize={1}>
|
|
||||||
Be safe while using this wallet, and be sure to store responsible amounts
|
export default Warning;
|
||||||
of BTC.
|
|
||||||
</Text>
|
|
||||||
<Text color="white" fontWeight='bold' fontSize={1}>
|
|
||||||
Always ensure that the checksum of the wallet matches that of the wallet's repo.
|
|
||||||
</Text>
|
|
||||||
<br/>
|
|
||||||
<Anchor href="https://urbit.org/bitcoin-wallet" target="_blank">
|
|
||||||
<Text color="white" fontWeight="bold" fontSize={1} style={{textDecoration:'underline'}}>
|
|
||||||
Learn more on urbit.org
|
|
||||||
</Text>
|
|
||||||
</Anchor>
|
|
||||||
</Col>
|
|
||||||
<Button children="I Understand"
|
|
||||||
backgroundColor="white"
|
|
||||||
fontSize={1}
|
|
||||||
mt={5}
|
|
||||||
color="red"
|
|
||||||
fontWeight="bold"
|
|
||||||
borderRadius="24px"
|
|
||||||
p="24px"
|
|
||||||
borderColor="none"
|
|
||||||
onClick={this.understand}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,67 +1,39 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { api } from '../api.js';
|
|
||||||
import { store } from '../store.js';
|
|
||||||
import { ThemeProvider } from 'styled-components';
|
import { ThemeProvider } from 'styled-components';
|
||||||
import light from './themes/light';
|
import light from './themes/light';
|
||||||
// import dark from './themes/dark';
|
|
||||||
import { Box, Reset } from '@tlon/indigo-react';
|
import { Box, Reset } from '@tlon/indigo-react';
|
||||||
import StartupModal from './lib/startupModal.js';
|
import StartupModal from './lib/startupModal.js';
|
||||||
import Body from './lib/body.js';
|
import Body from './lib/body.js';
|
||||||
import { subscription } from '../subscription.js';
|
import { useSettings } from '../hooks/useSettings.js';
|
||||||
|
|
||||||
const network = 'bitcoin';
|
const Root = () => {
|
||||||
|
const { loaded, wallet, provider } = useSettings();
|
||||||
|
const blur = !loaded ? false : !(wallet && provider);
|
||||||
|
|
||||||
export class Root extends Component {
|
return (
|
||||||
constructor(props) {
|
<BrowserRouter>
|
||||||
super(props);
|
<ThemeProvider theme={light}>
|
||||||
this.ship = window.ship;
|
<Reset />
|
||||||
this.state = store.state;
|
{loaded ? <StartupModal /> : null}
|
||||||
store.setStateHandler(this.setState.bind(this));
|
<Box
|
||||||
}
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
position="absolute"
|
||||||
|
alignItems="center"
|
||||||
|
backgroundColor="lightOrange"
|
||||||
|
width="100%"
|
||||||
|
minHeight={loaded ? '100%' : 'none'}
|
||||||
|
height={loaded ? 'none' : '100%'}
|
||||||
|
style={{ filter: blur ? 'blur(8px)' : 'none' }}
|
||||||
|
px={[0, 4]}
|
||||||
|
pb={[0, 4]}
|
||||||
|
>
|
||||||
|
<Body />
|
||||||
|
</Box>
|
||||||
|
</ThemeProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
export default Root;
|
||||||
this.props.channel.setOnChannelError(() => {
|
|
||||||
subscription.start();
|
|
||||||
});
|
|
||||||
subscription.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const loaded = this.state.loaded;
|
|
||||||
const warning = this.state.showWarning;
|
|
||||||
const blur = !loaded ? false : !(this.state.wallet && this.state.provider);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BrowserRouter>
|
|
||||||
<ThemeProvider theme={light}>
|
|
||||||
<Reset />
|
|
||||||
{loaded ? (
|
|
||||||
<StartupModal api={api} state={this.state} network={network} />
|
|
||||||
) : null}
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
flexDirection="column"
|
|
||||||
position="absolute"
|
|
||||||
alignItems="center"
|
|
||||||
backgroundColor="lightOrange"
|
|
||||||
width="100%"
|
|
||||||
minHeight={loaded ? '100%' : 'none'}
|
|
||||||
height={loaded ? 'none' : '100%'}
|
|
||||||
style={{ filter: blur ? 'blur(8px)' : 'none' }}
|
|
||||||
px={[0, 4]}
|
|
||||||
pb={[0, 4]}
|
|
||||||
>
|
|
||||||
<Body
|
|
||||||
loaded={loaded}
|
|
||||||
state={this.state}
|
|
||||||
api={api}
|
|
||||||
network={network}
|
|
||||||
warning={warning}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</ThemeProvider>
|
|
||||||
</BrowserRouter>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
|
|
||||||
export function uuid() {
|
export function uuid() {
|
||||||
let str = "0v"
|
let str = '0v';
|
||||||
str += Math.ceil(Math.random()*8)+"."
|
str += Math.ceil(Math.random() * 8) + '.';
|
||||||
for (var i = 0; i < 5; i++) {
|
for (var i = 0; i < 5; i++) {
|
||||||
let _str = Math.ceil(Math.random()*10000000).toString(32);
|
let _str = Math.ceil(Math.random() * 10000000).toString(32);
|
||||||
_str = ("00000"+_str).substr(-5,5);
|
_str = ('00000' + _str).substr(-5, 5);
|
||||||
str += _str+".";
|
str += _str + '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
return str.slice(0,-1);
|
return str.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPatTa(str) {
|
export function isPatTa(str) {
|
||||||
const r = /^[a-z,0-9,\-,\.,_,~]+$/.exec(str)
|
const r = /^[a-z,0-9,\-,.,_,~]+$/.exec(str);
|
||||||
return !!r;
|
return !!r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,13 +22,15 @@ export function isPatTa(str) {
|
|||||||
(javascript Date object)
|
(javascript Date object)
|
||||||
*/
|
*/
|
||||||
export function daToDate(st) {
|
export function daToDate(st) {
|
||||||
var dub = function(n) {
|
var dub = function (n) {
|
||||||
return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString();
|
return parseInt(n) < 10 ? '0' + parseInt(n) : n.toString();
|
||||||
};
|
};
|
||||||
var da = st.split('..');
|
var da = st.split('..');
|
||||||
var bigEnd = da[0].split('.');
|
var bigEnd = da[0].split('.');
|
||||||
var lilEnd = da[1].split('.');
|
var lilEnd = da[1].split('.');
|
||||||
var ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(lilEnd[0])}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
|
var ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(
|
||||||
|
lilEnd[0]
|
||||||
|
)}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
|
||||||
return new Date(ds);
|
return new Date(ds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,18 +42,18 @@ export function daToDate(st) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function dateToDa(d, mil) {
|
export function dateToDa(d, mil) {
|
||||||
var fil = function(n) {
|
var fil = function (n) {
|
||||||
return n >= 10 ? n : "0" + n;
|
return n >= 10 ? n : '0' + n;
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
`~${d.getUTCFullYear()}.` +
|
`~${d.getUTCFullYear()}.` +
|
||||||
`${(d.getUTCMonth() + 1)}.` +
|
`${d.getUTCMonth() + 1}.` +
|
||||||
`${fil(d.getUTCDate())}..` +
|
`${fil(d.getUTCDate())}..` +
|
||||||
`${fil(d.getUTCHours())}.` +
|
`${fil(d.getUTCHours())}.` +
|
||||||
`${fil(d.getUTCMinutes())}.` +
|
`${fil(d.getUTCMinutes())}.` +
|
||||||
`${fil(d.getUTCSeconds())}` +
|
`${fil(d.getUTCSeconds())}` +
|
||||||
`${mil ? "..0000" : ""}`
|
`${mil ? '..0000' : ''}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deSig(ship) {
|
export function deSig(ship) {
|
||||||
@ -64,49 +62,59 @@ export function deSig(ship) {
|
|||||||
|
|
||||||
// trim patps to match dojo, chat-cli
|
// trim patps to match dojo, chat-cli
|
||||||
export function cite(ship) {
|
export function cite(ship) {
|
||||||
let patp = ship, shortened = "";
|
let patp = ship,
|
||||||
if (patp.startsWith("~")) {
|
shortened = '';
|
||||||
|
if (patp.startsWith('~')) {
|
||||||
patp = patp.substr(1);
|
patp = patp.substr(1);
|
||||||
}
|
}
|
||||||
// comet
|
// comet
|
||||||
if (patp.length === 56) {
|
if (patp.length === 56) {
|
||||||
shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 56);
|
shortened = '~' + patp.slice(0, 6) + '_' + patp.slice(50, 56);
|
||||||
return shortened;
|
return shortened;
|
||||||
}
|
}
|
||||||
// moon
|
// moon
|
||||||
if (patp.length === 27) {
|
if (patp.length === 27) {
|
||||||
shortened = "~" + patp.slice(14, 20) + "^" + patp.slice(21, 27);
|
shortened = '~' + patp.slice(14, 20) + '^' + patp.slice(21, 27);
|
||||||
return shortened;
|
return shortened;
|
||||||
}
|
}
|
||||||
return `~${patp}`;
|
return `~${patp}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function satsToCurrency(sats, denomination, rates){
|
export function satsToCurrency(sats, denomination, rates) {
|
||||||
if (!rates) {
|
if (!rates) {
|
||||||
throw "nonexistent currency table"
|
throw 'nonexistent currency table';
|
||||||
}
|
}
|
||||||
if (!rates[denomination]){
|
if (!rates[denomination]) {
|
||||||
denomination = "BTC";
|
denomination = 'BTC';
|
||||||
}
|
}
|
||||||
let rate = rates[denomination];
|
let rate = rates[denomination];
|
||||||
let val = parseFloat(((sats * rate.last) * 0.00000001).toFixed(8));
|
let val = rate ? parseFloat((sats * rate.last * 0.00000001).toFixed(8)) : 0;
|
||||||
let text;
|
let text;
|
||||||
if (denomination === 'BTC'){
|
if (denomination === 'BTC' && rate) {
|
||||||
text = val + ' ' + rate.symbol
|
text = val + ' ' + rate.symbol;
|
||||||
} else {
|
} else if (rate) {
|
||||||
text = rate.symbol + val.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')
|
text =
|
||||||
|
rate.symbol + val.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||||
}
|
}
|
||||||
return text
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function currencyToSats(val, denomination, rates){
|
export function currencyToSats(val, denomination, rates) {
|
||||||
if (!rates) {
|
if (!rates) {
|
||||||
throw "nonexistent currency table"
|
throw 'nonexistent currency table';
|
||||||
}
|
}
|
||||||
if (!rates[denomination]){
|
if (!rates[denomination]) {
|
||||||
throw 'currency not in table'
|
throw 'currency not in table';
|
||||||
}
|
}
|
||||||
let rate = rates[denomination];
|
let rate = rates[denomination];
|
||||||
let sats = (parseFloat(val) / rate.last) * 100000000;
|
let sats = (parseFloat(val) / rate.last) * 100000000;
|
||||||
return sats;
|
return sats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function reduceHistory(history) {
|
||||||
|
return Object.values(history).sort((hest1, hest2) => {
|
||||||
|
if (hest1.recvd === null) return -1;
|
||||||
|
if (hest2.recvd === null) return +1;
|
||||||
|
return hest2.recvd - hest1.recvd;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export class CurrencyReducer {
|
|
||||||
reduce(json, state) {
|
|
||||||
if (!json) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (json.currencyRates) {
|
|
||||||
for (var c in json.currencyRates) {
|
|
||||||
state.currencyRates[c] = json.currencyRates[c];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json.denomination) {
|
|
||||||
if (state.currencyRates[json.denomination]) {
|
|
||||||
state.denomination = json.denomination
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export class InitialReducer {
|
|
||||||
reduce(json, state) {
|
|
||||||
let data = _.get(json, 'initial', false);
|
|
||||||
if (data) {
|
|
||||||
state.provider = data.provider;
|
|
||||||
state.wallet = data.wallet;
|
|
||||||
state.confirmedBalance = _.get(data.balance, 'confirmed', null);
|
|
||||||
state.unconfirmedBalance = _.get(data.balance, 'unconfirmed', null);
|
|
||||||
state.btcState = data['btc-state'];
|
|
||||||
state.history = this.reduceHistory(data.history);
|
|
||||||
state.address = data.address;
|
|
||||||
|
|
||||||
state.loadedBtc = true;
|
|
||||||
if (state.loadedSettings) {
|
|
||||||
state.loaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceHistory(history) {
|
|
||||||
return Object.values(history).sort((hest1, hest2) => {
|
|
||||||
if (hest1.recvd === null) return -1;
|
|
||||||
if (hest2.recvd === null) return +1;
|
|
||||||
return (hest2.recvd - hest1.recvd)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export class SettingsReducer {
|
|
||||||
reduce(json, state) {
|
|
||||||
let data = _.get(json, 'bucket', false);
|
|
||||||
if (data) {
|
|
||||||
let warning = _.get(json, 'bucket.warning', -1);
|
|
||||||
if (warning !== -1) {
|
|
||||||
state.showWarning = warning
|
|
||||||
}
|
|
||||||
let currency = _.get(json, 'bucket.currency', -1);
|
|
||||||
if (currency !== -1) {
|
|
||||||
state.denomination = currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.loadedSettings = true;
|
|
||||||
if (state.loadedBtc) {
|
|
||||||
state.loaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let entry = _.get(json, 'settings-event.put-entry.entry-key', false);
|
|
||||||
if (entry === 'currency') {
|
|
||||||
let value = _.get(json, 'settings-event.put-entry.value', false);
|
|
||||||
state.denomination = value;
|
|
||||||
} else if (entry === 'warning') {
|
|
||||||
let value = _.get(json, 'settings-event.put-entry.value', false);
|
|
||||||
state.showWarning = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
|
|
||||||
export class UpdateReducer {
|
|
||||||
reduce(json, state) {
|
|
||||||
if (!json) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (json.providerStatus) {
|
|
||||||
this.reduceProviderStatus(json.providerStatus, state);
|
|
||||||
}
|
|
||||||
if (json.checkPayee) {
|
|
||||||
this.reduceCheckPayee(json.checkPayee, state);
|
|
||||||
}
|
|
||||||
if ("change-provider" in json) {
|
|
||||||
this.reduceChangeProvider(json["change-provider"], state);
|
|
||||||
}
|
|
||||||
if (json["change-wallet"]) {
|
|
||||||
this.changeWallet(json["change-wallet"], state);
|
|
||||||
}
|
|
||||||
if (json.hasOwnProperty('psbt')) {
|
|
||||||
this.reducePsbt(json.psbt, state);
|
|
||||||
}
|
|
||||||
if (json["btc-state"]) {
|
|
||||||
this.reduceBtcState(json["btc-state"], state);
|
|
||||||
}
|
|
||||||
if (json["new-tx"]) {
|
|
||||||
this.reduceNewTx(json["new-tx"], state);
|
|
||||||
}
|
|
||||||
if (json["cancel-tx"]) {
|
|
||||||
this.reduceCancelTx(json["cancel-tx"], state);
|
|
||||||
}
|
|
||||||
if (json.address) {
|
|
||||||
this.reduceAddress(json.address, state);
|
|
||||||
}
|
|
||||||
if (json.balance) {
|
|
||||||
this.reduceBalance(json.balance, state);
|
|
||||||
}
|
|
||||||
if (json.hasOwnProperty('error')) {
|
|
||||||
this.reduceError(json.error, state);
|
|
||||||
}
|
|
||||||
if (json.hasOwnProperty('broadcast-success')){
|
|
||||||
state.broadcastSuccess = true;
|
|
||||||
}
|
|
||||||
if (json.hasOwnProperty('broadcast-fail')){
|
|
||||||
state.broadcastSuccess = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceProviderStatus(json, state) {
|
|
||||||
state.providerPerms[json.provider] = json.permitted;
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceCheckPayee(json, state) {
|
|
||||||
state.shipWallets[json.payee] = json.hasWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceChangeProvider(json, state) {
|
|
||||||
state.provider = json;
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceChangeWallet(json, state) {
|
|
||||||
state.wallet = json;
|
|
||||||
}
|
|
||||||
|
|
||||||
reducePsbt(json, state) {
|
|
||||||
state.psbt = json.pb;
|
|
||||||
state.fee = json.fee;
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceBtcState(json, state) {
|
|
||||||
state.btcState = json;
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceNewTx(json, state) {
|
|
||||||
let old = _.findIndex(state.history, (h) => {
|
|
||||||
return ( h.txid.dat === json.txid.dat &&
|
|
||||||
h.txid.wid === json.txid.wid );
|
|
||||||
});
|
|
||||||
if (old !== -1) {
|
|
||||||
delete state.history.splice(old, 1);
|
|
||||||
}
|
|
||||||
if (json.recvd === null) {
|
|
||||||
state.history.unshift(json);
|
|
||||||
} else {
|
|
||||||
// we expect history to have null recvd values first, and the rest in
|
|
||||||
// descending order
|
|
||||||
let insertionIndex = _.findIndex(state.history, (h) => {
|
|
||||||
return ((h.recvd < json.recvd) && (h.recvd !== null));
|
|
||||||
});
|
|
||||||
state.history.splice(insertionIndex, 0, json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceCancelTx(json, state) {
|
|
||||||
let entryIndex = _.findIndex(state.history, (h) => {
|
|
||||||
return ((json.wid === h.txid.wid) && (json.dat === h.txid.dat));
|
|
||||||
});
|
|
||||||
if (entryIndex > -1) {
|
|
||||||
state.history[entryIndex].failure = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceAddress(json, state) {
|
|
||||||
state.address = json;
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceBalance(json, state) {
|
|
||||||
state.unconfirmedBalance = json.unconfirmed;
|
|
||||||
state.confirmedBalance = json.confirmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
reduceError(json, state) {
|
|
||||||
state.error = json;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
import { InitialReducer } from './reducers/initial';
|
|
||||||
import { UpdateReducer } from './reducers/update';
|
|
||||||
import { CurrencyReducer } from './reducers/currency';
|
|
||||||
import { SettingsReducer } from './reducers/settings';
|
|
||||||
|
|
||||||
class Store {
|
|
||||||
constructor() {
|
|
||||||
this.state = {
|
|
||||||
loadedBtc: false,
|
|
||||||
loadedSettings: false,
|
|
||||||
loaded: false,
|
|
||||||
providerPerms: {},
|
|
||||||
shipWallets: {},
|
|
||||||
provider: null,
|
|
||||||
wallet: null,
|
|
||||||
confirmedBalance: null,
|
|
||||||
unconfirmedBalance: null,
|
|
||||||
btcState: null,
|
|
||||||
history: [],
|
|
||||||
psbt: '',
|
|
||||||
address: null,
|
|
||||||
currencyRates: {
|
|
||||||
BTC: { last: 1, symbol: 'BTC' }
|
|
||||||
},
|
|
||||||
denomination: 'BTC',
|
|
||||||
showWarning: true,
|
|
||||||
error: '',
|
|
||||||
broadcastSuccess: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.initialReducer = new InitialReducer();
|
|
||||||
this.updateReducer = new UpdateReducer();
|
|
||||||
this.currencyReducer = new CurrencyReducer();
|
|
||||||
this.settingsReducer = new SettingsReducer();
|
|
||||||
this.setState = () => { };
|
|
||||||
}
|
|
||||||
|
|
||||||
setStateHandler(setState) {
|
|
||||||
this.setState = setState;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEvent(data) {
|
|
||||||
let json = data.data;
|
|
||||||
this.initialReducer.reduce(json, this.state);
|
|
||||||
this.updateReducer.reduce(json, this.state);
|
|
||||||
this.currencyReducer.reduce(json, this.state);
|
|
||||||
this.settingsReducer.reduce(json, this.state);
|
|
||||||
|
|
||||||
this.setState(this.state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export let store = new Store();
|
|
||||||
window.store = store;
|
|
Loading…
Reference in New Issue
Block a user