From 69c123c22e28e56c81ba252feabfff8ac5b80609 Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Wed, 30 Jun 2021 16:16:02 -0500 Subject: [PATCH 01/19] Add hooks --- pkg/btc-wallet/src/js/hooks/useSettings.js | 301 +++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 pkg/btc-wallet/src/js/hooks/useSettings.js diff --git a/pkg/btc-wallet/src/js/hooks/useSettings.js b/pkg/btc-wallet/src/js/hooks/useSettings.js new file mode 100644 index 0000000000..5506583ba5 --- /dev/null +++ b/pkg/btc-wallet/src/js/hooks/useSettings.js @@ -0,0 +1,301 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import _ from 'lodash'; +import { api } from '../api'; +import { reduceHistory } from '../lib/util'; + +export const SettingsContext = createContext({ + network: 'bitcoin', + loadedBtc: false, + setLoadedBtc: () => {}, + loadedSettings: false, + setLoadedSettings: () => {}, + loaded: false, + setLoaded: () => {}, + providerPerms: {}, + setProviderPerms: () => {}, + shipWallets: {}, + setShipWallets: () => {}, + provider: null, + setProvider: () => {}, + wallet: null, + setWallet: () => {}, + confirmedBalance: 0, + setConfirmedBalance: () => {}, + unconfirmedBalance: 0, + setUnconfirmedBalance: () => {}, + btcState: null, + setBtcState: () => {}, + history: [], + setHistory: () => {}, + psbt: '', + setPsbt: () => {}, + address: null, + setAddress: () => {}, + currencyRates: { + BTC: { last: 1, symbol: 'BTC' }, + }, + denomination: 'BTC', + showWarning: true, + setShowWarning: () => {}, + error: '', + setError: () => {}, + broadcastSuccess: false, + setBroadcastSuccess: () => {}, +}); + +export const SettingsProvider = ({ channel, children }) => { + const [channelData, setChannelData] = useState(null); + const [loadedBtc, setLoadedBtc] = useState(false); + const [loadedSettings, setLoadedSettings] = useState(false); + const [loaded, setLoaded] = useState(false); + const [providerPerms, setProviderPerms] = useState({}); + const [shipWallets, setShipWallets] = useState({}); + const [provider, setProvider] = useState(null); + const [wallet, setWallet] = useState(null); + const [confirmedBalance, setConfirmedBalance] = useState(0); + const [unconfirmedBalance, setUnconfirmedBalance] = useState(0); + const [btcState, setBtcState] = useState(null); + const [history, setHistory] = useState([]); + const [psbt, setPsbt] = useState(''); + const [address, setAddress] = useState(null); + const [currencyRates, setCurrencyRates] = useState({ + BTC: { last: 1, symbol: 'BTC' }, + }); + const [denomination, setDenomination] = useState('BTC'); + const [showWarning, setShowWarning] = useState(false); + const [error, setError] = useState(''); + const [broadcastSuccess, setBroadcastSuccess] = useState(false); + + const { Provider } = SettingsContext; + + const success = (event) => { + console.log({ event }); + setChannelData(event); + }; + const fail = (error) => console.log({ error }); + + const initializeBtcWallet = () => { + api.bind('/all', 'PUT', api.ship, 'btc-wallet', success, fail); + }; + + const initializeSettings = () => { + let app = 'settings-store'; + let path = '/bucket/btc-wallet'; + + fetch(`/~/scry/${app}${path}.json`) + .then((res) => res.json()) + .then((n) => { + let data = _.get(n, 'initial', false); + let bucketData = _.get(n, 'bucket', false); + if (data) { + setChannelData(n); + } + if (bucketData) { + let bucketWarning = _.get(n, 'bucket.warning', -1); + if (bucketWarning !== -1) { + setShowWarning(bucketWarning); + } + let bucketCurrency = _.get(n, 'bucket.currency', -1); + if (bucketCurrency !== -1) { + setDenomination(bucketCurrency); + } + setLoadedSettings(true); + if (loadedBtc) { + setLoaded(true); + } + } + }); + + api.bind(path, 'PUT', api.ship, app, success, fail); + }; + + const initializeCurrencyPoll = () => { + fetch('https://blockchain.info/ticker') + .then((res) => res.json()) + .then((n) => { + const newCurrencyRates = currencyRates; + for (let c in n) { + newCurrencyRates[c] = n[c]; + } + setCurrencyRates(newCurrencyRates); + setTimeout(() => initializeCurrencyPoll(), 1000 * 60 * 15); + }); + }; + + const start = () => { + if (api.ship) { + initializeBtcWallet(); + initializeSettings(); + initializeCurrencyPoll(); + } + }; + + const handleNewTx = ({ txid, recvd }) => { + let old = _.findIndex(history, (h) => { + return h.txid.dat === txid.dat && h.txid.wid === txid.wid; + }); + if (old !== -1) { + delete history.splice(old, 1); + } + if (recvd === null) { + history.unshift({ txid, recvd }); + } else { + // we expect history to have null recvd values first, and the rest in + // descending order + let insertionIndex = _.findIndex(history, (h) => { + return h.recvd < recvd && h.recvd !== null; + }); + history.splice(insertionIndex, 0, { txid, recvd }); + } + }; + + const handleCancelTx = ({ wid, dat }) => { + let entryIndex = _.findIndex(history, (h) => { + return wid === h.txid.wid && dat === h.txid.dat; + }); + if (entryIndex > -1) { + history[entryIndex].failure = true; + } + }; + + useEffect(() => { + const initialData = channelData?.data?.initial; + const putEntryData = channelData?.data?.['settings-event']?.['put-entry']; + const btcStateData = channelData?.data?.['btc-state']; + const changeProvider = channelData?.data?.['change-provider']; + const newTx = channelData?.data?.['new-tx']; + const providerStatus = channelData?.data?.providerStatus; + const checkPayee = channelData?.data?.checkPayee; + const changeWallet = channelData?.data?.changeWallet; + const psbtData = channelData?.data.psbt; + const cancelTx = channelData?.data['cancel-tx']; + const addressData = channelData?.data?.address; + const balanceData = channelData?.data?.balance; + const errorData = channelData?.data?.error; + const broadcastSuccessData = channelData?.data?.['broadcast-success']; + const broadcastFailData = channelData?.data?.['broadcast-fail']; + if (initialData) { + setProvider(initialData.provider); + setWallet(initialData.wallet); + setConfirmedBalance(_.get(initialData.balance, 'confirmed', null)); + setUnconfirmedBalance(_.get(initialData.balance, 'unconfirmed', null)); + setBtcState(initialData['btc-state']); + setHistory(reduceHistory(initialData.history)); + setAddress(initialData.address); + setLoadedBtc(true); + if (loadedSettings) { + setLoaded(true); + } + } + if (putEntryData && putEntryData?.['entry-key'] === 'currency') { + setDenomination(putEntryData.value); + } + if (putEntryData && putEntryData?.['entry-key'] === 'warning') { + setShowWarning(putEntryData.value); + } + if (btcStateData) { + setBtcState(btcStateData); + } + if (changeProvider) { + setProvider(changeProvider); + } + if (newTx) { + handleNewTx(newTx); + } + if (providerStatus) { + let newProviderPerms = providerPerms; + for (let c in providerStatus) { + newProviderPerms[c] = providerStatus[c]; + } + setProviderPerms(newProviderPerms); + } + if (checkPayee) { + let newShipWallets = shipWallets; + + for (let c in checkPayee) { + newShipWallets[c] = checkPayee[c]; + } + console.log({ newShipWallets }); + setShipWallets(newShipWallets); + } + if (changeWallet) { + setWallet(changeWallet); + } + if (psbtData) { + setPsbt(psbtData.pb); + } + if (cancelTx) { + handleCancelTx(cancelTx); + } + if (addressData) { + setAddress(addressData); + } + if (balanceData) { + setUnconfirmedBalance(balanceData.unconfirmed); + setConfirmedBalance(balanceData.confirmed); + } + if (errorData) { + setError(errorData); + } + if (broadcastSuccessData) { + setBroadcastSuccess(true); + } + if (broadcastFailData) { + setBroadcastSuccess(false); + } + }, [channelData]); + + useEffect(() => { + channel.setOnChannelError(() => { + start(); + }); + start(); + }, []); + + return ( + + {children} + + ); +}; + +export const useSettings = () => useContext(SettingsContext); From f269be8ba96d2f48f0025e77f09a2017f46a04fd Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Wed, 30 Jun 2021 16:17:31 -0500 Subject: [PATCH 02/19] Convert components, remove old store --- pkg/btc-wallet/src/index.js | 16 +- .../src/js/components/lib/balance.js | 270 +++--- pkg/btc-wallet/src/js/components/lib/body.js | 115 +-- .../src/js/components/lib/bridgeInvoice.js | 392 ++++----- .../src/js/components/lib/currencyPicker.js | 71 +- pkg/btc-wallet/src/js/components/lib/error.js | 20 +- .../src/js/components/lib/feePicker.js | 166 ++-- .../src/js/components/lib/header.js | 139 +-- .../src/js/components/lib/invoice.js | 435 +++++---- .../src/js/components/lib/providerModal.js | 278 +++--- pkg/btc-wallet/src/js/components/lib/send.js | 833 +++++++++--------- pkg/btc-wallet/src/js/components/lib/sent.js | 63 +- .../src/js/components/lib/settings.js | 221 +++-- .../src/js/components/lib/signer.js | 81 +- .../src/js/components/lib/startupModal.js | 80 +- .../src/js/components/lib/transaction.js | 175 ++-- .../src/js/components/lib/transactions.js | 95 +- .../src/js/components/lib/tx-action.js | 130 ++- .../src/js/components/lib/tx-counterparty.js | 83 +- .../src/js/components/lib/walletModal.js | 432 ++++----- .../src/js/components/lib/warning.js | 141 ++- pkg/btc-wallet/src/js/components/root.js | 88 +- pkg/btc-wallet/src/js/lib/util.js | 96 +- pkg/btc-wallet/src/js/reducers/currency.js | 20 - pkg/btc-wallet/src/js/reducers/initial.js | 29 - pkg/btc-wallet/src/js/reducers/settings.js | 30 - pkg/btc-wallet/src/js/reducers/update.js | 116 --- pkg/btc-wallet/src/js/store.js | 54 -- 28 files changed, 2099 insertions(+), 2570 deletions(-) delete mode 100644 pkg/btc-wallet/src/js/reducers/currency.js delete mode 100644 pkg/btc-wallet/src/js/reducers/initial.js delete mode 100644 pkg/btc-wallet/src/js/reducers/settings.js delete mode 100644 pkg/btc-wallet/src/js/reducers/update.js delete mode 100644 pkg/btc-wallet/src/js/store.js 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; From 8e7379871397c9f5dd56896bfe6fbf07d2176212 Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Wed, 30 Jun 2021 16:58:05 -0500 Subject: [PATCH 03/19] Fix patp check in send --- pkg/btc-wallet/src/js/components/lib/send.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/btc-wallet/src/js/components/lib/send.js b/pkg/btc-wallet/src/js/components/lib/send.js index 9bb6cbbd8a..c55bc1fd77 100644 --- a/pkg/btc-wallet/src/js/components/lib/send.js +++ b/pkg/btc-wallet/src/js/components/lib/send.js @@ -64,15 +64,12 @@ const Send = ({ stopSending, value, conversion }) => { }; 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) { - console.log('isPatp', isPatp); let command = { 'check-payee': payeeReceived }; api.btcWalletCommand(command); setTimeout(() => { @@ -144,16 +141,14 @@ const Send = ({ stopSending, value, conversion }) => { }, []); useEffect(() => { - if (!ready && checkingPatp) { - console.log({ ready, checkingPatp, shipWallets, payee }); + if (!ready && !checkingPatp) { if (shipWallets.payee === payee.slice(1) && shipWallets.hasWallet) { - console.log('good'); setReady(true); setCheckingPatp(false); setValidPayee(true); } } - }, [ready, checkingPatp, shipWallets]); + }, [ready, checkingPatp, shipWallets, payee]); let payeeColor = 'black'; let payeeBg = 'white'; From 078d4991b3173b0eeec07054d958b5bccf04e0ec Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Wed, 30 Jun 2021 17:23:10 -0500 Subject: [PATCH 04/19] Fix feepicker --- .../src/js/components/lib/feePicker.js | 45 +++++-------------- pkg/btc-wallet/src/js/components/lib/send.js | 17 ++++--- 2 files changed, 22 insertions(+), 40 deletions(-) diff --git a/pkg/btc-wallet/src/js/components/lib/feePicker.js b/pkg/btc-wallet/src/js/components/lib/feePicker.js index 00e91c198d..9c7c77a542 100644 --- a/pkg/btc-wallet/src/js/components/lib/feePicker.js +++ b/pkg/btc-wallet/src/js/components/lib/feePicker.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from 'react'; +import React from 'react'; import { Box, Text, @@ -6,38 +6,15 @@ import { StatelessRadioButtonField as RadioButton, Label, } from '@tlon/indigo-react'; +import { feeLevels } from './send'; -const feeLevels = { - low: 'low', - mid: 'mid', - high: 'high', -}; - -const FeePicker = ({ feeChoices, feeSelect, feeDismiss }) => { - const [feeSelected, setFeeSelected] = useState(feeLevels.mid); - const [modalElement, setModalElement] = useState(); - const modalRef = useRef(); - - // const clickDismiss = (e) => { - // console.log(modalElement, e); - // // if (modalRef && !modalRef.contains(e.target)) { - // // feeDismiss(); - // // } - // }; - +const FeePicker = ({ feeChoices, feeValue, setFeeValue, feeDismiss }) => { const select = (which) => { - setFeeSelected(which); - feeSelect(which); + console.log(which); + setFeeValue(feeLevels[which]); feeDismiss(); }; - // useEffect(() => { - // document.addEventListener('click', (e) => clickDismiss(e)); - // setModalElement(modalRef.current); - // console.log(modalRef.current); - // return () => document.addEventListener('click', clickDismiss); - // }, []); - return ( { { - select('low'); + select(feeLevels.low); }} >
@@ -168,7 +168,7 @@ const BridgeInvoice = ({ payee, stopSending, satsAmount }) => { color={inputColor} backgroundColor={inputBg} borderColor={inputBorder} - style={{ 'line-height': '4' }} + style={{ lineHeight: '4' }} onChange={(e) => checkTxHex(e)} /> {localError !== '' && ( diff --git a/pkg/btc-wallet/src/js/components/lib/invoice.js b/pkg/btc-wallet/src/js/components/lib/invoice.js index 771a876698..4d9c6563c1 100644 --- a/pkg/btc-wallet/src/js/components/lib/invoice.js +++ b/pkg/btc-wallet/src/js/components/lib/invoice.js @@ -214,7 +214,7 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { mono color="gray" fontSize="14px" - style={{ display: 'block', 'overflow-wrap': 'anywhere' }} + style={{ display: 'block', overflowWrap: 'anywhere' }} > {payee} diff --git a/pkg/btc-wallet/src/js/components/lib/sent.js b/pkg/btc-wallet/src/js/components/lib/sent.js index be4a8e1093..fe570f0fb3 100644 --- a/pkg/btc-wallet/src/js/components/lib/sent.js +++ b/pkg/btc-wallet/src/js/components/lib/sent.js @@ -19,7 +19,7 @@ const Sent = ({ payee, stopSending, satsAmount }) => {
{`You sent BTC to ${payee}`}
From 40f80a94b84aca3676be3726c29d9f5fb3175fcc Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Wed, 30 Jun 2021 17:30:33 -0500 Subject: [PATCH 07/19] Remove more console logs --- pkg/btc-wallet/src/js/components/lib/feePicker.js | 1 - pkg/btc-wallet/src/js/hooks/useSettings.js | 1 - 2 files changed, 2 deletions(-) diff --git a/pkg/btc-wallet/src/js/components/lib/feePicker.js b/pkg/btc-wallet/src/js/components/lib/feePicker.js index 9c7c77a542..7fc5153a23 100644 --- a/pkg/btc-wallet/src/js/components/lib/feePicker.js +++ b/pkg/btc-wallet/src/js/components/lib/feePicker.js @@ -10,7 +10,6 @@ import { feeLevels } from './send'; const FeePicker = ({ feeChoices, feeValue, setFeeValue, feeDismiss }) => { const select = (which) => { - console.log(which); setFeeValue(feeLevels[which]); feeDismiss(); }; diff --git a/pkg/btc-wallet/src/js/hooks/useSettings.js b/pkg/btc-wallet/src/js/hooks/useSettings.js index 81a9f39701..8e35d52345 100644 --- a/pkg/btc-wallet/src/js/hooks/useSettings.js +++ b/pkg/btc-wallet/src/js/hooks/useSettings.js @@ -216,7 +216,6 @@ export const SettingsProvider = ({ channel, children }) => { for (let c in checkPayee) { newShipWallets[c] = checkPayee[c]; } - console.log({ newShipWallets }); setShipWallets(newShipWallets); } if (changeWallet) { From 6badb91b6f4780c62ad3fcef75462a47a935059f Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Wed, 30 Jun 2021 17:44:45 -0500 Subject: [PATCH 08/19] Add fee to useSettings hook --- pkg/btc-wallet/src/js/components/lib/send.js | 2 -- pkg/btc-wallet/src/js/hooks/useSettings.js | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/btc-wallet/src/js/components/lib/send.js b/pkg/btc-wallet/src/js/components/lib/send.js index abec450436..f3b050643a 100644 --- a/pkg/btc-wallet/src/js/components/lib/send.js +++ b/pkg/btc-wallet/src/js/components/lib/send.js @@ -125,7 +125,6 @@ const Send = ({ stopSending, value, conversion }) => { }; useEffect(() => { - console.log({ network }); if (network === 'bitcoin') { let url = 'https://bitcoiner.live/api/fees/estimates/latest'; fetch(url) @@ -134,7 +133,6 @@ const Send = ({ stopSending, value, conversion }) => { // let estimates = Object.keys(n.estimates); // let mid = Math.floor(estimates.length / 2); // let high = estimates.length - 1; - console.log('hello'); setFeeChoices({ high: [30, n.estimates[30]['sat_per_vbyte']], mid: [180, n.estimates[180]['sat_per_vbyte']], diff --git a/pkg/btc-wallet/src/js/hooks/useSettings.js b/pkg/btc-wallet/src/js/hooks/useSettings.js index 8e35d52345..02393afcb2 100644 --- a/pkg/btc-wallet/src/js/hooks/useSettings.js +++ b/pkg/btc-wallet/src/js/hooks/useSettings.js @@ -27,6 +27,8 @@ export const SettingsContext = createContext({ setBtcState: () => {}, history: [], setHistory: () => {}, + fee: 0, + setFee: () => {}, psbt: '', setPsbt: () => {}, address: null, @@ -58,6 +60,7 @@ export const SettingsProvider = ({ channel, children }) => { const [btcState, setBtcState] = useState(null); const [history, setHistory] = useState([]); const [psbt, setPsbt] = useState(''); + const [fee, setFee] = useState(0); const [address, setAddress] = useState(null); const [currencyRates, setCurrencyRates] = useState({ BTC: { last: 1, symbol: 'BTC' }, @@ -223,6 +226,7 @@ export const SettingsProvider = ({ channel, children }) => { } if (psbtData) { setPsbt(psbtData.pb); + setFee(psbtData.fee); } if (cancelTx) { handleCancelTx(cancelTx); @@ -281,6 +285,8 @@ export const SettingsProvider = ({ channel, children }) => { setHistory, psbt, setPsbt, + fee, + setFee, address, setAddress, currencyRates, From 12c5306ac0f0c39a3e3f59eb235c09ff38fe1f47 Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Thu, 1 Jul 2021 11:38:18 -0500 Subject: [PATCH 09/19] Fix newTx handler --- pkg/btc-wallet/src/js/hooks/useSettings.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pkg/btc-wallet/src/js/hooks/useSettings.js b/pkg/btc-wallet/src/js/hooks/useSettings.js index 02393afcb2..55e7038e48 100644 --- a/pkg/btc-wallet/src/js/hooks/useSettings.js +++ b/pkg/btc-wallet/src/js/hooks/useSettings.js @@ -134,22 +134,28 @@ export const SettingsProvider = ({ channel, children }) => { } }; - const handleNewTx = ({ txid, recvd }) => { + const handleNewTx = (newTx) => { + const { txid, recvd } = newTx; let old = _.findIndex(history, (h) => { return h.txid.dat === txid.dat && h.txid.wid === txid.wid; }); if (old !== -1) { - delete history.splice(old, 1); + const newHistory = history.filter((o, i) => i !== old); + setHistory(newHistory); } - if (recvd === null) { - history.unshift({ txid, recvd }); - } else { + if (recvd === null && old === -1) { + const newHistory = [...history, newTx]; + setHistory(newHistory); + } else if (recvd !== null && old === -1) { // we expect history to have null recvd values first, and the rest in // descending order let insertionIndex = _.findIndex(history, (h) => { return h.recvd < recvd && h.recvd !== null; }); - history.splice(insertionIndex, 0, { txid, recvd }); + const newHistory = history.map((o, i) => + i === insertionIndex ? newTx : o + ); + setHistory(newHistory); } }; From 6608c04186c06c06a45b56a0c16f51dc8eeb1e0f Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Thu, 1 Jul 2021 11:39:12 -0500 Subject: [PATCH 10/19] Cleanup walletModal, transaction, invoice --- .../src/js/components/lib/invoice.js | 27 +++++-------------- .../src/js/components/lib/transactions.js | 2 +- .../src/js/components/lib/walletModal.js | 6 +---- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/pkg/btc-wallet/src/js/components/lib/invoice.js b/pkg/btc-wallet/src/js/components/lib/invoice.js index 4d9c6563c1..9b843d617e 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, { useRef, useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import { Box, Icon, @@ -57,7 +57,6 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { const [ready, setReady] = useState(false); const [localError, setLocalError] = useState(error); const [broadcasting, setBroadcasting] = useState(false); - const invoiceRef = useRef(); useEffect(() => { if (broadcasting && localError !== '') { @@ -65,17 +64,6 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { } }, [error, broadcasting, setBroadcasting]); - const clickDismiss = (e) => { - if (invoiceRef && !invoiceRef.contains(e.target)) { - stopSending(); - } - }; - - useEffect(() => { - document.addEventListener('click', clickDismiss); - return () => document.removeEventListener('click', clickDismiss); - }, []); - const broadCastTx = (psbtHex) => { let command = { 'broadcast-tx': psbtHex, @@ -90,6 +78,7 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { ticket, ship: parseInt(patp2dec('~' + window.ship)), }).then((urbitWallet) => { + // this wasn't being used, not clear why it was pulled out. // const { xpub } = // network === 'testnet' // ? urbitWallet.bitcoinTestnet.keys @@ -127,11 +116,11 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { }); }; - const checkTicket = (e) => { + const checkTicket = ({ target: { value } }) => { // TODO: port over bridge ticket validation logic - setMasterTicket(e.target.value); - setReady(isValidPatq(e.target.value)); - setLocalError(isValidPatq(e.target.value) ? '' : 'invalid-master-ticket'); + setMasterTicket(value); + setReady(isValidPatq(value)); + setLocalError(isValidPatq(value) ? '' : 'invalid-master-ticket'); }; let inputColor = 'black'; @@ -168,13 +157,11 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { ) : ( stopSending()} > { color={inputColor} backgroundColor={inputBg} borderColor={inputBorder} - onChange={() => checkTicket()} + onChange={(e) => checkTicket(e)} /> {error !== '' && ( diff --git a/pkg/btc-wallet/src/js/components/lib/transactions.js b/pkg/btc-wallet/src/js/components/lib/transactions.js index 2bd7619956..c968320bcd 100644 --- a/pkg/btc-wallet/src/js/components/lib/transactions.js +++ b/pkg/btc-wallet/src/js/components/lib/transactions.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import { Box, Text, Col } from '@tlon/indigo-react'; import Transaction from './transaction.js'; import { useSettings } from '../../hooks/useSettings.js'; diff --git a/pkg/btc-wallet/src/js/components/lib/walletModal.js b/pkg/btc-wallet/src/js/components/lib/walletModal.js index a34a596c03..be0bc6ef07 100644 --- a/pkg/btc-wallet/src/js/components/lib/walletModal.js +++ b/pkg/btc-wallet/src/js/components/lib/walletModal.js @@ -39,11 +39,7 @@ const WalletModal = () => { } }; - const checkXPub = ({ - event: { - target: { value: xpubGiven }, - }, - }) => { + const checkXPub = ({ target: { value: xpubGiven } }) => { setXpub(xpubGiven); setReadyToSubmit(xpubGiven.length > 0); }; From 031d9d5a7de5902a5eae01ed53a0e06f5e80641f Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Sat, 10 Jul 2021 07:21:52 -0500 Subject: [PATCH 11/19] Fix destructured param in checkTicket --- pkg/btc-wallet/src/js/components/lib/walletModal.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/btc-wallet/src/js/components/lib/walletModal.js b/pkg/btc-wallet/src/js/components/lib/walletModal.js index be0bc6ef07..f8687441d5 100644 --- a/pkg/btc-wallet/src/js/components/lib/walletModal.js +++ b/pkg/btc-wallet/src/js/components/lib/walletModal.js @@ -24,11 +24,7 @@ const WalletModal = () => { const [confirmingMasterTicket, setConfirmingMasterTicket] = useState(false); const [error, setError] = useState(false); - const checkTicket = ({ - event: { - target: { value }, - }, - }) => { + const checkTicket = ({ target: { value } }) => { // TODO: port over bridge ticket validation logic if (confirmingMasterTicket) { setConfirmedMasterTicket(value); From 6bc8d247bd65f13c693c6bfd4b1ca0b5c9bf5f2e Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Sat, 10 Jul 2021 08:27:49 -0500 Subject: [PATCH 12/19] Map dollar sign to USD --- pkg/btc-wallet/src/js/hooks/useSettings.js | 3 ++- pkg/btc-wallet/src/js/lib/util.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/btc-wallet/src/js/hooks/useSettings.js b/pkg/btc-wallet/src/js/hooks/useSettings.js index 55e7038e48..c6e4917536 100644 --- a/pkg/btc-wallet/src/js/hooks/useSettings.js +++ b/pkg/btc-wallet/src/js/hooks/useSettings.js @@ -1,7 +1,7 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; import _ from 'lodash'; import { api } from '../api'; -import { reduceHistory } from '../lib/util'; +import { mapDenominationToSymbol, reduceHistory } from '../lib/util'; export const SettingsContext = createContext({ network: 'bitcoin', @@ -120,6 +120,7 @@ export const SettingsProvider = ({ channel, children }) => { const newCurrencyRates = currencyRates; for (let c in n) { newCurrencyRates[c] = n[c]; + newCurrencyRates[c].symbol = mapDenominationToSymbol(c); } setCurrencyRates(newCurrencyRates); setTimeout(() => initializeCurrencyPoll(), 1000 * 60 * 15); diff --git a/pkg/btc-wallet/src/js/lib/util.js b/pkg/btc-wallet/src/js/lib/util.js index 131ec4012c..2c133559a6 100644 --- a/pkg/btc-wallet/src/js/lib/util.js +++ b/pkg/btc-wallet/src/js/lib/util.js @@ -118,3 +118,12 @@ export function reduceHistory(history) { return hest2.recvd - hest1.recvd; }); } + +export function mapDenominationToSymbol(denomination) { + switch (denomination) { + case 'USD': + return '$'; + default: + return denomination; + } +} From e35eb52033d8ad79d97cd66dc20d1b9a9af2eeed Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Sat, 10 Jul 2021 09:44:18 -0500 Subject: [PATCH 13/19] Add sig to valid patp in send component --- pkg/btc-wallet/src/js/components/lib/send.js | 31 +++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/pkg/btc-wallet/src/js/components/lib/send.js b/pkg/btc-wallet/src/js/components/lib/send.js index f3b050643a..d5b6756311 100644 --- a/pkg/btc-wallet/src/js/components/lib/send.js +++ b/pkg/btc-wallet/src/js/components/lib/send.js @@ -68,18 +68,22 @@ const Send = ({ stopSending, value, conversion }) => { const checkPayee = (e) => { setError(''); - let payeeReceived = e.target.value; - let isPatp = ob.isValidPatp(payeeReceived); - let isAddress = validate(payeeReceived); - if (isPatp) { - let command = { 'check-payee': payeeReceived }; + const validPatPCommand = (validPatP) => { + let command = { 'check-payee': validPatP }; api.btcWalletCommand(command); setTimeout(() => { setCheckingPatp(false); }, 5000); setCheckingPatp(true); setPayeeType('ship'); - setPayee(payeeReceived); + setPayee(validPatP); + }; + + let payeeReceived = e.target.value; + let isPatp = ob.isValidPatp(payeeReceived); + let isAddress = validate(payeeReceived); + if (isPatp) { + validPatPCommand(payeeReceived); } else if (isAddress) { setPayee(payeeReceived); setReady(true); @@ -87,11 +91,16 @@ const Send = ({ stopSending, value, conversion }) => { setPayeeType('address'); setValidPayee(true); } else { - setPayee(payeeReceived); - setReady(false); - setCheckingPatp(false); - setPayeeType(''); - setValidPayee(false); + const possibleValidPatPMissingSig = '~'.concat(payeeReceived); + if (ob.isValidPatp(possibleValidPatPMissingSig)) { + validPatPCommand(possibleValidPatPMissingSig); + } else { + setPayee(payeeReceived); + setReady(false); + setCheckingPatp(false); + setPayeeType(''); + setValidPayee(false); + } } }; From 764438b248d87f5f30b246067c30356a617bf7d2 Mon Sep 17 00:00:00 2001 From: finned-palmer Date: Sat, 10 Jul 2021 14:17:52 -0500 Subject: [PATCH 14/19] Show wallet scan progress --- .../src/js/components/lib/balance.js | 25 ++++++++++++++++--- .../src/js/components/lib/walletModal.js | 24 ++++++++++-------- pkg/btc-wallet/src/js/components/root.js | 11 ++++---- pkg/btc-wallet/src/js/hooks/useSettings.js | 12 +++++++++ 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/pkg/btc-wallet/src/js/components/lib/balance.js b/pkg/btc-wallet/src/js/components/lib/balance.js index a963a3dac2..55a88adfac 100644 --- a/pkg/btc-wallet/src/js/components/lib/balance.js +++ b/pkg/btc-wallet/src/js/components/lib/balance.js @@ -16,10 +16,12 @@ const Balance = () => { setPsbt, setFee, setError, + scanProgress, } = useSettings(); const [sending, setSending] = useState(false); const [copiedButton, setCopiedButton] = useState(false); const [copiedString, setCopiedString] = useState(false); + const scanning = scanProgress?.main !== null || scanProgress?.change !== null; const copyAddress = (arg) => { navigator.clipboard.writeText(address); @@ -93,10 +95,25 @@ const Balance = () => { > {value} - {`${sats}${unconfirmedString} sats`} + {scanning ? ( + + + + Balance will be updated shortly: + + + + + {scanProgress.main}/24 addresses scanned + + + + ) : ( + {`${sats}${unconfirmedString} sats`} + )} - {broadcasting ? : null} + { + // @ts-ignore + broadcasting ? : null + } )} diff --git a/pkg/btc-wallet/src/js/components/lib/externalInvoice.js b/pkg/btc-wallet/src/components/Send/ExternalInvoice.tsx similarity index 91% rename from pkg/btc-wallet/src/js/components/lib/externalInvoice.js rename to pkg/btc-wallet/src/components/Send/ExternalInvoice.tsx index 866f00a731..0ac036b1b5 100644 --- a/pkg/btc-wallet/src/js/components/lib/externalInvoice.js +++ b/pkg/btc-wallet/src/components/Send/ExternalInvoice.tsx @@ -9,16 +9,26 @@ import { Col, LoadingSpinner, } from '@tlon/indigo-react'; -import { Sigil } from './sigil.js'; +import Sigil from '../Sigil'; import * as bitcoin from 'bitcoinjs-lib'; import { isValidPatp } from 'urbit-ob'; import Sent from './sent.js'; -import Error from './error.js'; +import Error from '../Error'; import { copyToClipboard, satsToCurrency } from '../../lib/util.js'; import { useSettings } from '../../hooks/useSettings.js'; -import { api } from '../../api'; +import { api } from '../../lib/api'; -const ExternalInvoice = ({ payee, stopSending, satsAmount }) => { +type Props = { + payee: string; + stopSending: () => void; + satsAmount: number; +}; + +const ExternalInvoice: React.FC = ({ + payee, + stopSending, + satsAmount, +}) => { const { error, currencyRates, fee, broadcastSuccess, denomination, psbt } = useSettings(); const [txHex, setTxHex] = useState(''); @@ -36,14 +46,14 @@ const ExternalInvoice = ({ payee, stopSending, satsAmount }) => { } }, [error, broadcasting, setBroadcasting]); - const broadCastTx = (hex) => { + const broadCastTx = (hex: string) => { let command = { 'broadcast-tx': hex, }; return api.btcWalletCommand(command); }; - const sendBitcoin = (hex) => { + const sendBitcoin = (hex: string) => { try { bitcoin.Transaction.fromHex(hex); broadCastTx(hex); @@ -54,7 +64,7 @@ const ExternalInvoice = ({ payee, stopSending, satsAmount }) => { } }; - const checkTxHex = (e) => { + const checkTxHex = (e: React.ChangeEvent) => { setTxHex(e.target.value); setReady(txHex.length > 0); setLocalError(''); @@ -211,11 +221,13 @@ const ExternalInvoice = ({ payee, stopSending, satsAmount }) => { backgroundColor={inputBg} borderColor={inputBorder} style={{ lineHeight: '4' }} - onChange={(e) => checkTxHex(e)} + onChange={(e: React.ChangeEvent) => + checkTxHex(e) + } /> {localError !== '' && ( - + )} @@ -249,7 +261,7 @@ const ExternalInvoice = ({ payee, stopSending, satsAmount }) => { > Send BTC - {broadcasting ? : null} + {broadcasting ? : null} )} diff --git a/pkg/btc-wallet/src/components/Send/FeePicker.tsx b/pkg/btc-wallet/src/components/Send/FeePicker.tsx new file mode 100644 index 0000000000..1dc48158ce --- /dev/null +++ b/pkg/btc-wallet/src/components/Send/FeePicker.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { + Box, + Text, + Col, + StatelessRadioButtonField as RadioButton, + Label, +} from '@tlon/indigo-react'; +import { FeeChoices, feeLevels } from './Send'; + +type Props = { + feeChoices: FeeChoices; + feeValue: number; + setFeeValue: React.Dispatch; + feeDismiss: () => void; +}; + +const FeePicker: React.FC = ({ + feeChoices, + feeValue, + setFeeValue, + feeDismiss, +}) => ( + + + Transaction Speed + + + { + setFeeValue(feeLevels.low); + feeDismiss(); + }} + > + + + + { + setFeeValue(feeLevels.mid); + feeDismiss(); + }} + > + + + + { + setFeeValue(feeLevels.high); + feeDismiss(); + }} + > + + + + +); + +export default FeePicker; diff --git a/pkg/btc-wallet/src/js/components/lib/invoice.js b/pkg/btc-wallet/src/components/Send/Invoice.tsx similarity index 85% rename from pkg/btc-wallet/src/js/components/lib/invoice.js rename to pkg/btc-wallet/src/components/Send/Invoice.tsx index 9b843d617e..cfccbf8664 100644 --- a/pkg/btc-wallet/src/js/components/lib/invoice.js +++ b/pkg/btc-wallet/src/components/Send/Invoice.tsx @@ -9,15 +9,16 @@ 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 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'; +import * as bitcoin from 'bitcoinjs-lib'; +import Sigil from '../Sigil'; +import Sent from './Sent'; +import { satsToCurrency } from '../../lib/util'; +import Error from '../Error'; +import { useSettings } from '../../hooks/useSettings'; +import { api } from '../../lib/api'; +import { UrbitWallet } from '../../types'; const BITCOIN_MAINNET_INFO = { messagePrefix: '\x18Bitcoin Signed Message:\n', @@ -43,7 +44,13 @@ const BITCOIN_TESTNET_INFO = { wif: 0xef, }; -const Invoice = ({ stopSending, payee, satsAmount }) => { +type Props = { + stopSending: () => void; + payee: string; + satsAmount: number; +}; + +const Invoice: React.FC = ({ stopSending, payee, satsAmount }) => { const { error, currencyRates, @@ -64,20 +71,20 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { } }, [error, broadcasting, setBroadcasting]); - const broadCastTx = (psbtHex) => { + const broadCastTx = (psbtHex: string) => { let command = { 'broadcast-tx': psbtHex, }; return api.btcWalletCommand(command); }; - const sendBitcoin = (ticket, psbt) => { + const sendBitcoin = (ticket: string, psbt: string) => { const newPsbt = bitcoin.Psbt.fromBase64(psbt); setBroadcasting(true); kg.generateWallet({ ticket, - ship: parseInt(patp2dec('~' + window.ship)), - }).then((urbitWallet) => { + ship: parseInt(patp2dec('~' + (window as any).ship)), + }).then((urbitWallet: UrbitWallet) => { // this wasn't being used, not clear why it was pulled out. // const { xpub } = // network === 'testnet' @@ -116,7 +123,9 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { }); }; - const checkTicket = ({ target: { value } }) => { + const checkTicket = ({ + target: { value }, + }: React.ChangeEvent) => { // TODO: port over bridge ticket validation logic setMasterTicket(value); setReady(isValidPatq(value)); @@ -216,19 +225,21 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { fontSize="14px" type="password" name="masterTicket" - obscure={(value) => value.replace(/[^~-]+/g, '••••••')} + obscure={(value: string) => value.replace(/[^~-]+/g, '••••••')} placeholder="••••••-••••••-••••••-••••••" autoCapitalize="none" autoCorrect="off" color={inputColor} backgroundColor={inputBg} borderColor={inputBorder} - onChange={(e) => checkTicket(e)} + onChange={(e: React.ChangeEvent) => + checkTicket(e) + } /> {error !== '' && ( - + )} @@ -252,7 +263,10 @@ const Invoice = ({ stopSending, payee, satsAmount }) => { > Send BTC - {broadcasting ? : null} + { + // @ts-ignore + broadcasting ? : null + } )} diff --git a/pkg/btc-wallet/src/js/components/lib/send.js b/pkg/btc-wallet/src/components/Send/Send.tsx similarity index 81% rename from pkg/btc-wallet/src/js/components/lib/send.js rename to pkg/btc-wallet/src/components/Send/Send.tsx index d7dea1efb9..1567c5c466 100644 --- a/pkg/btc-wallet/src/js/components/lib/send.js +++ b/pkg/btc-wallet/src/components/Send/Send.tsx @@ -9,81 +9,99 @@ import { Col, LoadingSpinner, } 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'; +import BridgeInvoice from './BridgeInvoice'; +import ExternalInvoice from './ExternalInvoice'; +import FeePicker from './FeePicker'; +import Error from '../Error'; +import Signer from './Signer'; import { validate } from 'bitcoin-address-validation'; import * as ob from 'urbit-ob'; -import { useSettings } from '../../hooks/useSettings.js'; -import { api } from '../../api'; -import { deSig } from '../../lib/util.js'; -import ExternalInvoice from './externalInvoice.js'; +import { useSettings } from '../../hooks/useSettings'; +import { api } from '../../lib/api'; +import { deSig } from '../../lib/util'; -const focusFields = { - empty: '', - payee: 'payee', - currency: 'currency', - sats: 'sats', - note: 'note', +enum focusFields { + payee, + currency, + sats, + note, + empty = '', +} + +export enum feeLevels { + low, + mid, + high, +} + +export enum signMethods { + bridge, + masterTicket, + external, +} + +enum payeeTypes { + ship, + address, + initial = '', +} + +export type FeeChoices = { + [feeLevels.low]: [number, number]; + [feeLevels.mid]: [number, number]; + [feeLevels.high]: [number, number]; }; -export const feeLevels = { - low: 'low', - mid: 'mid', - high: 'high', +type Props = { + stopSending: () => void; + value: string; + conversion: number; }; -export const signMethods = { - bridge: 'bridge', - masterTicket: 'masterTicket', - external: 'external', -}; - -const Send = ({ stopSending, value, conversion }) => { +const Send: React.FC = ({ 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 [denomAmount, setDenomAmount] = useState(0.0); + const [satsAmount, setSatsAmount] = useState(0); const [payee, setPayee] = useState(''); const [checkingPatp, setCheckingPatp] = useState(false); - const [payeeType, setPayeeType] = useState(''); + const [payeeType, setPayeeType] = useState(payeeTypes.initial); 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 [feeChoices, setFeeChoices] = useState({ + [feeLevels.low]: [10, 1], + [feeLevels.mid]: [10, 1], + [feeLevels.high]: [10, 1], }); const [feeValue, setFeeValue] = useState(feeLevels.mid); const [showFeePicker, setShowFeePicker] = useState(false); const [note, setNote] = useState(''); const [choosingSignMethod, setChoosingSignMethod] = useState(false); - const [signMethod, setSignMethod] = useState(signMethods.bridge); + const [signMethod, setSignMethod] = useState(signMethods.bridge); const feeDismiss = () => { setShowFeePicker(false); }; - const handleSetSignMethod = (signMethod) => { + const handleSetSignMethod = (signMethod: signMethods) => { setSignMethod(signMethod); setChoosingSignMethod(false); }; - const checkPayee = (e) => { + const checkPayee = (e: React.ChangeEvent) => { setError(''); - const validPatPCommand = (validPatP) => { + const validPatPCommand = (validPatP: string) => { let command = { 'check-payee': validPatP }; api.btcWalletCommand(command); setTimeout(() => { setCheckingPatp(false); }, 5000); setCheckingPatp(true); - setPayeeType('ship'); + setPayeeType(payeeTypes.ship); setPayee(validPatP); }; @@ -96,13 +114,13 @@ const Send = ({ stopSending, value, conversion }) => { setPayee(payeeReceived); setReady(true); setCheckingPatp(false); - setPayeeType('address'); + setPayeeType(payeeTypes.address); setValidPayee(true); } else { setPayee(payeeReceived); setReady(false); setCheckingPatp(false); - setPayeeType(''); + setPayeeType(payeeTypes.initial); setValidPayee(false); } }; @@ -112,22 +130,22 @@ const Send = ({ stopSending, value, conversion }) => { }; const initPayment = () => { - if (payeeType === 'ship') { + if (payeeType === payeeTypes.ship) { let command = { 'init-payment': { payee, - value: parseInt(satsAmount), + value: satsAmount, feyb: feeChoices[feeValue][1], note: note || null, }, }; api.btcWalletCommand(command).then(() => setSigning(true)); - } else if (payeeType === 'address') { + } else if (payeeType === payeeTypes.address) { let command = { 'init-payment-external': { address: payee, - value: parseInt(satsAmount), + value: satsAmount, feyb: 1, note: note || null, }, @@ -146,9 +164,9 @@ const Send = ({ stopSending, value, conversion }) => { // 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']], + [feeLevels.high]: [30, n.estimates[30]['sat_per_vbyte']], + [feeLevels.mid]: [180, n.estimates[180]['sat_per_vbyte']], + [feeLevels.low]: [360, n.estimates[360]['sat_per_vbyte']], }); }); } @@ -175,7 +193,7 @@ const Send = ({ stopSending, value, conversion }) => { payeeColor = 'green'; payeeBorder = 'green'; payeeBg = 'veryLightGreen'; - } else if (!focusedField === focusFields.payee && validPayee) { + } else if (focusedField !== focusFields.payee && validPayee) { payeeColor = 'blue'; payeeBorder = 'white'; payeeBg = 'white'; @@ -184,17 +202,17 @@ const Send = ({ stopSending, value, conversion }) => { payeeBorder = 'red'; payeeBg = 'veryLightRed'; } else if ( - focusedField === 'payee' && + focusedField === focusFields.payee && !validPayee && !checkingPatp && - payeeType === 'ship' + payeeType === payeeTypes.ship ) { payeeColor = 'red'; payeeBorder = 'red'; payeeBg = 'veryLightRed'; } - const signReady = ready && parseInt(satsAmount) > 0 && !signing; + const signReady = ready && satsAmount > 0 && !signing; let invoice = null; @@ -286,20 +304,16 @@ const Send = ({ stopSending, value, conversion }) => { value={payee} fontFamily="mono" disabled={signing} - onChange={(e) => checkPayee(e)} + onChange={(e: React.ChangeEvent) => + checkPayee(e) + } /> {error && ( {/* yes this is a hack */} - + )} @@ -321,8 +335,8 @@ const Send = ({ stopSending, value, conversion }) => { } disabled={signing} value={denomAmount} - onChange={(e) => { - setDenomAmount(e.target.value); + onChange={(e: React.ChangeEvent) => { + setDenomAmount(parseFloat(e.target.value)); setSatsAmount( Math.round( (parseFloat(e.target.value) / conversion) * 100000000 @@ -352,11 +366,11 @@ const Send = ({ stopSending, value, conversion }) => { } disabled={signing} value={satsAmount} - onChange={(e) => { + onChange={(e: React.ChangeEvent) => { setDenomAmount( parseFloat(e.target.value) * (conversion / 100000000) ); - setSatsAmount(e.target.value); + setSatsAmount(parseInt(e.target.value, 10)); }} /> @@ -431,7 +445,7 @@ const Send = ({ stopSending, value, conversion }) => { } disabled={signing} value={note} - onChange={(e) => { + onChange={(e: React.ChangeEvent) => { setNote(e.target.value); }} /> @@ -444,11 +458,7 @@ const Send = ({ stopSending, value, conversion }) => { justifyContent="flex-end" > {!(signing && !error) ? null : ( - + )} { fontWeight="bold" borderRadius="24px" height="48px" - onClick={() => toggleSignMethod(choosingSignMethod)} + onClick={() => toggleSignMethod()} color={signReady ? 'white' : 'lighterGray'} backgroundColor={ signReady ? 'rgba(33, 157, 255, 0.2)' : 'veryLightGray' @@ -479,7 +489,7 @@ const Send = ({ stopSending, value, conversion }) => { /> - {signMethod === signMethod.masterTicket && ( + {signMethod === signMethods.masterTicket && ( diff --git a/pkg/btc-wallet/src/js/components/lib/sent.js b/pkg/btc-wallet/src/components/Send/Sent.tsx similarity index 82% rename from pkg/btc-wallet/src/js/components/lib/sent.js rename to pkg/btc-wallet/src/components/Send/Sent.tsx index fe570f0fb3..1990d4b285 100644 --- a/pkg/btc-wallet/src/js/components/lib/sent.js +++ b/pkg/btc-wallet/src/components/Send/Sent.tsx @@ -1,9 +1,15 @@ import React from 'react'; import { Icon, Row, Col, Center, Text } from '@tlon/indigo-react'; -import { satsToCurrency } from '../../lib/util.js'; +import { satsToCurrency } from '../../lib/util'; import { useSettings } from '../../hooks/useSettings'; -const Sent = ({ payee, stopSending, satsAmount }) => { +type Props = { + payee: string; + stopSending: () => void; + satsAmount: number; +}; + +const Sent: React.FC = ({ payee, stopSending, satsAmount }) => { const { denomination, currencyRates } = useSettings(); return ( void; + choosingSignMethod: boolean; + signMethod: signMethods; + setSignMethod: (arg: signMethods) => void; +}; + +const Signer: React.FC = ({ signReady, initPayment, choosingSignMethod, @@ -24,13 +32,15 @@ const Signer = ({ backgroundColor="transparent" fontWeight="bold" cursor="pointer" - color={signMethod === signMethods[method] ? 'blue' : 'lightBlue'} + color={ + signMethod === (signMethods as any)[method] ? 'blue' : 'lightBlue' + } height="48px" - onClick={() => setSignMethod(signMethods[method])} + onClick={() => setSignMethod((signMethods as any)[method])} > - {signMethodLabels[method]} + {(signMethodLabels as any)[method]} - {signMethod === signMethods[method] && ( + {signMethod === (signMethods as any)[method] && ( ); }; diff --git a/pkg/btc-wallet/src/js/components/lib/settings.js b/pkg/btc-wallet/src/components/Settings.tsx similarity index 96% rename from pkg/btc-wallet/src/js/components/lib/settings.js rename to pkg/btc-wallet/src/components/Settings.tsx index 352cb5500f..61ceed9278 100644 --- a/pkg/btc-wallet/src/js/components/lib/settings.js +++ b/pkg/btc-wallet/src/components/Settings.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Row, Text, Button, Col } from '@tlon/indigo-react'; -import { useSettings } from '../../hooks/useSettings'; -import { api } from '../../api'; +import { useSettings } from '../hooks/useSettings'; +import { api } from '../lib/api'; const Settings = () => { const { wallet, provider } = useSettings(); diff --git a/pkg/btc-wallet/src/js/components/lib/sigil.js b/pkg/btc-wallet/src/components/Sigil.tsx similarity index 78% rename from pkg/btc-wallet/src/js/components/lib/sigil.js rename to pkg/btc-wallet/src/components/Sigil.tsx index e47eff93f1..5de8569bdf 100644 --- a/pkg/btc-wallet/src/js/components/lib/sigil.js +++ b/pkg/btc-wallet/src/components/Sigil.tsx @@ -2,11 +2,11 @@ import React, { memo } from 'react'; import { sigil, reactRenderer } from '@tlon/sigil-js'; import { Box } from '@tlon/indigo-react'; -export const foregroundFromBackground = (background) => { +export const foregroundFromBackground = (background: string) => { const rgb = { r: parseInt(background.slice(1, 3), 16), g: parseInt(background.slice(3, 5), 16), - b: parseInt(background.slice(5, 7), 16) + b: parseInt(background.slice(5, 7), 16), }; const brightness = (299 * rgb.r + 587 * rgb.g + 114 * rgb.b) / 1000; const whiteBrightness = 255; @@ -14,7 +14,19 @@ export const foregroundFromBackground = (background) => { return whiteBrightness - brightness < 50 ? 'black' : 'white'; }; -export const Sigil = memo( +type Props = { + classes?: string; + color: string; + foreground?: string; + ship: string; + size: number; + svgClass?: string; + icon?: boolean; + padding?: number; + display?: string; +}; + +const Sigil: React.FC = memo( ({ classes = '', color, @@ -24,7 +36,7 @@ export const Sigil = memo( svgClass = '', icon = false, padding = 0, - display = 'inline-block' + display = 'inline-block', }) => { const innerSize = Number(size) - 2 * padding; const paddingPx = `${padding}px`; @@ -55,7 +67,7 @@ export const Sigil = memo( size: innerSize, icon, colors: [color, foregroundColor], - class: svgClass + class: svgClass, })} ); diff --git a/pkg/btc-wallet/src/js/components/lib/startupModal.js b/pkg/btc-wallet/src/components/StartupModal.tsx similarity index 81% rename from pkg/btc-wallet/src/js/components/lib/startupModal.js rename to pkg/btc-wallet/src/components/StartupModal.tsx index c4ee50c0a7..e21f6ce2cc 100644 --- a/pkg/btc-wallet/src/js/components/lib/startupModal.js +++ b/pkg/btc-wallet/src/components/StartupModal.tsx @@ -1,10 +1,10 @@ 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'; +import ProviderModal from './ProviderModal'; +import { useSettings } from '../hooks/useSettings'; -const StartupModal = () => { +const StartupModal: React.FC = () => { const { wallet, provider } = useSettings(); let modal = null; diff --git a/pkg/btc-wallet/src/js/components/lib/transaction.js b/pkg/btc-wallet/src/components/Transactions/Transaction.tsx similarity index 74% rename from pkg/btc-wallet/src/js/components/lib/transaction.js rename to pkg/btc-wallet/src/components/Transactions/Transaction.tsx index 10b9a50951..8c6c505d7b 100644 --- a/pkg/btc-wallet/src/js/components/lib/transaction.js +++ b/pkg/btc-wallet/src/components/Transactions/Transaction.tsx @@ -1,23 +1,28 @@ 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 TxAction from './TxAction'; +import TxCounterparty from './TxCounterparty'; +import { satsToCurrency } from '../../lib/util'; +import { useSettings } from '../../hooks/useSettings'; +import { Transaction as TransactionType } from '../../types'; -const Transaction = ({ tx }) => { +const Transaction = ({ tx }: { tx: TransactionType }) => { const { denomination, currencyRates } = useSettings(); const pending = !tx.recvd; let weSent = _.find(tx.inputs, (input) => { - return input.ship === window.ship; + return input.ship === (window as any).ship; }); let weRecv = tx.outputs.every((output) => { - return output.ship === window.ship; + return output.ship === (window as any).ship; }); - let action = weRecv ? 'recv' : weSent ? 'sent' : 'recv'; + let action: 'sent' | 'recv' | 'fail' = weRecv + ? 'recv' + : weSent + ? 'sent' + : 'recv'; let counterShip = null; let counterAddress = null; @@ -26,7 +31,7 @@ const Transaction = ({ tx }) => { if (action === 'sent') { let counter = _.find(tx.outputs, (output) => { - return output.ship !== window.ship; + return output.ship !== (window as any).ship; }); counterShip = _.get(counter, 'ship', null); counterAddress = _.get(counter, 'val.address', null); @@ -36,7 +41,7 @@ const Transaction = ({ tx }) => { value = _.reduce( tx.outputs, (sum, output) => { - if (output.ship === window.ship) { + if (output.ship === (window as any).ship) { return sum + output.val.value; } else { return sum; @@ -48,14 +53,14 @@ const Transaction = ({ tx }) => { if (weSent && weRecv) { counterAddress = _.get( _.find(tx.inputs, (input) => { - return input.ship === window.ship; + return input.ship === (window as any).ship; }), 'val.address', null ); } else { let counter = _.find(tx.inputs, (input) => { - return input.ship !== window.ship; + return input.ship !== (window as any).ship; }); counterShip = _.get(counter, 'ship', null); counterAddress = _.get(counter, 'val.address', null); diff --git a/pkg/btc-wallet/src/js/components/lib/transactions.js b/pkg/btc-wallet/src/components/Transactions/Transactions.tsx similarity index 89% rename from pkg/btc-wallet/src/js/components/lib/transactions.js rename to pkg/btc-wallet/src/components/Transactions/Transactions.tsx index c968320bcd..0eec82fb96 100644 --- a/pkg/btc-wallet/src/js/components/lib/transactions.js +++ b/pkg/btc-wallet/src/components/Transactions/Transactions.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Box, Text, Col } from '@tlon/indigo-react'; -import Transaction from './transaction.js'; -import { useSettings } from '../../hooks/useSettings.js'; +import Transaction from './Transaction'; +import { useSettings } from '../../hooks/useSettings'; const Transactions = () => { const { history } = useSettings(); diff --git a/pkg/btc-wallet/src/js/components/lib/tx-action.js b/pkg/btc-wallet/src/components/Transactions/TxAction.tsx similarity index 91% rename from pkg/btc-wallet/src/js/components/lib/tx-action.js rename to pkg/btc-wallet/src/components/Transactions/TxAction.tsx index 12405f4934..6ed9e954d4 100644 --- a/pkg/btc-wallet/src/js/components/lib/tx-action.js +++ b/pkg/btc-wallet/src/components/Transactions/TxAction.tsx @@ -2,7 +2,13 @@ import React from 'react'; import { Box, Icon, Row, Text, LoadingSpinner } from '@tlon/indigo-react'; import { useSettings } from '../../hooks/useSettings'; -const TxAction = ({ action, pending, txid }) => { +type Props = { + action: 'sent' | 'recv' | 'fail'; + pending: boolean; + txid: string; +}; + +const TxAction: React.FC = ({ action, pending, txid }) => { const { network } = useSettings(); const leftIcon = action === 'sent' diff --git a/pkg/btc-wallet/src/js/components/lib/tx-counterparty.js b/pkg/btc-wallet/src/components/Transactions/TxCounterparty.tsx similarity index 83% rename from pkg/btc-wallet/src/js/components/lib/tx-counterparty.js rename to pkg/btc-wallet/src/components/Transactions/TxCounterparty.tsx index 76cf2fd286..8bbdad07dc 100644 --- a/pkg/btc-wallet/src/js/components/lib/tx-counterparty.js +++ b/pkg/btc-wallet/src/components/Transactions/TxCounterparty.tsx @@ -1,8 +1,13 @@ import React from 'react'; import { Box, Icon, Row, Text } from '@tlon/indigo-react'; -import { Sigil } from './sigil.js'; +import Sigil from '../Sigil'; -const TxCounterparty = ({ ship, address }) => { +type Props = { + ship: string; + address: string; +}; + +const TxCounterparty: React.FC = ({ ship, address }) => { const icon = ship ? ( ) : ( diff --git a/pkg/btc-wallet/src/js/components/lib/walletModal.js b/pkg/btc-wallet/src/components/WalletModal.tsx similarity index 86% rename from pkg/btc-wallet/src/js/components/lib/walletModal.js rename to pkg/btc-wallet/src/components/WalletModal.tsx index 38105c2482..cbdaf687a7 100644 --- a/pkg/btc-wallet/src/js/components/lib/walletModal.js +++ b/pkg/btc-wallet/src/components/WalletModal.tsx @@ -10,10 +10,11 @@ import { } 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'; +import { useSettings } from '../hooks/useSettings'; +import { api } from '../lib/api'; +import { UrbitWallet } from '../types'; -const WalletModal = () => { +const WalletModal: React.FC = () => { const { network } = useSettings(); const [mode, setMode] = useState('xpub'); const [masterTicket, setMasterTicket] = useState(''); @@ -24,7 +25,9 @@ const WalletModal = () => { const [confirmingMasterTicket, setConfirmingMasterTicket] = useState(false); const [error, setError] = useState(false); - const checkTicket = ({ target: { value } }) => { + const checkTicket = ({ + target: { value }, + }: React.ChangeEvent) => { // TODO: port over bridge ticket validation logic if (confirmingMasterTicket) { setConfirmedMasterTicket(value); @@ -35,13 +38,24 @@ const WalletModal = () => { } }; - const checkXPub = ({ target: { value: xpubGiven } }) => { + const checkXPub = ({ + target: { value: xpubGiven }, + }: React.ChangeEvent) => { setXpub(xpubGiven); setReadyToSubmit(xpubGiven.length > 0); }; - const submitXPub = (givenXpub) => { - const command = { + const submitXPub = (givenXpub: string) => { + type AddWalletCommand = { + 'add-wallet': { + xpub: string; + fprint: number[]; + 'scan-to': number | null; + 'max-gap': number; + confs: number; + }; + }; + const command: AddWalletCommand = { 'add-wallet': { xpub: givenXpub, fprint: [4, 0], @@ -54,12 +68,12 @@ const WalletModal = () => { setProcessingSubmission(true); }; - const submitMasterTicket = (ticket) => { + const submitMasterTicket = (ticket: string) => { setProcessingSubmission(true); kg.generateWallet({ ticket, - ship: parseInt(patp2dec('~' + window.ship)), - }).then((urbitWallet) => { + ship: parseInt(patp2dec('~' + (window as any).ship)), + }).then((urbitWallet: UrbitWallet) => { const { xpub: xpubFromWallet } = network === 'testnet' ? urbitWallet.bitcoinTestnet.keys @@ -117,11 +131,13 @@ const WalletModal = () => { fontSize="14px" type="password" name="masterTicket" - obscure={(value) => value.replace(/[^~-]+/g, '••••••')} + obscure={(value: string) => value.replace(/[^~-]+/g, '••••••')} placeholder="••••••-••••••-••••••-••••••" autoCapitalize="none" autoCorrect="off" - onChange={(e) => checkTicket(e)} + onChange={(e: React.ChangeEvent) => + checkTicket(e) + } /> {!inputDisabled ? null : } @@ -210,7 +226,7 @@ const WalletModal = () => { name="xpub" autoCapitalize="none" autoCorrect="off" - onChange={(e) => checkXPub(e)} + onChange={(e: React.ChangeEvent) => checkXPub(e)} mr={1} /> {!inputDisabled ? null : } diff --git a/pkg/btc-wallet/src/js/components/lib/warning.js b/pkg/btc-wallet/src/components/Warning.tsx similarity index 91% rename from pkg/btc-wallet/src/js/components/lib/warning.js rename to pkg/btc-wallet/src/components/Warning.tsx index d0183b7e50..0bc5814c49 100644 --- a/pkg/btc-wallet/src/js/components/lib/warning.js +++ b/pkg/btc-wallet/src/components/Warning.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { Box, Text, Button, Col, Anchor } from '@tlon/indigo-react'; -import { api } from '../../api'; -import { useSettings } from '../../hooks/useSettings'; +import { api } from '../lib/api'; +import { useSettings } from '../hooks/useSettings'; const Warning = () => { - const { setWarning } = useSettings(); + const { setShowWarning } = useSettings(); const understand = () => { - setWarning(false); + setShowWarning(false); let removeWarning = { 'put-entry': { value: false, diff --git a/pkg/btc-wallet/src/js/hooks/useSettings.js b/pkg/btc-wallet/src/hooks/useSettings.tsx similarity index 71% rename from pkg/btc-wallet/src/js/hooks/useSettings.js rename to pkg/btc-wallet/src/hooks/useSettings.tsx index fe2838e5f3..a83fa5f760 100644 --- a/pkg/btc-wallet/src/js/hooks/useSettings.js +++ b/pkg/btc-wallet/src/hooks/useSettings.tsx @@ -1,19 +1,76 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; import _ from 'lodash'; -import { api } from '../api'; +import { api } from '../lib/api'; import { mapDenominationToSymbol, reduceHistory } from '../lib/util'; +import { + CurrencyRate, + Denomination, + Network, + Provider, + ProviderPerms, + ScanProgress, + ShipWallets, + Transaction, + TxidType, +} from '../types'; -export const SettingsContext = createContext({ +type SettingsContextType = { + network: Network; + setNetwork: React.Dispatch>; + loadedBtc: boolean; + setLoadedBtc: React.Dispatch>; + loadedSettings: boolean; + setLoadedSettings: React.Dispatch>; + loaded: boolean; + setLoaded: React.Dispatch>; + providerPerms: ProviderPerms; + setProviderPerms: React.Dispatch>; + shipWallets: ShipWallets; + setShipWallets: React.Dispatch>; + provider: Provider; + setProvider: React.Dispatch>; + wallet: string | null; + setWallet: React.Dispatch>; + confirmedBalance: number; + setConfirmedBalance: React.Dispatch>; + unconfirmedBalance: number; + setUnconfirmedBalance: React.Dispatch>; + btcState: any; + setBtcState: React.Dispatch>; + history: Transaction[]; + setHistory: React.Dispatch>; + fee: number; + setFee: React.Dispatch>; + psbt: string; + setPsbt: React.Dispatch>; + address: string | null; + setAddress: React.Dispatch>; + currencyRates: CurrencyRate; + setCurrencyRates: React.Dispatch>; + denomination: Denomination; + setDenomination: React.Dispatch>; + showWarning: boolean; + setShowWarning: React.Dispatch>; + error: string; + setError: React.Dispatch>; + broadcastSuccess: boolean; + setBroadcastSuccess: React.Dispatch>; + scanProgress: ScanProgress; + setScanProgress: React.Dispatch>; +}; + +export const SettingsContext = createContext({ network: 'bitcoin', + setNetwork: () => {}, loadedBtc: false, setLoadedBtc: () => {}, loadedSettings: false, setLoadedSettings: () => {}, loaded: false, setLoaded: () => {}, - providerPerms: {}, + providerPerms: { provider: '', permitted: false }, setProviderPerms: () => {}, - shipWallets: {}, + shipWallets: { payee: '', hasWallet: false }, setShipWallets: () => {}, provider: null, setProvider: () => {}, @@ -36,7 +93,9 @@ export const SettingsContext = createContext({ currencyRates: { BTC: { last: 1, symbol: 'BTC' }, }, + setCurrencyRates: () => {}, denomination: 'BTC', + setDenomination: () => {}, showWarning: true, setShowWarning: () => {}, error: '', @@ -47,14 +106,24 @@ export const SettingsContext = createContext({ setScanProgress: () => {}, }); -export const SettingsProvider = ({ channel, children }) => { - const [network, setNetwork] = useState('bitcoin'); +type Props = { + channel: { setOnChannelError: (arg: () => void) => void }; +}; + +export const SettingsProvider: React.FC = ({ channel, children }) => { + const [network, setNetwork] = useState('bitcoin'); const [channelData, setChannelData] = useState(null); const [loadedBtc, setLoadedBtc] = useState(false); const [loadedSettings, setLoadedSettings] = useState(false); const [loaded, setLoaded] = useState(false); - const [providerPerms, setProviderPerms] = useState({}); - const [shipWallets, setShipWallets] = useState({}); + const [providerPerms, setProviderPerms] = useState({ + provider: '', + permitted: false, + }); + const [shipWallets, setShipWallets] = useState({ + payee: '', + hasWallet: false, + }); const [provider, setProvider] = useState(null); const [wallet, setWallet] = useState(null); const [confirmedBalance, setConfirmedBalance] = useState(0); @@ -67,7 +136,7 @@ export const SettingsProvider = ({ channel, children }) => { const [currencyRates, setCurrencyRates] = useState({ BTC: { last: 1, symbol: 'BTC' }, }); - const [denomination, setDenomination] = useState('BTC'); + const [denomination, setDenomination] = useState('BTC'); const [showWarning, setShowWarning] = useState(false); const [error, setError] = useState(''); const [broadcastSuccess, setBroadcastSuccess] = useState(false); @@ -78,11 +147,11 @@ export const SettingsProvider = ({ channel, children }) => { const { Provider } = SettingsContext; - const success = (event) => { + const success = (event: any) => { console.log({ event }); setChannelData(event); }; - const fail = (error) => console.log({ error }); + const fail = (error: any) => console.log({ error }); const initializeBtcWallet = () => { api.bind('/all', 'PUT', api.ship, 'btc-wallet', success, fail); @@ -123,7 +192,7 @@ export const SettingsProvider = ({ channel, children }) => { fetch('https://blockchain.info/ticker') .then((res) => res.json()) .then((n) => { - const newCurrencyRates = currencyRates; + const newCurrencyRates: any = currencyRates; for (let c in n) { newCurrencyRates[c] = n[c]; newCurrencyRates[c].symbol = mapDenominationToSymbol(c); @@ -141,13 +210,13 @@ export const SettingsProvider = ({ channel, children }) => { } }; - const handleNewTx = (newTx) => { + const handleNewTx = (newTx: Transaction) => { const { txid, recvd } = newTx; - let old = _.findIndex(history, (h) => { + let old = _.findIndex(history, (h: Transaction) => { return h.txid.dat === txid.dat && h.txid.wid === txid.wid; }); if (old !== -1) { - const newHistory = history.filter((o, i) => i !== old); + const newHistory = history.filter((_, i) => i !== old); setHistory(newHistory); } if (recvd === null && old === -1) { @@ -156,7 +225,7 @@ export const SettingsProvider = ({ channel, children }) => { } else if (recvd !== null && old === -1) { // we expect history to have null recvd values first, and the rest in // descending order - let insertionIndex = _.findIndex(history, (h) => { + let insertionIndex = _.findIndex(history, (h: Transaction) => { return h.recvd < recvd && h.recvd !== null; }); const newHistory = history.map((o, i) => @@ -166,8 +235,8 @@ export const SettingsProvider = ({ channel, children }) => { } }; - const handleCancelTx = ({ wid, dat }) => { - let entryIndex = _.findIndex(history, (h) => { + const handleCancelTx = ({ wid, dat }: TxidType) => { + let entryIndex = _.findIndex(history, (h: Transaction) => { return wid === h.txid.wid && dat === h.txid.dat; }); if (entryIndex > -1) { @@ -221,14 +290,14 @@ export const SettingsProvider = ({ channel, children }) => { handleNewTx(newTx); } if (providerStatus) { - let newProviderPerms = providerPerms; + let newProviderPerms: any = providerPerms; for (let c in providerStatus) { newProviderPerms[c] = providerStatus[c]; } setProviderPerms(newProviderPerms); } if (checkPayee) { - let newShipWallets = shipWallets; + let newShipWallets: any = shipWallets; for (let c in checkPayee) { newShipWallets[c] = checkPayee[c]; diff --git a/pkg/btc-wallet/src/index.js b/pkg/btc-wallet/src/index.tsx similarity index 56% rename from pkg/btc-wallet/src/index.js rename to pkg/btc-wallet/src/index.tsx index 09b51a1384..8257091480 100644 --- a/pkg/btc-wallet/src/index.js +++ b/pkg/btc-wallet/src/index.tsx @@ -1,17 +1,15 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import Root from './js/components/root.js'; -import { api } from './js/api.js'; -import { SettingsProvider } from './js/hooks/useSettings'; +import { api } from './lib/api'; +import { SettingsProvider } from './hooks/useSettings'; +import App from './App'; import './css/indigo-static.css'; import './css/fonts.css'; import './css/custom.css'; -// rebuild x3 - -const channel = new window.channel(); -api.setChannel(window.ship, channel); +const channel = new (window as any).channel(); +api.setChannel((window as any).ship, channel); if (module.hot) { module.hot.accept(); @@ -19,7 +17,7 @@ if (module.hot) { ReactDOM.render( - + , document.querySelectorAll('#root')[0] ); diff --git a/pkg/btc-wallet/src/js/components/lib/error.js b/pkg/btc-wallet/src/js/components/lib/error.js deleted file mode 100644 index 42d114d182..0000000000 --- a/pkg/btc-wallet/src/js/components/lib/error.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { Text } from '@tlon/indigo-react'; - -const errorToString = (error) => { - if (error === 'cant-pay-ourselves') { - return 'Cannot pay ourselves'; - } - if (error === 'no-comets') { - return 'Cannot pay comets'; - } - if (error === 'no-dust') { - return 'Cannot send dust'; - } - if (error === 'tx-being-signed') { - return 'Cannot pay when transaction is being signed'; - } - if (error === 'insufficient-balance') { - return 'Insufficient confirmed balance'; - } - if (error === 'broadcast-fail') { - return 'Transaction broadcast failed'; - } - if (error === 'invalid-master-ticket') { - return 'Invalid master ticket'; - } - if (error === 'invalid-signed') { - return 'Invalid signed bitcoin transaction'; - } -}; - -const Error = ({ error, ...rest }) => ( - - {errorToString(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 deleted file mode 100644 index febbd5d10c..0000000000 --- a/pkg/btc-wallet/src/js/components/lib/feePicker.js +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import { - Box, - Text, - Col, - StatelessRadioButtonField as RadioButton, - Label, -} from '@tlon/indigo-react'; -import { feeLevels } from './send'; - -const FeePicker = ({ feeChoices, feeValue, setFeeValue, feeDismiss }) => { - const select = (which) => { - setFeeValue(feeLevels[which]); - feeDismiss(); - }; - - return ( - - - Transaction Speed - - - { - select(feeLevels.low); - }} - > - - - - { - select(feeLevels.low); - }} - > - - - - { - select(feeLevels.high); - }} - > - - - - - ); -}; - -export default FeePicker; diff --git a/pkg/btc-wallet/src/js/components/themes/dark.js b/pkg/btc-wallet/src/js/components/themes/dark.js deleted file mode 100644 index a9d048bf71..0000000000 --- a/pkg/btc-wallet/src/js/components/themes/dark.js +++ /dev/null @@ -1,183 +0,0 @@ -import baseStyled from "styled-components"; - -const base = { - white: "rgba(255,255,255,1)", - black: "rgba(0,0,0,1)", - red: "rgba(255,65,54,1)", - yellow: "rgba(255,199,0,1)", - green: "rgba(0,159,101,1)", - blue: "rgba(0,142,255,1)", -}; - -const scales = { - white05: "rgba(255,255,255,0.05)", - white10: "rgba(255,255,255,0.1)", - white20: "rgba(255,255,255,0.2)", - white30: "rgba(255,255,255,0.3)", - white40: "rgba(255,255,255,0.4)", - white50: "rgba(255,255,255,0.5)", - white60: "rgba(255,255,255,0.6)", - white70: "rgba(255,255,255,0.7)", - white80: "rgba(255,255,255,0.8)", - white90: "rgba(255,255,255,0.9)", - white100: "rgba(255,255,255,1)", - black05: "rgba(0,0,0,0.05)", - black10: "rgba(0,0,0,0.1)", - black20: "rgba(0,0,0,0.2)", - black30: "rgba(0,0,0,0.3)", - black40: "rgba(0,0,0,0.4)", - black50: "rgba(0,0,0,0.5)", - black60: "rgba(0,0,0,0.6)", - black70: "rgba(0,0,0,0.7)", - black80: "rgba(0,0,0,0.8)", - black90: "rgba(0,0,0,0.9)", - black100: "rgba(0,0,0,1)", - red05: "rgba(255,65,54,0.05)", - red10: "rgba(255,65,54,0.1)", - red20: "rgba(255,65,54,0.2)", - red30: "rgba(255,65,54,0.3)", - red40: "rgba(255,65,54,0.4)", - red50: "rgba(255,65,54,0.5)", - red60: "rgba(255,65,54,0.6)", - red70: "rgba(255,65,54,0.7)", - red80: "rgba(255,65,54,0.8)", - red90: "rgba(255,65,54,0.9)", - red100: "rgba(255,65,54,1)", - yellow05: "rgba(255,199,0,0.05)", - yellow10: "rgba(255,199,0,0.1)", - yellow20: "rgba(255,199,0,0.2)", - yellow30: "rgba(255,199,0,0.3)", - yellow40: "rgba(255,199,0,0.4)", - yellow50: "rgba(255,199,0,0.5)", - yellow60: "rgba(255,199,0,0.6)", - yellow70: "rgba(255,199,0,0.7)", - yellow80: "rgba(255,199,0,0.8)", - yellow90: "rgba(255,199,0,0.9)", - yellow100: "rgba(255,199,0,1)", - green05: "rgba(0,159,101,0.05)", - green10: "rgba(0,159,101,0.1)", - green20: "rgba(0,159,101,0.2)", - green30: "rgba(0,159,101,0.3)", - green40: "rgba(0,159,101,0.4)", - green50: "rgba(0,159,101,0.5)", - green60: "rgba(0,159,101,0.6)", - green70: "rgba(0,159,101,0.7)", - green80: "rgba(0,159,101,0.8)", - green90: "rgba(0,159,101,0.9)", - green100: "rgba(0,159,101,1)", - blue05: "rgba(0,142,255,0.05)", - blue10: "rgba(0,142,255,0.1)", - blue20: "rgba(0,142,255,0.2)", - blue30: "rgba(0,142,255,0.3)", - blue40: "rgba(0,142,255,0.4)", - blue50: "rgba(0,142,255,0.5)", - blue60: "rgba(0,142,255,0.6)", - blue70: "rgba(0,142,255,0.7)", - blue80: "rgba(0,142,255,0.8)", - blue90: "rgba(0,142,255,0.9)", - blue100: "rgba(0,142,255,1)", -}; - -const util = { - cyan: "#00FFFF", - magenta: "#FF00FF", - yellow: "#FFFF00", - black: "#000000", - gray0: "#333333" -}; - -const theme = { - colors: { - white: util.gray0, - black: base.white, - - gray: scales.white60, - lightGray: scales.white30, - washedGray: scales.white05, - - red: base.red, - lightRed: scales.red30, - washedRed: scales.red05, - - yellow: base.yellow, - lightYellow: scales.yellow30, - washedYellow: scales.yellow10, - - green: base.green, - lightGreen: scales.green30, - washedGreen: scales.green10, - - blue: base.blue, - lightBlue: scales.blue30, - washedBlue: scales.blue10, - - none: "rgba(0,0,0,0)", - - scales: scales, - util: util, - }, - fonts: { - sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`, - mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`, - }, - // font-size - fontSizes: [ - 12, // 0 - 16, // 1 - 24, // 2 - 32, // 3 - 48, // 4 - 64, // 5 - ], - // font-weight - fontWeights: { - thin: 300, - regular: 400, - bold: 600, - }, - // line-height - lineHeights: { - min: 1.2, - short: 1.333333, - regular: 1.5, - tall: 1.666666, - }, - // border, border-top, border-right, border-bottom, border-left - borders: ["none", "1px solid"], - // margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap - space: [ - 0, // 0 - 4, // 1 - 8, // 2 - 16, // 3 - 24, // 4 - 32, // 5 - 48, // 6 - 64, // 7 - 96, // 8 - ], - // border-radius - radii: [ - 0, // 0 - 2, // 1 - 4, // 2 - 8, // 3 - ], - // width, height, min-width, max-width, min-height, max-height - sizes: [ - 0, // 0 - 4, // 1 - 8, // 2 - 16, // 3 - 24, // 4 - 32, // 5 - 48, // 6 - 64, // 7 - 96, // 8 - ], - // z-index - zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - breakpoints: ["550px", "750px", "960px"], -}; -export const styled = baseStyled; -export default theme; diff --git a/pkg/btc-wallet/src/js/components/themes/light.js b/pkg/btc-wallet/src/js/components/themes/light.js deleted file mode 100644 index 5688e07fe7..0000000000 --- a/pkg/btc-wallet/src/js/components/themes/light.js +++ /dev/null @@ -1,184 +0,0 @@ -import baseStyled from "styled-components"; - -const base = { - white: "rgba(255,255,255,1)", - black: "rgba(0,0,0,1)", - red: "rgba(255,65,54,1)", - yellow: "rgba(255,199,0,1)", - green: "rgba(0,159,101,1)", - blue: "rgba(0,142,255,1)", - none: "rgba(0,0,0,0)", -}; - -const scales = { - white10: "rgba(255,255,255,0.1)", - white20: "rgba(255,255,255,0.2)", - white30: "rgba(255,255,255,0.3)", - white40: "rgba(255,255,255,0.4)", - white50: "rgba(255,255,255,0.5)", - white60: "rgba(255,255,255,0.6)", - white70: "rgba(255,255,255,0.7)", - white80: "rgba(255,255,255,0.8)", - white90: "rgba(255,255,255,0.9)", - white100: "rgba(255,255,255,1)", - black04: "rgba(0,0,0,0.04)", - black10: "rgba(0,0,0,0.1)", - black20: "rgba(0,0,0,0.2)", - black30: "rgba(0,0,0,0.3)", - black40: "rgba(0,0,0,0.4)", - black50: "rgba(0,0,0,0.5)", - black60: "rgba(0,0,0,0.6)", - black70: "rgba(0,0,0,0.7)", - black80: "rgba(0,0,0,0.8)", - black90: "rgba(0,0,0,0.9)", - black100: "rgba(0,0,0,1)", - red05: "rgba(255,65,54,0.05)", - red10: "rgba(255,65,54,0.1)", - red20: "rgba(255,65,54,0.2)", - red30: "rgba(255,65,54,0.3)", - red40: "rgba(255,65,54,0.4)", - red50: "rgba(255,65,54,0.5)", - red60: "rgba(255,65,54,0.6)", - red70: "rgba(255,65,54,0.7)", - red80: "rgba(255,65,54,0.8)", - red90: "rgba(255,65,54,0.9)", - red100: "rgba(255,65,54,1)", - yellow10: "rgba(255,199,0,0.1)", - yellow20: "rgba(255,199,0,0.2)", - yellow30: "rgba(255,199,0,0.3)", - yellow40: "rgba(255,199,0,0.4)", - yellow50: "rgba(255,199,0,0.5)", - yellow60: "rgba(255,199,0,0.6)", - yellow70: "rgba(255,199,0,0.7)", - yellow80: "rgba(255,199,0,0.8)", - yellow90: "rgba(255,199,0,0.9)", - yellow100: "rgba(255,199,0,1)", - green05: "rgba(0,159,101,0.05)", - green10: "rgba(0,159,101,0.1)", - green20: "rgba(0,159,101,0.2)", - green30: "rgba(0,159,101,0.3)", - green40: "rgba(0,159,101,0.4)", - green50: "rgba(0,159,101,0.5)", - green60: "rgba(0,159,101,0.6)", - green70: "rgba(0,159,101,0.7)", - green80: "rgba(0,159,101,0.8)", - green90: "rgba(0,159,101,0.9)", - green100: "rgba(0,159,101,1)", - blue10: "rgba(0,142,255,0.1)", - blue20: "rgba(0,142,255,0.2)", - blue30: "rgba(0,142,255,0.3)", - blue40: "rgba(0,142,255,0.4)", - blue50: "rgba(0,142,255,0.5)", - blue60: "rgba(0,142,255,0.6)", - blue70: "rgba(0,142,255,0.7)", - blue80: "rgba(0,142,255,0.8)", - blue90: "rgba(0,142,255,0.9)", - blue100: "rgba(0,142,255,1)", -}; - -const theme = { - colors: { - white: base.white, - black: base.black, - - gray: scales.black60, - lighterGray: scales.black20, - lightGray: scales.black30, - washedGray: scales.black10, - veryLightGray: scales.black04, - - red: base.red, - lightRed: scales.red30, - washedRed: scales.red10, - veryLightRed: scales.red05, - - yellow: base.yellow, - lightYellow: scales.yellow30, - washedYellow: scales.yellow10, - - green: base.green, - lightGreen: scales.green30, - midGreen: scales.green60, - washedGreen: scales.green10, - veryLightGreen: scales.green05, - - blue: base.blue, - lightBlue: scales.blue30, - washedBlue: scales.blue10, - - none: "rgba(0,0,0,0)", - scales: scales, - - orange: "rgba(255, 153, 0, 1)", - midOrange: "rgba(255, 153, 0, 0.2)", - lightOrange: "rgba(255, 153, 0, 0.08)", - - sentBlue: "rgba(33,157,255,1)", - recvGreen: "rgba(0,159,101,1)", - }, - fonts: { - sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`, - mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`, - }, - // font-size - fontSizes: [ - 12, // 0 - 16, // 1 - 24, // 2 - 32, // 3 - 48, // 4 - 64, // 5 - ], - // font-weight - fontWeights: { - thin: 300, - regular: 400, - bold: 600, - }, - // line-height - lineHeights: { - min: 1.2, - short: 1.333333, - regular: 1.5, - tall: 1.666666, - }, - // border, border-top, border-right, border-bottom, border-left - borders: ["none", "1px solid"], - // margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap - space: [ - 0, // 0 - 4, // 1 - 8, // 2 - 16, // 3 - 24, // 4 - 32, // 5 - 48, // 6 - 64, // 7 - 96, // 8 - ], - // border-radius - radii: [ - 0, // 0 - 2, // 1 - 4, // 2 - 8, // 3 - 16, // 4 - ], - // width, height, min-width, max-width, min-height, max-height - sizes: [ - 0, // 0 - 4, // 1 - 8, // 2 - 16, // 3 - 24, // 4 - 32, // 5 - 48, // 6 - 64, // 7 - 96, // 8 - ], - // z-index - zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - breakpoints: ["550px", "750px", "960px"], -}; -export const styled = baseStyled; -export default theme; diff --git a/pkg/btc-wallet/src/js/subscription.js b/pkg/btc-wallet/src/js/subscription.js deleted file mode 100644 index d83f65d92b..0000000000 --- a/pkg/btc-wallet/src/js/subscription.js +++ /dev/null @@ -1,55 +0,0 @@ -import { api } from './api'; -import { store } from './store'; - -export class Subscription { - start() { - if (api.ship) { - this.initializeBtcWallet(); - this.initializeSettings(); - this.initializeCurrencyPoll(); - } else { - console.error("~~~ ERROR: Must set api.authTokens before operation ~~~"); - } - } - - initializeBtcWallet() { - api.bind('/all', 'PUT', api.ship, 'btc-wallet', - this.handleEvent.bind(this), - this.handleError.bind(this)); - } - - initializeSettings() { - let app = 'settings-store'; - let path = '/bucket/btc-wallet'; - - fetch(`/~/scry/${app}${path}.json`).then(res => res.json()) - .then(n => { - this.handleEvent({data: n}); - }); - - api.bind(path, 'PUT', api.ship, app, - this.handleEvent.bind(this), - this.handleError.bind(this)); - } - - initializeCurrencyPoll() { - fetch('https://blockchain.info/ticker') - .then(res => res.json()) - .then(n => { - store.handleEvent({data: {currencyRates: n}}) - setTimeout(this.initializeCurrencyPoll, 1000 * 60 * 15); - }); - } - - handleEvent(diff) { - store.handleEvent(diff); - } - - handleError(err) { - console.error(err); - this.initializeBtcWallet(); - this.initializeSettings(); - } -} - -export let subscription = new Subscription(); diff --git a/pkg/btc-wallet/src/js/api.js b/pkg/btc-wallet/src/lib/api.ts similarity index 51% rename from pkg/btc-wallet/src/js/api.js rename to pkg/btc-wallet/src/lib/api.ts index 6eadba0154..b2b2253722 100644 --- a/pkg/btc-wallet/src/js/api.js +++ b/pkg/btc-wallet/src/lib/api.ts @@ -1,16 +1,44 @@ import _ from 'lodash'; +type Channel = { + poke: ( + ship: string, + appl: string, + mark: string, + data: any, + postDataHandler: (json: any) => void, + errorHandler: (err: string) => void + ) => void; + subscribe: ( + ship: string, + appl: string, + path: string, + errorHandler: (err: string) => void, + eventHandler: (event: any) => void + ) => void; +}; + class UrbitApi { - setChannel(ship, channel) { + ship: string; + channel: Channel; + bindPaths: string[]; + setChannel(ship: string, channel: Channel) { this.ship = ship; this.channel = channel; this.bindPaths = []; } - bind(path, method, ship = this.ship, appl = 'btc-wallet', success, fail) { + bind( + path: string, + method: string, + ship = this.ship, + appl = 'btc-wallet', + success: any, + fail: any + ) { this.bindPaths = _.uniq([...this.bindPaths, path]); - window.subscriptionId = this.channel.subscribe( + (window as any).subscriptionId = this.channel.subscribe( ship, appl, path, @@ -25,22 +53,19 @@ class UrbitApi { path, }, }); - }, - (err) => { - fail(err); } ); } - btcWalletCommand(data) { + btcWalletCommand(data: any) { return this.action('btc-wallet', 'btc-wallet-command', data); } - settingsEvent(data) { + settingsEvent(data: any) { return this.action('settings-store', 'settings-event', data); } - action(appl, mark, data) { + action(appl: string, mark: string, data: string) { return new Promise((resolve, reject) => { this.channel.poke( this.ship, @@ -58,4 +83,4 @@ class UrbitApi { } } export let api = new UrbitApi(); -window.api = api; +(window as any).api = api; diff --git a/pkg/btc-wallet/src/js/lib/util.js b/pkg/btc-wallet/src/lib/util.ts similarity index 79% rename from pkg/btc-wallet/src/js/lib/util.js rename to pkg/btc-wallet/src/lib/util.ts index fa17b1ec80..56967a107d 100644 --- a/pkg/btc-wallet/src/js/lib/util.js +++ b/pkg/btc-wallet/src/lib/util.ts @@ -1,3 +1,5 @@ +import { CurrencyRate, Denomination, Transaction } from '../types'; + export function uuid() { let str = '0v'; str += Math.ceil(Math.random() * 8) + '.'; @@ -10,7 +12,7 @@ export function uuid() { return str.slice(0, -1); } -export function isPatTa(str) { +export function isPatTa(str: string) { const r = /^[a-z,0-9,\-,.,_,~]+$/.exec(str); return !!r; } @@ -21,8 +23,8 @@ export function isPatTa(str) { To: (javascript Date object) */ -export function daToDate(st) { - var dub = function (n) { +export function daToDate(st: string) { + var dub = function (n: string) { return parseInt(n) < 10 ? '0' + parseInt(n) : n.toString(); }; var da = st.split('..'); @@ -41,8 +43,8 @@ export function daToDate(st) { ~2018.7.17..23.15.09..5be5 // urbit @da */ -export function dateToDa(d, mil) { - var fil = function (n) { +export function dateToDa(d: Date, mil: boolean) { + var fil = function (n: number) { return n >= 10 ? n : '0' + n; }; return ( @@ -56,12 +58,12 @@ export function dateToDa(d, mil) { ); } -export function deSig(ship) { +export function deSig(ship: string) { return ship.replace('~', ''); } // trim patps to match dojo, chat-cli -export function cite(ship) { +export function cite(ship: string) { let patp = ship, shortened = ''; if (patp.startsWith('~')) { @@ -80,7 +82,11 @@ export function cite(ship) { return `~${patp}`; } -export function satsToCurrency(sats, denomination, rates) { +export function satsToCurrency( + sats: number, + denomination: Denomination, + rates: CurrencyRate +) { if (!rates) { throw 'nonexistent currency table'; } @@ -99,27 +105,7 @@ export function satsToCurrency(sats, denomination, rates) { return text; } -export function currencyToSats(val, denomination, rates) { - if (!rates) { - throw 'nonexistent currency 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; - }); -} - -export function mapDenominationToSymbol(denomination) { +export function mapDenominationToSymbol(denomination: string) { switch (denomination) { case 'USD': return '$'; @@ -128,7 +114,15 @@ export function mapDenominationToSymbol(denomination) { } } -export function copyToClipboard(textToCopy) { +export function reduceHistory(history: Transaction[]) { + return Object.values(history).sort((hest1, hest2) => { + if (hest1.recvd === null) return -1; + if (hest2.recvd === null) return +1; + return hest2.recvd - hest1.recvd; + }); +} + +export function copyToClipboard(textToCopy: string) { // navigator clipboard api needs a secure context (https or localhost) if (navigator.clipboard && window.isSecureContext) { return navigator.clipboard.writeText(textToCopy); @@ -141,7 +135,7 @@ export function copyToClipboard(textToCopy) { document.body.appendChild(textArea); textArea.focus(); textArea.select(); - return new Promise((res, rej) => { + return new Promise((res, rej) => { document.execCommand('copy') ? res() : rej(); textArea.remove(); }); diff --git a/pkg/btc-wallet/src/themes/dark.js b/pkg/btc-wallet/src/themes/dark.js new file mode 100644 index 0000000000..5095ef4686 --- /dev/null +++ b/pkg/btc-wallet/src/themes/dark.js @@ -0,0 +1,183 @@ +import baseStyled from 'styled-components'; + +const base = { + white: 'rgba(255,255,255,1)', + black: 'rgba(0,0,0,1)', + red: 'rgba(255,65,54,1)', + yellow: 'rgba(255,199,0,1)', + green: 'rgba(0,159,101,1)', + blue: 'rgba(0,142,255,1)', +}; + +const scales = { + white05: 'rgba(255,255,255,0.05)', + white10: 'rgba(255,255,255,0.1)', + white20: 'rgba(255,255,255,0.2)', + white30: 'rgba(255,255,255,0.3)', + white40: 'rgba(255,255,255,0.4)', + white50: 'rgba(255,255,255,0.5)', + white60: 'rgba(255,255,255,0.6)', + white70: 'rgba(255,255,255,0.7)', + white80: 'rgba(255,255,255,0.8)', + white90: 'rgba(255,255,255,0.9)', + white100: 'rgba(255,255,255,1)', + black05: 'rgba(0,0,0,0.05)', + black10: 'rgba(0,0,0,0.1)', + black20: 'rgba(0,0,0,0.2)', + black30: 'rgba(0,0,0,0.3)', + black40: 'rgba(0,0,0,0.4)', + black50: 'rgba(0,0,0,0.5)', + black60: 'rgba(0,0,0,0.6)', + black70: 'rgba(0,0,0,0.7)', + black80: 'rgba(0,0,0,0.8)', + black90: 'rgba(0,0,0,0.9)', + black100: 'rgba(0,0,0,1)', + red05: 'rgba(255,65,54,0.05)', + red10: 'rgba(255,65,54,0.1)', + red20: 'rgba(255,65,54,0.2)', + red30: 'rgba(255,65,54,0.3)', + red40: 'rgba(255,65,54,0.4)', + red50: 'rgba(255,65,54,0.5)', + red60: 'rgba(255,65,54,0.6)', + red70: 'rgba(255,65,54,0.7)', + red80: 'rgba(255,65,54,0.8)', + red90: 'rgba(255,65,54,0.9)', + red100: 'rgba(255,65,54,1)', + yellow05: 'rgba(255,199,0,0.05)', + yellow10: 'rgba(255,199,0,0.1)', + yellow20: 'rgba(255,199,0,0.2)', + yellow30: 'rgba(255,199,0,0.3)', + yellow40: 'rgba(255,199,0,0.4)', + yellow50: 'rgba(255,199,0,0.5)', + yellow60: 'rgba(255,199,0,0.6)', + yellow70: 'rgba(255,199,0,0.7)', + yellow80: 'rgba(255,199,0,0.8)', + yellow90: 'rgba(255,199,0,0.9)', + yellow100: 'rgba(255,199,0,1)', + green05: 'rgba(0,159,101,0.05)', + green10: 'rgba(0,159,101,0.1)', + green20: 'rgba(0,159,101,0.2)', + green30: 'rgba(0,159,101,0.3)', + green40: 'rgba(0,159,101,0.4)', + green50: 'rgba(0,159,101,0.5)', + green60: 'rgba(0,159,101,0.6)', + green70: 'rgba(0,159,101,0.7)', + green80: 'rgba(0,159,101,0.8)', + green90: 'rgba(0,159,101,0.9)', + green100: 'rgba(0,159,101,1)', + blue05: 'rgba(0,142,255,0.05)', + blue10: 'rgba(0,142,255,0.1)', + blue20: 'rgba(0,142,255,0.2)', + blue30: 'rgba(0,142,255,0.3)', + blue40: 'rgba(0,142,255,0.4)', + blue50: 'rgba(0,142,255,0.5)', + blue60: 'rgba(0,142,255,0.6)', + blue70: 'rgba(0,142,255,0.7)', + blue80: 'rgba(0,142,255,0.8)', + blue90: 'rgba(0,142,255,0.9)', + blue100: 'rgba(0,142,255,1)', +}; + +const util = { + cyan: '#00FFFF', + magenta: '#FF00FF', + yellow: '#FFFF00', + black: '#000000', + gray0: '#333333', +}; + +const theme = { + colors: { + white: util.gray0, + black: base.white, + + gray: scales.white60, + lightGray: scales.white30, + washedGray: scales.white05, + + red: base.red, + lightRed: scales.red30, + washedRed: scales.red05, + + yellow: base.yellow, + lightYellow: scales.yellow30, + washedYellow: scales.yellow10, + + green: base.green, + lightGreen: scales.green30, + washedGreen: scales.green10, + + blue: base.blue, + lightBlue: scales.blue30, + washedBlue: scales.blue10, + + none: 'rgba(0,0,0,0)', + + scales: scales, + util: util, + }, + fonts: { + sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`, + mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`, + }, + // font-size + fontSizes: [ + 12, // 0 + 16, // 1 + 24, // 2 + 32, // 3 + 48, // 4 + 64, // 5 + ], + // font-weight + fontWeights: { + thin: 300, + regular: 400, + bold: 600, + }, + // line-height + lineHeights: { + min: 1.2, + short: 1.333333, + regular: 1.5, + tall: 1.666666, + }, + // border, border-top, border-right, border-bottom, border-left + borders: ['none', '1px solid'], + // margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap + space: [ + 0, // 0 + 4, // 1 + 8, // 2 + 16, // 3 + 24, // 4 + 32, // 5 + 48, // 6 + 64, // 7 + 96, // 8 + ], + // border-radius + radii: [ + 0, // 0 + 2, // 1 + 4, // 2 + 8, // 3 + ], + // width, height, min-width, max-width, min-height, max-height + sizes: [ + 0, // 0 + 4, // 1 + 8, // 2 + 16, // 3 + 24, // 4 + 32, // 5 + 48, // 6 + 64, // 7 + 96, // 8 + ], + // z-index + zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + breakpoints: ['550px', '750px', '960px'], +}; +export const styled = baseStyled; +export default theme; diff --git a/pkg/btc-wallet/src/themes/light.js b/pkg/btc-wallet/src/themes/light.js new file mode 100644 index 0000000000..6dd797c696 --- /dev/null +++ b/pkg/btc-wallet/src/themes/light.js @@ -0,0 +1,184 @@ +import baseStyled from 'styled-components'; + +const base = { + white: 'rgba(255,255,255,1)', + black: 'rgba(0,0,0,1)', + red: 'rgba(255,65,54,1)', + yellow: 'rgba(255,199,0,1)', + green: 'rgba(0,159,101,1)', + blue: 'rgba(0,142,255,1)', + none: 'rgba(0,0,0,0)', +}; + +const scales = { + white10: 'rgba(255,255,255,0.1)', + white20: 'rgba(255,255,255,0.2)', + white30: 'rgba(255,255,255,0.3)', + white40: 'rgba(255,255,255,0.4)', + white50: 'rgba(255,255,255,0.5)', + white60: 'rgba(255,255,255,0.6)', + white70: 'rgba(255,255,255,0.7)', + white80: 'rgba(255,255,255,0.8)', + white90: 'rgba(255,255,255,0.9)', + white100: 'rgba(255,255,255,1)', + black04: 'rgba(0,0,0,0.04)', + black10: 'rgba(0,0,0,0.1)', + black20: 'rgba(0,0,0,0.2)', + black30: 'rgba(0,0,0,0.3)', + black40: 'rgba(0,0,0,0.4)', + black50: 'rgba(0,0,0,0.5)', + black60: 'rgba(0,0,0,0.6)', + black70: 'rgba(0,0,0,0.7)', + black80: 'rgba(0,0,0,0.8)', + black90: 'rgba(0,0,0,0.9)', + black100: 'rgba(0,0,0,1)', + red05: 'rgba(255,65,54,0.05)', + red10: 'rgba(255,65,54,0.1)', + red20: 'rgba(255,65,54,0.2)', + red30: 'rgba(255,65,54,0.3)', + red40: 'rgba(255,65,54,0.4)', + red50: 'rgba(255,65,54,0.5)', + red60: 'rgba(255,65,54,0.6)', + red70: 'rgba(255,65,54,0.7)', + red80: 'rgba(255,65,54,0.8)', + red90: 'rgba(255,65,54,0.9)', + red100: 'rgba(255,65,54,1)', + yellow10: 'rgba(255,199,0,0.1)', + yellow20: 'rgba(255,199,0,0.2)', + yellow30: 'rgba(255,199,0,0.3)', + yellow40: 'rgba(255,199,0,0.4)', + yellow50: 'rgba(255,199,0,0.5)', + yellow60: 'rgba(255,199,0,0.6)', + yellow70: 'rgba(255,199,0,0.7)', + yellow80: 'rgba(255,199,0,0.8)', + yellow90: 'rgba(255,199,0,0.9)', + yellow100: 'rgba(255,199,0,1)', + green05: 'rgba(0,159,101,0.05)', + green10: 'rgba(0,159,101,0.1)', + green20: 'rgba(0,159,101,0.2)', + green30: 'rgba(0,159,101,0.3)', + green40: 'rgba(0,159,101,0.4)', + green50: 'rgba(0,159,101,0.5)', + green60: 'rgba(0,159,101,0.6)', + green70: 'rgba(0,159,101,0.7)', + green80: 'rgba(0,159,101,0.8)', + green90: 'rgba(0,159,101,0.9)', + green100: 'rgba(0,159,101,1)', + blue10: 'rgba(0,142,255,0.1)', + blue20: 'rgba(0,142,255,0.2)', + blue30: 'rgba(0,142,255,0.3)', + blue40: 'rgba(0,142,255,0.4)', + blue50: 'rgba(0,142,255,0.5)', + blue60: 'rgba(0,142,255,0.6)', + blue70: 'rgba(0,142,255,0.7)', + blue80: 'rgba(0,142,255,0.8)', + blue90: 'rgba(0,142,255,0.9)', + blue100: 'rgba(0,142,255,1)', +}; + +const theme = { + colors: { + white: base.white, + black: base.black, + + gray: scales.black60, + lighterGray: scales.black20, + lightGray: scales.black30, + washedGray: scales.black10, + veryLightGray: scales.black04, + + red: base.red, + lightRed: scales.red30, + washedRed: scales.red10, + veryLightRed: scales.red05, + + yellow: base.yellow, + lightYellow: scales.yellow30, + washedYellow: scales.yellow10, + + green: base.green, + lightGreen: scales.green30, + midGreen: scales.green60, + washedGreen: scales.green10, + veryLightGreen: scales.green05, + + blue: base.blue, + lightBlue: scales.blue30, + washedBlue: scales.blue10, + + none: 'rgba(0,0,0,0)', + scales: scales, + + orange: 'rgba(255, 153, 0, 1)', + midOrange: 'rgba(255, 153, 0, 0.2)', + lightOrange: 'rgba(255, 153, 0, 0.08)', + + sentBlue: 'rgba(33,157,255,1)', + recvGreen: 'rgba(0,159,101,1)', + }, + fonts: { + sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`, + mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`, + }, + // font-size + fontSizes: [ + 12, // 0 + 16, // 1 + 24, // 2 + 32, // 3 + 48, // 4 + 64, // 5 + ], + // font-weight + fontWeights: { + thin: 300, + regular: 400, + bold: 600, + }, + // line-height + lineHeights: { + min: 1.2, + short: 1.333333, + regular: 1.5, + tall: 1.666666, + }, + // border, border-top, border-right, border-bottom, border-left + borders: ['none', '1px solid'], + // margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap + space: [ + 0, // 0 + 4, // 1 + 8, // 2 + 16, // 3 + 24, // 4 + 32, // 5 + 48, // 6 + 64, // 7 + 96, // 8 + ], + // border-radius + radii: [ + 0, // 0 + 2, // 1 + 4, // 2 + 8, // 3 + 16, // 4 + ], + // width, height, min-width, max-width, min-height, max-height + sizes: [ + 0, // 0 + 4, // 1 + 8, // 2 + 16, // 3 + 24, // 4 + 32, // 5 + 48, // 6 + 64, // 7 + 96, // 8 + ], + // z-index + zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + breakpoints: ['550px', '750px', '960px'], +}; +export const styled = baseStyled; +export default theme; diff --git a/pkg/btc-wallet/src/types.ts b/pkg/btc-wallet/src/types.ts new file mode 100644 index 0000000000..88a80269eb --- /dev/null +++ b/pkg/btc-wallet/src/types.ts @@ -0,0 +1,45 @@ +export type ProviderPerms = { + provider: string; + permitted: boolean; +}; + +export type ShipWallets = { + payee: string; + hasWallet: boolean; +}; + +export type Transaction = { + txid: TxidType; + recvd: number; + outputs: [{ ship: string; val: { value: number } }]; + inputs: [{ ship: string }]; + failure: string; +}; + +export type TxidType = { + dat: string; + wid: string; +}; + +export type ScanProgress = { + main: null | number; + change: null | number; +}; + +export type Network = 'bitcoin' | 'testnet'; + +export type Denomination = 'BTC' | 'USD'; + +export type UrbitWallet = { + bitcoinTestnet: { keys: { xpub: string; xprv: string } }; + bitcoinMainnet: { keys: { xpub: string; xprv: string } }; +}; + +export type CurrencyRate = { + [Denomination: string]: { last: number; symbol: string }; +}; + +export type Provider = { + host: string; + connected: boolean; +}; diff --git a/pkg/btc-wallet/src/urbit-key-generation.d.ts b/pkg/btc-wallet/src/urbit-key-generation.d.ts new file mode 100644 index 0000000000..f383788ae9 --- /dev/null +++ b/pkg/btc-wallet/src/urbit-key-generation.d.ts @@ -0,0 +1 @@ +declare module 'urbit-key-generation'; diff --git a/pkg/btc-wallet/src/urbit-ob.d.ts b/pkg/btc-wallet/src/urbit-ob.d.ts new file mode 100644 index 0000000000..10e6fb26de --- /dev/null +++ b/pkg/btc-wallet/src/urbit-ob.d.ts @@ -0,0 +1 @@ +declare module 'urbit-ob'; diff --git a/pkg/btc-wallet/tsconfig.json b/pkg/btc-wallet/tsconfig.json new file mode 100644 index 0000000000..5256dd7ee6 --- /dev/null +++ b/pkg/btc-wallet/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "noImplicitAny": true, + "module": "es6", + "target": "es5", + "jsx": "react", + "allowJs": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": ["ESNext", "DOM"] + } +}