Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Commit

Permalink
feat(wallet): bech32 address support
Browse files Browse the repository at this point in the history
  • Loading branch information
mrfelton committed May 3, 2019
1 parent e138c16 commit 46825ed
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 54 deletions.
3 changes: 2 additions & 1 deletion renderer/containers/Syncing.js
Expand Up @@ -3,6 +3,7 @@ import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import styled, { withTheme } from 'styled-components' import styled, { withTheme } from 'styled-components'
import { infoSelectors } from 'reducers/info' import { infoSelectors } from 'reducers/info'
import { addressSelectors } from 'reducers/address'
import { neutrinoSelectors } from 'reducers/neutrino' import { neutrinoSelectors } from 'reducers/neutrino'
import { setIsWalletOpen } from 'reducers/wallet' import { setIsWalletOpen } from 'reducers/wallet'
import { showNotification } from 'reducers/notification' import { showNotification } from 'reducers/notification'
Expand All @@ -11,7 +12,7 @@ import { Modal, ModalOverlayStyles } from 'components/UI'
import { useOnKeydown } from 'hooks' import { useOnKeydown } from 'hooks'


const mapStateToProps = state => ({ const mapStateToProps = state => ({
address: state.address.address, address: addressSelectors.currentAddress(state),
hasSynced: infoSelectors.hasSynced(state), hasSynced: infoSelectors.hasSynced(state),
syncStatus: neutrinoSelectors.neutrinoSyncStatus(state), syncStatus: neutrinoSelectors.neutrinoSyncStatus(state),
syncPercentage: neutrinoSelectors.neutrinoSyncPercentage(state), syncPercentage: neutrinoSelectors.neutrinoSyncPercentage(state),
Expand Down
4 changes: 2 additions & 2 deletions renderer/containers/Wallet/ReceiveModal.js
@@ -1,17 +1,17 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import get from 'lodash.get' import get from 'lodash.get'
import ReceiveModal from 'components/Wallet/ReceiveModal' import ReceiveModal from 'components/Wallet/ReceiveModal'
import { addressSelectors, closeWalletModal } from 'reducers/address'
import { walletSelectors } from 'reducers/wallet' import { walletSelectors } from 'reducers/wallet'
import { tickerSelectors } from 'reducers/ticker' import { tickerSelectors } from 'reducers/ticker'
import { infoSelectors } from 'reducers/info' import { infoSelectors } from 'reducers/info'
import { showNotification } from 'reducers/notification' import { showNotification } from 'reducers/notification'
import { closeWalletModal } from 'reducers/address'


const mapStateToProps = state => ({ const mapStateToProps = state => ({
networkInfo: infoSelectors.networkInfo(state), networkInfo: infoSelectors.networkInfo(state),
cryptoName: tickerSelectors.currencyAddressName(state), cryptoName: tickerSelectors.currencyAddressName(state),
pubkey: get(state.info, 'data.uris[0]') || get(state.info, 'data.identity_pubkey'), pubkey: get(state.info, 'data.uris[0]') || get(state.info, 'data.identity_pubkey'),
address: state.address.address, address: addressSelectors.currentAddress(state),
activeWalletSettings: walletSelectors.activeWalletSettings(state), activeWalletSettings: walletSelectors.activeWalletSettings(state),
alias: state.info.data.alias, alias: state.info.data.alias,
}) })
Expand Down
110 changes: 69 additions & 41 deletions renderer/reducers/address.js
@@ -1,71 +1,77 @@
import config from 'config'
import get from 'lodash.get'
import { createSelector } from 'reselect'
import { grpcService } from 'workers' import { grpcService } from 'workers'
import { openModal, closeModal } from './modal' import { openModal, closeModal } from './modal'


// ------------------------------------ // ------------------------------------
// Constants // Constants
// ------------------------------------ // ------------------------------------
export const GET_ADDRESS = 'GET_ADDRESS' export const FETCH_ADDRESSES = 'FETCH_ADDRESSES'
export const GET_ADDRESS_SUCCESS = 'GET_ADDRESS_SUCCESS' export const FETCH_ADDRESSES_SUCCESS = 'FETCH_ADDRESSES_SUCCESS'
export const GET_ADDRESS_FAILURE = 'GET_ADDRESS_FAILURE' export const NEW_ADDRESS = 'NEW_ADDRESS'

export const NEW_ADDRESS_SUCCESS = 'NEW_ADDRESS_SUCCESS'
export const NEW_ADDRESS_FAILURE = 'NEW_ADDRESS_FAILURE'
export const OPEN_WALLET_MODAL = 'OPEN_WALLET_MODAL' export const OPEN_WALLET_MODAL = 'OPEN_WALLET_MODAL'
export const CLOSE_WALLET_MODAL = 'CLOSE_WALLET_MODAL' export const CLOSE_WALLET_MODAL = 'CLOSE_WALLET_MODAL'


// LND expects types to be sent as int, so this object will allow mapping from string to int // LND expects types to be sent as int, so this object will allow mapping from string to int
const addressTypes = { const ADDRESS_TYPES = {
p2wkh: 0, p2wkh: 0,
np2wkh: 1, np2wkh: 1,
} }


// ------------------------------------ // ------------------------------------
// Actions // Actions
// ------------------------------------ // ------------------------------------
export function getAddress() {
return {
type: GET_ADDRESS,
}
}


export const openWalletModal = () => dispatch => dispatch(openModal('RECEIVE_MODAL')) export const openWalletModal = () => dispatch => dispatch(openModal('RECEIVE_MODAL'))
export const closeWalletModal = () => dispatch => dispatch(closeModal('RECEIVE_MODAL')) export const closeWalletModal = () => dispatch => dispatch(closeModal('RECEIVE_MODAL'))


// Get our existing address if there is one, otherwise generate a new one. /**
export const walletAddress = type => async (dispatch, getState) => { * Initialise addresses.
let address */
export const initAddresses = () => async (dispatch, getState) => {
dispatch({ type: FETCH_ADDRESSES })


// Wallet addresses are keyed under the node pubKey in our store.
const state = getState() const state = getState()
const pubKey = state.info.data.identity_pubkey

if (pubKey) {
const node = await window.db.nodes.get({ id: pubKey })
if (node) {
address = node.getCurrentAddress(type)
}
}


// If we have an address already, use that. Otherwise, generate a new address. // Get node information (addresses are keyed under the node pubkey).
if (address) { const pubKey = state.info.data.identity_pubkey
dispatch({ type: GET_ADDRESS_SUCCESS, address }) const node = await window.db.nodes.get({ id: pubKey })
} else {
dispatch(newAddress(type)) // Get existing addresses for the node.
} const addresses = get(node, 'addresses', {})
dispatch({ type: FETCH_ADDRESSES_SUCCESS, addresses })

// Ensure that we have an address for all supported address types.
await Promise.all(
Object.keys(ADDRESS_TYPES).map(type => {
if (!addresses[type]) {
return dispatch(newAddress(type))
}
})
)
} }


// Send IPC event for getinfo /**
* Generate a new address.
*/
export const newAddress = type => async dispatch => { export const newAddress = type => async dispatch => {
dispatch({ type: NEW_ADDRESS })
try { try {
dispatch(getAddress())
const grpc = await grpcService const grpc = await grpcService
const data = await grpc.services.Lightning.newAddress({ type: addressTypes[type] }) const data = await grpc.services.Lightning.newAddress({ type: ADDRESS_TYPES[type] })
dispatch(receiveAddressSuccess({ ...data, type })) await dispatch(newAddressSuccess({ ...data, type }))
} catch (error) { } catch (error) {
dispatch(newAddressFailure(error)) dispatch(newAddressFailure(error))
} }
} }


// Receive IPC event for info /**
export const receiveAddressSuccess = ({ type, address }) => async (dispatch, getState) => { * Generate a new address success callback
*/
export const newAddressSuccess = ({ type, address }) => async (dispatch, getState) => {
const state = getState() const state = getState()
const pubKey = state.info.data.identity_pubkey const pubKey = state.info.data.identity_pubkey


Expand All @@ -79,26 +85,33 @@ export const receiveAddressSuccess = ({ type, address }) => async (dispatch, get
} }
} }


dispatch({ type: GET_ADDRESS_SUCCESS, address: address }) dispatch({ type: NEW_ADDRESS_SUCCESS, payload: { address, type } })
} }


/**
* Generate a new address failure callback
*/
export const newAddressFailure = error => ({ export const newAddressFailure = error => ({
type: GET_ADDRESS_FAILURE, type: NEW_ADDRESS_FAILURE,
newAddressError: error, newAddressError: error,
}) })


// ------------------------------------ // ------------------------------------
// Action Handlers // Action Handlers
// ------------------------------------ // ------------------------------------
const ACTION_HANDLERS = { const ACTION_HANDLERS = {
[GET_ADDRESS]: state => ({ ...state, addressLoading: true }), [FETCH_ADDRESSES_SUCCESS]: (state, { addresses }) => ({
[GET_ADDRESS_SUCCESS]: (state, { address }) => ({ ...state,
addresses,
}),
[NEW_ADDRESS]: state => ({ ...state, addressLoading: true }),
[NEW_ADDRESS_SUCCESS]: (state, { payload: { address, type } }) => ({
...state, ...state,
addressLoading: false, addressLoading: false,
newAddressError: null, newAddressError: null,
address, addresses: { ...state.addresses, [type]: address },
}), }),
[GET_ADDRESS_FAILURE]: (state, { newAddressError }) => ({ [NEW_ADDRESS_FAILURE]: (state, { newAddressError }) => ({
...state, ...state,
addressLoading: false, addressLoading: false,
newAddressError, newAddressError,
Expand All @@ -107,12 +120,27 @@ const ACTION_HANDLERS = {
[CLOSE_WALLET_MODAL]: state => ({ ...state, walletModal: false }), [CLOSE_WALLET_MODAL]: state => ({ ...state, walletModal: false }),
} }


// ------------------------------------
// Selectors
// ------------------------------------

const addressSelectors = {}
addressSelectors.currentAddresses = state => state.address.addresses
addressSelectors.currentAddress = createSelector(
addressSelectors.currentAddresses,
currentAddresses => currentAddresses[config.address]
)
export { addressSelectors }

// ------------------------------------ // ------------------------------------
// Reducer // Reducer
// ------------------------------------ // ------------------------------------
const initialState = { const initialState = {
addressLoading: false, addressLoading: false,
address: '', addresses: {
np2wkh: null,
p2wkh: null,
},
newAddressError: null, newAddressError: null,
walletModal: false, walletModal: false,
} }
Expand Down
7 changes: 3 additions & 4 deletions renderer/reducers/info.js
@@ -1,9 +1,8 @@
import { createSelector } from 'reselect' import { createSelector } from 'reselect'
import get from 'lodash.get' import get from 'lodash.get'
import config from 'config'
import { networks } from '@zap/utils/crypto' import { networks } from '@zap/utils/crypto'
import { grpcService } from 'workers' import { grpcService } from 'workers'
import { walletAddress } from './address' import { initAddresses } from './address'
import { putWallet, walletSelectors } from './wallet' import { putWallet, walletSelectors } from './wallet'
import { receiveCryptocurrency } from './ticker' import { receiveCryptocurrency } from './ticker'


Expand Down Expand Up @@ -101,8 +100,8 @@ export const receiveInfo = data => async (dispatch, getState) => {
dispatch(setHasSynced(node.hasSynced)) dispatch(setHasSynced(node.hasSynced))
} }


// Now that we have the node info, get the current wallet address. // Now that we have the node info, get the current wallet addresses.
dispatch(walletAddress(config.address)) dispatch(initAddresses())


// Update the active wallet settings with info discovered from getinfo. // Update the active wallet settings with info discovered from getinfo.
const chain = get(state, 'info.chain') const chain = get(state, 'info.chain')
Expand Down
16 changes: 10 additions & 6 deletions renderer/reducers/transaction.js
Expand Up @@ -5,7 +5,7 @@ import { convert } from '@zap/utils/btc'
import delay from '@zap/utils/delay' import delay from '@zap/utils/delay'
import errorToUserFriendly from '@zap/utils/userFriendlyErrors' import errorToUserFriendly from '@zap/utils/userFriendlyErrors'
import { grpcService } from 'workers' import { grpcService } from 'workers'
import { newAddress } from './address' import { addressSelectors, newAddress } from './address'
import { fetchBalance } from './balance' import { fetchBalance } from './balance'
import { fetchChannels, channelsSelectors, getChannelData } from './channels' import { fetchChannels, channelsSelectors, getChannelData } from './channels'


Expand Down Expand Up @@ -63,21 +63,25 @@ export const fetchTransactions = () => async dispatch => {
export const receiveTransactions = ({ transactions }) => (dispatch, getState) => { export const receiveTransactions = ({ transactions }) => (dispatch, getState) => {
const state = getState() const state = getState()


const currentAddress = state.address.address const currentAddresses = addressSelectors.currentAddresses(state)
let usedAddresses = [] let usedAddresses = []


// Decorate transactions with additional metadata. // Decorate transactions with additional metadata.
transactions.forEach(transaction => { transactions.forEach(transaction => {
decorateTransaction(transaction) decorateTransaction(transaction)
// If our current wallet address has been used, generate a new one. // Keep track of used addresses.
usedAddresses = usedAddresses.concat(transaction.dest_addresses) usedAddresses = usedAddresses.concat(transaction.dest_addresses)
}) })


dispatch({ type: RECEIVE_TRANSACTIONS, transactions }) dispatch({ type: RECEIVE_TRANSACTIONS, transactions })


if (usedAddresses.includes(currentAddress)) { // If our current wallet address has been used, generate a new one.
dispatch(newAddress(config.address)) Object.entries(currentAddresses).forEach(([type, address]) => {
} if (usedAddresses.includes(address)) {
dispatch(newAddress(type))
}
})

// fetch new balance // fetch new balance
dispatch(fetchBalance()) dispatch(fetchBalance())
} }
Expand Down

0 comments on commit 46825ed

Please sign in to comment.