Skip to content

Commit

Permalink
Merge branch 'main' into caravan-descriptors
Browse files Browse the repository at this point in the history
  • Loading branch information
bucko13 committed Feb 22, 2024
2 parents 02a6b97 + 83e9654 commit de755b2
Show file tree
Hide file tree
Showing 21 changed files with 181 additions and 91 deletions.
5 changes: 5 additions & 0 deletions .changeset/fast-toys-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@caravan/coordinator": minor
---

Use @caravan/clients for client requests and revert to use blockstream as default. This is to avoid rate limiting issues with mempool.space
1 change: 0 additions & 1 deletion apps/coordinator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@
"@mui/material": "^5.5.0",
"@mui/styles": "^5.5.0",
"@trezor/transport": "^1.1.15",
"@types/redux": "^3.6.0",
"@vitejs/plugin-react": "^3.1.0",
"axios": "^1.6.7",
"base58check": "^2.0.0",
Expand Down
12 changes: 6 additions & 6 deletions apps/coordinator/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 "../blockchain";
import { setErrorNotification } from "./errorNotificationActions";
import { getBlockchainClientFromStore } from "./clientActions";

export const UPDATE_BRAID_SLICE = "UPDATE_BRAID_SLICE";

Expand All @@ -15,9 +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.
Expand All @@ -27,8 +27,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 apps/coordinator/src/actions/clientActions.js

This file was deleted.

50 changes: 50 additions & 0 deletions apps/coordinator/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 "@caravan/clients";

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 apps/coordinator/src/actions/walletActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
} from "@caravan/bitcoin";

import BigNumber from "bignumber.js";
import { fetchAddressUTXOs } from "../blockchain";
import { isChange } from "../utils/slices";
import { naiveCoinSelection } from "../utils";
import { getBlockchainClientFromStore } from "./clientActions";
import {
setBalanceError,
setChangeOutput,
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
File renamed without changes.
File renamed without changes.
File renamed without changes.
23 changes: 16 additions & 7 deletions apps/coordinator/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 "../../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,20 +66,26 @@ 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) => {
Expand All @@ -87,18 +95,19 @@ const ClientPicker = ({
setUsernameError(error);
};

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 +148,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
2 changes: 1 addition & 1 deletion apps/coordinator/src/components/ImportAddressesButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
bitcoindImportMulti,
bitcoindGetAddressStatus,
bitcoindParams,
} from "../bitcoind";
} from "../clients/bitcoind";
import { ClientType } from "./types/client";

const useStyles = makeStyles(() => ({
Expand Down
10 changes: 7 additions & 3 deletions apps/coordinator/src/components/ScriptExplorer/OutputsForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
finalizeOutputs as finalizeOutputsAction,
resetOutputs as resetOutputsAction,
} from "../../actions/transactionActions";
import { fetchFeeEstimate } from "../../blockchain";
import { getBlockchainClientFromStore } from "../../actions/clientActions";
import { MIN_SATS_PER_BYTE_FEE } from "../Wallet/constants";
import OutputEntry from "./OutputEntry";
import styles from "./styles.module.scss";
Expand Down Expand Up @@ -181,13 +181,15 @@ 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.";
console.error(e);
} finally {
setFeeRate(
!Number.isNaN(feeEstimate)
Expand Down Expand Up @@ -485,6 +487,7 @@ OutputsForm.propTypes = {
setOutputAmount: PropTypes.func.isRequired,
signatureImporters: PropTypes.shape({}).isRequired,
updatesComplete: PropTypes.bool,
getBlockchainClient: PropTypes.func.isRequired,
};

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

export default connect(mapStateToProps, mapDispatchToProps)(OutputsForm);
13 changes: 6 additions & 7 deletions apps/coordinator/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 "../../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);
14 changes: 7 additions & 7 deletions apps/coordinator/src/components/ScriptExplorer/Transaction.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
CardContent,
} from "@mui/material";
import { OpenInNew } from "@mui/icons-material";
import { broadcastTransaction } from "../../blockchain";

import { getBlockchainClientFromStore } from "../../actions/clientActions";
import Copyable from "../Copyable";
import { externalLink } from "utils/ExternalLink";
import { setTXID } from "../../actions/transactionActions";
Expand Down Expand Up @@ -44,17 +45,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 +125,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 +141,7 @@ function mapStateToProps(state) {

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

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

0 comments on commit de755b2

Please sign in to comment.