Skip to content

Commit 16bf2d9

Browse files
authored
Release v2.3.3 (#3010)
* check address for GasHawk integration * validate invalid API schema payload * Invalid API schema: enhance request handling * don't throw 429 error for noves_describe_txs resource
1 parent 77d590f commit 16bf2d9

File tree

9 files changed

+55
-24
lines changed

9 files changed

+55
-24
lines changed

icons/advanced-filter.svg

Lines changed: 1 addition & 1 deletion
Loading

icons/files/csv.svg

Lines changed: 2 additions & 2 deletions
Loading

lib/api/resources.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ export const RESOURCES = {
4545
userOps: USER_OPS_API_RESOURCES,
4646
visualize: VISUALIZE_API_RESOURCES,
4747
zetachain: ZETA_CHAIN_API_RESOURCES,
48+
// external API resources
49+
// there is no type definition for them, use valibot to parse the response
50+
external: {
51+
gas_hawk_saving_potential: {
52+
path: '/api/v2/gas-hawk-saving-potential',
53+
},
54+
safe_transaction_api: {
55+
path: '',
56+
},
57+
},
4858
} satisfies Record<ApiName, Record<string, ApiResource>>;
4959

5060
export const resourceKey = (x: ResourceName) => x;

lib/api/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export type ApiName =
2-
'general' | 'admin' | 'bens' | 'contractInfo' | 'clusters' |
2+
'general' | 'admin' | 'bens' | 'contractInfo' | 'clusters' | 'external' |
33
'metadata' | 'multichain' | 'rewards' | 'stats' | 'tac' |
44
'userOps' | 'visualize' | 'zetachain';
55

lib/api/useQueryClientConfig.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ export default function useQueryClientConfig() {
3030
return false;
3131
}
3232

33-
const EXTERNAL_API_RESOURCES: Array<ResourceName | 'safe_transaction_api' | 'gas_hawk_saving_potential'> = [
33+
const EXTERNAL_API_RESOURCES: Array<ResourceName> = [
3434
'general:contract_solidity_scan_report',
3535
'general:address_xstar_score',
3636
'general:address_3rd_party_info',
3737
'general:noves_transaction',
3838
'general:noves_address_history',
3939
'general:noves_describe_txs',
4040
// these resources are not proxied by the backend
41-
'safe_transaction_api',
42-
'gas_hawk_saving_potential',
41+
'external:safe_transaction_api',
42+
'external:gas_hawk_saving_potential',
4343
];
4444
const isExternalApiResource = EXTERNAL_API_RESOURCES.some((resource) => query.queryKey[0] === resource);
4545

lib/hooks/useIsSafeAddress.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default function useIsSafeAddress(hash: string | undefined): boolean {
99
const fetch = useFetch();
1010

1111
const { data } = useQuery({
12-
queryKey: [ 'safe_transaction_api', hash ],
12+
queryKey: [ 'external:safe_transaction_api', hash ],
1313
queryFn: async() => {
1414
if (!feature.isEnabled || !hash) {
1515
return Promise.reject();
Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
import type { NextApiRequest, NextApiResponse } from 'next';
2-
import * as v from 'valibot';
32

4-
import metrics from 'lib/monitoring/metrics';
3+
import type { ApiName } from 'lib/api/types';
4+
5+
import { httpLogger } from 'nextjs/utils/logger';
56

6-
const PayloadSchema = v.object({
7-
resource: v.string(),
8-
url: v.string(),
9-
});
7+
import { RESOURCES } from 'lib/api/resources';
8+
import getErrorMessage from 'lib/errors/getErrorMessage';
9+
import metrics from 'lib/monitoring/metrics';
1010

1111
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
1212
try {
13-
const payload = JSON.parse(req.body);
14-
metrics?.invalidApiSchema.inc(v.parse(PayloadSchema, payload));
15-
} catch (error) {}
13+
const payload: { resource?: string; url?: string } = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
14+
15+
if (!payload.resource) {
16+
throw new Error('Resource not found');
17+
}
18+
19+
const [ apiName, resourceName ] = payload.resource.split(':');
20+
const api = RESOURCES[apiName as ApiName];
21+
const resource = api?.[resourceName as keyof typeof api];
22+
23+
if (!resource) {
24+
throw new Error(`Resource not found: ${ payload.resource }`);
25+
}
26+
27+
const url = payload.url ? new URL(payload.url) : undefined;
28+
29+
metrics?.invalidApiSchema.inc({
30+
resource: payload.resource,
31+
url: url?.toString(),
32+
});
33+
} catch (error) {
34+
httpLogger.logger.error({ message: 'Unable to process invalid API schema', error: getErrorMessage(error) || 'Unknown error' });
35+
}
1636
res.status(200).json({ status: 'ok' });
1737
}

ui/address/details/AddressSaveOnGas.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import config from 'configs/app';
66
import { Image } from 'toolkit/chakra/image';
77
import { Link } from 'toolkit/chakra/link';
88
import { Skeleton } from 'toolkit/chakra/skeleton';
9+
import { HEX_REGEXP_WITH_0X } from 'toolkit/utils/regexp';
910
import TextSeparator from 'ui/shared/TextSeparator';
1011

1112
const feature = config.features.saveOnGas;
@@ -26,9 +27,9 @@ const AddressSaveOnGas = ({ gasUsed, address }: Props) => {
2627
const gasUsedNumber = Number(gasUsed);
2728

2829
const query = useQuery({
29-
queryKey: [ 'gas_hawk_saving_potential', { address } ],
30+
queryKey: [ 'external:gas_hawk_saving_potential', { address } ],
3031
queryFn: async() => {
31-
if (!feature.isEnabled) {
32+
if (!feature.isEnabled || !HEX_REGEXP_WITH_0X.test(address)) {
3233
return;
3334
}
3435

@@ -40,7 +41,7 @@ const AddressSaveOnGas = ({ gasUsed, address }: Props) => {
4041
const parsedResponse = v.safeParse(responseSchema, response);
4142

4243
if (!parsedResponse.success) {
43-
throw Error('Invalid response schema');
44+
throw Error(ERROR_NAME);
4445
}
4546

4647
return parsedResponse.output;
@@ -52,12 +53,12 @@ const AddressSaveOnGas = ({ gasUsed, address }: Props) => {
5253
const errorMessage = query.error && 'message' in query.error ? query.error.message : undefined;
5354

5455
React.useEffect(() => {
55-
if (errorMessage === ERROR_NAME) {
56+
if (feature.isEnabled && ERROR_NAME === errorMessage) {
5657
fetch('/node-api/monitoring/invalid-api-schema', {
5758
method: 'POST',
5859
body: JSON.stringify({
59-
resource: 'gas_hawk_saving_potential',
60-
url: feature.isEnabled ? feature.apiUrlTemplate.replace('<address>', address) : undefined,
60+
resource: 'external:gas_hawk_saving_potential',
61+
url: feature.isEnabled && HEX_REGEXP_WITH_0X.test(address) ? feature.apiUrlTemplate.replace('<address>', address) : undefined,
6162
}),
6263
});
6364
}

ui/txs/noves/useDescribeTxs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default function useDescribeTxs(items: Array<Transaction> | undefined, vi
2525
};
2626

2727
const describeQuery = useQuery({
28-
queryKey: [ 'noves_describe_txs', queryKey ],
28+
queryKey: [ 'general:noves_describe_txs', queryKey ],
2929
queryFn: async() => {
3030
const queries = txChunks.map((hashes) => {
3131
if (hashes.length === 0) {

0 commit comments

Comments
 (0)