Skip to content
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 @@ -5,6 +5,7 @@ import {
NetworksTicker,
toHex,
BUILT_IN_NETWORKS,
ORIGIN_METAMASK,
} from '@metamask/controller-utils';
import type {
BlockTracker,
Expand All @@ -22,7 +23,7 @@ import type {
TransactionConfig,
} from './TransactionController';
import { TransactionController } from './TransactionController';
import type { TransactionMeta } from './types';
import type { TransactionMeta, DappSuggestedGasFees } from './types';
import { WalletDevice, TransactionStatus } from './types';
import { ESTIMATE_GAS_ERROR } from './utils';
import { FakeBlockTracker } from '../../../tests/fake-block-tracker';
Expand Down Expand Up @@ -734,6 +735,54 @@ describe('TransactionController', () => {
);
});

describe('adds dappSuggestedGasFees to transaction', () => {
it.each([
['origin is MM', ORIGIN_METAMASK],
['origin is not defined', undefined],
['no fee information is given', 'MockDappOrigin'],
])('as undefined if %s', async (_testName, origin) => {
const controller = newController();
await controller.addTransaction(
{
from: ACCOUNT_MOCK,
to: ACCOUNT_MOCK,
},
{
origin,
},
);
expect(
controller.state.transactions[0]?.dappSuggestedGasFees,
).toBeUndefined();
});

it.each([
['gasPrice'],
['maxFeePerGas'],
['maxPriorityFeePerGas'],
['gas'],
])('if %s is defined', async (gasPropName) => {
const controller = newController();
const mockDappOrigin = 'MockDappOrigin';
const mockGasValue = '0x1';
await controller.addTransaction(
{
from: ACCOUNT_MOCK,
to: ACCOUNT_MOCK,
[gasPropName]: mockGasValue,
},
{
origin: mockDappOrigin,
},
);
expect(
controller.state.transactions[0]?.dappSuggestedGasFees?.[
gasPropName as keyof DappSuggestedGasFees
],
).toBe(mockGasValue);
});
});

it.each([
['mainnet', MOCK_MAINNET_NETWORK],
['custom network', MOCK_CUSTOM_NETWORK],
Expand Down
51 changes: 50 additions & 1 deletion packages/transaction-controller/src/TransactionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ import { v1 as random } from 'uuid';

import { EtherscanRemoteTransactionSource } from './EtherscanRemoteTransactionSource';
import { IncomingTransactionHelper } from './IncomingTransactionHelper';
import type { Transaction, TransactionMeta, WalletDevice } from './types';
import type {
Transaction,
TransactionMeta,
WalletDevice,
DappSuggestedGasFees,
} from './types';
import { TransactionStatus } from './types';
import {
getAndFormatTransactionsForNonceTracker,
Expand Down Expand Up @@ -399,6 +404,11 @@ export class TransactionController extends BaseController<
transaction = normalizeTransaction(transaction);
validateTransaction(transaction);

const dappSuggestedGasFees = this.generateDappSuggestedGasFees(
transaction,
origin,
);

const transactionMeta: TransactionMeta = {
id: random(),
networkID: networkId ?? undefined,
Expand All @@ -409,6 +419,7 @@ export class TransactionController extends BaseController<
transaction,
deviceConfirmedOn,
verifiedOnBlockchain: false,
dappSuggestedGasFees,
};

try {
Expand Down Expand Up @@ -1364,6 +1375,44 @@ export class TransactionController extends BaseController<
this.update({ lastFetchedBlockNumbers });
this.hub.emit('incomingTransactionBlock', blockNumber);
}

private generateDappSuggestedGasFees(
transaction: Transaction,
origin?: string,
): DappSuggestedGasFees | undefined {
if (!origin || origin === ORIGIN_METAMASK) {
return undefined;
}

const { gasPrice, maxFeePerGas, maxPriorityFeePerGas, gas } = transaction;

if (
gasPrice === undefined &&
maxFeePerGas === undefined &&
maxPriorityFeePerGas === undefined &&
gas === undefined
) {
return undefined;
}

const dappSuggestedGasFees: DappSuggestedGasFees = {};

if (gasPrice !== undefined) {
dappSuggestedGasFees.gasPrice = gasPrice;
} else if (
maxFeePerGas !== undefined ||
maxPriorityFeePerGas !== undefined
) {
dappSuggestedGasFees.maxFeePerGas = maxFeePerGas;
dappSuggestedGasFees.maxPriorityFeePerGas = maxPriorityFeePerGas;
}

if (gas !== undefined) {
dappSuggestedGasFees.gas = gas;
}

return dappSuggestedGasFees;
}
}

export default TransactionController;
173 changes: 139 additions & 34 deletions packages/transaction-controller/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,106 @@
import type { Hex } from '@metamask/utils';

/**
* @type TransactionMeta
*
* TransactionMeta representation
* @property baseFeePerGas - Base fee of the block as a hex value, introduced in EIP-1559.
* @property error - Synthesized error information for failed transactions.
* @property id - Generated UUID associated with this transaction.
* @property networkID - Network code as per EIP-155 for this transaction.
* @property origin - Origin this transaction was sent from.
* @property deviceConfirmedOn - string to indicate what device the transaction was confirmed.
* @property rawTransaction - Hex representation of the underlying transaction.
* @property status - String status of this transaction.
* @property time - Timestamp associated with this transaction.
* @property toSmartContract - Whether transaction recipient is a smart contract.
* @property transaction - Underlying Transaction object.
* @property txReceipt - Transaction receipt.
* @property transactionHash - Hash of a successful transaction.
* @property blockNumber - Number of the block where the transaction has been included.
* Representation of transaction metadata.
*/
export type TransactionMeta =
| ({
status: Exclude<TransactionStatus, TransactionStatus.failed>;
} & TransactionMetaBase)
| ({ status: TransactionStatus.failed; error: Error } & TransactionMetaBase);

/**
* Information about a single transaction such as status and block number.
*/
type TransactionMetaBase = {
/**
* Base fee of the block as a hex value, introduced in EIP-1559.
*/
baseFeePerGas?: Hex;

/**
* Number of the block where the transaction has been included.
*/
blockNumber?: string;

/**
* Network code as per EIP-155 for this transaction.
*/
chainId?: Hex;

/**
* Gas values provided by the dApp.
*/
dappSuggestedGasFees?: DappSuggestedGasFees;

/**
* String to indicate what device the transaction was confirmed on.
*/
deviceConfirmedOn?: WalletDevice;

/**
* Generated UUID associated with this transaction.
*/
id: string;

/**
* Whether the transaction is a transfer.
*/
isTransfer?: boolean;

/**
* Network code as per EIP-155 for this transaction.
*/
networkID?: string;

/**
* Origin this transaction was sent from.
*/
origin?: string;

/**
* Hex representation of the underlying transaction.
*/
rawTransaction?: string;

/**
* Timestamp associated with this transaction.
*/
time: number;

/**
* Whether transaction recipient is a smart contract.
*/
toSmartContract?: boolean;

/**
* Underlying Transaction object.
*/
transaction: Transaction;

/**
* Hash of a successful transaction.
*/
transactionHash?: string;

/**
* Additional transfer information.
*/
transferInformation?: {
contractAddress: string;
decimals: number;
symbol: string;
};
verifiedOnBlockchain?: boolean;

/**
* Transaction receipt.
*/
txReceipt?: TransactionReceipt;

/**
* Whether the transaction is verified on the blockchain.
*/
verifiedOnBlockchain?: boolean;
};

/**
Expand Down Expand Up @@ -74,33 +129,73 @@ export enum WalletDevice {
}

/**
* @type Transaction
*
* Transaction representation
* @property chainId - Network ID as per EIP-155
* @property data - Data to pass with this transaction
* @property from - Address to send this transaction from
* @property gas - Gas to send with this transaction
* @property gasPrice - Price of gas with this transaction
* @property gasUsed - Gas used in the transaction
* @property nonce - Unique number to prevent replay attacks
* @property to - Address to send this transaction to
* @property value - Value associated with this transaction
* Standard data concerning a transaction to be processed by the blockchain.
*/
export interface Transaction {
/**
* Network ID as per EIP-155.
*/
chainId?: Hex;

/**
* Data to pass with this transaction.
*/
data?: string;

/**
* Error message for gas estimation failure.
*/
estimateGasError?: string;

/**
* Estimated base fee for this transaction.
*/
estimatedBaseFee?: string;

/**
* Address to send this transaction from.
*/
from: string;

/**
* Gas to send with this transaction.
*/
gas?: string;

/**
* Price of gas with this transaction.
*/
gasPrice?: string;

/**
* Gas used in the transaction.
*/
gasUsed?: string;

/**
* Maximum fee per gas for this transaction.
*/
maxFeePerGas?: string;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're alphabetical up to here, shall we take the opportunity to order the rest?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Member Author

@OGPoyraz OGPoyraz Aug 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk how did I missed it heh


/**
* Maximum priority fee per gas for this transaction.
*/
maxPriorityFeePerGas?: string;

/**
* Unique number to prevent replay attacks.
*/
nonce?: string;

/**
* Address to send this transaction to.
*/
to?: string;

/**
* Value associated with this transaction.
*/
value?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
estimatedBaseFee?: string;
estimateGasError?: string;
}

/**
Expand Down Expand Up @@ -208,3 +303,13 @@ export interface RemoteTransactionSource {
request: RemoteTransactionSourceRequest,
) => Promise<TransactionMeta[]>;
}

/**
* Gas values initially suggested by the dApp.
*/
export interface DappSuggestedGasFees {
gas?: string;
gasPrice?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
}