Skip to content
This repository has been archived by the owner on Apr 25, 2024. It is now read-only.

Commit

Permalink
feat(clients): use new blockchain client via redux
Browse files Browse the repository at this point in the history
  • Loading branch information
bucko13 committed Jan 29, 2024
1 parent 219120f commit 5b5d127
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 64 deletions.
13 changes: 6 additions & 7 deletions src/actions/braidActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {
updateDepositSliceAction,
updateChangeSliceAction,
} from "./walletActions";
import { fetchAddressUTXOs, getAddressStatus } from "../clients/blockchain";
import { setErrorNotification } from "./errorNotificationActions";
import { getBlockchainClientFromStore } from "./clientActions";

export const UPDATE_BRAID_SLICE = "UPDATE_BRAID_SLICE";

Expand All @@ -15,10 +15,9 @@ export const UPDATE_BRAID_SLICE = "UPDATE_BRAID_SLICE";
* @param {array<object>} slices - array of slices from one or more braids
*/
export const fetchSliceData = async (slices) => {
return async (dispatch, getState) => {
const { network } = getState().settings;
const { client } = getState();

return async (dispatch) => {
const blockchainClient = await dispatch(getBlockchainClientFromStore());
if (!blockchainClient) return;
try {
// Create a list of the async calls for updating the slice data.
// This lets us run these requests in parallel with a Promise.all
Expand All @@ -27,8 +26,8 @@ export const fetchSliceData = async (slices) => {
// creating a tuple of async calls that will need to be resolved
// for each slice we're querying for
return Promise.all([
fetchAddressUTXOs(address, network, client),
getAddressStatus(address, network, client),
blockchainClient.fetchAddressUTXOs(address),
blockchainClient.getAddressStatus(address),
]);
});

Expand Down
8 changes: 0 additions & 8 deletions src/actions/clientActions.js

This file was deleted.

50 changes: 50 additions & 0 deletions src/actions/clientActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Dispatch } from "react";
import { BlockchainClient, ClientType } from "../clients/client";

export const SET_CLIENT_TYPE = "SET_CLIENT_TYPE";
export const SET_CLIENT_URL = "SET_CLIENT_URL";
export const SET_CLIENT_USERNAME = "SET_CLIENT_USERNAME";
export const SET_CLIENT_PASSWORD = "SET_CLIENT_PASSWORD";

export const SET_CLIENT_URL_ERROR = "SET_CLIENT_URL_ERROR";
export const SET_CLIENT_USERNAME_ERROR = "SET_CLIENT_USERNAME_ERROR";
export const SET_CLIENT_PASSWORD_ERROR = "SET_CLIENT_PASSWORD_ERROR";

export const SET_BLOCKCHAIN_CLIENT = "SET_BLOCKCHAIN_CLIENT";

// TODO: use this to add more flexibility to client support
// For example, this defaults to blockstream for public client
// but can also support mempool.space as an option
export const getBlockchainClientFromStore = async () => {
return async (
dispatch: Dispatch<any>,
getState: () => { settings: any; client: any }
) => {
const { network } = getState().settings;
const { client } = getState();
if (!client) return;
let clientType: ClientType;

switch (client.type) {
case "public":
clientType = ClientType.BLOCKSTREAM;
break;
case "private":
clientType = ClientType.PRIVATE;
break;
default:
// this allows us to support other clients in the future
// like mempool.space
clientType = client.type;
}

const blockchainClient = new BlockchainClient({
client,
type: clientType,
network,
throttled: true,
});
dispatch({ type: SET_BLOCKCHAIN_CLIENT, value: blockchainClient });
return blockchainClient;
};
};
8 changes: 3 additions & 5 deletions src/actions/walletActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
} from "unchained-bitcoin";

import BigNumber from "bignumber.js";
import { fetchAddressUTXOs } from "../clients/blockchain";
import { isChange } from "../utils/slices";
import { naiveCoinSelection } from "../utils";
import {
Expand All @@ -16,6 +15,7 @@ import {
} from "./transactionActions";
import { setErrorNotification } from "./errorNotificationActions";
import { getSpendableSlices } from "../selectors/wallet";
import { getBlockchainClientFromStore } from "./clientActions";

export const UPDATE_DEPOSIT_SLICE = "UPDATE_DEPOSIT_SLICE";
export const UPDATE_CHANGE_SLICE = "UPDATE_CHANGE_SLICE";
Expand Down Expand Up @@ -173,8 +173,6 @@ export function updateTxSlices(
// eslint-disable-next-line consistent-return
return async (dispatch, getState) => {
const {
settings: { network },
client,
spend: {
transaction: { changeAddress, inputs, txid },
},
Expand All @@ -183,11 +181,11 @@ export function updateTxSlices(
change: { nodes: changeSlices },
},
} = getState();

const client = await dispatch(getBlockchainClientFromStore());
// utility function for getting utxo set of an address
// and formatting the result in a way we can use
const fetchSliceStatus = async (address, bip32Path) => {
const utxos = await fetchAddressUTXOs(address, network, client);
const utxos = await client.fetchAddressUtxos(address);
return {
addressUsed: true,
change: isChange(bip32Path),
Expand Down
26 changes: 18 additions & 8 deletions src/components/ClientPicker/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { connect, useDispatch } from "react-redux";
import {
Grid,
Card,
Expand All @@ -12,7 +12,6 @@ import {
RadioGroup,
FormHelperText,
} from "@mui/material";
import { fetchFeeEstimate } from "../../clients/blockchain";

// Components

Expand All @@ -26,6 +25,7 @@ import {
SET_CLIENT_URL_ERROR,
SET_CLIENT_USERNAME_ERROR,
SET_CLIENT_PASSWORD_ERROR,
getBlockchainClientFromStore,
} from "../../actions/clientActions";

import PrivateClientSettings from "./PrivateClientSettings";
Expand All @@ -49,6 +49,8 @@ const ClientPicker = ({
const [urlEdited, setUrlEdited] = useState(false);
const [connectError, setConnectError] = useState("");
const [connectSuccess, setConnectSuccess] = useState(false);
const dispatch = useDispatch();
const [blockchainClient, setClient] = useState();

const validatePassword = () => {
return "";
Expand All @@ -64,41 +66,49 @@ const ClientPicker = ({
return "";
};

const handleTypeChange = (event) => {
const updateBlockchainClient = async () => {
setClient(await dispatch(getBlockchainClientFromStore()));
};

const handleTypeChange = async (event) => {
const clientType = event.target.value;
if (clientType === "private" && !urlEdited) {
setUrl(`http://localhost:${network === "mainnet" ? 8332 : 18332}`);
}
setType(clientType);
await updateBlockchainClient();
};

const handleUrlChange = (event) => {
const handleUrlChange = async (event) => {
const url = event.target.value;
const error = validateUrl(url);
if (!urlEdited && !error) setUrlEdited(true);
setUrl(url);
setUrlError(error);
await updateBlockchainClient();
};

const handleUsernameChange = (event) => {
const handleUsernameChange = async (event) => {
const username = event.target.value;
const error = validateUsername(username);
setUsername(username);
setUsernameError(error);
await updateBlockchainClient();
};

const handlePasswordChange = (event) => {
const handlePasswordChange = async (event) => {
const password = event.target.value;
const error = validatePassword(password);
setPassword(password);
setPasswordError(error);
await updateBlockchainClient();
};

const testConnection = async () => {
setConnectError("");
setConnectSuccess(false);
try {
await fetchFeeEstimate(network, client);
await blockchainClient.getFeeEstimate();
if (onSuccess) {
onSuccess();
}
Expand Down Expand Up @@ -139,7 +149,7 @@ const ClientPicker = ({
{client.type === "public" && (
<FormHelperText>
{"'Public' uses the "}
<code>mempool.space</code>
<code>{blockchainClient?.type}</code>
{" API. Switch to private to use a "}
<code>bitcoind</code>
{" node."}
Expand Down
9 changes: 6 additions & 3 deletions src/components/ScriptExplorer/OutputsForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import {
finalizeOutputs as finalizeOutputsAction,
resetOutputs as resetOutputsAction,
} from "../../actions/transactionActions";
import { fetchFeeEstimate } from "../../clients/blockchain";
import { MIN_SATS_PER_BYTE_FEE } from "../Wallet/constants";
import OutputEntry from "./OutputEntry";
import styles from "./styles.module.scss";
import { getBlockchainClientFromStore } from "../../actions/clientActions";

class OutputsForm extends React.Component {
static unitLabel(label, options) {
Expand Down Expand Up @@ -181,11 +181,12 @@ class OutputsForm extends React.Component {
};

getFeeEstimate = async () => {
const { client, network, setFeeRate } = this.props;
const { getBlockchainClient, setFeeRate } = this.props;
const client = await getBlockchainClient();
let feeEstimate;
let feeRateFetchError = "";
try {
feeEstimate = await fetchFeeEstimate(network, client);
feeEstimate = await client.getFeeEstimate();
} catch (e) {
feeRateFetchError = "There was an error fetching the fee rate.";
} finally {
Expand Down Expand Up @@ -485,6 +486,7 @@ OutputsForm.propTypes = {
setOutputAmount: PropTypes.func.isRequired,
signatureImporters: PropTypes.shape({}).isRequired,
updatesComplete: PropTypes.bool,
getBlockchainClient: PropTypes.func.isRequired,
};

OutputsForm.defaultProps = {
Expand All @@ -511,6 +513,7 @@ const mapDispatchToProps = {
setFee: setFeeAction,
finalizeOutputs: finalizeOutputsAction,
resetOutputs: resetOutputsAction,
getBlockchainClient: getBlockchainClientFromStore,
};

export default connect(mapStateToProps, mapDispatchToProps)(OutputsForm);
13 changes: 6 additions & 7 deletions src/components/ScriptExplorer/ScriptEntry.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
TextField,
FormHelperText,
} from "@mui/material";
import { fetchAddressUTXOs } from "../../clients/blockchain";

// Components
import MultisigDetails from "../MultisigDetails";
Expand All @@ -48,6 +47,7 @@ import {
chooseConfirmOwnership as chooseConfirmOwnershipAction,
setOwnershipMultisig as setOwnershipMultisigAction,
} from "../../actions/ownershipActions";
import { getBlockchainClientFromStore } from "../../actions/clientActions";

class ScriptEntry extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -249,12 +249,9 @@ class ScriptEntry extends React.Component {
};

fetchUTXOs = async (multisig) => {
const { network, client } = this.props;
const addressData = await fetchAddressUTXOs(
multisig.address,
network,
client
);
const { getBlockchainClient } = this.props;
const client = await getBlockchainClient();
const addressData = await client.fetchAddressUtxos(multisig.address);

return addressData;
};
Expand Down Expand Up @@ -415,6 +412,7 @@ ScriptEntry.propTypes = {
setTotalSigners: PropTypes.func.isRequired,
importLegacyPSBT: PropTypes.func.isRequired,
setUnsignedPSBT: PropTypes.func.isRequired,
getBlockchainClient: PropTypes.func.isRequired,
};

function mapStateToProps(state) {
Expand Down Expand Up @@ -443,6 +441,7 @@ const mapDispatchToProps = {
setFee: setFeeAction,
setUnsignedPSBT: setUnsignedPSBTAction,
finalizeOutputs: finalizeOutputsAction,
getBlockchainClient: getBlockchainClientFromStore,
};

export default connect(mapStateToProps, mapDispatchToProps)(ScriptEntry);
13 changes: 6 additions & 7 deletions src/components/ScriptExplorer/Transaction.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import {
CardContent,
} from "@mui/material";
import { OpenInNew } from "@mui/icons-material";
import { broadcastTransaction } from "../../clients/blockchain";
import Copyable from "../Copyable";
import { externalLink } from "utils/ExternalLink";
import { setTXID } from "../../actions/transactionActions";
import { getBlockchainClientFromStore } from "../../actions/clientActions";

class Transaction extends React.Component {
constructor(props) {
Expand All @@ -44,17 +44,14 @@ class Transaction extends React.Component {
};

handleBroadcast = async () => {
const { client, network, setTxid } = this.props;
const { getBlockchainClient, setTxid } = this.props;
const client = await getBlockchainClient();
const signedTransaction = this.buildSignedTransaction();
let error = "";
let txid = "";
this.setState({ broadcasting: true });
try {
txid = await broadcastTransaction(
signedTransaction.toHex(),
network,
client
);
txid = await client.broadcastTransaction(signedTransaction.toHex());
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
Expand Down Expand Up @@ -127,6 +124,7 @@ Transaction.propTypes = {
outputs: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
setTxid: PropTypes.func.isRequired,
signatureImporters: PropTypes.shape({}).isRequired,
getBlockchainClient: PropTypes.func.isRequired,
};

function mapStateToProps(state) {
Expand All @@ -142,6 +140,7 @@ function mapStateToProps(state) {

const mapDispatchToProps = {
setTxid: setTXID,
getBlockchainClient: getBlockchainClientFromStore,
};

export default connect(mapStateToProps, mapDispatchToProps)(Transaction);
Loading

0 comments on commit 5b5d127

Please sign in to comment.