From 23e1dbffcecce6911df6c0780e1ff6e2983dc859 Mon Sep 17 00:00:00 2001 From: kyranjamie Date: Tue, 7 May 2024 11:03:05 +0200 Subject: [PATCH] fix: add schema validation for stamps --- package.json | 1 + pnpm-lock.yaml | 10 +- .../bitcoin/stamps/stamps-by-address.query.ts | 124 ++++++++++-------- 3 files changed, 77 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index 3c670d84347..abb6d5d25e2 100644 --- a/package.json +++ b/package.json @@ -244,6 +244,7 @@ "varuint-bitcoin": "1.1.2", "webextension-polyfill": "0.10.0", "yup": "1.3.3", + "zod": "3.23.6", "zxcvbn": "4.4.2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d146ebca8e2..24bca004867 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -356,6 +356,9 @@ dependencies: yup: specifier: 1.3.3 version: 1.3.3 + zod: + specifier: 3.23.6 + version: 3.23.6 zxcvbn: specifier: 4.4.2 version: 4.4.2 @@ -22656,9 +22659,6 @@ packages: /sqlite3@5.1.6: resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==} requiresBuild: true - peerDependenciesMeta: - node-gyp: - optional: true dependencies: '@mapbox/node-pre-gyp': 1.0.11 node-addon-api: 4.3.0 @@ -25199,6 +25199,10 @@ packages: jszip: 3.10.1 dev: true + /zod@3.23.6: + resolution: {integrity: sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA==} + dev: false + /zone-file@2.0.0-beta.3: resolution: {integrity: sha512-6tE3PSRcpN5lbTTLlkLez40WkNPc9vw/u1J2j6DBiy0jcVX48nCkWrx2EC+bWHqC2SLp069Xw4AdnYn/qp/W5g==} engines: {node: '>=10'} diff --git a/src/app/query/bitcoin/stamps/stamps-by-address.query.ts b/src/app/query/bitcoin/stamps/stamps-by-address.query.ts index f5d109f91c9..8fd3a11b8a0 100644 --- a/src/app/query/bitcoin/stamps/stamps-by-address.query.ts +++ b/src/app/query/bitcoin/stamps/stamps-by-address.query.ts @@ -1,65 +1,74 @@ import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; +import { ZodError, z } from 'zod'; + +import { analytics } from '@shared/utils/analytics'; import { AppUseQueryConfig } from '@app/query/query-config'; import { QueryPrefixes } from '@app/query/query-prefixes'; -export interface Stamp { - stamp: number; - block_index: number; - cpid: string; - asset_longname: string; - creator: string; - divisible: number; - keyburn: number; - locked: number; - message_index: number; - stamp_base64: string; - stamp_mimetype: string; - stamp_url: string; - supply: number; - timestamp: string; - tx_hash: string; - tx_index: number; - src_data: string; - ident: string; - creator_name: string; - stamp_gen: string; - stamp_hash: string; - is_btc_stamp: number; - is_reissue: number; - file_hash: string; -} +const stampSchema = z.object({ + stamp: z.number(), + block_index: z.number(), + cpid: z.string(), + asset_longname: z.string(), + creator: z.string(), + divisible: z.number(), + keyburn: z.number(), + locked: z.number(), + message_index: z.number(), + stamp_base64: z.string(), + stamp_mimetype: z.string(), + stamp_url: z.string(), + supply: z.number(), + timestamp: z.string(), + tx_hash: z.string(), + tx_index: z.number(), + src_data: z.string(), + ident: z.string(), + creator_name: z.string(), + stamp_gen: z.string(), + stamp_hash: z.string(), + is_btc_stamp: z.number(), + is_reissue: z.number(), + file_hash: z.string(), +}); -export interface Src20Token { - id: string; - address: string; - cpid: string; - p: string; - tick: string; - amt: number; - block_time: string; - last_update: number; -} +export type Stamp = z.infer; -interface StampsByAddressQueryResponse { - page: number; - limit: number; - totalPages: number; - total: number; - last_block: number; - btc: { - address: string; - balance: number; - txCount: number; - unconfirmedBalance: number; - unconfirmedTxCount: number; - }; - data: { - stamps: Stamp[]; - src20: Src20Token[]; - }; -} +const src20TokenSchema = z.object({ + id: z.string(), + address: z.string(), + cpid: z.string(), + p: z.string(), + tick: z.string(), + amt: z.number(), + block_time: z.string(), + last_update: z.number(), +}); + +export type Src20Token = z.infer; + +const stampsByAdressSchema = z.object({ + page: z.number(), + limit: z.number(), + totalPages: z.number(), + total: z.number(), + last_block: z.number().optional(), + btc: z.object({ + address: z.string(), + balance: z.number(), + txCount: z.number(), + unconfirmedBalance: z.number(), + unconfirmedTxCount: z.number(), + }), + data: z.object({ + stamps: z.array(stampSchema), + src20: z.array(src20TokenSchema), + }), +}); + +type StampsByAddressQueryResponse = z.infer; /** * @see https://stampchain.io/docs#/default/get_api_v2_balance__address_ @@ -68,7 +77,12 @@ async function fetchStampsByAddress(address: string): Promise( `https://stampchain.io/api/v2/balance/${address}` ); - return resp.data; + try { + return stampsByAdressSchema.parse(resp.data); + } catch (e) { + if (e instanceof ZodError) void analytics.track('schema_fail', e); + throw e; + } } type FetchStampsByAddressResp = Awaited>;