Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions src/SmartTransactionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@

#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.
Expand Down Expand Up @@ -443,9 +451,9 @@
isSmartTransactionPending,
);
if (!this.timeoutHandle && pendingTransactions?.length > 0) {
this.poll();

Check warning on line 454 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 454 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
} else if (this.timeoutHandle && pendingTransactions?.length === 0) {
this.stop();

Check warning on line 456 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 456 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}
}

Expand All @@ -470,7 +478,7 @@
}

this.timeoutHandle = setInterval(() => {
safelyExecute(async () => this.updateSmartTransactions());

Check warning on line 481 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 481 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}, this.#interval);
await safelyExecute(async () => this.updateSmartTransactions());
}
Expand Down Expand Up @@ -537,7 +545,7 @@
ethQuery = new EthQuery(provider);
}

this.#createOrUpdateSmartTransaction(smartTransaction, {

Check warning on line 548 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 548 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
chainId,
ethQuery,
});
Expand Down Expand Up @@ -818,9 +826,13 @@
});

// 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,
Expand Down Expand Up @@ -912,15 +924,21 @@
);
}
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;
Expand Down Expand Up @@ -977,11 +995,18 @@
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({
Expand Down Expand Up @@ -1129,13 +1154,18 @@
} = {},
): Promise<void> {
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 }),
},
),
);
}

Expand Down
117 changes: 117 additions & 0 deletions src/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
18 changes: 17 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,17 @@
) => 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`;
}

Expand All @@ -51,14 +58,23 @@
}

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`;
}

Expand Down Expand Up @@ -289,8 +305,8 @@
}

const errorMessage = originalTransactionStatus
? `Smart transaction failed with status: ${status}, originalTransactionStatus: ${originalTransactionStatus}`

Check warning on line 308 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Invalid type "string | undefined" of template literal expression

Check warning on line 308 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Invalid type "string | undefined" of template literal expression
: `Smart transaction failed with status: ${status}`;

Check warning on line 309 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Invalid type "string | undefined" of template literal expression

Check warning on line 309 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Invalid type "string | undefined" of template literal expression

for (const tx of transactionsToFail) {
if (tx.status === TransactionStatus.failed) {
Expand All @@ -307,7 +323,7 @@

updateTransaction(
updatedTransaction,
`Smart transaction status: ${status}`,

Check warning on line 326 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Invalid type "string | undefined" of template literal expression

Check warning on line 326 in src/utils.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Invalid type "string | undefined" of template literal expression
);
}
};
Loading