Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding replacement transactions #24

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface PendingTransaction {
txid: string;
feeRate: number;
timestamp: string; // ISO format string
amount: string;
}

export type tiers = 'low' | 'med' | 'high';
Expand Down Expand Up @@ -102,6 +103,7 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
txid: result.txid,
feeRate: selectedFee,
timestamp: new Date().toISOString(), // Capture the current time in ISO format
amount: formData.amount
};

// Send the transaction details to the pending-transactions endpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export const TransactionWrapper = styled.div`
`;
export const ButtonWrapper = styled.div`
disply: flex;
margin: .5rem;
`;

export const NoTransactionsText = styled.p`
text-align: center;
color: #888;
font-size: 16px;
`;

export const RowWrapper = styled.div`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,60 @@ import * as S from './UnconfirmedTransactions.styles';
import UnconfirmedTransaction from './components/UnconfirmedTransaction/UnconfirmedTransaction';
import ReplaceTransaction from './components/ReplaceTransaction/ReplaceTransaction';
import { BaseButton } from '@app/components/common/BaseButton/BaseButton';
interface UnconfirmedTransactionsProps {
transactions: any[]; //TODO: update the type
}
import usePendingTransactions, { PendingTransaction } from '@app/hooks/usePendingTransactions';

const UnconfirmedTransactions: React.FC<UnconfirmedTransactionsProps> = ({}) => {
const UnconfirmedTransactions: React.FC = () => {
const [isReplacingTransaction, setIsReplacingTransaction] = useState(false);
const [selectedTransaction, setSelectedTransaction] = useState<PendingTransaction | null>(null);
const { pendingTransactions } = usePendingTransactions();

const handleOpenReplaceTransaction = () => {
const handleOpenReplaceTransaction = (transaction: PendingTransaction) => {
setSelectedTransaction(transaction);
setIsReplacingTransaction(true);
};

const handleCancelReplaceTransaction = () => {
setSelectedTransaction(null);
setIsReplacingTransaction(false);
};

const onReplaceTransaction = () => {}; //define any behavior after replacing transaction
const onReplaceTransaction = () => {
// Define any behavior after replacing a transaction
};

return (
<S.ContentWrapper>
{isReplacingTransaction ? (
{isReplacingTransaction && selectedTransaction ? (
<>
<S.PanelHeaderText>Replace Transaction</S.PanelHeaderText>
<ReplaceTransaction onReplace={onReplaceTransaction} onCancel={handleCancelReplaceTransaction} />
<ReplaceTransaction
transaction={selectedTransaction}
onReplace={onReplaceTransaction}
onCancel={handleCancelReplaceTransaction}
/>
</>
) : (
<>
<S.PanelHeaderText>Unconfirmed Transactions</S.PanelHeaderText>
<S.PanelContent>
<S.RowWrapper>
<S.TransactionWrapper>
<UnconfirmedTransaction tx_id="123456" date_created="2021-09-01" amount="0.0001" />
</S.TransactionWrapper>
<S.ButtonWrapper>
<BaseButton onClick={handleOpenReplaceTransaction}>Replace</BaseButton>
</S.ButtonWrapper>
</S.RowWrapper>
{pendingTransactions.length === 0 ? (
<S.NoTransactionsText>No unconfirmed transactions available.</S.NoTransactionsText>
) : (
pendingTransactions.map((transaction) => (
<S.RowWrapper key={transaction.TxID}>
<S.TransactionWrapper>
<UnconfirmedTransaction
tx_id={transaction.TxID}
date_created={new Date(transaction.Timestamp).toLocaleDateString()}
amount={transaction.Amount !== undefined && transaction.Amount !== null ? transaction.Amount.toString() : 'N/A'}
/>
</S.TransactionWrapper>
<S.ButtonWrapper>
<BaseButton onClick={() => handleOpenReplaceTransaction(transaction)}>Replace</BaseButton>
</S.ButtonWrapper>
</S.RowWrapper>
))
)}
</S.PanelContent>
</>
)}
Expand All @@ -45,3 +65,7 @@ const UnconfirmedTransactions: React.FC<UnconfirmedTransactionsProps> = ({}) =>
};

export default UnconfirmedTransactions;




Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import React from 'react';
import * as S from './UnconfirmedTxModal.styles';
import UnconfirmedTransactions from '../../UnconfirmedTransactions';

interface UnconfirmedTxModalProps {
isOpen: boolean;
onOpenChange: () => void;
}
const transactions:any = [

]
const UnconfirmedTxModal: React.FC<UnconfirmedTxModalProps> = ({ isOpen, onOpenChange }) => {
return (
<S.Modal open={isOpen} centered={true} onCancel = {onOpenChange} footer={null} destroyOnClose>
<UnconfirmedTransactions transactions={transactions}></UnconfirmedTransactions>
<S.Modal open={isOpen} centered={true} onCancel={onOpenChange} footer={null} destroyOnClose>
<UnconfirmedTransactions />
</S.Modal>
);
};

export default UnconfirmedTxModal;

Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,118 @@ import React, { useState } from 'react';
import * as S from './ReplaceTransaction.styles';
import { useResponsive } from '@app/hooks/useResponsive';
import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees';
import { PendingTransaction } from '@app/hooks/usePendingTransactions';
import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin';
import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen';
import config from '@app/config/config';

interface ReplaceTransactionProps {
onCancel: () => void;
onReplace: () => void;
onReplace: () => void;
transaction: PendingTransaction;
}

const ReplaceTransaction: React.FC<ReplaceTransactionProps> = ({ onCancel, onReplace }) => {
const ReplaceTransaction: React.FC<ReplaceTransactionProps> = ({ onCancel, onReplace, transaction }) => {
const { isDesktop, isTablet } = useResponsive();
const [inValidAmount, setInvalidAmount] = React.useState(false);
const [inValidAmount, setInvalidAmount] = useState(false);
const [newFee, setNewFee] = useState(transaction.FeeRate);
const [loading, setLoading] = useState(false); // Add loading state
const [isFinished, setIsFinished] = useState(false); // Add finished state
const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({
isSuccess: false,
message: '',
txid: '',
});

const handleFeeChange = (fee: number) => {
setNewFee(fee); // Update the new fee when it changes
};

const handleReplace = async (e: React.MouseEvent) => {
e.stopPropagation();
setLoading(true); // Start loading
console.log('Replace button clicked');

const handleFeeChange = (fee: number) => {}; //use this for calculating new total
try {
const replaceRequest = {
choice: 2, // Replace transaction option
original_tx_id: transaction.TxID, // Send the original transaction ID
new_fee_rate: newFee, // Send the updated fee rate
};

const handleReplace = () => {
//function to replace transaction
onReplace();
const response = await fetch('http://localhost:9003/transaction', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(replaceRequest),
});

const result = await response.json();
setLoading(false); // Stop loading

if (result.status === 'success') {
setResult({ isSuccess: true, message: result.message, txid: result.txid });
setIsFinished(true);
onReplace(); // Notify the parent that the replace was successful
} else {
setResult({ isSuccess: false, message: result.message, txid: '' });
setIsFinished(true);
}
} catch (error) {
console.error('RBF transaction failed:', error);
setLoading(false); // Stop loading
setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' });
setIsFinished(true);
}
};

if (isFinished) {
return (
<ResultScreen
isSuccess={result.isSuccess}
amount={parseFloat(transaction.Amount.toString())} // Convert string to number here
receiver={transaction.TxID}
txid={result.txid}
message={result.message}
/>
);
}

return (
<S.ContentWrapper>
<S.FieldDisplay>
<S.FieldLabel>Transaction ID</S.FieldLabel>
<S.ValueWrapper isMobile={!isDesktop || !isTablet}>
<S.FieldValue>123456</S.FieldValue>
</S.ValueWrapper>
</S.FieldDisplay>
<S.FieldDisplay>
<S.FieldLabel>Amount</S.FieldLabel>
<S.ValueWrapper isMobile={!isDesktop || !isTablet}>
<S.FieldValue>123456</S.FieldValue>
</S.ValueWrapper>
</S.FieldDisplay>
<TieredFees inValidAmount={inValidAmount} handleFeeChange={handleFeeChange} />
<S.FieldDisplay>
<S.FieldLabel>New Fee</S.FieldLabel>
<S.ValueWrapper isMobile={!isDesktop || !isTablet}>
<S.FieldValue>123456</S.FieldValue>
</S.ValueWrapper>
</S.FieldDisplay>
<S.FieldDisplay>
<S.FieldLabel>Total</S.FieldLabel>
<S.ValueWrapper isMobile={!isDesktop || !isTablet}>
<S.FieldValue>123456</S.FieldValue>
</S.ValueWrapper>
</S.FieldDisplay>
<S.ButtonRow>
<S.Button onClick={onCancel}>Cancel</S.Button>
<S.Button onClick={handleReplace}>Replace</S.Button>
</S.ButtonRow>
</S.ContentWrapper>
<BaseSpin spinning={loading}>
<S.ContentWrapper>
<S.FieldDisplay>
<S.FieldLabel>Transaction ID</S.FieldLabel>
<S.ValueWrapper isMobile={!isDesktop || !isTablet}>
<S.FieldValue>{transaction.TxID}</S.FieldValue>
</S.ValueWrapper>
</S.FieldDisplay>
<S.FieldDisplay>
<S.FieldLabel>Amount</S.FieldLabel>
<S.ValueWrapper isMobile={!isDesktop || !isTablet}>
<S.FieldValue>{transaction.Amount}</S.FieldValue>
</S.ValueWrapper>
</S.FieldDisplay>
<TieredFees inValidAmount={inValidAmount} handleFeeChange={handleFeeChange} />
<S.FieldDisplay>
<S.FieldLabel>New Fee</S.FieldLabel>
<S.ValueWrapper isMobile={!isDesktop || !isTablet}>
<S.FieldValue>{newFee}</S.FieldValue>
</S.ValueWrapper>
</S.FieldDisplay>
<S.FieldDisplay>
<S.FieldLabel>Total</S.FieldLabel>
<S.ValueWrapper isMobile={!isDesktop || !isTablet}>
<S.FieldValue>{Number(transaction.Amount) + newFee}</S.FieldValue>
</S.ValueWrapper>
</S.FieldDisplay>
<S.ButtonRow>
<S.Button onClick={onCancel}>Cancel</S.Button>
<S.Button onClick={handleReplace}>Replace</S.Button>
</S.ButtonRow>
</S.ContentWrapper>
</BaseSpin>
);
};

Expand Down
19 changes: 12 additions & 7 deletions src/hooks/usePendingTransactions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useState, useEffect } from 'react';
import config from '@app/config/config';

interface PendingTransaction {
txid: string;
feeRate: number;
timestamp: string;
}
export interface PendingTransaction {
TxID: string;
FeeRate: number;
Timestamp: string;
Amount: number;
}

const usePendingTransactions = () => {
const [pendingTransactions, setPendingTransactions] = useState<PendingTransaction[]>([]);
Expand All @@ -24,10 +25,12 @@ const usePendingTransactions = () => {
if (!response.ok) {
throw new Error(`Network response was not ok (status: ${response.status})`);
}
const data: PendingTransaction[] = await response.json();
setPendingTransactions(data);
const data: PendingTransaction[] | null = await response.json();
console.log('Fetched Pending Transactions:', data);
setPendingTransactions(data || []); // Ensuring it is always an array
} catch (error) {
console.error('Error fetching pending transactions:', error);
setPendingTransactions([]); // Setting an empty array on error
} finally {
setIsLoading(false);
}
Expand All @@ -40,3 +43,5 @@ const usePendingTransactions = () => {
};

export default usePendingTransactions;