Skip to content

Commit 5eec8f3

Browse files
authored
feat(STX-501): route sentinel migration via remote feature flags (#580)
Gate each sentinel API endpoint behind a dedicated remote feature flag.
1 parent 1afaeac commit 5eec8f3

3 files changed

Lines changed: 175 additions & 12 deletions

File tree

src/SmartTransactionsController.ts

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

289289
#trace: TraceCallback;
290290

291+
#isStxMigrationFlagEnabled(flagName: string): boolean {
292+
const flag = this.messenger.call('RemoteFeatureFlagController:getState')
293+
?.remoteFeatureFlags?.[flagName];
294+
return Boolean(
295+
flag && typeof flag === 'object' && 'value' in flag && flag.value,
296+
);
297+
}
298+
291299
/**
292300
* Validates the smart transactions feature flags from the remote feature flag controller
293301
* and reports any validation errors to Sentry via ErrorReportingService.
@@ -818,9 +826,13 @@ export class SmartTransactionsController extends StaticIntervalPollingController
818826
});
819827

820828
// Construct the URL and fetch the data
829+
const useSentinelForBatchStatus = this.#isStxMigrationFlagEnabled(
830+
'stxMigrationBatchStatus',
831+
);
821832
const url = `${getAPIRequestURL(
822833
APIType.BATCH_STATUS,
823834
chainId,
835+
useSentinelForBatchStatus,
824836
)}?${params.toString()}`;
825837
const data = (await this.#fetch(url)) as Record<
826838
string,
@@ -912,15 +924,21 @@ export class SmartTransactionsController extends StaticIntervalPollingController
912924
);
913925
}
914926
transactions.push(unsignedTradeTransactionWithNonce);
927+
const useSentinelForGetFees = this.#isStxMigrationFlagEnabled(
928+
'stxMigrationGetFees',
929+
);
915930
const data = await this.#trace(
916931
{ name: SmartTransactionsTraceName.GetFees },
917932
async () =>
918-
await this.#fetch(getAPIRequestURL(APIType.GET_FEES, chainId), {
919-
method: 'POST',
920-
body: JSON.stringify({
921-
txs: transactions,
922-
}),
923-
}),
933+
await this.#fetch(
934+
getAPIRequestURL(APIType.GET_FEES, chainId, useSentinelForGetFees),
935+
{
936+
method: 'POST',
937+
body: JSON.stringify({
938+
txs: transactions,
939+
}),
940+
},
941+
),
924942
);
925943
let approvalTxFees: IndividualTxFees | null;
926944
let tradeTxFees: IndividualTxFees | null;
@@ -977,11 +995,18 @@ export class SmartTransactionsController extends StaticIntervalPollingController
977995
const ethQuery = this.#getEthQuery({
978996
networkClientId: selectedNetworkClientId,
979997
});
998+
const useSentinelForSubmitTransactions = this.#isStxMigrationFlagEnabled(
999+
'stxMigrationSubmitTransactions',
1000+
);
9801001
const data = await this.#trace(
9811002
{ name: SmartTransactionsTraceName.SubmitTransactions },
9821003
async () =>
9831004
await this.#fetch(
984-
getAPIRequestURL(APIType.SUBMIT_TRANSACTIONS, chainId),
1005+
getAPIRequestURL(
1006+
APIType.SUBMIT_TRANSACTIONS,
1007+
chainId,
1008+
useSentinelForSubmitTransactions,
1009+
),
9851010
{
9861011
method: 'POST',
9871012
body: JSON.stringify({
@@ -1129,13 +1154,18 @@ export class SmartTransactionsController extends StaticIntervalPollingController
11291154
} = {},
11301155
): Promise<void> {
11311156
const chainId = this.#getChainId({ networkClientId });
1157+
const useSentinelForCancel =
1158+
this.#isStxMigrationFlagEnabled('stxMigrationCancel');
11321159
await this.#trace(
11331160
{ name: SmartTransactionsTraceName.CancelTransaction },
11341161
async () =>
1135-
await this.#fetch(getAPIRequestURL(APIType.CANCEL, chainId), {
1136-
method: 'POST',
1137-
body: JSON.stringify({ uuid }),
1138-
}),
1162+
await this.#fetch(
1163+
getAPIRequestURL(APIType.CANCEL, chainId, useSentinelForCancel),
1164+
{
1165+
method: 'POST',
1166+
body: JSON.stringify({ uuid }),
1167+
},
1168+
),
11391169
);
11401170
}
11411171

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)