// @flow import * as ACTIONS from 'constants/action_types'; import { ACTIONS as LBRY_REDUX_ACTIONS } from 'lbry-redux'; import { handleActions } from 'util/redux-utils'; const SWAP_HISTORY_LENGTH_LIMIT = 10; function getBottomEntries(array, count) { const curCount = array.length; if (curCount < count) { return array; } else { return array.slice(curCount - count); } } const defaultState: CoinSwapState = { coinSwaps: [], }; export default handleActions( { [ACTIONS.ADD_COIN_SWAP]: (state: CoinSwapState, action: CoinSwapAddAction): CoinSwapState => { const { coinSwaps } = state; const { chargeCode } = action.data; const newCoinSwaps = coinSwaps.slice(); if (!newCoinSwaps.find((x) => x.chargeCode === chargeCode)) { newCoinSwaps.push({ ...action.data }); } return { ...state, coinSwaps: newCoinSwaps, }; }, [ACTIONS.REMOVE_COIN_SWAP]: (state: CoinSwapState, action: CoinSwapRemoveAction): CoinSwapState => { const { coinSwaps } = state; const { chargeCode } = action.data; let newCoinSwaps = coinSwaps.slice(); newCoinSwaps = newCoinSwaps.filter((x) => x.chargeCode !== chargeCode); return { ...state, coinSwaps: newCoinSwaps, }; }, [ACTIONS.COIN_SWAP_STATUS_RECEIVED]: (state: CoinSwapState, action: any) => { const { coinSwaps } = state; const newCoinSwaps = coinSwaps.slice(); let exchange; let charge; if (action.data.event_data) { // Source: Websocket exchange = { lbc_txid: action.data.lbc_txid }; charge = action.data.event_data; } else { // Source: btc/status exchange = action.data.Exchange; charge = action.data.Charge.data; } const calculateLbcAmount = (pricing, exchange, fallback) => { if (!exchange || !exchange.rate) { return fallback || 0; } const btcAmount = pricing['bitcoin'].amount; const SATOSHIS = 100000000; return (btcAmount * SATOSHIS) / exchange.rate; }; const timeline = charge.timeline; const lastTimeline = timeline[timeline.length - 1]; const index = newCoinSwaps.findIndex((x) => x.chargeCode === charge.code); if (index > -1) { newCoinSwaps[index] = { chargeCode: charge.code, coins: Object.keys(charge.addresses), sendAddresses: charge.addresses, sendAmounts: charge.pricing, lbcAmount: calculateLbcAmount(charge.pricing, exchange, newCoinSwaps[index].lbcAmount), status: { status: lastTimeline.status, receiptCurrency: lastTimeline.payment.value.currency, receiptTxid: lastTimeline.payment.transaction_id, lbcTxid: exchange.lbc_txid || '', }, }; } else { // If a pending swap is removed, the websocket will return an update // when it expires, for example, causing the entry to re-appear. This // might be a good thing (e.g. to get back accidental removals), but it // actually causes synchronization confusion across multiple instances. const IGNORED_DELETED_SWAPS = true; if (!IGNORED_DELETED_SWAPS) { newCoinSwaps.push({ chargeCode: charge.code, coins: Object.keys(charge.addresses), sendAddresses: charge.addresses, sendAmounts: charge.pricing, lbcAmount: calculateLbcAmount(charge.pricing, exchange, 0), status: { status: lastTimeline.status, receiptCurrency: lastTimeline.payment.value.currency, receiptTxid: lastTimeline.payment.transaction_id, lbcTxid: exchange.lbc_txid || '', }, }); } } return { ...state, coinSwaps: newCoinSwaps, }; }, [LBRY_REDUX_ACTIONS.USER_STATE_POPULATE]: ( state: CoinSwapState, action: { data: { coinSwapCodes: ?Array } } ) => { const { coinSwapCodes } = action.data; const newCoinSwaps = []; if (coinSwapCodes) { coinSwapCodes.forEach((chargeCode) => { if (chargeCode && typeof chargeCode === 'string') { const existingSwap = state.coinSwaps.find((x) => x.chargeCode === chargeCode); if (existingSwap) { newCoinSwaps.push({ ...existingSwap }); } else { newCoinSwaps.push({ // Just restore the 'chargeCode', and query the other data // via 'btc/status' later. chargeCode: chargeCode, coins: [], sendAddresses: {}, sendAmounts: {}, lbcAmount: 0, }); } } }); } return { ...state, coinSwaps: getBottomEntries(newCoinSwaps, SWAP_HISTORY_LENGTH_LIMIT), }; }, }, defaultState );