Skip to content

Commit ac311a1

Browse files
committed
feat(STX-501): route sentinel migration via remote feature flags
Use RemoteFeatureFlagController to gate the four sentinel API endpoints (batch status, get fees, submit transactions, cancel) behind per-endpoint feature flags.
1 parent 1afaeac commit ac311a1

3 files changed

Lines changed: 189 additions & 12 deletions

File tree

src/SmartTransactionsController.ts

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,34 @@ export class SmartTransactionsController extends StaticIntervalPollingController
288288

289289
#trace: TraceCallback;
290290

291+
#isStxMigrationBatchStatusEnabled(): boolean {
292+
return Boolean(
293+
this.messenger.call('RemoteFeatureFlagController:getState')
294+
?.remoteFeatureFlags?.stxMigrationBatchStatus,
295+
);
296+
}
297+
298+
#isStxMigrationGetFeesEnabled(): boolean {
299+
return Boolean(
300+
this.messenger.call('RemoteFeatureFlagController:getState')
301+
?.remoteFeatureFlags?.stxMigrationGetFees,
302+
);
303+
}
304+
305+
#isStxMigrationSubmitTransactionsEnabled(): boolean {
306+
return Boolean(
307+
this.messenger.call('RemoteFeatureFlagController:getState')
308+
?.remoteFeatureFlags?.stxMigrationSubmitTransactions,
309+
);
310+
}
311+
312+
#isStxMigrationCancelEnabled(): boolean {
313+
return Boolean(
314+
this.messenger.call('RemoteFeatureFlagController:getState')
315+
?.remoteFeatureFlags?.stxMigrationCancel,
316+
);
317+
}
318+
291319
/**
292320
* Validates the smart transactions feature flags from the remote feature flag controller
293321
* and reports any validation errors to Sentry via ErrorReportingService.
@@ -818,9 +846,11 @@ export class SmartTransactionsController extends StaticIntervalPollingController
818846
});
819847

820848
// Construct the URL and fetch the data
849+
const useSentinelForBatchStatus = this.#isStxMigrationBatchStatusEnabled();
821850
const url = `${getAPIRequestURL(
822851
APIType.BATCH_STATUS,
823852
chainId,
853+
useSentinelForBatchStatus,
824854
)}?${params.toString()}`;
825855
const data = (await this.#fetch(url)) as Record<
826856
string,
@@ -912,15 +942,19 @@ export class SmartTransactionsController extends StaticIntervalPollingController
912942
);
913943
}
914944
transactions.push(unsignedTradeTransactionWithNonce);
945+
const useSentinelForGetFees = this.#isStxMigrationGetFeesEnabled();
915946
const data = await this.#trace(
916947
{ name: SmartTransactionsTraceName.GetFees },
917948
async () =>
918-
await this.#fetch(getAPIRequestURL(APIType.GET_FEES, chainId), {
919-
method: 'POST',
920-
body: JSON.stringify({
921-
txs: transactions,
922-
}),
923-
}),
949+
await this.#fetch(
950+
getAPIRequestURL(APIType.GET_FEES, chainId, useSentinelForGetFees),
951+
{
952+
method: 'POST',
953+
body: JSON.stringify({
954+
txs: transactions,
955+
}),
956+
},
957+
),
924958
);
925959
let approvalTxFees: IndividualTxFees | null;
926960
let tradeTxFees: IndividualTxFees | null;
@@ -977,11 +1011,17 @@ export class SmartTransactionsController extends StaticIntervalPollingController
9771011
const ethQuery = this.#getEthQuery({
9781012
networkClientId: selectedNetworkClientId,
9791013
});
1014+
const useSentinelForSubmitTransactions =
1015+
this.#isStxMigrationSubmitTransactionsEnabled();
9801016
const data = await this.#trace(
9811017
{ name: SmartTransactionsTraceName.SubmitTransactions },
9821018
async () =>
9831019
await this.#fetch(
984-
getAPIRequestURL(APIType.SUBMIT_TRANSACTIONS, chainId),
1020+
getAPIRequestURL(
1021+
APIType.SUBMIT_TRANSACTIONS,
1022+
chainId,
1023+
useSentinelForSubmitTransactions,
1024+
),
9851025
{
9861026
method: 'POST',
9871027
body: JSON.stringify({
@@ -1129,13 +1169,17 @@ export class SmartTransactionsController extends StaticIntervalPollingController
11291169
} = {},
11301170
): Promise<void> {
11311171
const chainId = this.#getChainId({ networkClientId });
1172+
const useSentinelForCancel = this.#isStxMigrationCancelEnabled();
11321173
await this.#trace(
11331174
{ name: SmartTransactionsTraceName.CancelTransaction },
11341175
async () =>
1135-
await this.#fetch(getAPIRequestURL(APIType.CANCEL, chainId), {
1136-
method: 'POST',
1137-
body: JSON.stringify({ uuid }),
1138-
}),
1176+
await this.#fetch(
1177+
getAPIRequestURL(APIType.CANCEL, chainId, useSentinelForCancel),
1178+
{
1179+
method: 'POST',
1180+
body: JSON.stringify({ uuid }),
1181+
},
1182+
),
11391183
);
11401184
}
11411185

src/utils.test.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,123 @@ describe('src/utils.js', () => {
118118
`${SENTINEL_API_BASE_URL_MAP[baseChainIdDec]}/network`,
119119
);
120120
});
121+
122+
// Sentinel routing via useSentinel flag
123+
describe('GET_FEES sentinel routing', () => {
124+
it('returns a sentinel URL when useSentinel is true and chain is in SENTINEL_API_BASE_URL_MAP', () => {
125+
expect(
126+
utils.getAPIRequestURL(APIType.GET_FEES, ChainId.mainnet, true),
127+
).toBe(
128+
`${SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec]}/v1/networks/${ethereumChainIdDec}/getFees`,
129+
);
130+
});
131+
132+
it('returns the API_BASE_URL when useSentinel is false', () => {
133+
expect(
134+
utils.getAPIRequestURL(APIType.GET_FEES, ChainId.mainnet, false),
135+
).toBe(`${API_BASE_URL}/networks/${ethereumChainIdDec}/getFees`);
136+
});
137+
138+
it('returns the API_BASE_URL when useSentinel is true but chain is not in SENTINEL_API_BASE_URL_MAP', () => {
139+
const unsupportedChainId = '0x539'; // 1337 — local dev chain, not in map
140+
const chainIdDec = parseInt(unsupportedChainId, 16);
141+
expect(
142+
utils.getAPIRequestURL(APIType.GET_FEES, unsupportedChainId, true),
143+
).toBe(`${API_BASE_URL}/networks/${chainIdDec}/getFees`);
144+
});
145+
});
146+
147+
describe('SUBMIT_TRANSACTIONS sentinel routing', () => {
148+
it('returns a sentinel URL when useSentinel is true and chain is in SENTINEL_API_BASE_URL_MAP', () => {
149+
expect(
150+
utils.getAPIRequestURL(
151+
APIType.SUBMIT_TRANSACTIONS,
152+
ChainId.mainnet,
153+
true,
154+
),
155+
).toBe(
156+
`${SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec]}/v1/networks/${ethereumChainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`,
157+
);
158+
});
159+
160+
it('returns the API_BASE_URL when useSentinel is false', () => {
161+
expect(
162+
utils.getAPIRequestURL(
163+
APIType.SUBMIT_TRANSACTIONS,
164+
ChainId.mainnet,
165+
false,
166+
),
167+
).toBe(
168+
`${API_BASE_URL}/networks/${ethereumChainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`,
169+
);
170+
});
171+
172+
it('returns the API_BASE_URL when useSentinel is true but chain is not in SENTINEL_API_BASE_URL_MAP', () => {
173+
const unsupportedChainId = '0x539';
174+
const chainIdDec = parseInt(unsupportedChainId, 16);
175+
expect(
176+
utils.getAPIRequestURL(
177+
APIType.SUBMIT_TRANSACTIONS,
178+
unsupportedChainId,
179+
true,
180+
),
181+
).toBe(
182+
`${API_BASE_URL}/networks/${chainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`,
183+
);
184+
});
185+
});
186+
187+
describe('CANCEL sentinel routing', () => {
188+
it('returns a sentinel URL when useSentinel is true and chain is in SENTINEL_API_BASE_URL_MAP', () => {
189+
expect(
190+
utils.getAPIRequestURL(APIType.CANCEL, ChainId.mainnet, true),
191+
).toBe(
192+
`${SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec]}/v1/networks/${ethereumChainIdDec}/cancel`,
193+
);
194+
});
195+
196+
it('returns the API_BASE_URL when useSentinel is false', () => {
197+
expect(
198+
utils.getAPIRequestURL(APIType.CANCEL, ChainId.mainnet, false),
199+
).toBe(`${API_BASE_URL}/networks/${ethereumChainIdDec}/cancel`);
200+
});
201+
202+
it('returns the API_BASE_URL when useSentinel is true but chain is not in SENTINEL_API_BASE_URL_MAP', () => {
203+
const unsupportedChainId = '0x539';
204+
const chainIdDec = parseInt(unsupportedChainId, 16);
205+
expect(
206+
utils.getAPIRequestURL(APIType.CANCEL, unsupportedChainId, true),
207+
).toBe(`${API_BASE_URL}/networks/${chainIdDec}/cancel`);
208+
});
209+
});
210+
211+
describe('BATCH_STATUS sentinel routing', () => {
212+
it('returns a sentinel URL when useSentinel is true and chain is in SENTINEL_API_BASE_URL_MAP', () => {
213+
expect(
214+
utils.getAPIRequestURL(APIType.BATCH_STATUS, ChainId.mainnet, true),
215+
).toBe(
216+
`${SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec]}/v1/networks/${ethereumChainIdDec}/batchStatus`,
217+
);
218+
});
219+
220+
it('returns the API_BASE_URL when useSentinel is false', () => {
221+
expect(
222+
utils.getAPIRequestURL(APIType.BATCH_STATUS, ChainId.mainnet, false),
223+
).toBe(`${API_BASE_URL}/networks/${ethereumChainIdDec}/batchStatus`);
224+
});
225+
226+
it('returns the API_BASE_URL when useSentinel is true but chain is not in SENTINEL_API_BASE_URL_MAP', () => {
227+
const unsupportedChainId = '0x539';
228+
const chainIdDec = parseInt(unsupportedChainId, 16);
229+
expect(
230+
utils.getAPIRequestURL(
231+
APIType.BATCH_STATUS,
232+
unsupportedChainId,
233+
true,
234+
),
235+
).toBe(`${API_BASE_URL}/networks/${chainIdDec}/batchStatus`);
236+
});
237+
});
121238
});
122239

123240
describe('isSmartTransactionStatusResolved', () => {

src/utils.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,17 @@ export const isSmartTransactionStatusResolved = (
3939
) => stxStatus === 'uuid_not_found';
4040

4141
// TODO use actual url once API is defined
42-
export function getAPIRequestURL(apiType: APIType, chainId: string): string {
42+
export function getAPIRequestURL(
43+
apiType: APIType,
44+
chainId: string,
45+
useSentinel = false,
46+
): string {
4347
const chainIdDec = parseInt(chainId, 16);
4448
switch (apiType) {
4549
case APIType.GET_FEES: {
50+
if (useSentinel && SENTINEL_API_BASE_URL_MAP[chainIdDec]) {
51+
return `${SENTINEL_API_BASE_URL_MAP[chainIdDec]}/v1/networks/${chainIdDec}/getFees`;
52+
}
4653
return `${API_BASE_URL}/networks/${chainIdDec}/getFees`;
4754
}
4855

@@ -51,14 +58,23 @@ export function getAPIRequestURL(apiType: APIType, chainId: string): string {
5158
}
5259

5360
case APIType.SUBMIT_TRANSACTIONS: {
61+
if (useSentinel && SENTINEL_API_BASE_URL_MAP[chainIdDec]) {
62+
return `${SENTINEL_API_BASE_URL_MAP[chainIdDec]}/v1/networks/${chainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`;
63+
}
5464
return `${API_BASE_URL}/networks/${chainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`;
5565
}
5666

5767
case APIType.CANCEL: {
68+
if (useSentinel && SENTINEL_API_BASE_URL_MAP[chainIdDec]) {
69+
return `${SENTINEL_API_BASE_URL_MAP[chainIdDec]}/v1/networks/${chainIdDec}/cancel`;
70+
}
5871
return `${API_BASE_URL}/networks/${chainIdDec}/cancel`;
5972
}
6073

6174
case APIType.BATCH_STATUS: {
75+
if (useSentinel && SENTINEL_API_BASE_URL_MAP[chainIdDec]) {
76+
return `${SENTINEL_API_BASE_URL_MAP[chainIdDec]}/v1/networks/${chainIdDec}/batchStatus`;
77+
}
6278
return `${API_BASE_URL}/networks/${chainIdDec}/batchStatus`;
6379
}
6480

0 commit comments

Comments
 (0)