Skip to content
Permalink
Browse files

feat(wallet): bech32 address support

  • Loading branch information...
mrfelton committed May 3, 2019
1 parent e138c16 commit 46825ed788e6e5e60742ab052c5d10d3dc1ff5a4
@@ -3,6 +3,7 @@ import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import styled, { withTheme } from 'styled-components'
import { infoSelectors } from 'reducers/info'
import { addressSelectors } from 'reducers/address'
import { neutrinoSelectors } from 'reducers/neutrino'
import { setIsWalletOpen } from 'reducers/wallet'
import { showNotification } from 'reducers/notification'
@@ -11,7 +12,7 @@ import { Modal, ModalOverlayStyles } from 'components/UI'
import { useOnKeydown } from 'hooks'

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

const mapStateToProps = state => ({
networkInfo: infoSelectors.networkInfo(state),
cryptoName: tickerSelectors.currencyAddressName(state),
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),
alias: state.info.data.alias,
})
@@ -1,71 +1,77 @@
import config from 'config'
import get from 'lodash.get'
import { createSelector } from 'reselect'
import { grpcService } from 'workers'
import { openModal, closeModal } from './modal'

// ------------------------------------
// Constants
// ------------------------------------
export const GET_ADDRESS = 'GET_ADDRESS'
export const GET_ADDRESS_SUCCESS = 'GET_ADDRESS_SUCCESS'
export const GET_ADDRESS_FAILURE = 'GET_ADDRESS_FAILURE'

export const FETCH_ADDRESSES = 'FETCH_ADDRESSES'
export const FETCH_ADDRESSES_SUCCESS = 'FETCH_ADDRESSES_SUCCESS'
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 CLOSE_WALLET_MODAL = 'CLOSE_WALLET_MODAL'

// 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,
np2wkh: 1,
}

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

export const openWalletModal = () => dispatch => dispatch(openModal('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) => {
let address
/**
* Initialise addresses.
*/
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 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.
if (address) {
dispatch({ type: GET_ADDRESS_SUCCESS, address })
} else {
dispatch(newAddress(type))
}
// Get node information (addresses are keyed under the node pubkey).
const pubKey = state.info.data.identity_pubkey
const node = await window.db.nodes.get({ id: pubKey })

// 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 => {
dispatch({ type: NEW_ADDRESS })
try {
dispatch(getAddress())
const grpc = await grpcService
const data = await grpc.services.Lightning.newAddress({ type: addressTypes[type] })
dispatch(receiveAddressSuccess({ ...data, type }))
const data = await grpc.services.Lightning.newAddress({ type: ADDRESS_TYPES[type] })
await dispatch(newAddressSuccess({ ...data, type }))
} catch (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 pubKey = state.info.data.identity_pubkey

@@ -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 => ({
type: GET_ADDRESS_FAILURE,
type: NEW_ADDRESS_FAILURE,
newAddressError: error,
})

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[GET_ADDRESS]: state => ({ ...state, addressLoading: true }),
[GET_ADDRESS_SUCCESS]: (state, { address }) => ({
[FETCH_ADDRESSES_SUCCESS]: (state, { addresses }) => ({
...state,
addresses,
}),
[NEW_ADDRESS]: state => ({ ...state, addressLoading: true }),
[NEW_ADDRESS_SUCCESS]: (state, { payload: { address, type } }) => ({
...state,
addressLoading: false,
newAddressError: null,
address,
addresses: { ...state.addresses, [type]: address },
}),
[GET_ADDRESS_FAILURE]: (state, { newAddressError }) => ({
[NEW_ADDRESS_FAILURE]: (state, { newAddressError }) => ({
...state,
addressLoading: false,
newAddressError,
@@ -107,12 +120,27 @@ const ACTION_HANDLERS = {
[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
// ------------------------------------
const initialState = {
addressLoading: false,
address: '',
addresses: {
np2wkh: null,
p2wkh: null,
},
newAddressError: null,
walletModal: false,
}
@@ -1,9 +1,8 @@
import { createSelector } from 'reselect'
import get from 'lodash.get'
import config from 'config'
import { networks } from '@zap/utils/crypto'
import { grpcService } from 'workers'
import { walletAddress } from './address'
import { initAddresses } from './address'
import { putWallet, walletSelectors } from './wallet'
import { receiveCryptocurrency } from './ticker'

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

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

// Update the active wallet settings with info discovered from getinfo.
const chain = get(state, 'info.chain')
@@ -5,7 +5,7 @@ import { convert } from '@zap/utils/btc'
import delay from '@zap/utils/delay'
import errorToUserFriendly from '@zap/utils/userFriendlyErrors'
import { grpcService } from 'workers'
import { newAddress } from './address'
import { addressSelectors, newAddress } from './address'
import { fetchBalance } from './balance'
import { fetchChannels, channelsSelectors, getChannelData } from './channels'

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

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

// Decorate transactions with additional metadata.
transactions.forEach(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)
})

dispatch({ type: RECEIVE_TRANSACTIONS, transactions })

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

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

0 comments on commit 46825ed

Please sign in to comment.
You can’t perform that action at this time.