Skip to content

Commit

Permalink
Merge branch 'TransactionFee'
Browse files Browse the repository at this point in the history
  • Loading branch information
bznein committed Jan 21, 2025
2 parents 16cec98 + 2c29f11 commit 4815ca3
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 29 deletions.
8 changes: 8 additions & 0 deletions backend/accounts/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ type TransactionData struct {
Type TxType
// Amount is always >0 and is the amount received or sent (not including the fee).
Amount coin.Amount
// DeductedAmount is the amount sent, including the fee.
// This field is only used if Type is equal to either Send or SendToSelf.
// Whem the type is SendToSelf, DeductedAmount is equal to only the fee.
DeductedAmount coin.Amount
// Balance is balance of the account at the time of this transaction. It is the sum of all
// transactions up to this point.
// This value is only valid as part of `OrderedTransactions`.
Expand Down Expand Up @@ -163,6 +167,7 @@ func NewOrderedTransactions(txs []*TransactionData) OrderedTransactions {

balance := big.NewInt(0)
for i := len(txs) - 1; i >= 0; i-- {
deductedAmount := coin.NewAmountFromInt64(0)
tx := txs[i]
switch tx.Type {
case TxTypeReceive:
Expand All @@ -177,15 +182,18 @@ func NewOrderedTransactions(txs []*TransactionData) OrderedTransactions {
// mined.
if tx.Fee != nil && !tx.FeeIsDifferentUnit {
balance.Sub(balance, tx.Fee.BigInt())
deductedAmount = coin.SumAmounts(tx.Amount, *tx.Fee)
}
case TxTypeSendSelf:
// Subtract only fee. Ethereum: it is deducted even if the tx failed, as the tx was
// mined.
if tx.Fee != nil && !tx.FeeIsDifferentUnit {
balance.Sub(balance, tx.Fee.BigInt())
deductedAmount = *tx.Fee
}
}
tx.Balance = coin.NewAmount(balance)
tx.DeductedAmount = deductedAmount
}
return txs
}
Expand Down
55 changes: 55 additions & 0 deletions backend/accounts/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,58 @@ func TestOrderedTransactionsWithFailedTransactions(t *testing.T) {
require.Equal(t, coin.NewAmountFromInt64(expectedBalances[i]), ordered[i].Balance, i)
}
}

func requireAmountIsEqualTo(t *testing.T, amount coin.Amount, total int64) {
t.Helper()
value, err := amount.Int64()
require.NoError(t, err)
require.Equal(t, total, value)
}

func TestOrderedTransactionsDeductedAmount(t *testing.T) {
tt := func(t time.Time) *time.Time { return &t }
amount := coin.NewAmountFromInt64(100)
fee := coin.NewAmountFromInt64(10)
txs := []*TransactionData{
{
// Send tx, deductedAmount is amount+fee
Timestamp: tt(time.Date(2020, 9, 15, 12, 0, 0, 0, time.UTC)),
Height: 15,
Type: TxTypeSend,
Amount: amount,
Fee: &fee,
},
{
// SendToSelf tx, deductedAmount is equal to just the fee.
Timestamp: tt(time.Date(2020, 9, 16, 12, 0, 0, 0, time.UTC)),
Height: 15,
Type: TxTypeSendSelf,
Amount: amount,
Fee: &fee,
},
{
// Recv tx, deductedAmount is empty
Timestamp: tt(time.Date(2020, 9, 17, 12, 0, 0, 0, time.UTC)),
Height: 15,
Type: TxTypeReceive,
Amount: amount,
Fee: &fee,
},
{
// Fee is in different unit (e.g. erc20 tx), deductedAmount is empty.
Timestamp: tt(time.Date(2020, 9, 17, 12, 0, 0, 0, time.UTC)),
Height: 15,
Type: TxTypeSend,
FeeIsDifferentUnit: true,
Amount: amount,
Fee: &fee,
},
}

orderedTxs := NewOrderedTransactions(txs)

requireAmountIsEqualTo(t, orderedTxs[0].DeductedAmount, 110)
requireAmountIsEqualTo(t, orderedTxs[1].DeductedAmount, 10)
requireAmountIsEqualTo(t, orderedTxs[2].DeductedAmount, 0)
requireAmountIsEqualTo(t, orderedTxs[3].DeductedAmount, 0)
}
25 changes: 12 additions & 13 deletions backend/coins/btc/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ type Transaction struct {
Status accounts.TxStatus `json:"status"`
Amount FormattedAmount `json:"amount"`
AmountAtTime FormattedAmount `json:"amountAtTime"`
DeductedAmount FormattedAmount `json:"deductedAmount"`
DeductedAmountAtTime FormattedAmount `json:"deductedAmountAtTime"`
Fee FormattedAmount `json:"fee"`
Time *string `json:"time"`
Addresses []string `json:"addresses"`
Expand Down Expand Up @@ -190,18 +190,17 @@ func (handlers *Handlers) getTxInfoJSON(txInfo *accounts.TransactionData, detail
Unit: amount.Unit,
}
var formattedTime *string
var deductedAmount FormattedAmount
timestamp := txInfo.Timestamp
if timestamp == nil {
timestamp = txInfo.CreatedTimestamp
}

var deductedAmountAtTime FormattedAmount
if timestamp != nil {
t := timestamp.Format(time.RFC3339)
formattedTime = &t
amountAtTime = handlers.formatAmountAtTimeAsJSON(txInfo.Amount, timestamp)
if txInfo.Fee != nil && txInfo.Type == accounts.TxTypeSend {
deductedAmount = handlers.formatAmountAtTimeAsJSON(coin.SumAmounts(txInfo.Amount, *txInfo.Fee), timestamp)
}
deductedAmountAtTime = handlers.formatAmountAtTimeAsJSON(txInfo.DeductedAmount, timestamp)
}

addresses := []string{}
Expand All @@ -218,14 +217,14 @@ func (handlers *Handlers) getTxInfoJSON(txInfo *accounts.TransactionData, detail
accounts.TxTypeSend: "send",
accounts.TxTypeSendSelf: "send_to_self",
}[txInfo.Type],
Status: txInfo.Status,
Amount: amount,
AmountAtTime: amountAtTime,
DeductedAmount: deductedAmount,
Time: formattedTime,
Addresses: addresses,
Note: handlers.account.TxNote(txInfo.InternalID),
Fee: feeString,
Status: txInfo.Status,
Amount: amount,
AmountAtTime: amountAtTime,
DeductedAmountAtTime: deductedAmountAtTime,
Time: formattedTime,
Addresses: addresses,
Note: handlers.account.TxNote(txInfo.InternalID),
Fee: feeString,
}

if detail {
Expand Down
2 changes: 1 addition & 1 deletion frontends/web/src/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export interface ITransaction {
amountAtTime: IAmount;
fee: IAmount;
feeRatePerKb: IAmount;
deductedAmount: IAmount;
deductedAmountAtTime: IAmount;
gas: number;
nonce: number | null;
internalID: string;
Expand Down
12 changes: 8 additions & 4 deletions frontends/web/src/components/amount/conversion-amount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import styles from './conversion-amount.module.css';

type TConversionAmountProps = {
amount: IAmount;
deductedAmount: IAmount;
type: TTransactionType;
}

Expand All @@ -34,17 +35,20 @@ const btcUnits: Readonly<string[]> = ['BTC', 'TBTC', 'sat', 'tsat'];
*/
export const ConversionAmount = ({
amount,
deductedAmount,
type,
}: TConversionAmountProps) => {
const { defaultCurrency } = useContext(RatesContext);
const conversion = amount?.conversions && amount?.conversions[defaultCurrency];
const sign = getTxSign(type);
const estimatedPrefix = '\u2248'; // ≈
const sendToSelf = type === 'send_to_self';
const conversionUnit = sendToSelf ? amount.unit : defaultCurrency;
const recv = type === 'receive';
const amountToShow = recv || sendToSelf ? amount : deductedAmount;
const conversionUnit = sendToSelf ? amountToShow.unit : defaultCurrency;

// we skip the estimated conversion prefix when the Tx is send to self, or both coin and conversion are in BTC units.
const skipEstimatedPrefix = sendToSelf || (btcUnits.includes(conversionUnit) && btcUnits.includes(amount.unit));
const skipEstimatedPrefix = sendToSelf || (btcUnits.includes(conversionUnit) && btcUnits.includes(amountToShow.unit));

return (
<span className={styles.txConversionAmount}>
Expand All @@ -53,15 +57,15 @@ export const ConversionAmount = ({
<Arrow type="send_to_self" />
</span>
)}
{amount.estimated && !skipEstimatedPrefix && (
{amountToShow.estimated && !skipEstimatedPrefix && (
<span className={styles.txPrefix}>
{estimatedPrefix}
{' '}
</span>
)}
{conversion && !sendToSelf ? sign : null}
<Amount
amount={sendToSelf ? amount.amount : conversion || ''}
amount={sendToSelf ? amountToShow.amount : conversion || ''}
unit={conversionUnit}
/>
<span className={styles.txUnit}>
Expand Down
21 changes: 10 additions & 11 deletions frontends/web/src/components/transactions/transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ type TTransactionProps = ITransaction & {
export const Transaction = ({
addresses,
amountAtTime,
fee,
deductedAmount,
deductedAmountAtTime,
onShowDetail,
internalID,
note,
Expand Down Expand Up @@ -67,8 +66,8 @@ export const Transaction = ({
type={type}
/>
<Amounts
amount={type === 'send' ? deductedAmount : amountAtTime}
fee={fee}
amount={amountAtTime}
deductedAmount={deductedAmountAtTime}
type={type}
/>
<button
Expand Down Expand Up @@ -151,35 +150,35 @@ const Status = ({

type TAmountsProps = {
amount: IAmount;
fee: IAmount;
deductedAmount: IAmount,
type: TTransactionType;
}

const Amounts = ({
amount,
fee,
deductedAmount,
type,
}: TAmountsProps) => {
const txTypeClass = `txAmount-${type}`;
const sendToSelf = type === 'send_to_self';
const sign = getTxSign(type);
const recv = type === 'receive';

return (
<span className={`${styles.txAmountsColumn} ${styles[txTypeClass]}`}>
{/* <data value={amount.amount}> */}
<span className={styles.txAmount}>
{sign}
<Amount
amount={sendToSelf ? fee.amount : amount.amount}
unit={amount.unit}
amount={recv ? amount.amount : deductedAmount.amount}
unit={recv ? amount.unit : deductedAmount.unit}
/>
<span className={styles.txUnit}>
{' '}
{sendToSelf ? fee.unit : amount.unit}
{deductedAmount.unit}
</span>
</span>
{/* </data> */}
<ConversionAmount amount={amount} type={type} />
<ConversionAmount amount={amount} deductedAmount={deductedAmount} type={type} />
</span>
);
};
Expand Down

0 comments on commit 4815ca3

Please sign in to comment.