mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
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
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
<svg viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.334 13.723a.835.835 0 0 1 0 1.667H7.668a.834.834 0 1 1 0-1.667h1.666ZM6.668 9.557a.833.833 0 0 1 0 1.666H4.334a.833.833 0 1 1 0-1.666h2.334Zm3.5-4.167a.833.833 0 0 1 0 1.667H1.834a.834.834 0 0 1 0-1.667h8.334ZM12.66 9.676c.345 0 .624-.28.624-.625V6.015c0-.345.28-.625.625-.625h.179c.345 0 .625.28.625.625v3.036c0 .345.28.625.625.625h3.036c.345 0 .625.28.625.625v.178c0 .346-.28.625-.625.625h-3.036a.625.625 0 0 0-.625.625v3.036c0 .345-.28.625-.625.625h-.179a.625.625 0 0 1-.625-.625V11.73a.625.625 0 0 0-.625-.625H9.624a.625.625 0 0 1-.625-.625v-.178c0-.345.28-.625.625-.625h3.035Z" fill="currentColor"/>
|
||||
<path d="M9.334 13.723a.835.835 0 0 1 0 1.667H7.668a.834.834 0 1 1 0-1.667h1.666ZM6.668 9.557a.833.833 0 0 1 0 1.666H4.334a.833.833 0 1 1 0-1.666h2.334Zm3.5-4.167a.833.833 0 0 1 0 1.667H1.834a.834.834 0 0 1 0-1.667h8.334Zm2.492 4.286c.345 0 .624-.28.624-.625V6.015c0-.345.28-.625.625-.625h.179c.345 0 .625.28.625.625v3.036c0 .345.28.625.625.625h3.036c.345 0 .625.28.625.625v.178c0 .346-.28.625-.625.625h-3.036a.625.625 0 0 0-.625.625v3.036c0 .345-.28.625-.625.625h-.179a.625.625 0 0 1-.625-.625V11.73a.625.625 0 0 0-.625-.625H9.624a.625.625 0 0 1-.625-.625v-.178c0-.345.28-.625.625-.625h3.035Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 699 B After Width: | Height: | Size: 699 B |
+2
-2
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
@@ -45,6 +45,16 @@ export const RESOURCES = {
|
||||
userOps: USER_OPS_API_RESOURCES,
|
||||
visualize: VISUALIZE_API_RESOURCES,
|
||||
zetachain: ZETA_CHAIN_API_RESOURCES,
|
||||
// external API resources
|
||||
// there is no type definition for them, use valibot to parse the response
|
||||
external: {
|
||||
gas_hawk_saving_potential: {
|
||||
path: '/api/v2/gas-hawk-saving-potential',
|
||||
},
|
||||
safe_transaction_api: {
|
||||
path: '',
|
||||
},
|
||||
},
|
||||
} satisfies Record<ApiName, Record<string, ApiResource>>;
|
||||
|
||||
export const resourceKey = (x: ResourceName) => x;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
export type ApiName =
|
||||
'general' | 'admin' | 'bens' | 'contractInfo' | 'clusters' |
|
||||
'general' | 'admin' | 'bens' | 'contractInfo' | 'clusters' | 'external' |
|
||||
'metadata' | 'multichain' | 'rewards' | 'stats' | 'tac' |
|
||||
'userOps' | 'visualize' | 'zetachain';
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function useQueryClientConfig() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const EXTERNAL_API_RESOURCES: Array<ResourceName | 'safe_transaction_api' | 'gas_hawk_saving_potential'> = [
|
||||
const EXTERNAL_API_RESOURCES: Array<ResourceName> = [
|
||||
'general:contract_solidity_scan_report',
|
||||
'general:address_xstar_score',
|
||||
'general:address_3rd_party_info',
|
||||
@@ -38,8 +38,8 @@ export default function useQueryClientConfig() {
|
||||
'general:noves_address_history',
|
||||
'general:noves_describe_txs',
|
||||
// these resources are not proxied by the backend
|
||||
'safe_transaction_api',
|
||||
'gas_hawk_saving_potential',
|
||||
'external:safe_transaction_api',
|
||||
'external:gas_hawk_saving_potential',
|
||||
];
|
||||
const isExternalApiResource = EXTERNAL_API_RESOURCES.some((resource) => query.queryKey[0] === resource);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function useIsSafeAddress(hash: string | undefined): boolean {
|
||||
const fetch = useFetch();
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: [ 'safe_transaction_api', hash ],
|
||||
queryKey: [ 'external:safe_transaction_api', hash ],
|
||||
queryFn: async() => {
|
||||
if (!feature.isEnabled || !hash) {
|
||||
return Promise.reject();
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import * as v from 'valibot';
|
||||
|
||||
import type { ApiName } from 'lib/api/types';
|
||||
|
||||
import { httpLogger } from 'nextjs/utils/logger';
|
||||
|
||||
import { RESOURCES } from 'lib/api/resources';
|
||||
import getErrorMessage from 'lib/errors/getErrorMessage';
|
||||
import metrics from 'lib/monitoring/metrics';
|
||||
|
||||
const PayloadSchema = v.object({
|
||||
resource: v.string(),
|
||||
url: v.string(),
|
||||
});
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const payload = JSON.parse(req.body);
|
||||
metrics?.invalidApiSchema.inc(v.parse(PayloadSchema, payload));
|
||||
} catch (error) {}
|
||||
const payload: { resource?: string; url?: string } = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
||||
|
||||
if (!payload.resource) {
|
||||
throw new Error('Resource not found');
|
||||
}
|
||||
|
||||
const [ apiName, resourceName ] = payload.resource.split(':');
|
||||
const api = RESOURCES[apiName as ApiName];
|
||||
const resource = api?.[resourceName as keyof typeof api];
|
||||
|
||||
if (!resource) {
|
||||
throw new Error(`Resource not found: ${ payload.resource }`);
|
||||
}
|
||||
|
||||
const url = payload.url ? new URL(payload.url) : undefined;
|
||||
|
||||
metrics?.invalidApiSchema.inc({
|
||||
resource: payload.resource,
|
||||
url: url?.toString(),
|
||||
});
|
||||
} catch (error) {
|
||||
httpLogger.logger.error({ message: 'Unable to process invalid API schema', error: getErrorMessage(error) || 'Unknown error' });
|
||||
}
|
||||
res.status(200).json({ status: 'ok' });
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import config from 'configs/app';
|
||||
import { Image } from 'toolkit/chakra/image';
|
||||
import { Link } from 'toolkit/chakra/link';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import { HEX_REGEXP_WITH_0X } from 'toolkit/utils/regexp';
|
||||
import TextSeparator from 'ui/shared/TextSeparator';
|
||||
|
||||
const feature = config.features.saveOnGas;
|
||||
@@ -26,9 +27,9 @@ const AddressSaveOnGas = ({ gasUsed, address }: Props) => {
|
||||
const gasUsedNumber = Number(gasUsed);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ 'gas_hawk_saving_potential', { address } ],
|
||||
queryKey: [ 'external:gas_hawk_saving_potential', { address } ],
|
||||
queryFn: async() => {
|
||||
if (!feature.isEnabled) {
|
||||
if (!feature.isEnabled || !HEX_REGEXP_WITH_0X.test(address)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ const AddressSaveOnGas = ({ gasUsed, address }: Props) => {
|
||||
const parsedResponse = v.safeParse(responseSchema, response);
|
||||
|
||||
if (!parsedResponse.success) {
|
||||
throw Error('Invalid response schema');
|
||||
throw Error(ERROR_NAME);
|
||||
}
|
||||
|
||||
return parsedResponse.output;
|
||||
@@ -52,12 +53,12 @@ const AddressSaveOnGas = ({ gasUsed, address }: Props) => {
|
||||
const errorMessage = query.error && 'message' in query.error ? query.error.message : undefined;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (errorMessage === ERROR_NAME) {
|
||||
if (feature.isEnabled && ERROR_NAME === errorMessage) {
|
||||
fetch('/node-api/monitoring/invalid-api-schema', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
resource: 'gas_hawk_saving_potential',
|
||||
url: feature.isEnabled ? feature.apiUrlTemplate.replace('<address>', address) : undefined,
|
||||
resource: 'external:gas_hawk_saving_potential',
|
||||
url: feature.isEnabled && HEX_REGEXP_WITH_0X.test(address) ? feature.apiUrlTemplate.replace('<address>', address) : undefined,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function useDescribeTxs(items: Array<Transaction> | undefined, vi
|
||||
};
|
||||
|
||||
const describeQuery = useQuery({
|
||||
queryKey: [ 'noves_describe_txs', queryKey ],
|
||||
queryKey: [ 'general:noves_describe_txs', queryKey ],
|
||||
queryFn: async() => {
|
||||
const queries = txChunks.map((hashes) => {
|
||||
if (hashes.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user