Skip to content

Commit f2d5eed

Browse files
rarquevauxclaude
andcommitted
feat(STX-501): add remote feature flag routing to getAPIRequestURL for sentinel migration
Adds an optional useSentinel parameter to getAPIRequestURL() and reads four LaunchDarkly boolean flags (stxMigrationBatchStatus, stxMigrationGetFees, stxMigrationSubmitTransactions, stxMigrationCancel) from RemoteFeatureFlagController at each call site. All flags default to false, so no production traffic shifts on merge. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1afaeac commit f2d5eed

3 files changed

Lines changed: 174 additions & 12 deletions

File tree

src/SmartTransactionsController.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -818,9 +818,14 @@ export class SmartTransactionsController extends StaticIntervalPollingController
818818
});
819819

820820
// Construct the URL and fetch the data
821+
const { remoteFeatureFlags: rfFlags } = this.messenger.call(
822+
'RemoteFeatureFlagController:getState',
823+
);
824+
const useSentinelForBatchStatus = Boolean(rfFlags?.stxMigrationBatchStatus);
821825
const url = `${getAPIRequestURL(
822826
APIType.BATCH_STATUS,
823827
chainId,
828+
useSentinelForBatchStatus,
824829
)}?${params.toString()}`;
825830
const data = (await this.#fetch(url)) as Record<
826831
string,
@@ -912,15 +917,22 @@ export class SmartTransactionsController extends StaticIntervalPollingController
912917
);
913918
}
914919
transactions.push(unsignedTradeTransactionWithNonce);
920+
const { remoteFeatureFlags: rfFlagsGetFees } = this.messenger.call(
921+
'RemoteFeatureFlagController:getState',
922+
);
923+
const useSentinelForGetFees = Boolean(rfFlagsGetFees?.stxMigrationGetFees);
915924
const data = await this.#trace(
916925
{ name: SmartTransactionsTraceName.GetFees },
917926
async () =>
918-
await this.#fetch(getAPIRequestURL(APIType.GET_FEES, chainId), {
919-
method: 'POST',
920-
body: JSON.stringify({
921-
txs: transactions,
922-
}),
923-
}),
927+
await this.#fetch(
928+
getAPIRequestURL(APIType.GET_FEES, chainId, useSentinelForGetFees),
929+
{
930+
method: 'POST',
931+
body: JSON.stringify({
932+
txs: transactions,
933+
}),
934+
},
935+
),
924936
);
925937
let approvalTxFees: IndividualTxFees | null;
926938
let tradeTxFees: IndividualTxFees | null;
@@ -977,11 +989,21 @@ export class SmartTransactionsController extends StaticIntervalPollingController
977989
const ethQuery = this.#getEthQuery({
978990
networkClientId: selectedNetworkClientId,
979991
});
992+
const { remoteFeatureFlags: rfFlagsSubmit } = this.messenger.call(
993+
'RemoteFeatureFlagController:getState',
994+
);
995+
const useSentinelForSubmitTransactions = Boolean(
996+
rfFlagsSubmit?.stxMigrationSubmitTransactions,
997+
);
980998
const data = await this.#trace(
981999
{ name: SmartTransactionsTraceName.SubmitTransactions },
9821000
async () =>
9831001
await this.#fetch(
984-
getAPIRequestURL(APIType.SUBMIT_TRANSACTIONS, chainId),
1002+
getAPIRequestURL(
1003+
APIType.SUBMIT_TRANSACTIONS,
1004+
chainId,
1005+
useSentinelForSubmitTransactions,
1006+
),
9851007
{
9861008
method: 'POST',
9871009
body: JSON.stringify({
@@ -1129,13 +1151,20 @@ export class SmartTransactionsController extends StaticIntervalPollingController
11291151
} = {},
11301152
): Promise<void> {
11311153
const chainId = this.#getChainId({ networkClientId });
1154+
const { remoteFeatureFlags: rfFlagsCancel } = this.messenger.call(
1155+
'RemoteFeatureFlagController:getState',
1156+
);
1157+
const useSentinelForCancel = Boolean(rfFlagsCancel?.stxMigrationCancel);
11321158
await this.#trace(
11331159
{ name: SmartTransactionsTraceName.CancelTransaction },
11341160
async () =>
1135-
await this.#fetch(getAPIRequestURL(APIType.CANCEL, chainId), {
1136-
method: 'POST',
1137-
body: JSON.stringify({ uuid }),
1138-
}),
1161+
await this.#fetch(
1162+
getAPIRequestURL(APIType.CANCEL, chainId, useSentinelForCancel),
1163+
{
1164+
method: 'POST',
1165+
body: JSON.stringify({ uuid }),
1166+
},
1167+
),
11391168
);
11401169
}
11411170

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)