lbry-desktop/ui/redux/actions/wallet.js

754 lines
19 KiB
JavaScript
Raw Permalink Normal View History

import * as ACTIONS from 'constants/action_types';
import Lbry from 'lbry';
Bringing in emotes, stickers, and refactors from ody (#7435) * [New Feature] Comment Emotes (#125) * Refactor form-field * Create new Emote Menu * Add Emotes * Add Emote Selector and Emote Comment creation ability * Fix and Split CSS * [New Feature] Stickers (#131) * Refactor filePrice * Refactor Wallet Tip Components * Add backend sticker support for comments * Add stickers * Refactor commentCreate * Add Sticker Selector and sticker comment creation * Add stickers display to comments and hyperchats * Fix wrong checks for total Super Chats * Stickers/emojis fall out / improvements (#220) * Fix error logs * Improve LBC sticker flow/clarity * Show inline error if custom sticker amount below min * Sort emojis alphabetically * Improve loading of Images * Improve quality and display of emojis and fix CSS * Display both USD and LBC prices * Default to LBC tip if creator can't receive USD * Don't clear text-field after sticker is sent * Refactor notification component * Handle notifications * Don't show profile pic on sticker livestream comments * Change Sticker icon * Fix wording and number rounding * Fix blurring emojis * Disable non functional emote buttons * new Stickers! (#248) * Add new stickers (#347) * Fix cancel sending sticker (#447) * Refactor scrollbar CSS for portal components outside of main Refactor channelMention suggestions into new textareaSuggestions component Install @mui/material packages Move channel mentioning to use @mui/Autocomplete combobox without search functionality Add support for suggesting Emotes while typing ':' Improve label to display matching term Add back and improved support for searching while mentioning Add support for suggesting emojis Fix non concatenated strings Add key to groups and options Fix dispatch props Fix Popper positioning to be consistent Fix and Improve searching Add back support for Winning Uri Filter default emojis with the same name as emotes Remove unused topSuggestion component Fix text color on darkmode Fix livestream updating state from both websocket and reducer and causing double of the same comments to appear Fix blur and focus commentCreate events Fix no name after @ error * desktop tweaks Co-authored-by: saltrafael <76502841+saltrafael@users.noreply.github.com> Co-authored-by: Thomas Zarebczan <tzarebczan@users.noreply.github.com> Co-authored-by: Rafael <rafael.saes@odysee.com>
2022-01-24 17:07:09 +01:00
import { Lbryio } from 'lbryinc';
import { doToast } from 'redux/actions/notifications';
import {
selectBalance,
selectPendingSupportTransactions,
selectTxoPageParams,
selectPendingOtherTransactions,
selectPendingConsolidateTxid,
selectPendingMassClaimTxid,
} from 'redux/selectors/wallet';
import { creditsToString } from 'util/format-credits';
import { selectMyClaimsRaw, selectClaimsById } from 'redux/selectors/claims';
import { doFetchChannelListMine, doFetchClaimListMine, doClaimSearch } from 'redux/actions/claims';
const FIFTEEN_SECONDS = 15000;
let walletBalancePromise = null;
export function doUpdateBalance() {
return (dispatch, getState) => {
const {
wallet: { totalBalance: totalInStore },
} = getState();
if (walletBalancePromise === null) {
walletBalancePromise = Lbry.wallet_balance()
.then((response) => {
walletBalancePromise = null;
const { available, reserved, reserved_subtotals, total } = response;
const { claims, supports, tips } = reserved_subtotals;
const totalFloat = parseFloat(total);
if (totalInStore !== totalFloat) {
dispatch({
type: ACTIONS.UPDATE_BALANCE,
data: {
totalBalance: totalFloat,
balance: parseFloat(available),
reservedBalance: parseFloat(reserved),
claimsBalance: parseFloat(claims),
supportsBalance: parseFloat(supports),
tipsBalance: parseFloat(tips),
},
});
}
})
.catch(() => {
walletBalancePromise = null;
});
}
return walletBalancePromise;
};
}
export function doBalanceSubscribe() {
return (dispatch) => {
dispatch(doUpdateBalance());
setInterval(() => dispatch(doUpdateBalance()), 10000);
};
}
export function doFetchTransactions(page = 1, pageSize = 999999) {
return (dispatch) => {
dispatch({
type: ACTIONS.FETCH_TRANSACTIONS_STARTED,
});
Lbry.transaction_list({ page, page_size: pageSize }).then((result) => {
dispatch({
type: ACTIONS.FETCH_TRANSACTIONS_COMPLETED,
data: {
transactions: result.items,
},
});
});
};
}
export function doFetchTxoPage() {
return (dispatch, getState) => {
const fetchId = Math.random().toString(36).substr(2, 9);
dispatch({
type: ACTIONS.FETCH_TXO_PAGE_STARTED,
data: fetchId,
});
const state = getState();
const queryParams = selectTxoPageParams(state);
Lbry.txo_list(queryParams)
.then((res) => {
const items = res.items || [];
const claimsById = selectClaimsById(state);
const channelIds = items.reduce((acc, cur) => {
if (cur.type === 'support' && cur.signing_channel && !claimsById[cur.signing_channel.channel_id]) {
acc.push(cur.signing_channel.channel_id);
}
return acc;
}, []);
if (channelIds.length) {
const searchParams = {
page_size: 9999,
page: 1,
no_totals: true,
claim_ids: channelIds,
};
// make sure redux has these channels resolved
dispatch(doClaimSearch(searchParams));
}
return res;
})
.then((res) => {
dispatch({
type: ACTIONS.FETCH_TXO_PAGE_COMPLETED,
data: {
result: res,
fetchId: fetchId,
},
});
})
.catch((e) => {
dispatch({
type: ACTIONS.FETCH_TXO_PAGE_COMPLETED,
data: {
error: e.message,
fetchId: fetchId,
},
});
});
};
}
export function doUpdateTxoPageParams(params) {
return (dispatch) => {
dispatch({
type: ACTIONS.UPDATE_TXO_FETCH_PARAMS,
data: params,
});
dispatch(doFetchTxoPage());
};
}
export function doFetchSupports(page = 1, pageSize = 99999) {
return (dispatch) => {
dispatch({
type: ACTIONS.FETCH_SUPPORTS_STARTED,
});
Lbry.support_list({ page, page_size: pageSize }).then((result) => {
dispatch({
type: ACTIONS.FETCH_SUPPORTS_COMPLETED,
data: {
supports: result.items,
},
});
});
};
}
export function doFetchUtxoCounts() {
return async (dispatch) => {
dispatch({
type: ACTIONS.FETCH_UTXO_COUNT_STARTED,
});
let resultSets = await Promise.all([
Lbry.txo_list({ type: 'other', is_not_spent: true, page: 1, page_size: 1 }),
Lbry.txo_list({ type: 'support', is_not_spent: true, page: 1, page_size: 1 }),
]);
const counts = {};
const paymentCount = resultSets[0]['total_items'];
const supportCount = resultSets[1]['total_items'];
counts['other'] = typeof paymentCount === 'number' ? paymentCount : 0;
counts['support'] = typeof supportCount === 'number' ? supportCount : 0;
dispatch({
type: ACTIONS.FETCH_UTXO_COUNT_COMPLETED,
data: counts,
debug: { resultSets },
});
};
}
export function doUtxoConsolidate() {
return async (dispatch) => {
dispatch({
type: ACTIONS.DO_UTXO_CONSOLIDATE_STARTED,
});
const results = await Lbry.txo_spend({ type: 'other' });
const result = results[0];
dispatch({
type: ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED,
data: { txids: [result.txid] },
});
dispatch({
type: ACTIONS.DO_UTXO_CONSOLIDATE_COMPLETED,
data: { txid: result.txid },
});
dispatch(doCheckPendingTxs());
};
}
export function doTipClaimMass() {
return async (dispatch) => {
dispatch({
type: ACTIONS.TIP_CLAIM_MASS_STARTED,
});
const results = await Lbry.txo_spend({ type: 'support', is_not_my_input: true });
const result = results[0];
dispatch({
type: ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED,
data: { txids: [result.txid] },
});
dispatch({
type: ACTIONS.TIP_CLAIM_MASS_COMPLETED,
data: { txid: result.txid },
});
dispatch(doCheckPendingTxs());
};
}
export function doGetNewAddress() {
return (dispatch) => {
dispatch({
type: ACTIONS.GET_NEW_ADDRESS_STARTED,
});
Lbry.address_unused().then((address) => {
dispatch({
type: ACTIONS.GET_NEW_ADDRESS_COMPLETED,
data: { address },
});
});
};
}
export function doCheckAddressIsMine(address) {
return (dispatch) => {
dispatch({
type: ACTIONS.CHECK_ADDRESS_IS_MINE_STARTED,
});
Lbry.address_is_mine({ address }).then((isMine) => {
if (!isMine) dispatch(doGetNewAddress());
dispatch({
type: ACTIONS.CHECK_ADDRESS_IS_MINE_COMPLETED,
});
});
};
}
export function doSendDraftTransaction(address, amount) {
return (dispatch, getState) => {
const state = getState();
const balance = selectBalance(state);
if (balance - amount <= 0) {
dispatch(
doToast({
title: __('Insufficient credits'),
message: __('Insufficient credits'),
})
);
return;
}
dispatch({
type: ACTIONS.SEND_TRANSACTION_STARTED,
});
const successCallback = (response) => {
if (response.txid) {
dispatch({
type: ACTIONS.SEND_TRANSACTION_COMPLETED,
});
dispatch(
doToast({
message: __('You sent %amount% LBRY Credits', { amount: amount }),
linkText: __('History'),
linkTarget: '/wallet',
})
);
} else {
dispatch({
type: ACTIONS.SEND_TRANSACTION_FAILED,
data: { error: response },
});
dispatch(
doToast({
message: __('Transaction failed'),
isError: true,
})
);
}
};
const errorCallback = (error) => {
dispatch({
type: ACTIONS.SEND_TRANSACTION_FAILED,
data: { error: error.message },
});
dispatch(
doToast({
message: __('Transaction failed'),
isError: true,
})
);
};
Lbry.wallet_send({
addresses: [address],
amount: creditsToString(amount),
}).then(successCallback, errorCallback);
};
}
export function doSetDraftTransactionAmount(amount) {
return {
type: ACTIONS.SET_DRAFT_TRANSACTION_AMOUNT,
data: { amount },
};
}
export function doSetDraftTransactionAddress(address) {
return {
type: ACTIONS.SET_DRAFT_TRANSACTION_ADDRESS,
data: { address },
};
}
export function doSendTip(params, isSupport, successCallback, errorCallback, shouldNotify = true) {
return (dispatch, getState) => {
const state = getState();
const balance = selectBalance(state);
const myClaims = selectMyClaimsRaw(state);
const shouldSupport =
isSupport || (myClaims ? myClaims.find((claim) => claim.claim_id === params.claim_id) : false);
if (balance - params.amount <= 0) {
dispatch(
doToast({
message: __('Insufficient credits'),
isError: true,
})
);
return;
}
const success = (response) => {
if (shouldNotify) {
dispatch(
doToast({
message: shouldSupport
? __('You deposited %amount% LBRY Credits as a support!', { amount: params.amount })
: __('You sent %amount% LBRY Credits as a tip, Mahalo!', { amount: params.amount }),
linkText: __('History'),
linkTarget: '/wallet',
})
);
}
dispatch({
type: ACTIONS.SUPPORT_TRANSACTION_COMPLETED,
});
if (successCallback) {
successCallback(response);
}
};
const error = (err) => {
dispatch(
doToast({
message: __(`There was an error sending support funds.`),
isError: true,
})
);
dispatch({
type: ACTIONS.SUPPORT_TRANSACTION_FAILED,
data: {
error: err,
},
});
if (errorCallback) {
errorCallback();
}
};
dispatch({
type: ACTIONS.SUPPORT_TRANSACTION_STARTED,
});
Lbry.support_create({
...params,
tip: !shouldSupport,
blocking: true,
amount: creditsToString(params.amount),
}).then(success, error);
};
}
export function doClearSupport() {
return {
type: ACTIONS.CLEAR_SUPPORT_TRANSACTION,
};
}
export function doWalletEncrypt(newPassword) {
return (dispatch) => {
dispatch({
type: ACTIONS.WALLET_ENCRYPT_START,
});
Lbry.wallet_encrypt({ new_password: newPassword }).then((result) => {
if (result === true) {
dispatch({
type: ACTIONS.WALLET_ENCRYPT_COMPLETED,
result,
});
} else {
dispatch({
type: ACTIONS.WALLET_ENCRYPT_FAILED,
result,
});
}
});
};
}
export function doWalletUnlock(password) {
return (dispatch) => {
dispatch({
type: ACTIONS.WALLET_UNLOCK_START,
});
Lbry.wallet_unlock({ password }).then((result) => {
if (result === true) {
dispatch({
type: ACTIONS.WALLET_UNLOCK_COMPLETED,
result,
});
} else {
dispatch({
type: ACTIONS.WALLET_UNLOCK_FAILED,
result,
});
}
});
};
}
export function doWalletLock() {
return (dispatch) => {
dispatch({
type: ACTIONS.WALLET_LOCK_START,
});
Lbry.wallet_lock().then((result) => {
if (result === true) {
dispatch({
type: ACTIONS.WALLET_LOCK_COMPLETED,
result,
});
} else {
dispatch({
type: ACTIONS.WALLET_LOCK_FAILED,
result,
});
}
});
};
}
// Collect all tips for a claim
export function doSupportAbandonForClaim(claimId, claimType, keep, preview) {
return (dispatch) => {
if (preview) {
dispatch({
type: ACTIONS.ABANDON_CLAIM_SUPPORT_PREVIEW,
});
} else {
dispatch({
type: ACTIONS.ABANDON_CLAIM_SUPPORT_STARTED,
});
}
const params = { claim_id: claimId };
if (preview) params['preview'] = true;
if (keep) params['keep'] = keep;
return Lbry.support_abandon(params)
.then((res) => {
if (!preview) {
dispatch({
type: ACTIONS.ABANDON_CLAIM_SUPPORT_COMPLETED,
data: { claimId, txid: res.txid, effective: res.outputs[0].amount, type: claimType },
});
dispatch(doCheckPendingTxs());
}
return res;
})
.catch((e) => {
dispatch({
type: ACTIONS.ABANDON_CLAIM_SUPPORT_FAILED,
data: e.message,
});
});
};
}
export function doWalletReconnect() {
2022-02-04 22:09:39 +01:00
return (dispatch, getState) => {
dispatch({
type: ACTIONS.WALLET_RESTART,
});
let failed = false;
// this basically returns null when it's done. :(
// might be good to dispatch ACTIONS.WALLET_RESTARTED
const walletTimeout = setTimeout(() => {
2022-02-04 22:09:39 +01:00
const state = getState();
const { settings } = state;
const { daemonStatus } = settings || {};
const { wallet } = daemonStatus || {};
const availableServers = wallet.available_servers;
if (!availableServers) {
failed = true;
dispatch({
type: ACTIONS.WALLET_RESTART_COMPLETED,
});
dispatch(
doToast({
message: __('Your servers were not available. Check your url and port, or switch back to defaults.'),
isError: true,
})
);
}
}, FIFTEEN_SECONDS);
Lbry.wallet_reconnect().then(() => {
clearTimeout(walletTimeout);
if (!failed) dispatch({ type: ACTIONS.WALLET_RESTART_COMPLETED });
});
};
}
export function doWalletDecrypt() {
return (dispatch) => {
dispatch({
type: ACTIONS.WALLET_DECRYPT_START,
});
Lbry.wallet_decrypt().then((result) => {
if (result === true) {
dispatch({
type: ACTIONS.WALLET_DECRYPT_COMPLETED,
result,
});
} else {
dispatch({
type: ACTIONS.WALLET_DECRYPT_FAILED,
result,
});
}
});
};
}
export function doWalletStatus() {
return (dispatch) => {
dispatch({
type: ACTIONS.WALLET_STATUS_START,
});
Lbry.wallet_status().then((status) => {
if (status) {
dispatch({
type: ACTIONS.WALLET_STATUS_COMPLETED,
result: status.is_encrypted,
});
}
});
};
}
export function doSetTransactionListFilter(filterOption) {
return {
type: ACTIONS.SET_TRANSACTION_LIST_FILTER,
data: filterOption,
};
}
export function doUpdateBlockHeight() {
return (dispatch) =>
Lbry.status().then((status) => {
if (status.wallet) {
dispatch({
type: ACTIONS.UPDATE_CURRENT_HEIGHT,
data: status.wallet.blocks,
});
}
});
}
// Calls transaction_show on txes until any pending txes are confirmed
export const doCheckPendingTxs = () => (dispatch, getState) => {
const state = getState();
const pendingTxsById = selectPendingSupportTransactions(state); // {}
const pendingOtherTxes = selectPendingOtherTransactions(state);
if (!Object.keys(pendingTxsById).length && !pendingOtherTxes.length) {
return;
}
let txCheckInterval;
const checkTxList = () => {
const state = getState();
const pendingSupportTxs = selectPendingSupportTransactions(state); // {}
const pendingConsolidateTxes = selectPendingOtherTransactions(state);
const pendingConsTxid = selectPendingConsolidateTxid(state);
const pendingMassCLaimTxid = selectPendingMassClaimTxid(state);
const promises = [];
const newPendingTxes = {};
const noLongerPendingConsolidate = [];
const types = new Set([]);
// { claimId: {txid: 123, amount 12.3}, }
const entries = Object.entries(pendingSupportTxs);
entries.forEach(([claim, data]) => {
promises.push(Lbry.transaction_show({ txid: data.txid }));
types.add(data.type);
});
if (pendingConsolidateTxes.length) {
pendingConsolidateTxes.forEach((txid) => promises.push(Lbry.transaction_show({ txid })));
}
Promise.all(promises).then((txShows) => {
let changed = false;
txShows.forEach((result) => {
if (pendingConsolidateTxes.includes(result.txid)) {
if (result.height > 0) {
noLongerPendingConsolidate.push(result.txid);
}
} else {
if (result.height <= 0) {
const match = entries.find((entry) => entry[1].txid === result.txid);
newPendingTxes[match[0]] = match[1];
} else {
changed = true;
}
}
});
if (changed) {
dispatch({
type: ACTIONS.PENDING_SUPPORTS_UPDATED,
data: newPendingTxes,
});
if (types.has('channel')) {
dispatch(doFetchChannelListMine());
}
if (types.has('stream')) {
dispatch(doFetchClaimListMine());
}
}
if (noLongerPendingConsolidate.length) {
if (noLongerPendingConsolidate.includes(pendingConsTxid)) {
dispatch(
doToast({
message: __('Your wallet is finished consolidating'),
})
);
}
if (noLongerPendingConsolidate.includes(pendingMassCLaimTxid)) {
dispatch(
doToast({
message: __('Your tips have been collected'),
})
);
}
dispatch({
type: ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED,
data: { txids: noLongerPendingConsolidate, remove: true },
});
}
if (!Object.keys(pendingTxsById).length && !pendingOtherTxes.length) {
clearInterval(txCheckInterval);
}
});
};
txCheckInterval = setInterval(() => {
checkTxList();
}, 30000);
};
Bringing in emotes, stickers, and refactors from ody (#7435) * [New Feature] Comment Emotes (#125) * Refactor form-field * Create new Emote Menu * Add Emotes * Add Emote Selector and Emote Comment creation ability * Fix and Split CSS * [New Feature] Stickers (#131) * Refactor filePrice * Refactor Wallet Tip Components * Add backend sticker support for comments * Add stickers * Refactor commentCreate * Add Sticker Selector and sticker comment creation * Add stickers display to comments and hyperchats * Fix wrong checks for total Super Chats * Stickers/emojis fall out / improvements (#220) * Fix error logs * Improve LBC sticker flow/clarity * Show inline error if custom sticker amount below min * Sort emojis alphabetically * Improve loading of Images * Improve quality and display of emojis and fix CSS * Display both USD and LBC prices * Default to LBC tip if creator can't receive USD * Don't clear text-field after sticker is sent * Refactor notification component * Handle notifications * Don't show profile pic on sticker livestream comments * Change Sticker icon * Fix wording and number rounding * Fix blurring emojis * Disable non functional emote buttons * new Stickers! (#248) * Add new stickers (#347) * Fix cancel sending sticker (#447) * Refactor scrollbar CSS for portal components outside of main Refactor channelMention suggestions into new textareaSuggestions component Install @mui/material packages Move channel mentioning to use @mui/Autocomplete combobox without search functionality Add support for suggesting Emotes while typing ':' Improve label to display matching term Add back and improved support for searching while mentioning Add support for suggesting emojis Fix non concatenated strings Add key to groups and options Fix dispatch props Fix Popper positioning to be consistent Fix and Improve searching Add back support for Winning Uri Filter default emojis with the same name as emotes Remove unused topSuggestion component Fix text color on darkmode Fix livestream updating state from both websocket and reducer and causing double of the same comments to appear Fix blur and focus commentCreate events Fix no name after @ error * desktop tweaks Co-authored-by: saltrafael <76502841+saltrafael@users.noreply.github.com> Co-authored-by: Thomas Zarebczan <tzarebczan@users.noreply.github.com> Co-authored-by: Rafael <rafael.saes@odysee.com>
2022-01-24 17:07:09 +01:00
// don't need hthis
export const doSendCashTip = (tipParams, anonymous, userParams, claimId, stripeEnvironment, successCallback) => (
dispatch
) => {
Lbryio.call(
'customer',
'tip',
{
// round to fix issues with floating point numbers
amount: Math.round(100 * tipParams.tipAmount), // convert from dollars to cents
creator_channel_name: tipParams.tipChannelName, // creator_channel_name
creator_channel_claim_id: tipParams.channelClaimId,
tipper_channel_name: anonymous ? '' : userParams.activeChannelName,
tipper_channel_claim_id: anonymous ? '' : userParams.activeChannelId,
currency: 'USD',
anonymous: anonymous,
source_claim_id: claimId,
environment: stripeEnvironment,
},
'post'
)
.then((customerTipResponse) => {
dispatch(
doToast({
message: __("You sent $%tipAmount% as a tip to %tipChannelName%, I'm sure they appreciate it!", {
tipAmount: tipParams.tipAmount,
tipChannelName: tipParams.tipChannelName,
}),
})
);
if (successCallback) successCallback(customerTipResponse);
})
.catch((error) => {
// show error message from Stripe if one exists (being passed from backend by Beamer's API currently)
dispatch(
doToast({
message: error.message || __('Sorry, there was an error in processing your payment!'),
isError: true,
})
);
});
};