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

Add custom transaction fees #270

Closed
wants to merge 8 commits into from
Closed
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
7 changes: 7 additions & 0 deletions packages/constants/src/network/network.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,10 @@ export function getNetwork(chain: Chain, network: Network): NetworkNode {

throw new Error(`Network ${network} is not valid for chain ${chain}`);
}

export const getMaxFee = (chain: Chain): number => {
switch (chain) {
default:
return 1;
}
};
46 changes: 46 additions & 0 deletions packages/extension/cypress/e2e/payments.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,50 @@ describe('Make payment from the UI', () => {
});
cy.get('p[data-testid="transaction-subtitle"]').should('have.text', 'Transaction Successful');
});

it('Should correctly pay with custom fees', () => {
navigate('http://localhost:3000', PASSWORD);

cy.contains('button', 'Send').click();

// Input recipient's address
cy.get('#recipient-address').type(DESTINATION_ADDRESS);

// Input amount
cy.get('#amount').clear().type('0.01').should('not.have.class', 'Mui-error');

// Click on the Send Payment button
cy.get('button').contains('Send Payment').click();

// Click on the custom fees to display the input
cy.contains('Network fees').next().contains('XRP').click();
cy.get('[type="number"]').clear().type('0.000025').blur();

// Check that the fees are correct
cy.contains('Network fees').next().should('have.text', '0.000025 XRP (MANUAL)');

// Confirm the payment
cy.contains('button', 'Submit').click();

cy.get('h1[data-testid="transaction-title"]').should('have.text', 'Transaction in progress');
cy.get('p[data-testid="transaction-subtitle"]').should(
'have.text',
'We are processing your transactionPlease wait'
);

cy.get('h1[data-testid="transaction-title"]').contains('Transaction accepted', {
timeout: 10000
});
cy.get('p[data-testid="transaction-subtitle"]').should('have.text', 'Transaction Successful');

// Go to transaction history
cy.contains('button', 'Close').click();
cy.contains('button', 'History').click();

// Find a Payment transaction
cy.contains('Payment').closest('.MuiPaper-root').click();

// Find the fee and check that it is correct
cy.contains('Fees').next().should('have.text', '0.000025');
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FC } from 'react';
import { FC, useMemo, useState } from 'react';

import ErrorIcon from '@mui/icons-material/Error';
import { IconButton, Paper, Tooltip, Typography } from '@mui/material';
import MuiInput from '@mui/material/Input';
import { dropsToXrp, xrpToDrops } from 'xrpl';

import {
CreateNFTOfferFlags,
Expand All @@ -10,10 +12,12 @@ import {
MintNFTFlags,
PaymentFlags,
SetAccountFlags,
TrustSetFlags
TrustSetFlags,
getMaxFee
} from '@gemwallet/constants';

import { ERROR_RED } from '../../../constants';
import { ERROR_RED, WARNING_ORANGE } from '../../../constants';
import { useNetwork } from '../../../contexts';
import { formatAmount, formatFlags, formatToken } from '../../../utils';
import { TileLoader } from '../../atoms';

Expand All @@ -32,13 +36,16 @@ type BaseTransactionProps = {
| null;
errorFees: string | undefined;
estimatedFees: string;
minimumFees: string;
};

type FeeProps = {
fee: number | null;
errorFees: string | undefined;
estimatedFees: string;
minimumFees?: string;
isBulk?: boolean;
onFeeChange?: (newFee: number) => void;
useLegacy?: boolean;
};

Expand All @@ -47,7 +54,8 @@ export const BaseTransaction: FC<BaseTransactionProps> = ({
memos,
flags,
errorFees,
estimatedFees
estimatedFees,
minimumFees
}) => (
<>
{memos && memos.length > 0 ? (
Expand Down Expand Up @@ -78,11 +86,71 @@ export const BaseTransaction: FC<BaseTransactionProps> = ({
</Typography>
</Paper>
) : null}
<Fee errorFees={errorFees} estimatedFees={estimatedFees} fee={fee} />
<Fee errorFees={errorFees} estimatedFees={estimatedFees} fee={fee} minimumFees={minimumFees} />
</>
);

export const Fee: FC<FeeProps> = ({ errorFees, estimatedFees, fee, isBulk, useLegacy = true }) => {
export const Fee: FC<FeeProps> = ({
errorFees,
estimatedFees,
minimumFees,
fee,
isBulk,
onFeeChange,
useLegacy = true
}) => {
const { chainName } = useNetwork();
const [inputValue, setInputValue] = useState<string | undefined>(undefined);
const [isEditing, setIsEditing] = useState(false);

const handleFeeClick = () => {
if (onFeeChange !== undefined) {
setIsEditing(true);
}
};

const handleFeeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
setInputValue(newValue);
const newFee = Number(newValue);
if (onFeeChange && !isNaN(newFee)) {
onFeeChange(Number(xrpToDrops(newFee)));
}
};

const handleBlur = () => {
setIsEditing(false);
};

const warningFee = useMemo(() => {
if (minimumFees === undefined || minimumFees === DEFAULT_FEES) {
return null;
}

if (
(fee !== null && fee < Number(minimumFees)) ||
(estimatedFees !== DEFAULT_FEES && Number(estimatedFees) < Number(minimumFees))
) {
return 'The fee is lower than the estimated fee, the transaction may fail';
}
}, [estimatedFees, fee, minimumFees]);

const inputDisplayValue = useMemo(() => {
if (inputValue !== undefined) {
return inputValue;
}

if (fee !== null) {
return dropsToXrp(fee);
}

if (estimatedFees !== DEFAULT_FEES) {
return dropsToXrp(estimatedFees);
}

return undefined;
}, [inputValue, fee, estimatedFees]);

if (useLegacy) {
return (
<Paper elevation={24} style={{ padding: '10px', marginBottom: '5px' }}>
Expand Down Expand Up @@ -134,18 +202,49 @@ export const Fee: FC<FeeProps> = ({ errorFees, estimatedFees, fee, isBulk, useLe
{isBulk ? 'Total network fees' : 'Network fees'}
</Typography>
<Typography variant="body2" gutterBottom align="right">
{errorFees ? (
<Typography variant="caption" style={{ color: ERROR_RED }}>
{isEditing ? (
<MuiInput
value={inputDisplayValue}
onChange={handleFeeChange}
onBlur={handleBlur}
autoFocus
size="small"
inputProps={{
step: dropsToXrp(1),
min: dropsToXrp(1),
max: getMaxFee(chainName),
type: 'number'
}}
/>
) : errorFees ? (
<Typography
variant="caption"
style={{ color: ERROR_RED, cursor: 'pointer' }}
onClick={handleFeeClick}
>
{errorFees}
</Typography>
) : estimatedFees === DEFAULT_FEES ? (
<TileLoader secondLineOnly />
) : fee ? (
formatToken(fee, 'XRP (manual)', true)
) : fee !== null ? (
<span onClick={handleFeeClick} style={{ cursor: 'pointer' }}>
{formatToken(fee, 'XRP (manual)', true)}
</span>
) : (
formatAmount(estimatedFees)
<span onClick={handleFeeClick} style={{ cursor: 'pointer' }}>
{formatAmount(estimatedFees)}
</span>
)}
</Typography>
{warningFee ? (
<Typography
variant="caption"
style={{ color: WARNING_ORANGE, cursor: 'pointer' }}
onClick={handleFeeClick}
>
{warningFee}
</Typography>
) : null}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ import { LoadingOverlay } from '../../templates';
interface TransactionDetailsProps {
txParam: Transaction | null;
estimatedFees: string;
minimumFees?: string;
errorFees?: string;
isConnectionFailed?: boolean;
displayTransactionType?: boolean;
onFeeChange?: (newFee: number) => void;
}

export const TransactionDetails: FC<TransactionDetailsProps> = ({
txParam,
errorFees,
estimatedFees,
minimumFees,
isConnectionFailed,
displayTransactionType
displayTransactionType,
onFeeChange
}) => {
const [isTxExpanded, setIsTxExpanded] = useState(false);
const [isRawTxExpanded, setIsRawTxExpanded] = useState(false);
Expand Down Expand Up @@ -63,10 +67,13 @@ export const TransactionDetails: FC<TransactionDetailsProps> = ({
<Fee
errorFees={errorFees}
estimatedFees={estimatedFees}
minimumFees={minimumFees}
fee={txParam?.Fee ? Number(txParam?.Fee) : null}
useLegacy={false}
onFeeChange={onFeeChange}
/>
}
alwaysExpand={true}
isExpanded={isFeeExpanded}
setIsExpanded={setIsFeeExpanded}
paddingTop={10}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const AcceptNFTOffer: FC = () => {
const { getCurrentWallet } = useWallet();
const { networkName } = useNetwork();
const { setTransactionProgress } = useTransactionProgress();
const { estimatedFees, errorFees, difference } = useFees(
const { estimatedFees, minimumFees, errorFees, difference } = useFees(
params.transaction ?? [],
params.transaction?.Fee
);
Expand Down Expand Up @@ -178,6 +178,21 @@ export const AcceptNFTOffer: FC = () => {
});
}, [params, acceptNFTOffer, sendMessageToBackground, createMessage]);

const handleFeeChange = useCallback(
(fee: number) => {
if (params.transaction) {
setParams({
...params,
transaction: {
...params.transaction,
Fee: fee.toString()
}
});
}
},
[params]
);

if (transactionStatusComponent) {
return <div>{transactionStatusComponent}</div>;
}
Expand All @@ -194,8 +209,10 @@ export const AcceptNFTOffer: FC = () => {
<TransactionDetails
txParam={params.transaction}
estimatedFees={estimatedFees}
minimumFees={minimumFees}
errorFees={errorFees}
displayTransactionType={false}
onFeeChange={handleFeeChange}
/>
</TransactionPage>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const AddNewTrustline: FC = () => {
const { getCurrentWallet } = useWallet();
const { networkName } = useNetwork();
const { setTransactionProgress } = useTransactionProgress();
const { estimatedFees, errorFees, difference } = useFees(
const { estimatedFees, minimumFees, errorFees, difference } = useFees(
params.transaction ?? [],
params.transaction?.Fee
);
Expand Down Expand Up @@ -377,8 +377,9 @@ export const AddNewTrustline: FC = () => {
transactionStatusComponent
) : (
<StepConfirm
params={params}
inputParams={params}
estimatedFees={estimatedFees}
minimumFees={minimumFees}
errorFees={errorFees}
hasEnoughFunds={hasEnoughFunds}
onReject={handleReject}
Expand Down
Loading
Loading