All files / app/store/transaction transaction.actions.ts

0% Statements 0/57
0% Branches 0/8
0% Functions 0/6
0% Lines 0/55

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134                                                                                                                                                                                                                                                                           
import { Api } from '../../api/api';
import { createAction } from '@reduxjs/toolkit';
import { AddressTransactionWithTransfers } from '@stacks/stacks-blockchain-api-types';
import { StacksTransaction, TxBroadcastResultRejected } from '@stacks/transactions';
import { safeAwait } from '@stacks/ui';
import { Dispatch, GetState } from '@store/index';
import { selectActiveNodeApi, selectActiveStacksNetwork } from '@store/stacks-node';
import { safelyFormatHexTxid } from '@utils/safe-handle-txid';
import { isObject } from 'formik';
import urljoin from 'url-join';
 
export const pendingTransactionSuccessful = createAction<AddressTransactionWithTransfers>(
  'transactions/pending-transaction-successful'
);
 
export const addNewTransaction = createAction<AddressTransactionWithTransfers>(
  'transactions/new-transaction'
);
 
const fetchTxName = 'transactions/fetch-transactions';
export const fetchTransactions = createAction<{ displayLoading?: boolean }>(fetchTxName);
export const fetchTransactionsDone = createAction<AddressTransactionWithTransfers[]>(
  fetchTxName + '-done'
);
export const fetchTransactionsFail = createAction<string>(fetchTxName + '-fail');
 
export function getAddressTransactions(
  address: string,
  options: { displayLoading?: boolean } = {}
) {
  return async (dispatch: Dispatch, getState: GetState) => {
    dispatch(fetchTransactions(options));
    const activeNode = selectActiveNodeApi(getState());
    const client = new Api(activeNode.url);
    const [error, response] = await safeAwait(client.getAddressTransactionsWithTransfers(address));
    Iif (error) {
      dispatch(fetchTransactionsFail('Unable to fetch recent transactions'));
      return;
    }
    Iif (response) {
      const transactions = response.data.results;
      dispatch(fetchTransactionsDone(transactions));
    }
  };
}
 
export const broadcastTx = createAction('transactions/broadcast-transactions');
export const broadcastTxDone = createAction('transactions/broadcast-transactions-done');
interface BroadcastTxFail {
  reason: string;
  message: string;
}
export const broadcastTxFail = createAction<BroadcastTxFail>(
  'transactions/broadcast-transactions-fail'
);
 
function hasMessageProp(arg: unknown): arg is { message: string } {
  Iif (!isObject(arg)) return false;
  Iif (!Object.hasOwn(arg, 'message')) return false;
 
  return true;
}
export interface BroadcastTransactionArgs {
  transaction: StacksTransaction;
  onBroadcastSuccess(txId: string): void;
  onBroadcastFail(errorResponse?: TxBroadcastResultRejected): void;
}
export function broadcastTransaction(args: BroadcastTransactionArgs) {
  const { transaction, onBroadcastSuccess, onBroadcastFail } = args;
  return async (dispatch: Dispatch, getState: GetState) => {
    dispatch(broadcastTx());
 
    const activeNode = selectActiveStacksNetwork(getState());
 
    try {
      const blockchainResponse = await broadcastRawTransaction(
        transaction.serialize(),
        activeNode.coreApiUrl
      );
      Iif (typeof blockchainResponse !== 'string') {
        // setError for ui
        const reasonData = blockchainResponse.reason_data;
        const message = hasMessageProp(reasonData) ? reasonData.message : '';
        dispatch(
          broadcastTxFail({
            reason: blockchainResponse.reason,
            message,
          })
        );
        onBroadcastFail(blockchainResponse);
        return;
      }
      onBroadcastSuccess(safelyFormatHexTxid(blockchainResponse));
      return blockchainResponse;
    } catch (e) {
      dispatch(broadcastTxFail(e as BroadcastTxFail));
      onBroadcastFail();
      return;
    }
  };
}
 
export async function broadcastRawTransaction(
  rawTx: Uint8Array,
  url: string
): Promise<TxBroadcastResultRejected | string> {
  const requestHeaders = {
    'Content-Type': 'application/octet-stream',
  };
 
  const options = {
    method: 'POST',
    headers: requestHeaders,
    body: rawTx,
  };
 
  const response = await fetch(urljoin(url, '/v2/transactions'), options);
 
  /**
   * Variable `text` can either be,
   * - a string of the transaction id, when the request is successful
   * - a stringified JSON object when the request is unsuccessful
   *
   * source,
   * https://docs.hiro.so/api#tag/Transactions/operation/post_core_node_transactions
   *
   * Note that in the documentation above the mimetype of successful responses
   * is documented as `text/plain`, yet the responses have have a mimetype of
   * `application/json`.
   */
  const text = await response.text();
  return JSON.parse(text);
}