diff --git a/src/SmartTransactionsController.test.ts b/src/SmartTransactionsController.test.ts index 7f100415..b61156a2 100644 --- a/src/SmartTransactionsController.test.ts +++ b/src/SmartTransactionsController.test.ts @@ -47,59 +47,104 @@ const createUnsignedTransaction = () => { const createGetFeesApiResponse = () => { return { - cancelFees: [ - { maxFeePerGas: 2100001000, maxPriorityFeePerGas: 466503987 }, - { maxFeePerGas: 2310003200, maxPriorityFeePerGas: 513154852 }, - { maxFeePerGas: 2541005830, maxPriorityFeePerGas: 564470851 }, - { maxFeePerGas: 2795108954, maxPriorityFeePerGas: 620918500 }, - { maxFeePerGas: 3074622644, maxPriorityFeePerGas: 683010971 }, - { maxFeePerGas: 3382087983, maxPriorityFeePerGas: 751312751 }, - { maxFeePerGas: 3720300164, maxPriorityFeePerGas: 826444778 }, - { maxFeePerGas: 4092333900, maxPriorityFeePerGas: 909090082 }, - { maxFeePerGas: 4501571383, maxPriorityFeePerGas: 1000000000 }, - { maxFeePerGas: 4951733023, maxPriorityFeePerGas: 1100001000 }, - { maxFeePerGas: 5446911277, maxPriorityFeePerGas: 1210002200 }, - { maxFeePerGas: 5991607851, maxPriorityFeePerGas: 1331003630 }, - { maxFeePerGas: 6590774628, maxPriorityFeePerGas: 1464105324 }, - { maxFeePerGas: 7249858682, maxPriorityFeePerGas: 1610517320 }, - { maxFeePerGas: 7974851800, maxPriorityFeePerGas: 1771570663 }, - { maxFeePerGas: 8772344955, maxPriorityFeePerGas: 1948729500 }, - { maxFeePerGas: 9649588222, maxPriorityFeePerGas: 2143604399 }, - { maxFeePerGas: 10614556694, maxPriorityFeePerGas: 2357966983 }, - { maxFeePerGas: 11676022978, maxPriorityFeePerGas: 2593766039 }, - ], - feeEstimate: 42000000000000, - fees: [ - { maxFeePerGas: 2310003200, maxPriorityFeePerGas: 513154852 }, - { maxFeePerGas: 2541005830, maxPriorityFeePerGas: 564470850 }, - { maxFeePerGas: 2795108954, maxPriorityFeePerGas: 620918500 }, - { maxFeePerGas: 3074622644, maxPriorityFeePerGas: 683010970 }, - { maxFeePerGas: 3382087983, maxPriorityFeePerGas: 751312751 }, - { maxFeePerGas: 3720300163, maxPriorityFeePerGas: 826444777 }, - { maxFeePerGas: 4092333900, maxPriorityFeePerGas: 909090082 }, - { maxFeePerGas: 4501571382, maxPriorityFeePerGas: 999999999 }, - { maxFeePerGas: 4951733022, maxPriorityFeePerGas: 1100001000 }, - { maxFeePerGas: 5446911277, maxPriorityFeePerGas: 1210002200 }, - { maxFeePerGas: 5991607851, maxPriorityFeePerGas: 1331003630 }, - { maxFeePerGas: 6590774627, maxPriorityFeePerGas: 1464105324 }, - { maxFeePerGas: 7249858681, maxPriorityFeePerGas: 1610517320 }, - { maxFeePerGas: 7974851800, maxPriorityFeePerGas: 1771570662 }, - { maxFeePerGas: 8772344954, maxPriorityFeePerGas: 1948729500 }, - { maxFeePerGas: 9649588222, maxPriorityFeePerGas: 2143604398 }, - { maxFeePerGas: 10614556693, maxPriorityFeePerGas: 2357966982 }, - { maxFeePerGas: 11676022977, maxPriorityFeePerGas: 2593766039 }, - { maxFeePerGas: 12843636951, maxPriorityFeePerGas: 2853145236 }, + txs: [ + { + // Approval tx. + cancelFees: [ + { maxFeePerGas: 2100001000, maxPriorityFeePerGas: 466503987 }, + { maxFeePerGas: 2310003200, maxPriorityFeePerGas: 513154852 }, + { maxFeePerGas: 2541005830, maxPriorityFeePerGas: 564470851 }, + { maxFeePerGas: 2795108954, maxPriorityFeePerGas: 620918500 }, + { maxFeePerGas: 3074622644, maxPriorityFeePerGas: 683010971 }, + { maxFeePerGas: 3382087983, maxPriorityFeePerGas: 751312751 }, + { maxFeePerGas: 3720300164, maxPriorityFeePerGas: 826444778 }, + { maxFeePerGas: 4092333900, maxPriorityFeePerGas: 909090082 }, + { maxFeePerGas: 4501571383, maxPriorityFeePerGas: 1000000000 }, + { maxFeePerGas: 4951733023, maxPriorityFeePerGas: 1100001000 }, + { maxFeePerGas: 5446911277, maxPriorityFeePerGas: 1210002200 }, + { maxFeePerGas: 5991607851, maxPriorityFeePerGas: 1331003630 }, + { maxFeePerGas: 6590774628, maxPriorityFeePerGas: 1464105324 }, + { maxFeePerGas: 7249858682, maxPriorityFeePerGas: 1610517320 }, + { maxFeePerGas: 7974851800, maxPriorityFeePerGas: 1771570663 }, + { maxFeePerGas: 8772344955, maxPriorityFeePerGas: 1948729500 }, + { maxFeePerGas: 9649588222, maxPriorityFeePerGas: 2143604399 }, + { maxFeePerGas: 10614556694, maxPriorityFeePerGas: 2357966983 }, + { maxFeePerGas: 11676022978, maxPriorityFeePerGas: 2593766039 }, + ], + feeEstimate: 42000000000000, + fees: [ + { maxFeePerGas: 2310003200, maxPriorityFeePerGas: 513154852 }, + { maxFeePerGas: 2541005830, maxPriorityFeePerGas: 564470850 }, + { maxFeePerGas: 2795108954, maxPriorityFeePerGas: 620918500 }, + { maxFeePerGas: 3074622644, maxPriorityFeePerGas: 683010970 }, + { maxFeePerGas: 3382087983, maxPriorityFeePerGas: 751312751 }, + { maxFeePerGas: 3720300163, maxPriorityFeePerGas: 826444777 }, + { maxFeePerGas: 4092333900, maxPriorityFeePerGas: 909090082 }, + { maxFeePerGas: 4501571382, maxPriorityFeePerGas: 999999999 }, + { maxFeePerGas: 4951733022, maxPriorityFeePerGas: 1100001000 }, + { maxFeePerGas: 5446911277, maxPriorityFeePerGas: 1210002200 }, + { maxFeePerGas: 5991607851, maxPriorityFeePerGas: 1331003630 }, + { maxFeePerGas: 6590774627, maxPriorityFeePerGas: 1464105324 }, + { maxFeePerGas: 7249858681, maxPriorityFeePerGas: 1610517320 }, + { maxFeePerGas: 7974851800, maxPriorityFeePerGas: 1771570662 }, + { maxFeePerGas: 8772344954, maxPriorityFeePerGas: 1948729500 }, + { maxFeePerGas: 9649588222, maxPriorityFeePerGas: 2143604398 }, + { maxFeePerGas: 10614556693, maxPriorityFeePerGas: 2357966982 }, + { maxFeePerGas: 11676022977, maxPriorityFeePerGas: 2593766039 }, + { maxFeePerGas: 12843636951, maxPriorityFeePerGas: 2853145236 }, + ], + gasLimit: 21000, + gasUsed: 21000, + }, + { + // Trade tx. + cancelFees: [ + { maxFeePerGas: 2100001000, maxPriorityFeePerGas: 466503987 }, + { maxFeePerGas: 2310003200, maxPriorityFeePerGas: 513154852 }, + { maxFeePerGas: 2541005830, maxPriorityFeePerGas: 564470851 }, + { maxFeePerGas: 2795108954, maxPriorityFeePerGas: 620918500 }, + { maxFeePerGas: 3074622644, maxPriorityFeePerGas: 683010971 }, + { maxFeePerGas: 3382087983, maxPriorityFeePerGas: 751312751 }, + { maxFeePerGas: 3720300164, maxPriorityFeePerGas: 826444778 }, + { maxFeePerGas: 4092333900, maxPriorityFeePerGas: 909090082 }, + { maxFeePerGas: 4501571383, maxPriorityFeePerGas: 1000000000 }, + { maxFeePerGas: 4951733023, maxPriorityFeePerGas: 1100001000 }, + { maxFeePerGas: 5446911277, maxPriorityFeePerGas: 1210002200 }, + { maxFeePerGas: 5991607851, maxPriorityFeePerGas: 1331003630 }, + { maxFeePerGas: 6590774628, maxPriorityFeePerGas: 1464105324 }, + { maxFeePerGas: 7249858682, maxPriorityFeePerGas: 1610517320 }, + { maxFeePerGas: 7974851800, maxPriorityFeePerGas: 1771570663 }, + { maxFeePerGas: 8772344955, maxPriorityFeePerGas: 1948729500 }, + { maxFeePerGas: 9649588222, maxPriorityFeePerGas: 2143604399 }, + { maxFeePerGas: 10614556694, maxPriorityFeePerGas: 2357966983 }, + { maxFeePerGas: 11676022978, maxPriorityFeePerGas: 2593766039 }, + ], + feeEstimate: 42000000000000, + fees: [ + { maxFeePerGas: 2310003200, maxPriorityFeePerGas: 513154852 }, + { maxFeePerGas: 2541005830, maxPriorityFeePerGas: 564470850 }, + { maxFeePerGas: 2795108954, maxPriorityFeePerGas: 620918500 }, + { maxFeePerGas: 3074622644, maxPriorityFeePerGas: 683010970 }, + { maxFeePerGas: 3382087983, maxPriorityFeePerGas: 751312751 }, + { maxFeePerGas: 3720300163, maxPriorityFeePerGas: 826444777 }, + { maxFeePerGas: 4092333900, maxPriorityFeePerGas: 909090082 }, + { maxFeePerGas: 4501571382, maxPriorityFeePerGas: 999999999 }, + { maxFeePerGas: 4951733022, maxPriorityFeePerGas: 1100001000 }, + { maxFeePerGas: 5446911277, maxPriorityFeePerGas: 1210002200 }, + { maxFeePerGas: 5991607851, maxPriorityFeePerGas: 1331003630 }, + { maxFeePerGas: 6590774627, maxPriorityFeePerGas: 1464105324 }, + { maxFeePerGas: 7249858681, maxPriorityFeePerGas: 1610517320 }, + { maxFeePerGas: 7974851800, maxPriorityFeePerGas: 1771570662 }, + { maxFeePerGas: 8772344954, maxPriorityFeePerGas: 1948729500 }, + { maxFeePerGas: 9649588222, maxPriorityFeePerGas: 2143604398 }, + { maxFeePerGas: 10614556693, maxPriorityFeePerGas: 2357966982 }, + { maxFeePerGas: 11676022977, maxPriorityFeePerGas: 2593766039 }, + { maxFeePerGas: 12843636951, maxPriorityFeePerGas: 2853145236 }, + ], + gasLimit: 21000, + gasUsed: 21000, + }, ], - gasLimit: 21000, - gasUsed: 21000, - }; -}; - -const createEstimateGasApiResponse = () => { - return { - feeEstimate: 42000000000000, - gasLimit: 21000, - gasUsed: 21000, }; }; @@ -255,12 +300,11 @@ describe('SmartTransactionsController', () => { [CHAIN_IDS.ETHEREUM]: [], }, userOptIn: undefined, - fees: undefined, - liveness: true, - estimatedGas: { - approvalTxData: undefined, - txData: undefined, + fees: { + approvalTxFees: undefined, + tradeTxFees: undefined, }, + liveness: true, }, }); }); @@ -391,15 +435,20 @@ describe('SmartTransactionsController', () => { describe('getFees', () => { it('gets unsigned transactions and estimates based on an unsigned transaction', async () => { - const unsignedTransaction = createUnsignedTransaction(); + const tradeTx = createUnsignedTransaction(); + const approvalTx = createUnsignedTransaction(); const getFeesApiResponse = createGetFeesApiResponse(); nock(API_BASE_URL) .post(`/networks/${ethereumChainIdDec}/getFees`) .reply(200, getFeesApiResponse); const fees = await smartTransactionsController.getFees( - unsignedTransaction, + tradeTx, + approvalTx, ); - expect(fees).toStrictEqual(getFeesApiResponse); + expect(fees).toMatchObject({ + approvalTxFees: getFeesApiResponse.txs[0], + tradeTxFees: getFeesApiResponse.txs[1], + }); }); }); @@ -451,12 +500,11 @@ describe('SmartTransactionsController', () => { [CHAIN_IDS.ETHEREUM]: [pendingTransaction], }, userOptIn: undefined, - fees: undefined, - liveness: true, - estimatedGas: { - approvalTxData: undefined, - txData: undefined, + fees: { + approvalTxFees: undefined, + tradeTxFees: undefined, }, + liveness: true, }, }); }); @@ -488,10 +536,9 @@ describe('SmartTransactionsController', () => { ], }, userOptIn: undefined, - fees: undefined, - estimatedGas: { - approvalTxData: undefined, - txData: undefined, + fees: { + approvalTxFees: undefined, + tradeTxFees: undefined, }, liveness: true, }, @@ -688,19 +735,4 @@ describe('SmartTransactionsController', () => { expect(actual).toBe(false); }); }); - - describe('estimateGas', () => { - it('gets estimated gas for a transaction', async () => { - const unsignedTransaction = createUnsignedTransaction(); - const estimateGasApiResponse = createEstimateGasApiResponse(); - nock(API_BASE_URL) - .post(`/networks/${ethereumChainIdDec}/estimateGas`) - .reply(200, estimateGasApiResponse); - const estimatedGas = await smartTransactionsController.estimateGas( - unsignedTransaction, - null, - ); - expect(estimatedGas).toStrictEqual(estimateGasApiResponse); - }); - }); }); diff --git a/src/SmartTransactionsController.ts b/src/SmartTransactionsController.ts index 77ab5813..77bd4600 100644 --- a/src/SmartTransactionsController.ts +++ b/src/SmartTransactionsController.ts @@ -18,7 +18,7 @@ import { SmartTransactionsStatus, SmartTransactionStatuses, Fees, - EstimatedGas, + IndividualTxFees, } from './types'; import { getAPIRequestURL, @@ -30,6 +30,7 @@ import { getStxProcessingTime, handleFetch, isSmartTransactionCancellable, + incrementNonceInHex, } from './utils'; import { CHAIN_IDS } from './constants'; @@ -50,10 +51,9 @@ export interface SmartTransactionsControllerState extends BaseState { smartTransactions: Record; userOptIn: boolean | undefined; liveness: boolean | undefined; - fees: Fees | undefined; - estimatedGas: { - txData: EstimatedGas | undefined; - approvalTxData: EstimatedGas | undefined; + fees: { + approvalTxFees: IndividualTxFees | undefined; + tradeTxFees: IndividualTxFees | undefined; }; }; } @@ -122,12 +122,11 @@ export default class SmartTransactionsController extends BaseController< smartTransactionsState: { smartTransactions: {}, userOptIn: undefined, - fees: undefined, - liveness: true, - estimatedGas: { - txData: undefined, - approvalTxData: undefined, + fees: { + approvalTxFees: undefined, + tradeTxFees: undefined, }, + liveness: true, }, }; @@ -469,72 +468,57 @@ export default class SmartTransactionsController extends BaseController< }; } - async getFees(unsignedTransaction: UnsignedTransaction): Promise { + async getFees( + tradeTx: UnsignedTransaction, + approvalTx: UnsignedTransaction, + ): Promise { const { chainId } = this.config; - - const unsignedTransactionWithNonce = await this.addNonceToTransaction( - unsignedTransaction, - ); + const transactions = []; + let unsignedTradeTransactionWithNonce; + if (approvalTx) { + const unsignedApprovalTransactionWithNonce = await this.addNonceToTransaction( + approvalTx, + ); + transactions.push(unsignedApprovalTransactionWithNonce); + unsignedTradeTransactionWithNonce = { + ...tradeTx, + // If there is an approval tx, the trade tx's nonce is increased by 1. + nonce: incrementNonceInHex(unsignedApprovalTransactionWithNonce.nonce), + }; + } else { + unsignedTradeTransactionWithNonce = await this.addNonceToTransaction( + tradeTx, + ); + } + transactions.push(unsignedTradeTransactionWithNonce); const data = await this.fetch(getAPIRequestURL(APIType.GET_FEES, chainId), { method: 'POST', body: JSON.stringify({ - tx: unsignedTransactionWithNonce, + txs: transactions, }), }); - this.update({ - smartTransactionsState: { - ...this.state.smartTransactionsState, - fees: data, - }, - }); - return data; - } - - async estimateGas( - unsignedTransaction: UnsignedTransaction, - approveTxParams: UnsignedTransaction, - ): Promise { - const { chainId } = this.config; - - let approvalTxData; - if (approveTxParams) { - const unsignedApprovalTransactionWithNonce = await this.addNonceToTransaction( - approveTxParams, - ); - approvalTxData = await this.fetch( - getAPIRequestURL(APIType.ESTIMATE_GAS, chainId), - { - method: 'POST', - body: JSON.stringify({ - tx: unsignedApprovalTransactionWithNonce, - }), - }, - ); + let approvalTxFees; + let tradeTxFees; + if (approvalTx) { + approvalTxFees = data?.txs[0]; + tradeTxFees = data?.txs[1]; + } else { + tradeTxFees = data?.txs[0]; } - const unsignedTransactionWithNonce = await this.addNonceToTransaction( - unsignedTransaction, - ); - const data = await this.fetch( - getAPIRequestURL(APIType.ESTIMATE_GAS, chainId), - { - method: 'POST', - body: JSON.stringify({ - tx: unsignedTransactionWithNonce, - ...(approveTxParams && { pending_txs: [approveTxParams] }), - }), - }, - ); + this.update({ smartTransactionsState: { ...this.state.smartTransactionsState, - estimatedGas: { - txData: data, - approvalTxData, + fees: { + approvalTxFees, + tradeTxFees, }, }, }); - - return data; + return { + approvalTxFees, + tradeTxFees, + }; } // * After this successful call client must add a nonce representative to diff --git a/src/types.ts b/src/types.ts index 573f7358..221ecc99 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,6 +26,7 @@ export enum SmartTransactionCancellationReason { INVALID_NONCE = 'invalid_nonce', USER_CANCELLED = 'user_cancelled', NOT_CANCELLED = 'not_cancelled', + PREVIOUS_TX_CANCELLED = 'previous_tx_cancelled', } export enum SmartTransactionStatuses { @@ -39,6 +40,7 @@ export enum SmartTransactionStatuses { CANCELLED_DEADLINE_MISSED = 'cancelled_deadline_missed', CANCELLED_INVALID_NONCE = 'cancelled_invalid_nonce', CANCELLED_USER_CANCELLED = 'cancelled_user_cancelled', + CANCELLED_PREVIOUS_TX_CANCELLED = 'cancelled_previous_tx_cancelled', RESOLVED = 'resolved', } @@ -53,6 +55,8 @@ export const cancellationReasonToStatusMap = { SmartTransactionStatuses.CANCELLED_INVALID_NONCE, [SmartTransactionCancellationReason.USER_CANCELLED]: SmartTransactionStatuses.CANCELLED_USER_CANCELLED, + [SmartTransactionCancellationReason.PREVIOUS_TX_CANCELLED]: + SmartTransactionStatuses.CANCELLED_PREVIOUS_TX_CANCELLED, }; export interface SmartTransactionsStatus { @@ -92,7 +96,7 @@ export interface Fee { maxPriorityFeePerGas: number; } -export interface Fees { +export interface IndividualTxFees { fees: Fee[]; cancelFees: Fee[]; feeEstimate: number; @@ -100,13 +104,12 @@ export interface Fees { gasUsed: number; } -export interface EstimatedGas { - gasUsed: number; - gasLimit: number; - feeEstimate: number; +export interface Fees { + approvalTxFees: IndividualTxFees | undefined; + tradeTxFees: IndividualTxFees | undefined; } -// TODO: maybe grab the type from transactions controller? +// TODO export type UnsignedTransaction = any; // TODO diff --git a/src/utils.test.ts b/src/utils.test.ts index 565088ad..e1246d30 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -157,6 +157,17 @@ describe('src/utils.js', () => { SmartTransactionStatuses.CANCELLED_USER_CANCELLED, ); }); + + it('returns cancellation state "CANCELLED_PREVIOUS_TX_CANCELLED" if cancellationReason provided', () => { + const statusResponse = { + ...createStatusResponse(), + cancellationReason: + SmartTransactionCancellationReason.PREVIOUS_TX_CANCELLED, + }; + expect(utils.calculateStatus(statusResponse)).toStrictEqual( + SmartTransactionStatuses.CANCELLED_PREVIOUS_TX_CANCELLED, + ); + }); }); describe('getStxProcessingTime', () => { @@ -212,4 +223,11 @@ describe('src/utils.js', () => { expect(utils.isSmartTransactionCancellable(stxStatus)).toBe(false); }); }); + + describe('incrementNonceInHex', () => { + it('returns "0x57" if we pass "0x56"', () => { + const incrementedNonce = utils.incrementNonceInHex('0x56'); + expect(incrementedNonce).toStrictEqual('0x57'); + }); + }); }); diff --git a/src/utils.ts b/src/utils.ts index d8e3e042..4e504e80 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,7 @@ import jsonDiffer from 'fast-json-patch'; import { cloneDeep } from 'lodash'; +import { BigNumber } from 'bignumber.js'; +import { ethers } from 'ethers'; import { APIType, SmartTransaction, @@ -63,6 +65,7 @@ export const calculateStatus = (stxStatus: SmartTransactionsStatus) => { SmartTransactionCancellationReason.DEADLINE_MISSED, SmartTransactionCancellationReason.INVALID_NONCE, SmartTransactionCancellationReason.USER_CANCELLED, + SmartTransactionCancellationReason.PREVIOUS_TX_CANCELLED, ]; if (stxStatus?.minedTx === SmartTransactionMinedTx.NOT_MINED) { if ( @@ -183,3 +186,8 @@ export const isSmartTransactionCancellable = ( SmartTransactionCancellationReason.NOT_CANCELLED) ); }; + +export const incrementNonceInHex = (nonceInHex: string): string => { + const nonceInDec = new BigNumber(nonceInHex, 16).toString(10); + return ethers.utils.hexlify(Number(nonceInDec) + 1); +};