f94f98e0f3
(1) Due to IAPI/commerce query limit, and also to not pollute the wallet with infinite chargeCodes, we'll only show the last 10 swaps. Beamer mentioned that it's possible to tracked back the past chargeCodes of the user, and potential provide a `list` endpoint to handle disputes. (2) If a user explicitly removed an entry, don't repopulate that entry even if websocket returned an updated status for it. While it might be useful to handle accidental removals, it looks weird when the list gets repopulated with 'Expired' entries. (3) Add sanitization when repopulating the chargeCodes from the wallet data (i.e. remove 'null' entries). (4) Always repopulate the list per wallet data so every instance looks the same.
156 lines
4.9 KiB
JavaScript
156 lines
4.9 KiB
JavaScript
// @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<string> } }
|
|
) => {
|
|
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
|
|
);
|