diff --git a/pkg/btc-wallet/src/index.js b/pkg/btc-wallet/src/index.js index d4852ef264..09b51a1384 100644 --- a/pkg/btc-wallet/src/index.js +++ b/pkg/btc-wallet/src/index.js @@ -1,8 +1,8 @@ import React from 'react'; 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 { subscription } from "./js/subscription.js"; +import { SettingsProvider } from './js/hooks/useSettings'; import './css/indigo-static.css'; import './css/fonts.css'; @@ -13,11 +13,13 @@ import './css/custom.css'; const channel = new window.channel(); api.setChannel(window.ship, channel); - if (module.hot) { - module.hot.accept() + module.hot.accept(); } -ReactDOM.render(( - -), document.querySelectorAll("#root")[0]); +ReactDOM.render( + + + , + document.querySelectorAll('#root')[0] +); diff --git a/pkg/btc-wallet/src/js/components/lib/balance.js b/pkg/btc-wallet/src/js/components/lib/balance.js index 7d2e8eb300..a963a3dac2 100644 --- a/pkg/btc-wallet/src/js/components/lib/balance.js +++ b/pkg/btc-wallet/src/js/components/lib/balance.js @@ -1,170 +1,140 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import { Row, Text, Button, Col } from '@tlon/indigo-react'; import Send from './send.js'; import CurrencyPicker from './currencyPicker.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 { - constructor(props) { - super(props); +const Balance = () => { + const { + 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 = { - sending: false, - copiedButton: false, - copiedString: false, - }; - - this.copyAddress = this.copyAddress.bind(this); - } - - copyAddress(arg) { - let address = this.props.state.address; + const copyAddress = (arg) => { navigator.clipboard.writeText(address); - this.props.api.btcWalletCommand({ 'gen-new-address': null }); + api.btcWalletCommand({ 'gen-new-address': null }); if (arg === 'button') { - this.setState({ copiedButton: true }); + setCopiedButton(true); setTimeout(() => { - this.setState({ copiedButton: false }); + setCopiedButton(false); }, 2000); } else if (arg === 'string') { - this.setState({ copiedString: true }); + setCopiedString(true); setTimeout(() => { - this.setState({ copiedString: false }); + setCopiedString(false); }, 2000); } - } + }; - render() { - const sats = this.props.state.confirmedBalance || 0; - const unconfirmedSats = this.props.state.unconfirmedBalance; + const unconfirmedString = unconfirmedSats ? ` (${unconfirmedSats}) ` : ''; - 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 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 = currencyRates[denomination]?.last; - const conversion = this.props.state.currencyRates[denomination].last; - - return ( - <> - {this.state.sending ? ( - { - this.setState({ sending: false }); - store.handleEvent({ - data: { psbt: '', fee: 0, error: '', 'broadcast-fail': null }, - }); - }} - /> - ) : ( - - - - Balance - - { - this.copyAddress('string'); - }} - > - {this.state.copiedString ? 'copied' : addressText} - - - - - - {value} - - {`${sats}${unconfirmedString} sats`} - - - - - + return ( + <> + {sending ? ( + { + setSending(false); + setPsbt(''); + setFee(0); + setError(''); + }} + /> + ) : ( + + + + Balance + + copyAddress('string')} + > + {copiedString ? 'copied' : addressText} + + + + + + {value} + + {`${sats}${unconfirmedString} sats`} - )} - - ); - } -} + + + + + + )} + + ); +}; + +export default Balance; diff --git a/pkg/btc-wallet/src/js/components/lib/body.js b/pkg/btc-wallet/src/js/components/lib/body.js index eff8c8d8ef..334a622a32 100644 --- a/pkg/btc-wallet/src/js/components/lib/body.js +++ b/pkg/btc-wallet/src/js/components/lib/body.js @@ -1,72 +1,49 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - Row, - Text, - LoadingSpinner, - Col, -} from '@tlon/indigo-react'; -import { - Switch, - Route, -} from 'react-router-dom'; -import Balance from './balance.js'; +import React from 'react'; +import { Box, LoadingSpinner, Col } from '@tlon/indigo-react'; +import { Switch, Route } from 'react-router-dom'; +import Balance from './balance.js'; import Transactions from './transactions.js'; -import Warning from './warning.js'; -import Header from './header.js'; -import Settings from './settings.js'; +import Warning from './warning.js'; +import Header from './header.js'; +import Settings from './settings.js'; +import { useSettings } from '../../hooks/useSettings.js'; -export default class Body extends Component { - constructor(props) { - super(props); - } +const Body = () => { + const { loaded, showWarning: warning } = useSettings(); + const cardWidth = window.innerWidth <= 475 ? '350px' : '400px'; + return !loaded ? ( + + + + ) : ( + + + +
+ + + + + +
+ {!warning ? null : } + + + + + + ); +}; - render() { - - const cardWidth = window.innerWidth <= 475 ? '350px' : '400px' - - if (!this.props.loaded) { - return ( - - - - ); - } else { - return ( - - - -
- - - - - -
- { (!this.props.warning) ? null : } - - - - - - ); - } - } -} +export default Body; diff --git a/pkg/btc-wallet/src/js/components/lib/bridgeInvoice.js b/pkg/btc-wallet/src/js/components/lib/bridgeInvoice.js index acc147a8e1..3b3a2df34b 100644 --- a/pkg/btc-wallet/src/js/components/lib/bridgeInvoice.js +++ b/pkg/btc-wallet/src/js/components/lib/bridgeInvoice.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Box, Icon, @@ -9,231 +9,203 @@ import { Col, LoadingSpinner, } from '@tlon/indigo-react'; - import { Sigil } from './sigil.js'; - import * as bitcoin from 'bitcoinjs-lib'; -import * as kg from 'urbit-key-generation'; import { isValidPatp } from 'urbit-ob'; - -import Sent from './sent.js' -import Error from './error.js' - +import Sent from './sent.js'; +import Error from './error.js'; import { satsToCurrency } from '../../lib/util.js'; +import { useSettings } from '../../hooks/useSettings.js'; +import { api } from '../../api'; -export default class BridgeInvoice extends Component { - constructor(props) { - super(props); +const BridgeInvoice = ({ payee, stopSending, satsAmount }) => { + const { error, currencyRates, fee, broadcastSuccess, denomination, psbt } = + useSettings(); + const [txHex, setTxHex] = useState(''); + const [ready, setReady] = useState(false); + const [localError, setLocalError] = useState(''); + const [broadcasting, setBroadcasting] = useState(false); + const invoiceRef = useRef(); - this.state = { - txHex: '', - ready: 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 + useEffect(() => { + if (broadcasting && localError !== '') { + setBroadcasting(false); } - 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 !== '') { - inputColor = 'red'; - inputBg = 'veryLightRed'; - inputBorder = 'red'; + setLocalError(error); } + }, [error, broadcasting, setBroadcasting]); - const isShip = isValidPatp(payee); + useEffect(() => { + window.open('https://bridge.urbit.org/?kind=btc&utx=' + psbt); + }); - const icon = (isShip) - ? - : ; + const broadCastTx = (hex) => { + let command = { + 'broadcast-tx': hex, + }; + return api.btcWalletCommand(command); + }; - return ( - <> - { this.props.state.broadcastSuccess ? - : + const sendBitcoin = (hex) => { + try { + bitcoin.Transaction.fromHex(hex); + broadCastTx(hex); + setBroadcasting(true); + } catch (e) { + setLocalError('invalid-signed'); + setBroadcasting(false); + } + }; + + 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 ? ( + + ) : ( + + + + ); + + return ( + <> + {broadcastSuccess ? ( + + ) : ( + - - - {satsToCurrency(satsAmount, denomination, currencyRates)} - - - {`${satsAmount} sats`} - - - {`Fee: ${satsToCurrency(fee, denomination, currencyRates)} (${fee} sats)`} - - - You are paying - - - {icon} - {payee} - - - - - Bridge signed transaction + + + {satsToCurrency(satsAmount, denomination, currencyRates)} - - - - Copy the signed transaction from Bridge + + + {`${satsAmount} sats`} + + + {`Fee: ${satsToCurrency( + fee, + denomination, + currencyRates + )} (${fee} sats)`} + + + + You are paying + + + + {icon} + + {payee} - - - { (error !== '') && - - - - } - - + {broadcasting ? : null} + + + )} + + ); +}; + +export default BridgeInvoice; diff --git a/pkg/btc-wallet/src/js/components/lib/currencyPicker.js b/pkg/btc-wallet/src/js/components/lib/currencyPicker.js index 053201bf61..30dc627560 100644 --- a/pkg/btc-wallet/src/js/components/lib/currencyPicker.js +++ b/pkg/btc-wallet/src/js/components/lib/currencyPicker.js @@ -1,50 +1,37 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - Row, - Text, - Button, - Col, - LoadingSpinner, -} from '@tlon/indigo-react'; -import _ from 'lodash'; +import React from 'react'; +import { Icon, Row, Text } from '@tlon/indigo-react'; +import { api } from '../../api'; +import { useSettings } from '../../hooks/useSettings'; -import { satsToCurrency } from '../../lib/util.js' -import { store } from '../../store'; - -export default class CurrencyPicker extends Component { - constructor(props) { - super(props); - this.switchCurrency = this.switchCurrency.bind(this); - } - - switchCurrency(){ +const CurrencyPicker = () => { + const { denomination, currencyRates } = useSettings(); + const switchCurrency = () => { let newCurrency; - if (this.props.denomination === 'BTC') { - if (this.props.currencies['USD']) { - newCurrency = "USD"; + if (denomination === 'BTC') { + if (currencyRates['USD']) { + newCurrency = 'USD'; } - } else if (this.props.denomination === 'USD') { - newCurrency = "BTC"; + } else if (denomination === 'USD') { + newCurrency = 'BTC'; } let setCurrency = { - "put-entry": { + 'put-entry': { value: newCurrency, - "entry-key": "currency", - "bucket-key": "btc-wallet", - } - } - this.props.api.settingsEvent(setCurrency); - } + 'entry-key': 'currency', + 'bucket-key': 'btc-wallet', + }, + }; + api.settingsEvent(setCurrency); + }; + return ( + switchCurrency()}> + + + {denomination} + + + ); +}; - render() { - return ( - - - {this.props.denomination} - - ); - } -} +export default CurrencyPicker; diff --git a/pkg/btc-wallet/src/js/components/lib/error.js b/pkg/btc-wallet/src/js/components/lib/error.js index f7c9ec473a..42d114d182 100644 --- a/pkg/btc-wallet/src/js/components/lib/error.js +++ b/pkg/btc-wallet/src/js/components/lib/error.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import { Text } from '@tlon/indigo-react'; const errorToString = (error) => { @@ -26,16 +26,12 @@ const errorToString = (error) => { if (error === 'invalid-signed') { return 'Invalid signed bitcoin transaction'; } -} +}; -export default function Error(props) { - const error = errorToString(props.error); +const Error = ({ error, ...rest }) => ( + + {errorToString(error)} + +); - return( - - {error} - - ); -} +export default Error; diff --git a/pkg/btc-wallet/src/js/components/lib/feePicker.js b/pkg/btc-wallet/src/js/components/lib/feePicker.js index ee419dd7ab..00e91c198d 100644 --- a/pkg/btc-wallet/src/js/components/lib/feePicker.js +++ b/pkg/btc-wallet/src/js/components/lib/feePicker.js @@ -1,99 +1,99 @@ -import React, { Component } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { Box, - Icon, - Row, Text, - Button, Col, StatelessRadioButtonField as RadioButton, Label, } from '@tlon/indigo-react'; +const feeLevels = { + low: 'low', + mid: 'mid', + high: 'high', +}; -export default class FeePicker extends Component { - constructor(props) { - super(props); +const FeePicker = ({ feeChoices, feeSelect, feeDismiss }) => { + const [feeSelected, setFeeSelected] = useState(feeLevels.mid); + const [modalElement, setModalElement] = useState(); + const modalRef = useRef(); - this.state = { - selected: 'mid' - } + // const clickDismiss = (e) => { + // console.log(modalElement, e); + // // if (modalRef && !modalRef.contains(e.target)) { + // // feeDismiss(); + // // } + // }; - this.select = this.select.bind(this); - this.clickDismiss = this.clickDismiss.bind(this); - this.setModalRef = this.setModalRef.bind(this); - } + const select = (which) => { + setFeeSelected(which); + feeSelect(which); + feeDismiss(); + }; - componentDidMount() { - document.addEventListener("click", this.clickDismiss); - } + // useEffect(() => { + // document.addEventListener('click', (e) => clickDismiss(e)); + // setModalElement(modalRef.current); + // console.log(modalRef.current); + // return () => document.addEventListener('click', clickDismiss); + // }, []); - componentWillUnount() { - document.removeEventListener("click", this.clickDismiss); - } + return ( + feeDismiss()} + position="absolute" + p={4} + border="1px solid green" + zIndex={10} + backgroundColor="white" + borderRadius={3} + > + + Transaction Speed + + + { + select('low'); + }} + > + + - setModalRef(n) { - this.modalRef = n; - } + { + select('mid'); + }} + > + + - clickDismiss(e) { - if (this.modalRef && !(this.modalRef.contains(e.target))){ - this.props.feeDismiss(); - } - } + { + select('high'); + }} + > + + + + + ); +}; - select(which) { - this.setState({selected: which}); - this.props.feeSelect(which); - } - - render() { - return ( - - - Transaction Speed - - - { - this.select('low'); - }} - > - - - - { - this.select('mid'); - }} - > - - - - { - this.select('high'); - }} - > - - - - - - ); - } -} +export default FeePicker; diff --git a/pkg/btc-wallet/src/js/components/lib/header.js b/pkg/btc-wallet/src/js/components/lib/header.js index 9b6225effb..88f4687fff 100644 --- a/pkg/btc-wallet/src/js/components/lib/header.js +++ b/pkg/btc-wallet/src/js/components/lib/header.js @@ -1,78 +1,81 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - Row, - Text, -} from '@tlon/indigo-react'; +import React from 'react'; +import { Box, Icon, Row, Text } from '@tlon/indigo-react'; import { Link } from 'react-router-dom'; +import { useSettings } from '../../hooks/useSettings'; -export default class Header extends Component { - constructor(props) { - super(props); +const Header = ({ settings }) => { + const { provider } = useSettings(); + 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 = ( + + Provider Offline + + ); + + if (!settings) { + badge = ( + + ); + } } - - render() { - let icon = this.props.settings ? "X" : "Adjust"; - let iconColor = this.props.settings ? "black" : "orange"; - let iconLink = this.props.settings ? "/~btc" : "/~btc/settings"; - - let connection = null; - let badge = null; - if (!(this.props.state.provider && this.props.state.provider.connected)) { - connection = - - Provider Offline + return ( + + + + + + + Bitcoin - - if (!this.props.settings) { - badge = - - } - } - - - - return ( - - - + + {connection} + + - + {badge} + - - Bitcoin - - - - {connection} - - - {badge} - - - - + - ); - } -} + + ); +}; + +export default Header; diff --git a/pkg/btc-wallet/src/js/components/lib/invoice.js b/pkg/btc-wallet/src/js/components/lib/invoice.js index 6850fb7f21..771a876698 100644 --- a/pkg/btc-wallet/src/js/components/lib/invoice.js +++ b/pkg/btc-wallet/src/js/components/lib/invoice.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { Box, Icon, @@ -9,18 +9,15 @@ import { Col, LoadingSpinner, } from '@tlon/indigo-react'; - -import { Sigil } from './sigil.js' - +import { Sigil } from './sigil.js'; import * as bitcoin from 'bitcoinjs-lib'; 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 { satsToCurrency } from '../../lib/util.js'; import Error from './error.js'; +import { useSettings } from '../../hooks/useSettings.js'; +import { api } from '../../api'; const BITCOIN_MAINNET_INFO = { messagePrefix: '\x18Bitcoin Signed Message:\n', @@ -46,246 +43,234 @@ const BITCOIN_TESTNET_INFO = { wif: 0xef, }; -export default class Invoice extends Component { - constructor(props) { - super(props); +const Invoice = ({ stopSending, payee, satsAmount }) => { + const { + 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 = { - masterTicket: '', - ready: 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(); + useEffect(() => { + if (broadcasting && localError !== '') { + setBroadcasting(false); } - } + }, [error, broadcasting, setBroadcasting]); - componentDidUpdate(prevProps, prevState) { - if (this.state.broadcasting) { - if (this.state.error !== '') { - this.setState({broadcasting: false}); - } + const clickDismiss = (e) => { + if (invoiceRef && !invoiceRef.contains(e.target)) { + stopSending(); } - } + }; - broadCastTx(psbtHex) { + useEffect(() => { + document.addEventListener('click', clickDismiss); + return () => document.removeEventListener('click', clickDismiss); + }, []); + + const broadCastTx = (psbtHex) => { let command = { - 'broadcast-tx': psbtHex - } - return this.props.api.btcWalletCommand(command) - } + 'broadcast-tx': psbtHex, + }; + return api.btcWalletCommand(command); + }; - sendBitcoin(ticket, psbt) { + const sendBitcoin = (ticket, psbt) => { const newPsbt = bitcoin.Psbt.fromBase64(psbt); - this.setState({broadcasting: true}); - kg.generateWallet({ ticket, ship: parseInt(patp2dec('~' + window.ship)) }) - .then(urbitWallet => { - const { xpub } = this.props.network === 'testnet' - ? urbitWallet.bitcoinTestnet.keys - : urbitWallet.bitcoinMainnet.keys; + setBroadcasting(true); + kg.generateWallet({ + ticket, + ship: parseInt(patp2dec('~' + window.ship)), + }).then((urbitWallet) => { + // const { xpub } = + // network === 'testnet' + // ? urbitWallet.bitcoinTestnet.keys + // : urbitWallet.bitcoinMainnet.keys; - const { xprv: zprv } = urbitWallet.bitcoinMainnet.keys; - const { xprv: vprv } = urbitWallet.bitcoinTestnet.keys; + const { xprv: zprv } = urbitWallet.bitcoinMainnet.keys; + const { xprv: vprv } = urbitWallet.bitcoinTestnet.keys; - const isTestnet = (this.props.network === 'testnet'); - const derivationPrefix = isTestnet ? "m/84'/1'/0'/" : "m/84'/0'/0'/"; + const isTestnet = network === 'testnet'; + const derivationPrefix = isTestnet ? "m/84'/1'/0'/" : "m/84'/0'/0'/"; - const btcWallet = (isTestnet) - ? bitcoin.bip32.fromBase58(vprv, BITCOIN_TESTNET_INFO) - : bitcoin.bip32.fromBase58(zprv, BITCOIN_MAINNET_INFO); + const btcWallet = isTestnet + ? bitcoin.bip32.fromBase58(vprv, BITCOIN_TESTNET_INFO) + : bitcoin.bip32.fromBase58(zprv, BITCOIN_MAINNET_INFO); - try { - const hex = newPsbt.data.inputs - .reduce((psbt, input, idx) => { - // removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0 - const path = input.bip32Derivation[0].path - .split(derivationPrefix) - .join(''); - const prv = btcWallet.derivePath(path).privateKey; - return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv)); - }, newPsbt) - .finalizeAllInputs() - .extractTransaction() - .toHex(); + try { + const hex = newPsbt.data.inputs + .reduce((psbt, input, idx) => { + // removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0 + const path = input.bip32Derivation[0].path + .split(derivationPrefix) + .join(''); + const prv = btcWallet.derivePath(path).privateKey; + return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv)); + }, newPsbt) + .finalizeAllInputs() + .extractTransaction() + .toHex(); - this.broadCastTx(hex); - } - catch(e) { - this.setState({error: 'invalid-master-ticket', broadcasting: false}); - } - }); + broadCastTx(hex); + } catch (e) { + setLocalError('invalid-master-ticket'); + setBroadcasting(false); + } + }); + }; - } - - - checkTicket(e){ + const checkTicket = (e) => { // TODO: port over bridge ticket validation logic - let masterTicket = e.target.value; - let ready = isValidPatq(masterTicket); - let error = (ready) ? '' : 'invalid-master-ticket'; - this.setState({masterTicket, ready, error}); + setMasterTicket(e.target.value); + setReady(isValidPatq(e.target.value)); + setLocalError(isValidPatq(e.target.value) ? '' : 'invalid-master-ticket'); + }; + + let inputColor = 'black'; + let inputBg = 'white'; + let inputBorder = 'lightGray'; + + if (error !== '') { + inputColor = 'red'; + inputBg = 'veryLightRed'; + inputBorder = 'red'; } - render() { - const broadcastSuccess = this.props.state.broadcastSuccess; - const { stopSending, payee, denomination, satsAmount, psbt, currencyRates, fee } = this.props; - const { sent, error } = this.state; + const isShip = isValidPatp(payee); - let inputColor = 'black'; - let inputBg = 'white'; - let inputBorder = 'lightGray'; + const icon = isShip ? ( + + ) : ( + + + + ); - if (error !== '') { - inputColor = 'red'; - inputBg = 'veryLightRed'; - inputBorder = 'red'; - } - - const isShip = isValidPatp(payee); - - const icon = (isShip) - ? - : ; - - return ( - <> - { broadcastSuccess ? - : + return ( + <> + {broadcastSuccess ? ( + + ) : ( + stopSending()} + > - - - {satsToCurrency(satsAmount, denomination, currencyRates)} - - - {`${satsAmount} sats`} - - - {`Fee: ${satsToCurrency(fee, denomination, currencyRates)} (${fee} sats)`} - - - You are paying - - - {icon} - {payee} - - - - - Ticket + + + {satsToCurrency(satsAmount, denomination, currencyRates)} - value.replace(/[^~-]+/g, '••••••')} - placeholder="••••••-••••••-••••••-••••••" - autoCapitalize="none" - autoCorrect="off" - color={inputColor} - backgroundColor={inputBg} - borderColor={inputBorder} - onChange={this.checkTicket} - /> - {(error !== '') && - - - - } - - + {broadcasting ? : null} + + + )} + + ); +}; + +export default Invoice; diff --git a/pkg/btc-wallet/src/js/components/lib/providerModal.js b/pkg/btc-wallet/src/js/components/lib/providerModal.js index 9b85b449fd..ddb7dc58dd 100644 --- a/pkg/btc-wallet/src/js/components/lib/providerModal.js +++ b/pkg/btc-wallet/src/js/components/lib/providerModal.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useEffect, useState } from 'react'; import { Box, Text, @@ -8,159 +8,157 @@ import { Row, LoadingSpinner, } from '@tlon/indigo-react'; - import { isValidPatp } from 'urbit-ob'; +import { api } from '../../api'; +import { useSettings } from '../../hooks/useSettings'; -export default class ProviderModal extends Component { - constructor(props) { - super(props); +const providerStatuses = { + checking: 'checking', + failed: 'failed', + ready: 'ready', + initial: '', +}; - this.state = { - potentialProvider: null, - checkingProvider: false, - providerFailed: false, - ready: false, - provider: null, - connecting: false, - }; +const ProviderModal = () => { + const { providerPerms } = useSettings(); + const [providerStatus, setProviderStatus] = useState( + providerStatuses.initial + ); + const [potentialProvider, setPotentialProvider] = useState(null); + const [provider, setProvider] = useState(null); + const [connecting, setConnecting] = useState(false); - this.checkProvider = this.checkProvider.bind(this); - this.submitProvider = this.submitProvider.bind(this); - } - - checkProvider(e) { + const checkProvider = (e) => { // TODO: loading states - let provider = e.target.value; - let ready = false; - let checkingProvider = false; - let potentialProvider = this.state.potentialProvider; - - if (isValidPatp(provider)) { + setProviderStatus(providerStatuses.initial); + let givenProvider = e.target.value; + if (isValidPatp(givenProvider)) { let command = { - 'check-provider': provider, + 'check-provider': givenProvider, }; - potentialProvider = provider; - checkingProvider = true; - this.props.api.btcWalletCommand(command); + setPotentialProvider(givenProvider); + setProviderStatus(providerStatuses.checking); + api.btcWalletCommand(command); setTimeout(() => { - this.setState({ providerFailed: true, checkingProvider: false }); + setProviderStatus(providerStatuses.failed); }, 5000); } - this.setState({ provider, ready, checkingProvider, potentialProvider }); - } + setProvider(givenProvider); + }; - componentDidUpdate() { - if (!this.state.ready) { - if (this.props.providerPerms[this.state.provider]) { - this.setState({ - ready: true, - checkingProvider: false, - providerFailed: false, - }); + const submitProvider = () => { + if (providerStatus === providerStatuses.ready) { + let command = { + 'set-provider': provider, + }; + api.btcWalletCommand(command); + setConnecting(true); + } + }; + + useEffect(() => { + if (providerStatus !== providerStatuses.ready) { + if (providerPerms.provider === provider && providerPerms.permitted) { + setProviderStatus(providerStatuses.ready); } } - } + }, [providerStatus, providerPerms, provider, setProviderStatus]); - submitProvider() { - if (this.state.ready) { - let command = { - 'set-provider': this.state.provider, - }; - this.props.api.btcWalletCommand(command); - this.setState({ connecting: true }); - } - } - - render() { - let workingNode = null; - let workingColor = null; - let workingBg = null; - if (this.state.ready) { - workingColor = 'green'; - workingBg = 'veryLightGreen'; - workingNode = ( - - - {this.state.provider} is a working provider node - - - ); - } else if (this.state.providerFailed) { - workingColor = 'red'; - workingBg = 'veryLightRed'; - workingNode = ( - - - {this.state.potentialProvider} is not a working provider node - - - ); - } - - return ( - - - - - Step 1 of 2: Set up Bitcoin Provider Node - - - - - 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. - - {' '} - Learn More - - - - - - Provider Node - - - - - {this.state.checkingProvider ? : null} - - {workingNode} - - - {this.state.connecting ? : null} - + let workingNode = null; + let workingColor = null; + let workingBg = null; + if (providerStatus === providerStatuses.ready) { + workingColor = 'green'; + workingBg = 'veryLightGreen'; + workingNode = ( + + + {provider} is a working provider node + + + ); + } else if (providerStatus === providerStatuses.failed) { + workingColor = 'red'; + workingBg = 'veryLightRed'; + workingNode = ( + + + {potentialProvider} is not a working provider node + ); } -} + + return ( + + + + + Step 1 of 2: Set up Bitcoin Provider Node + + + + + 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. + + {' '} + Learn More + + + + + + Provider Node + + + + checkProvider(e)} + /> + {providerStatus === providerStatuses.checking ? ( + + ) : null} + + {workingNode} + + + {connecting ? : null} + + + ); +}; + +export default ProviderModal; diff --git a/pkg/btc-wallet/src/js/components/lib/send.js b/pkg/btc-wallet/src/js/components/lib/send.js index df5eef8f5c..9bb6cbbd8a 100644 --- a/pkg/btc-wallet/src/js/components/lib/send.js +++ b/pkg/btc-wallet/src/js/components/lib/send.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useEffect, useState } from 'react'; import { Box, Icon, @@ -8,450 +8,443 @@ import { Button, Col, LoadingSpinner, - StatelessRadioButtonField as RadioButton, } from '@tlon/indigo-react'; - -import Invoice from './invoice.js' -import BridgeInvoice from './bridgeInvoice.js' -import FeePicker from './feePicker.js' -import Error from './error.js' -import Signer from './signer.js' - +import Invoice from './invoice.js'; +import BridgeInvoice from './bridgeInvoice.js'; +import FeePicker from './feePicker.js'; +import Error from './error.js'; +import Signer from './signer.js'; import { validate } from 'bitcoin-address-validation'; - import * as ob from 'urbit-ob'; +import { useSettings } from '../../hooks/useSettings.js'; +import { api } from '../../api'; -export default class Send extends Component { - constructor(props) { - super(props); +const focusFields = { + empty: '', + payee: 'payee', + currency: 'currency', + sats: 'sats', + note: 'note', +}; - this.state = { - signing: false, - denomAmount: '0.00', - satsAmount: '0', - payee: '', - checkingPatp: false, - payeeType: '', - ready: false, - validPayee: false, - focusPayee: true, - focusCurrency: false, - focusSats: false, - focusNote: false, - submitting: false, - feeChoices: { - low: [10, 1], - mid: [10, 1], - high: [10, 1], - }, - feeValue: "mid", - showModal: false, - note: '', - choosingSignMethod: false, - signMethod: 'bridge', - }; +const Send = ({ stopSending, value, conversion }) => { + const { error, setError, network, psbt, denomination, shipWallets } = + useSettings(); + const [signing, setSigning] = useState(false); + const [denomAmount, setDenomAmount] = useState('0.00'); + const [satsAmount, setSatsAmount] = useState('0'); + const [payee, setPayee] = useState(''); + const [checkingPatp, setCheckingPatp] = useState(false); + const [payeeType, setPayeeType] = useState(''); + const [ready, setReady] = useState(false); + const [validPayee, setValidPayee] = useState(false); + const [focusedField, setFocusedField] = useState(focusFields.empty); + const [feeChoices, setFeeChoices] = useState({ + low: [10, 1], + mid: [10, 1], + high: [10, 1], + }); + const [feeValue, setFeeValue] = useState('mid'); + const [showModal, setShowModal] = useState(false); + const [note, setNote] = useState(''); + const [choosingSignMethod, setChoosingSignMethod] = useState(false); + const [signMethod, setSignMethod] = useState('bridge'); - this.initPayment = this.initPayment.bind(this); - this.checkPayee = this.checkPayee.bind(this); - this.feeSelect = this.feeSelect.bind(this); - this.feeDismiss = this.feeDismiss.bind(this); - this.toggleSignMethod = this.toggleSignMethod.bind(this); - this.setSignMethod = this.setSignMethod.bind(this); - } + const feeDismiss = () => { + setShowModal(false); + }; - feeDismiss() { - this.setState({showModal: false}); - } + const feeSelect = (which) => { + setFeeValue(which); + }; - feeSelect(which) { - this.setState({feeValue: which}); - } - - 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 handleSetSignMethod = (signMethod) => { + setSignMethod(signMethod); + setChoosingSignMethod(false); + }; + 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) { - let command = {'check-payee': payee} - this.props.api.btcWalletCommand(command) + console.log('isPatp', isPatp); + let command = { 'check-payee': payeeReceived }; + api.btcWalletCommand(command); setTimeout(() => { - this.setState({checkingPatp: false}); + setCheckingPatp(false); }, 5000); - this.setState({ - checkingPatp: true, - payeeType: 'ship', - payee, - }); + setCheckingPatp(true); + setPayeeType('ship'); + setPayee(payeeReceived); } else if (isAddress) { - this.setState({ - payee, - ready: true, - checkingPatp: false, - payeeType: 'address', - validPayee: true, - }); + setPayee(payeeReceived); + setReady(true); + setCheckingPatp(false); + setPayeeType('address'); + setValidPayee(true); } else { - this.setState({ - payee, - ready: false, - checkingPatp: false, - payeeType: '', - validPayee: false, - }); + setPayee(payeeReceived); + setReady(false); + setCheckingPatp(false); + setPayeeType(''); + setValidPayee(false); } - } + }; - componentDidUpdate(prevProps, prevState) { - if ((prevProps.error !== this.props.error) && - (this.props.error !== '') && (this.props.error !== 'broadcast-fail')) { - this.setState({signing: false}); - } + const toggleSignMethod = () => { + setChoosingSignMethod(!choosingSignMethod); + }; - if (!this.state.ready && this.state.checkingPatp) { - if (this.props.shipWallets[this.state.payee.slice(1)]) { - this.setState({ready: true, checkingPatp: false, validPayee: true}); - } - } - } - - toggleSignMethod(toggle) { - this.setState({choosingSignMethod: !toggle}); - } - - initPayment() { - if (this.state.payeeType === 'ship') { + const initPayment = () => { + if (payeeType === 'ship') { let command = { 'init-payment': { - 'payee': this.state.payee, - 'value': parseInt(this.state.satsAmount), - 'feyb': this.state.feeChoices[this.state.feeValue][1], - 'note': (this.state.note || null), - } - } - this.props.api.btcWalletCommand(command).then(res => this.setState({signing: true})); - } else if (this.state.payeeType === 'address') { + payee, + value: parseInt(satsAmount), + feyb: feeChoices[feeValue][1], + note: note || null, + }, + }; + + api.btcWalletCommand(command).then(() => setSigning(true)); + } else if (payeeType === 'address') { let command = { 'init-payment-external': { - 'address': this.state.payee, - 'value': parseInt(this.state.satsAmount), - 'feyb': 1, - 'note': (this.state.note || null), - } - } - this.props.api.btcWalletCommand(command).then(res => this.setState({signing: true})); + address: payee, + value: parseInt(satsAmount), + feyb: 1, + note: note || null, + }, + }; + 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() { - 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"; - } + const signReady = ready && parseInt(satsAmount) > 0 && !signing; - - const { api, value, conversion, stopSending, denomination, psbt, currencyRates, error, network, fee } = this.props; - const { denomAmount, satsAmount, signing, payee, choosingSignMethod, signMethod } = this.state; - - const signReady = (this.state.ready && (parseInt(this.state.satsAmount) > 0)) && !signing; - - let invoice = null; - if (signMethod === 'masterTicket') { - invoice = - - } else if (signMethod === 'bridge') { - invoice = - - } - - return ( - <> - { (signing && psbt) ? invoice : - - - - Send BTC - {value} - stopSending()} - /> - - - - To - {this.state.checkingPatp ? - : null - } - - {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} - /> - - {error && - - {/* yes this is a hack */} - - - - } - - Amount - {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) - }); - }} - /> - {denomination} - - - {/* yes this is a hack */} - - {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 - }); - }} - /> - sats - - - Fee - - - {this.state.feeChoices[this.state.feeValue][1]} sats/vbyte - - {if (!this.state.showModal) this.setState({showModal: true}); }} - cursor="pointer"/> - - - - {!this.state.showModal ? null : - - } - - - Note - {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, - }); - }} - /> - - - - - { (!(signing && !error)) ? null : - - } - + + {signMethod === 'masterTicket' && ( + + + + We recommend that you sign transactions using Bridge to protect + your master ticket. + + + )} + + )} + + ); +}; + +export default Send; diff --git a/pkg/btc-wallet/src/js/components/lib/sent.js b/pkg/btc-wallet/src/js/components/lib/sent.js index 994b02b97f..be4a8e1093 100644 --- a/pkg/btc-wallet/src/js/components/lib/sent.js +++ b/pkg/btc-wallet/src/js/components/lib/sent.js @@ -1,59 +1,36 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - StatelessTextInput as Input, - Row, - Center, - Text, - Button, - Col, -} from '@tlon/indigo-react'; - +import React from 'react'; +import { Icon, Row, Col, Center, Text } from '@tlon/indigo-react'; import { satsToCurrency } from '../../lib/util.js'; +import { useSettings } from '../../hooks/useSettings'; -export default function Sent(props) { - const { payee, denomination, satsAmount, stopSending, currencyRates } = props; +const Sent = ({ payee, stopSending, satsAmount }) => { + const { denomination, currencyRates } = useSettings(); return ( - - + +
{`You sent BTC to ${payee}`} + style={{ display: 'block', 'overflow-wrap': 'anywhere' }} + color="white" + >{`You sent BTC to ${payee}`}
-
- +
+ {satsToCurrency(satsAmount, denomination, currencyRates)} - - {`${satsAmount} sats`} - + {`${satsAmount} sats`}
); -} +}; + +export default Sent; diff --git a/pkg/btc-wallet/src/js/components/lib/settings.js b/pkg/btc-wallet/src/js/components/lib/settings.js index 445e527504..352cb5500f 100644 --- a/pkg/btc-wallet/src/js/components/lib/settings.js +++ b/pkg/btc-wallet/src/js/components/lib/settings.js @@ -1,121 +1,114 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - Row, - Text, - Button, - Col, -} from '@tlon/indigo-react'; +import React from 'react'; +import { Row, Text, Button, Col } from '@tlon/indigo-react'; +import { useSettings } from '../../hooks/useSettings'; +import { api } from '../../api'; -export default class Settings extends Component { - constructor(props) { - super(props); - this.changeProvider = this.changeProvider.bind(this); - this.replaceWallet = this.replaceWallet.bind(this); - } +const Settings = () => { + const { wallet, provider } = useSettings(); - changeProvider(){ - this.props.api.btcWalletCommand({'set-provider': null}); - } + const changeProvider = () => { + api.btcWalletCommand({ 'set-provider': null }); + }; - replaceWallet(){ - this.props.api.btcWalletCommand({ - 'delete-wallet': this.props.state.wallet, + const replaceWallet = () => { + api.btcWalletCommand({ + 'delete-wallet': wallet, }); - } + }; - render() { - let connColor = "red"; - let connBackground = "veryLightRed"; - let conn = 'Offline' - let host = ''; - if (this.props.state.provider){ - if (this.props.state.provider.connected) conn = 'Connected'; - if (this.props.state.provider.host) host = this.props.state.provider.host; - if (this.props.state.provider.connected && this.props.state.provider.host) { - connColor = "orange"; - connBackground = "lightOrange"; - } + let connColor = 'red'; + let connBackground = 'veryLightRed'; + let conn = 'Offline'; + let host = ''; + if (provider) { + if (provider.connected) conn = 'Connected'; + if (provider.host) host = provider.host; + if (provider.connected && provider.host) { + connColor = 'orange'; + connBackground = 'lightOrange'; } - - return ( - - - - XPub Derivation - - - - - {this.props.state.wallet} - - - - + + + + BTC Node Provider + + + + + ~{host} + + + {conn} + + + + + + + ); +}; + +export default Settings; diff --git a/pkg/btc-wallet/src/js/components/lib/signer.js b/pkg/btc-wallet/src/js/components/lib/signer.js index 094a6e8dd4..df371f76f5 100644 --- a/pkg/btc-wallet/src/js/components/lib/signer.js +++ b/pkg/btc-wallet/src/js/components/lib/signer.js @@ -1,52 +1,55 @@ -import React, { Component } from 'react'; +import React from 'react'; +import { Box, Button } from '@tlon/indigo-react'; -import { - Box, - Button, -} from '@tlon/indigo-react'; - -export default function Signer(props) { - const { signReady, initPayment, choosingSignMethod, signMethod, setSignMethod } = props; - - return ( - choosingSignMethod ? - +const Signer = ({ + signReady, + initPayment, + choosingSignMethod, + signMethod, + setSignMethod, +}) => { + return choosingSignMethod ? ( + - : + ) : ( + ); +}; + +export default Signer; diff --git a/pkg/btc-wallet/src/js/components/lib/startupModal.js b/pkg/btc-wallet/src/js/components/lib/startupModal.js index 9507452ebc..c4ee50c0a7 100644 --- a/pkg/btc-wallet/src/js/components/lib/startupModal.js +++ b/pkg/btc-wallet/src/js/components/lib/startupModal.js @@ -1,52 +1,44 @@ -import React, { Component } from 'react'; +import React from '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' -import ProviderModal from './providerModal.js' +const StartupModal = () => { + const { wallet, provider } = useSettings(); + let modal = null; - -export default class StartupModal extends Component { - constructor(props) { - super(props); + if (wallet && provider) { + return null; + } else if (!provider) { + modal = ; + } else if (!wallet) { + modal = ; } - - - render() { - let modal = null; - - if (this.props.state.wallet && this.props.state.provider) { - return null; - } else if (!this.props.state.provider){ - modal = - - } else if (!this.props.state.wallet){ - modal = - } - return ( + return ( + - - {modal} - + {modal} - ); - } -} + + ); +}; + +export default StartupModal; diff --git a/pkg/btc-wallet/src/js/components/lib/transaction.js b/pkg/btc-wallet/src/js/components/lib/transaction.js index ba18a32aca..10b9a50951 100644 --- a/pkg/btc-wallet/src/js/components/lib/transaction.js +++ b/pkg/btc-wallet/src/js/components/lib/transaction.js @@ -1,105 +1,96 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - Row, - Text, - Button, - Col, - LoadingSpinner, -} from '@tlon/indigo-react'; +import React from 'react'; +import { Box, Row, Text, Col } from '@tlon/indigo-react'; 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' -import TxAction from './tx-action.js' -import TxCounterparty from './tx-counterparty.js' -import { satsToCurrency } from '../../lib/util.js' +const Transaction = ({ tx }) => { + const { denomination, currencyRates } = useSettings(); + const pending = !tx.recvd; -export default class Transaction extends Component { - constructor(props) { - super(props); - } + let weSent = _.find(tx.inputs, (input) => { + return input.ship === window.ship; + }); + let weRecv = tx.outputs.every((output) => { + return output.ship === window.ship; + }); - render() { - const pending = (!this.props.tx.recvd); + let action = weRecv ? 'recv' : weSent ? 'sent' : 'recv'; - let weSent = _.find(this.props.tx.inputs, (input) => { - return (input.ship === window.ship); + let counterShip = null; + 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) => { - return (output.ship === window.ship) - }); - - let action = - (weRecv) ? "recv" : - (weSent) ? "sent" : "recv"; - - 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) => { + counterShip = _.get(counter, 'ship', null); + counterAddress = _.get(counter, 'val.address', null); + value = _.get(counter, 'val.value', null); + sign = '-'; + } else if (action === 'recv') { + value = _.reduce( + tx.outputs, + (sum, output) => { if (output.ship === window.ship) { return sum + output.val.value; } else { return sum; } - }, 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 ( - - - - - {sign}{value} sats - - - - - - - {currencyValue} - - + }, + 0 ); + + 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 ( + + + + + {sign} + {value} sats + + + + + + {currencyValue} + + + ); +}; + +export default Transaction; diff --git a/pkg/btc-wallet/src/js/components/lib/transactions.js b/pkg/btc-wallet/src/js/components/lib/transactions.js index a038cd0296..2bd7619956 100644 --- a/pkg/btc-wallet/src/js/components/lib/transactions.js +++ b/pkg/btc-wallet/src/js/components/lib/transactions.js @@ -1,62 +1,43 @@ import React, { Component } from 'react'; -import { - Box, - Icon, - Row, - Text, - Button, - Col, -} from '@tlon/indigo-react'; - +import { Box, Text, Col } from '@tlon/indigo-react'; import Transaction from './transaction.js'; +import { useSettings } from '../../hooks/useSettings.js'; - -export default class Transactions extends Component { - constructor(props) { - super(props); +const Transactions = () => { + const { history } = useSettings(); + if (!history || history.length <= 0) { + return ( + + + No Transactions Yet + + + ); + } else { + return ( + + {history.map((tx, i) => { + return ; + })} + + ); } +}; - - render() { - if (!this.props.state.history || this.props.state.history.length <= 0) { - return ( - - No Transactions Yet - - ); - } else { - return ( - - { - this.props.state.history.map((tx, i) => { - return( - - ); - }) - } - - ); - } - } -} +export default Transactions; diff --git a/pkg/btc-wallet/src/js/components/lib/tx-action.js b/pkg/btc-wallet/src/js/components/lib/tx-action.js index a8434f5155..12405f4934 100644 --- a/pkg/btc-wallet/src/js/components/lib/tx-action.js +++ b/pkg/btc-wallet/src/js/components/lib/tx-action.js @@ -1,74 +1,72 @@ -import React, { Component } from 'react'; +import React from 'react'; import { Box, Icon, Row, Text, LoadingSpinner } from '@tlon/indigo-react'; +import { useSettings } from '../../hooks/useSettings'; -export default class TxAction extends Component { - constructor(props) { - super(props); - } +const TxAction = ({ action, pending, txid }) => { + const { network } = useSettings(); + const leftIcon = + action === 'sent' + ? 'ArrowSouth' + : action === 'recv' + ? 'ArrowNorth' + : action === 'fail' + ? 'X' + : 'NullIcon'; - render() { - const leftIcon = - this.props.action === 'sent' - ? 'ArrowSouth' - : this.props.action === 'recv' - ? 'ArrowNorth' - : this.props.action === 'fail' - ? 'X' - : 'NullIcon'; + const actionColor = + action === 'sent' + ? 'sentBlue' + : action === 'recv' + ? 'recvGreen' + : action === 'fail' + ? 'gray' + : 'red'; - const actionColor = - this.props.action === 'sent' - ? 'sentBlue' - : this.props.action === 'recv' - ? 'recvGreen' - : this.props.action === 'fail' - ? 'gray' - : 'red'; + const actionText = + action === 'sent' && !pending + ? 'Sent BTC' + : action === 'sent' && pending + ? 'Sending BTC' + : action === 'recv' && !pending + ? 'Received BTC' + : action === 'recv' && pending + ? 'Receiving BTC' + : action === 'fail' + ? 'Failed' + : 'error'; - const actionText = - this.props.action === 'sent' && !this.props.pending - ? '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 pendingSpinner = !pending ? null : ( + + ); - const pending = !this.props.pending ? null : ( - - ); + const url = + network === 'testnet' + ? `http://blockstream.info/testnet/tx/${txid}` + : `http://blockstream.info/tx/${txid}`; - const url = - this.props.network === 'testnet' - ? `http://blockstream.info/testnet/tx/${this.props.txid}` - : `http://blockstream.info/tx/${this.props.txid}`; + return ( + + + + + + {actionText} + + + + + {pendingSpinner} + + ); +}; - return ( - - - - - - {actionText} - - - - - {pending} - - ); - } -} +export default TxAction; diff --git a/pkg/btc-wallet/src/js/components/lib/tx-counterparty.js b/pkg/btc-wallet/src/js/components/lib/tx-counterparty.js index 27bf40d573..76cf2fd286 100644 --- a/pkg/btc-wallet/src/js/components/lib/tx-counterparty.js +++ b/pkg/btc-wallet/src/js/components/lib/tx-counterparty.js @@ -1,53 +1,36 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - Row, - Text, - Button, - Col, -} from '@tlon/indigo-react'; +import React from 'react'; +import { Box, Icon, Row, Text } from '@tlon/indigo-react'; +import { Sigil } from './sigil.js'; -import { Sigil } from './sigil.js' -import TxAction from './tx-action.js' +const TxCounterparty = ({ ship, address }) => { + const icon = ship ? ( + + ) : ( + + + + ); + const addressText = !address + ? '' + : address.slice(0, 6) + '...' + address.slice(-6); + const text = ship ? `~${ship}` : addressText; -export default class TxCounterparty extends Component { - constructor(props) { - super(props); - } + return ( + + {icon} + + {text} + + + ); +}; - - render() { - const icon = (this.props.ship) - ? - : - - - 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 ( - - {icon} - {text} - - ); - } -} +export default TxCounterparty; diff --git a/pkg/btc-wallet/src/js/components/lib/walletModal.js b/pkg/btc-wallet/src/js/components/lib/walletModal.js index 49796cd11e..a34a596c03 100644 --- a/pkg/btc-wallet/src/js/components/lib/walletModal.js +++ b/pkg/btc-wallet/src/js/components/lib/walletModal.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useState } from 'react'; import { Box, Text, @@ -6,247 +6,251 @@ import { StatelessTextInput, Icon, Row, - Input, LoadingSpinner, } from '@tlon/indigo-react'; - 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 bitcoin = require('bitcoinjs-lib'); -const bs58check = require('bs58check') -import { Buffer } from 'buffer'; +const WalletModal = () => { + const { network } = useSettings(); + const [mode, setMode] = useState('xpub'); + 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 { - constructor(props) { - super(props); - - 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){ + const checkTicket = ({ + event: { + target: { value }, + }, + }) => { // TODO: port over bridge ticket validation logic - if (this.state.confirmingMasterTicket) { - let confirmedMasterTicket = e.target.value; - let readyToSubmit = isValidPatq(confirmedMasterTicket); - this.setState({confirmedMasterTicket, readyToSubmit}); + if (confirmingMasterTicket) { + setConfirmedMasterTicket(value); + setReadyToSubmit(isValidPatq(value)); } else { - let masterTicket = e.target.value; - let readyToSubmit = isValidPatq(masterTicket); - this.setState({masterTicket, readyToSubmit}); + setMasterTicket(value); + setReadyToSubmit(isValidPatq(value)); } - } + }; - checkXPub(e){ - let xpub = e.target.value; - let readyToSubmit = (xpub.length > 0); - this.setState({xpub, readyToSubmit}); - } + const checkXPub = ({ + event: { + target: { value: xpubGiven }, + }, + }) => { + setXpub(xpubGiven); + setReadyToSubmit(xpubGiven.length > 0); + }; - submitMasterTicket(ticket){ - 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 submitXPub = (givenXpub) => { const command = { - "add-wallet": { - "xpub": xpub, - "fprint": [4, 0], - "scan-to": null, - "max-gap": 8, - "confs": 1 - } - } + 'add-wallet': { + xpub: givenXpub, + fprint: [4, 0], + 'scan-to': null, + 'max-gap': 8, + confs: 1, + }, + }; api.btcWalletCommand(command); - this.setState({processingSubmission: true}); - } + setProcessingSubmission(true); + }; - render() { - const buttonDisabled = (!this.state.readyToSubmit || this.state.processingSubmission ); - const inputDisabled = this.state.processingSubmission; - const processingSpinner = (!this.state.processingSubmission) ? null : - + const submitMasterTicket = (ticket) => { + setProcessingSubmission(true); + kg.generateWallet({ + ticket, + ship: parseInt(patp2dec('~' + window.ship)), + }).then((urbitWallet) => { + const { xpub: xpubFromWallet } = + network === 'testnet' + ? urbitWallet.bitcoinTestnet.keys + : urbitWallet.bitcoinMainnet.keys; - if (this.state.mode === 'masterTicket'){ - return ( - - - - - Step 2 of 2: Import your extended public key - - - - - - We recommend that you import your wallet using Bridge to protect your master ticket. - - - - {this.state.confirmingMasterTicket && - this.setState({ - confirmingMasterTicket: false, - masterTicket: '', - confirmedMasterTicket: '', - error: false - })}/>} - - {this.state.confirmingMasterTicket ? 'Confirm Master Ticket' : 'Master Ticket'} - - - - value.replace(/[^~-]+/g, '••••••')} - placeholder="••••••-••••••-••••••-••••••" - autoCapitalize="none" - autoCorrect="off" - onChange={this.checkTicket} - /> - {(!inputDisabled) ? null : } - - {this.state.error && - - - Master tickets do not match - - - } - - + + + + ); + } else if (mode === 'xpub') { + return ( + + + + + Step 2 of 2: Import your extended public key + + + + + Visit{' '} + + bridge.urbit.org + {' '} + to obtain your key + - ); - } + + + Extended Public Key (XPub) + + + checkXPub(e)} + /> + + { + if (inputDisabled) return; + setMode('masterTicket'); + setXpub(''); + setMasterTicket(''); + setReadyToSubmit(false); + }} + > + Import using master ticket -> + + + + + ); } -} +}; + +export default WalletModal; diff --git a/pkg/btc-wallet/src/js/components/lib/warning.js b/pkg/btc-wallet/src/js/components/lib/warning.js index 0feaf75ada..d0183b7e50 100644 --- a/pkg/btc-wallet/src/js/components/lib/warning.js +++ b/pkg/btc-wallet/src/js/components/lib/warning.js @@ -1,79 +1,72 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - Row, - Text, - Button, - Col, - Anchor, -} from '@tlon/indigo-react'; +import React from 'react'; +import { Box, Text, Button, Col, Anchor } from '@tlon/indigo-react'; +import { api } from '../../api'; +import { useSettings } from '../../hooks/useSettings'; -import { store } from '../../store' - - - -export default class Warning extends Component { - constructor(props) { - super(props); - - this.understand = this.understand.bind(this); - } - - understand(){ - store.handleEvent({ data: { bucket: { warning: false}}}); +const Warning = () => { + const { setWarning } = useSettings(); + const understand = () => { + setWarning(false); let removeWarning = { - "put-entry": { + 'put-entry': { value: false, - "entry-key": "warning", - "bucket-key": "btc-wallet", - } - } - this.props.api.settingsEvent(removeWarning); - } + 'entry-key': 'warning', + 'bucket-key': 'btc-wallet', + }, + }; + api.settingsEvent(removeWarning); + }; - render() { - return ( - + + + Warning! + +
+ + Be safe while using this wallet, and be sure to store responsible + amounts of BTC. + + + Always ensure that the checksum of the wallet matches that of the + wallet's repo. + +
+ + + Learn more on urbit.org + + + + +
+ ); +}; + +export default Warning; diff --git a/pkg/btc-wallet/src/js/components/root.js b/pkg/btc-wallet/src/js/components/root.js index 082ffe68b4..e3b6288cf0 100644 --- a/pkg/btc-wallet/src/js/components/root.js +++ b/pkg/btc-wallet/src/js/components/root.js @@ -1,67 +1,39 @@ -import React, { Component } from 'react'; +import React from 'react'; import { BrowserRouter } from 'react-router-dom'; -import { api } from '../api.js'; -import { store } from '../store.js'; import { ThemeProvider } from 'styled-components'; import light from './themes/light'; -// import dark from './themes/dark'; import { Box, Reset } from '@tlon/indigo-react'; import StartupModal from './lib/startupModal.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 { - constructor(props) { - super(props); - this.ship = window.ship; - this.state = store.state; - store.setStateHandler(this.setState.bind(this)); - } + return ( + + + + {loaded ? : null} + + + + + + ); +}; - componentDidMount() { - 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 ( - - - - {loaded ? ( - - ) : null} - - - - - - ); - } -} +export default Root; diff --git a/pkg/btc-wallet/src/js/lib/util.js b/pkg/btc-wallet/src/js/lib/util.js index 2bc6f7a404..131ec4012c 100644 --- a/pkg/btc-wallet/src/js/lib/util.js +++ b/pkg/btc-wallet/src/js/lib/util.js @@ -1,21 +1,17 @@ -import _ from 'lodash'; -import classnames from 'classnames'; - - export function uuid() { - let str = "0v" - str += Math.ceil(Math.random()*8)+"." + let str = '0v'; + str += Math.ceil(Math.random() * 8) + '.'; for (var i = 0; i < 5; i++) { - let _str = Math.ceil(Math.random()*10000000).toString(32); - _str = ("00000"+_str).substr(-5,5); - str += _str+"."; + let _str = Math.ceil(Math.random() * 10000000).toString(32); + _str = ('00000' + _str).substr(-5, 5); + str += _str + '.'; } - return str.slice(0,-1); + return str.slice(0, -1); } export function isPatTa(str) { - const r = /^[a-z,0-9,\-,\.,_,~]+$/.exec(str) + const r = /^[a-z,0-9,\-,.,_,~]+$/.exec(str); return !!r; } @@ -26,13 +22,15 @@ export function isPatTa(str) { (javascript Date object) */ export function daToDate(st) { - var dub = function(n) { - return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString(); + var dub = function (n) { + return parseInt(n) < 10 ? '0' + parseInt(n) : n.toString(); }; var da = st.split('..'); var bigEnd = da[0].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); } @@ -44,18 +42,18 @@ export function daToDate(st) { */ export function dateToDa(d, mil) { -  var fil = function(n) { -    return n >= 10 ? n : "0" + n; -  }; -  return ( -    `~${d.getUTCFullYear()}.` + -    `${(d.getUTCMonth() + 1)}.` + -    `${fil(d.getUTCDate())}..` + -    `${fil(d.getUTCHours())}.` + -    `${fil(d.getUTCMinutes())}.` + -    `${fil(d.getUTCSeconds())}` + - `${mil ? "..0000" : ""}` -  ); + var fil = function (n) { + return n >= 10 ? n : '0' + n; + }; + return ( + `~${d.getUTCFullYear()}.` + + `${d.getUTCMonth() + 1}.` + + `${fil(d.getUTCDate())}..` + + `${fil(d.getUTCHours())}.` + + `${fil(d.getUTCMinutes())}.` + + `${fil(d.getUTCSeconds())}` + + `${mil ? '..0000' : ''}` + ); } export function deSig(ship) { @@ -64,49 +62,59 @@ export function deSig(ship) { // trim patps to match dojo, chat-cli export function cite(ship) { - let patp = ship, shortened = ""; - if (patp.startsWith("~")) { + let patp = ship, + shortened = ''; + if (patp.startsWith('~')) { patp = patp.substr(1); } // comet if (patp.length === 56) { - shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 56); + shortened = '~' + patp.slice(0, 6) + '_' + patp.slice(50, 56); return shortened; } // moon 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 `~${patp}`; } -export function satsToCurrency(sats, denomination, rates){ +export function satsToCurrency(sats, denomination, rates) { if (!rates) { - throw "nonexistent currency table" + throw 'nonexistent currency table'; } - if (!rates[denomination]){ - denomination = "BTC"; + if (!rates[denomination]) { + denomination = 'BTC'; } 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; - if (denomination === 'BTC'){ - text = val + ' ' + rate.symbol - } else { - text = rate.symbol + val.toFixed(2).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,') + if (denomination === 'BTC' && rate) { + text = val + ' ' + rate.symbol; + } else if (rate) { + 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) { - throw "nonexistent currency table" + throw 'nonexistent currency table'; } - if (!rates[denomination]){ - throw 'currency not in table' + if (!rates[denomination]) { + throw 'currency not in table'; } let rate = rates[denomination]; let sats = (parseFloat(val) / rate.last) * 100000000; 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; + }); +} diff --git a/pkg/btc-wallet/src/js/reducers/currency.js b/pkg/btc-wallet/src/js/reducers/currency.js deleted file mode 100644 index 80470bce42..0000000000 --- a/pkg/btc-wallet/src/js/reducers/currency.js +++ /dev/null @@ -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 - } - } - } -} diff --git a/pkg/btc-wallet/src/js/reducers/initial.js b/pkg/btc-wallet/src/js/reducers/initial.js deleted file mode 100644 index 2445eae6b8..0000000000 --- a/pkg/btc-wallet/src/js/reducers/initial.js +++ /dev/null @@ -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) - }) - } -} diff --git a/pkg/btc-wallet/src/js/reducers/settings.js b/pkg/btc-wallet/src/js/reducers/settings.js deleted file mode 100644 index dce62613bb..0000000000 --- a/pkg/btc-wallet/src/js/reducers/settings.js +++ /dev/null @@ -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; - } - } -} diff --git a/pkg/btc-wallet/src/js/reducers/update.js b/pkg/btc-wallet/src/js/reducers/update.js deleted file mode 100644 index b47ced0b5d..0000000000 --- a/pkg/btc-wallet/src/js/reducers/update.js +++ /dev/null @@ -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; - } -} diff --git a/pkg/btc-wallet/src/js/store.js b/pkg/btc-wallet/src/js/store.js deleted file mode 100644 index 40cd3270fe..0000000000 --- a/pkg/btc-wallet/src/js/store.js +++ /dev/null @@ -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;