diff --git a/src/SmartTransactionsController.ts b/src/SmartTransactionsController.ts index 06fdd38..3f4bd60 100644 --- a/src/SmartTransactionsController.ts +++ b/src/SmartTransactionsController.ts @@ -288,6 +288,14 @@ export class SmartTransactionsController extends StaticIntervalPollingController #trace: TraceCallback; + #isStxMigrationFlagEnabled(flagName: string): boolean { + const flag = this.messenger.call('RemoteFeatureFlagController:getState') + ?.remoteFeatureFlags?.[flagName]; + return Boolean( + flag && typeof flag === 'object' && 'value' in flag && flag.value, + ); + } + /** * Validates the smart transactions feature flags from the remote feature flag controller * and reports any validation errors to Sentry via ErrorReportingService. @@ -818,9 +826,13 @@ export class SmartTransactionsController extends StaticIntervalPollingController }); // Construct the URL and fetch the data + const useSentinelForBatchStatus = this.#isStxMigrationFlagEnabled( + 'stxMigrationBatchStatus', + ); const url = `${getAPIRequestURL( APIType.BATCH_STATUS, chainId, + useSentinelForBatchStatus, )}?${params.toString()}`; const data = (await this.#fetch(url)) as Record< string, @@ -912,15 +924,21 @@ export class SmartTransactionsController extends StaticIntervalPollingController ); } transactions.push(unsignedTradeTransactionWithNonce); + const useSentinelForGetFees = this.#isStxMigrationFlagEnabled( + 'stxMigrationGetFees', + ); const data = await this.#trace( { name: SmartTransactionsTraceName.GetFees }, async () => - await this.#fetch(getAPIRequestURL(APIType.GET_FEES, chainId), { - method: 'POST', - body: JSON.stringify({ - txs: transactions, - }), - }), + await this.#fetch( + getAPIRequestURL(APIType.GET_FEES, chainId, useSentinelForGetFees), + { + method: 'POST', + body: JSON.stringify({ + txs: transactions, + }), + }, + ), ); let approvalTxFees: IndividualTxFees | null; let tradeTxFees: IndividualTxFees | null; @@ -977,11 +995,18 @@ export class SmartTransactionsController extends StaticIntervalPollingController const ethQuery = this.#getEthQuery({ networkClientId: selectedNetworkClientId, }); + const useSentinelForSubmitTransactions = this.#isStxMigrationFlagEnabled( + 'stxMigrationSubmitTransactions', + ); const data = await this.#trace( { name: SmartTransactionsTraceName.SubmitTransactions }, async () => await this.#fetch( - getAPIRequestURL(APIType.SUBMIT_TRANSACTIONS, chainId), + getAPIRequestURL( + APIType.SUBMIT_TRANSACTIONS, + chainId, + useSentinelForSubmitTransactions, + ), { method: 'POST', body: JSON.stringify({ @@ -1129,13 +1154,18 @@ export class SmartTransactionsController extends StaticIntervalPollingController } = {}, ): Promise { const chainId = this.#getChainId({ networkClientId }); + const useSentinelForCancel = + this.#isStxMigrationFlagEnabled('stxMigrationCancel'); await this.#trace( { name: SmartTransactionsTraceName.CancelTransaction }, async () => - await this.#fetch(getAPIRequestURL(APIType.CANCEL, chainId), { - method: 'POST', - body: JSON.stringify({ uuid }), - }), + await this.#fetch( + getAPIRequestURL(APIType.CANCEL, chainId, useSentinelForCancel), + { + method: 'POST', + body: JSON.stringify({ uuid }), + }, + ), ); } diff --git a/src/utils.test.ts b/src/utils.test.ts index 8ad50b2..db936b5 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -118,6 +118,123 @@ describe('src/utils.js', () => { `${SENTINEL_API_BASE_URL_MAP[baseChainIdDec]}/network`, ); }); + + // Sentinel routing via useSentinel flag + describe('GET_FEES sentinel routing', () => { + it('returns a sentinel URL when useSentinel is true and chain is in SENTINEL_API_BASE_URL_MAP', () => { + expect( + utils.getAPIRequestURL(APIType.GET_FEES, ChainId.mainnet, true), + ).toBe( + `${SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec]}/v1/networks/${ethereumChainIdDec}/getFees`, + ); + }); + + it('returns the API_BASE_URL when useSentinel is false', () => { + expect( + utils.getAPIRequestURL(APIType.GET_FEES, ChainId.mainnet, false), + ).toBe(`${API_BASE_URL}/networks/${ethereumChainIdDec}/getFees`); + }); + + it('returns the API_BASE_URL when useSentinel is true but chain is not in SENTINEL_API_BASE_URL_MAP', () => { + const unsupportedChainId = '0x539'; // 1337 — local dev chain, not in map + const chainIdDec = parseInt(unsupportedChainId, 16); + expect( + utils.getAPIRequestURL(APIType.GET_FEES, unsupportedChainId, true), + ).toBe(`${API_BASE_URL}/networks/${chainIdDec}/getFees`); + }); + }); + + describe('SUBMIT_TRANSACTIONS sentinel routing', () => { + it('returns a sentinel URL when useSentinel is true and chain is in SENTINEL_API_BASE_URL_MAP', () => { + expect( + utils.getAPIRequestURL( + APIType.SUBMIT_TRANSACTIONS, + ChainId.mainnet, + true, + ), + ).toBe( + `${SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec]}/v1/networks/${ethereumChainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`, + ); + }); + + it('returns the API_BASE_URL when useSentinel is false', () => { + expect( + utils.getAPIRequestURL( + APIType.SUBMIT_TRANSACTIONS, + ChainId.mainnet, + false, + ), + ).toBe( + `${API_BASE_URL}/networks/${ethereumChainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`, + ); + }); + + it('returns the API_BASE_URL when useSentinel is true but chain is not in SENTINEL_API_BASE_URL_MAP', () => { + const unsupportedChainId = '0x539'; + const chainIdDec = parseInt(unsupportedChainId, 16); + expect( + utils.getAPIRequestURL( + APIType.SUBMIT_TRANSACTIONS, + unsupportedChainId, + true, + ), + ).toBe( + `${API_BASE_URL}/networks/${chainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`, + ); + }); + }); + + describe('CANCEL sentinel routing', () => { + it('returns a sentinel URL when useSentinel is true and chain is in SENTINEL_API_BASE_URL_MAP', () => { + expect( + utils.getAPIRequestURL(APIType.CANCEL, ChainId.mainnet, true), + ).toBe( + `${SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec]}/v1/networks/${ethereumChainIdDec}/cancel`, + ); + }); + + it('returns the API_BASE_URL when useSentinel is false', () => { + expect( + utils.getAPIRequestURL(APIType.CANCEL, ChainId.mainnet, false), + ).toBe(`${API_BASE_URL}/networks/${ethereumChainIdDec}/cancel`); + }); + + it('returns the API_BASE_URL when useSentinel is true but chain is not in SENTINEL_API_BASE_URL_MAP', () => { + const unsupportedChainId = '0x539'; + const chainIdDec = parseInt(unsupportedChainId, 16); + expect( + utils.getAPIRequestURL(APIType.CANCEL, unsupportedChainId, true), + ).toBe(`${API_BASE_URL}/networks/${chainIdDec}/cancel`); + }); + }); + + describe('BATCH_STATUS sentinel routing', () => { + it('returns a sentinel URL when useSentinel is true and chain is in SENTINEL_API_BASE_URL_MAP', () => { + expect( + utils.getAPIRequestURL(APIType.BATCH_STATUS, ChainId.mainnet, true), + ).toBe( + `${SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec]}/v1/networks/${ethereumChainIdDec}/batchStatus`, + ); + }); + + it('returns the API_BASE_URL when useSentinel is false', () => { + expect( + utils.getAPIRequestURL(APIType.BATCH_STATUS, ChainId.mainnet, false), + ).toBe(`${API_BASE_URL}/networks/${ethereumChainIdDec}/batchStatus`); + }); + + it('returns the API_BASE_URL when useSentinel is true but chain is not in SENTINEL_API_BASE_URL_MAP', () => { + const unsupportedChainId = '0x539'; + const chainIdDec = parseInt(unsupportedChainId, 16); + expect( + utils.getAPIRequestURL( + APIType.BATCH_STATUS, + unsupportedChainId, + true, + ), + ).toBe(`${API_BASE_URL}/networks/${chainIdDec}/batchStatus`); + }); + }); }); describe('isSmartTransactionStatusResolved', () => { diff --git a/src/utils.ts b/src/utils.ts index 92b4aee..2e2f61c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -39,10 +39,17 @@ export const isSmartTransactionStatusResolved = ( ) => stxStatus === 'uuid_not_found'; // TODO use actual url once API is defined -export function getAPIRequestURL(apiType: APIType, chainId: string): string { +export function getAPIRequestURL( + apiType: APIType, + chainId: string, + useSentinel = false, +): string { const chainIdDec = parseInt(chainId, 16); switch (apiType) { case APIType.GET_FEES: { + if (useSentinel && SENTINEL_API_BASE_URL_MAP[chainIdDec]) { + return `${SENTINEL_API_BASE_URL_MAP[chainIdDec]}/v1/networks/${chainIdDec}/getFees`; + } return `${API_BASE_URL}/networks/${chainIdDec}/getFees`; } @@ -51,14 +58,23 @@ export function getAPIRequestURL(apiType: APIType, chainId: string): string { } case APIType.SUBMIT_TRANSACTIONS: { + if (useSentinel && SENTINEL_API_BASE_URL_MAP[chainIdDec]) { + return `${SENTINEL_API_BASE_URL_MAP[chainIdDec]}/v1/networks/${chainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`; + } return `${API_BASE_URL}/networks/${chainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`; } case APIType.CANCEL: { + if (useSentinel && SENTINEL_API_BASE_URL_MAP[chainIdDec]) { + return `${SENTINEL_API_BASE_URL_MAP[chainIdDec]}/v1/networks/${chainIdDec}/cancel`; + } return `${API_BASE_URL}/networks/${chainIdDec}/cancel`; } case APIType.BATCH_STATUS: { + if (useSentinel && SENTINEL_API_BASE_URL_MAP[chainIdDec]) { + return `${SENTINEL_API_BASE_URL_MAP[chainIdDec]}/v1/networks/${chainIdDec}/batchStatus`; + } return `${API_BASE_URL}/networks/${chainIdDec}/batchStatus`; }