diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 7e80dbb84..6fc972be2 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -38,7 +38,7 @@ concurrency: jobs: deploy: - runs-on: ubuntu-latest + runs-on: self-hosted steps: - uses: actions/checkout@v4 with: diff --git a/.vscode/extensions.json b/.vscode/extensions.json index f0448fb53..a8ff3b517 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,7 @@ "yoavbls.pretty-ts-errors", "dbaeumer.vscode-eslint", "usernamehw.errorlens", - "naumovs.color-highlight" + "naumovs.color-highlight", + "mkhl.direnv" ] } diff --git a/apps/distributor/package.json b/apps/distributor/package.json index 6cce24853..40f447539 100644 --- a/apps/distributor/package.json +++ b/apps/distributor/package.json @@ -24,7 +24,7 @@ "@supabase/supabase-js": "2.44.2", "@wagmi/core": "^2.10.5", "app": "workspace:*", - "express": "^4.18.2", + "express": "^4.19.2", "pino": "^8.16.1", "viem": "^2.13.7" }, @@ -32,7 +32,7 @@ "@types/bun": "latest", "@types/express": "^4", "@types/supertest": "^2.0.16", - "debug": "^4.3.4", + "debug": "^4.3.5", "dotenv-cli": "^7.3.0", "supertest": "^6.3.3", "typescript": "^5.5.3" diff --git a/apps/next/package.json b/apps/next/package.json index fe680dbd3..65c27f771 100644 --- a/apps/next/package.json +++ b/apps/next/package.json @@ -40,7 +40,7 @@ "react-native-web": "~0.19.10", "sharp": "0.32.6", "vercel": "^33.5.2", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@next/bundle-analyzer": "^13.4.19", diff --git a/docs/testing-like-a-playwright-boss.md b/docs/testing-like-a-playwright-boss.md new file mode 100644 index 000000000..07761e931 --- /dev/null +++ b/docs/testing-like-a-playwright-boss.md @@ -0,0 +1,65 @@ + +# Testing Like a Playwright Boss + +This is a guide to testing like a playwright boss. It dives into how to leverage playwrght to test Send app. It also covers how to debug and troubleshoot tests. + +## Setup + +Ensure you have already followed the [CONTRIBUTING](/CONTRIBUTING.md) guide. + +- Install [VSCode](https://code.visualstudio.com/): A code editor that supports running and debugging Playwright tests. +- Install ["ms-playwright.playwright"](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright): A VSCode extension that provides syntax highlighting and code completion for Playwright tests. +- Install [direnv](https://direnv.net/): A tool that loads environment variables from `.envrc` files. +- Install ["mkhl.direnv"](https://marketplace.visualstudio.com/items?itemName=mkhl.direnv): A VSCode extension that load environment variables from `.envrc` files into the VSCode environment. +- Install browsers: + - Using VSCode: Press Shift+Command+P to open the Command Palette in VSCode, type 'Playwright' and select 'Install Playwright Browsers'. + - Using the Playwright CLI: Run `npx playwright install chromium firefox webkit` to install the browsers. +Once those are installed, you will need to allow the `.envrc` file to load environment variables. Any time it changes, it should prompt you to reload the VSCode environment. + +For the rest of the guide, we will assume you are using VSCode. + +### Running Tests + +You'll want to visit the **Testing** tab in the VSCode sidebar. From there, in the **Test Explorer** panel, you can run and debug tests. + +There should be icons for running and debugging tests. Typically, I only run one test at a time. There are also a number of settings that are available for Playwright when running from VSCode. You can find them at the bottom of the **Testing** sidebar. + +A few of my favorite settings are: + +- **Projects**: This is where you can select which projects to run tests for. Typically, I select one, either chromium or firefox. +- **Settings**: This is where you can select which settings to use for the test run such as running the browser in headless mode or not. +- **Tools**: + - **Pick Locator** allows you to select which locator to use for finding elements on the page from the currently running test and open browser. + - **Record New**: Gives you the ability to record a new test + - **Record at Cursor**: Gives you the ability to record a test which will input at the current editor's pane cursor position. + +### Debugging Tests + +When debugging tests, you can use the **Debug Console** to see the output of the test. You can also use the **Debug Console** to interact with the browser. + +This runs Playwright in debug mode which will pause at breakpoints and allow you to interact with the browser. It also removes the global timeout which is set by default to 30 seconds. This is useful for debugging tests as it allows you to run tests for longer periods of time. + +You also have access to the console developer tools which can be useful for debugging to see what is happening in the browser. + +### Writing Tests + +Playwright tests are written in TypeScript. You can find the test files in the [`/packages/playwright/tests`](/packages/playwright) directory. There are a number of examples of tests in there. Send app leverages a number of fixtures to make testing easier. These fixtures are located in the [`/packages/playwright/tests/fixtures`](/packages/playwright/tests/fixtures) directory. + +#### Considerations + +You want to put yourself in the shoes of a user, not a developer. However, you should isolate the feature you are testing as much as possible. E.g. auth vs onboarding vs sending tokens. They are all different features and should be tested separately. Use fixtures to isolate them and reuse them across tests. + +#### Test Recording + +Use the **Record New** button or **Record at Cursor** to record a new tests or use the pick locator to select the locator to use for the test. This way you can easily interact with the browser and see what is happening. + +## Non-VSCode Setup (Playwright CLI) + +Running playwright tests in VSCode is a great way to get started, but you can also leverage the playwright CLI to run tests and debug them. You can use the [**Playwright Inspector**](https://playwright.dev/docs/inspector) to debug tests. To start Playwright in debug mode, you can run the following command: + +```shell +cd packages/playwright +npx playwright test --debug +``` + +This will run the tests in debug mode and pause at breakpoints. There should be a popup which gives you commands during this interactive debugging session. diff --git a/packages/api/package.json b/packages/api/package.json index a51863290..2338ecf64 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -14,10 +14,10 @@ "@trpc/server": "11.0.0-next-beta.264", "@wagmi/core": "^2.10.5", "app": "workspace:*", - "debug": "^4.3.4", + "debug": "^4.3.5", "p-queue": "^8.0.1", "superjson": "^1.13.1", "viem": "^2.13.7", - "zod": "^3.22.4" + "zod": "^3.23.8" } } diff --git a/packages/app/components/SearchBar.tsx b/packages/app/components/SearchBar.tsx index 28e0308eb..a7cd2a4bc 100644 --- a/packages/app/components/SearchBar.tsx +++ b/packages/app/components/SearchBar.tsx @@ -1,7 +1,9 @@ +import type { Functions } from '@my/supabase/database.types' import { Avatar, Button, ButtonText, + Card, H4, Paragraph, ScrollView, @@ -11,16 +13,25 @@ import { XStack, YStack, isWeb, + useMedia, } from '@my/ui' -import { Link } from 'solito/link' +import { ExternalLink } from '@tamagui/lucide-icons' +import { useThemeSetting } from '@tamagui/next-theme' import { SearchSchema, useTagSearch } from 'app/provider/tag-search' -import { FormProvider } from 'react-hook-form' +import { useRootScreenParams } from 'app/routers/params' import { SchemaForm } from 'app/utils/SchemaForm' -import { useThemeSetting } from '@tamagui/next-theme' -import { IconX } from 'app/components/icons' -import { useState } from 'react' -import type { Functions } from '@my/supabase/database.types' +import { shorten } from 'app/utils/strings' import { useSearchResultHref } from 'app/utils/useSearchResultHref' +import * as Linking from 'expo-linking' +import { useEffect, useState } from 'react' +import { FormProvider } from 'react-hook-form' +import { Pressable } from 'react-native' +import { Link } from 'solito/link' +import { useRouter } from 'solito/router' +import { Adapt, Dialog, Sheet } from 'tamagui' +import { type Address, isAddress } from 'viem' +import { IconAccount } from './icons' +import { baseMainnet } from '@my/wagmi' type SearchResultsType = Functions<'tag_search'>[number] type SearchResultsKeysType = keyof SearchResultsType @@ -35,9 +46,11 @@ const formatResultsKey = (str: string): string => { } function SearchResults() { - const { form, results, isLoading, error } = useTagSearch() + const { results, isLoading, error } = useTagSearch() + const [queryParams] = useRootScreenParams() + const { search: query } = queryParams + const [resultsFilter, setResultsFilter] = useState(null) - const query = form.watch('query', '') if (isLoading) { return ( @@ -48,6 +61,28 @@ function SearchResults() { if (!results || error) { return null } + + if (isAddress(query ?? '')) { + return ( + + + + + + ) + } + const matchesCount = Object.values(results).filter( (value) => Array.isArray(value) && value.length ).length @@ -107,7 +142,6 @@ function SearchResults() { key={`${key}-${item.tag_name}-${item.send_id}`} keyField={key as SearchResultsKeysType} profile={item} - query={query} /> ))} @@ -174,19 +208,152 @@ function SearchFilterButton({ ) } +const AddressSearchResultRow = ({ address }: { address: Address }) => { + const href = useSearchResultHref() + const router = useRouter() + const { gtMd } = useMedia() + const [sendConfirmDialogIsOpen, setSendConfirmDialogIsOpen] = useState(false) + + return ( + + setSendConfirmDialogIsOpen(true)} + accessibilityRole="link" + accessibilityLabel={address} + > + + + + + + + + + External Address + + + {gtMd ? address : shorten(address, 6, 6)} + + + + + setSendConfirmDialogIsOpen(false)} + onConfirm={() => router.push(href)} + address={address} + /> + + ) +} + +function ConfirmSendDialog({ isOpen, onClose, onConfirm, address }) { + return ( + + + + + + + + + + + + + + + Confirm External Send + + Please confirm you agree to the following before sending: + + 1. The external address is on Base Network. + + + 2. I have double checked the address: + + + + + 3. I understand that if I make any mistakes, there is no way to recover the funds. + + + + + + + + + + + + + ) +} + function SearchResultRow({ keyField, profile, - query, }: { keyField: SearchResultsKeysType profile: SearchResultCommonType - query: string }) { + const [queryParams] = useRootScreenParams() + const { search: query } = queryParams const href = useSearchResultHref(profile) + const { resolvedTheme } = useThemeSetting() const rowBC = resolvedTheme?.startsWith('dark') ? '$metalTouch' : '$gray2Light' + if (!query) return null + return ( { + const subscription = form.watch(({ query }) => { + setRootParams( + { + ...queryParams, + search: query, + }, + { webBehavior: 'replace' } + ) + }) + return () => subscription.unsubscribe() + }, [form, setRootParams, queryParams]) return ( <> @@ -259,7 +441,7 @@ function Search() { { // noop }} @@ -267,7 +449,7 @@ function Search() { props={{ query: { accessibilityRole: 'search', - placeholder: 'Sendtag, Phone, Send ID', + placeholder: 'Sendtag, Phone, Send ID, Address', pr: '$size.3.5', }, }} @@ -283,27 +465,6 @@ function Search() { {({ query }) => query} - ) diff --git a/packages/app/features/activity/__snapshots__/screen.test.tsx.snap b/packages/app/features/activity/__snapshots__/screen.test.tsx.snap index 640f3fab5..5fe6bc08b 100644 --- a/packages/app/features/activity/__snapshots__/screen.test.tsx.snap +++ b/packages/app/features/activity/__snapshots__/screen.test.tsx.snap @@ -99,7 +99,7 @@ exports[`ActivityScreen renders activity screen: ActivityScreen 1`] = ` control={ { "_defaultValues": { - "query": "", + "query": "test", }, "_disableForm": [Function], "_executeSchema": [Function], @@ -111,7 +111,7 @@ exports[`ActivityScreen renders activity screen: ActivityScreen 1`] = ` "ref": { "name": "query", }, - "value": "", + "value": "test", }, }, }, @@ -130,7 +130,7 @@ exports[`ActivityScreen renders activity screen: ActivityScreen 1`] = ` "touchedFields": {}, }, "_formValues": { - "query": "", + "query": "test", }, "_getDirty": [Function], "_getFieldArray": [Function], @@ -196,6 +196,9 @@ exports[`ActivityScreen renders activity screen: ActivityScreen 1`] = ` { "next": [Function], }, + { + "next": [Function], + }, ], "subscribe": [Function], "unsubscribe": [Function], @@ -219,7 +222,7 @@ exports[`ActivityScreen renders activity screen: ActivityScreen 1`] = ` onBlur={[Function]} onChangeText={[Function]} onFocus={[Function]} - placeholder="Sendtag, Phone, Send ID" + placeholder="Sendtag, Phone, Send ID, Address" placeholderTextColor="#081619" readOnly={false} style={ @@ -241,8 +244,8 @@ exports[`ActivityScreen renders activity screen: ActivityScreen 1`] = ` "color": "#FFFFFF", "fontFamily": "System", "fontSize": 19.2, - "fontStyle": "italic", - "fontWeight": "normal", + "fontStyle": "normal", + "fontWeight": "bold", "height": 44, "minWidth": 0, "paddingLeft": 16, @@ -250,7 +253,7 @@ exports[`ActivityScreen renders activity screen: ActivityScreen 1`] = ` "position": "relative", } } - value="" + value="test" /> + + + + - + tag + + - - + - - + testID="tag-search-3665" + > + + + + + + + + + + + + ?? + + + + + + + + + test + + + + /test + + + + + + - + ({ usePathname: jest.fn(), })) +jest.mock('app/routers/params', () => ({ + useRootScreenParams: jest.fn().mockReturnValue([{ search: 'test' }, jest.fn()]), +})) + jest.mock('app/utils/supabase/useSupabase', () => ({ useSupabase: jest.fn().mockReturnValue({ rpc: jest.fn().mockReturnValue({ @@ -27,6 +31,7 @@ jest.mock('app/utils/supabase/useSupabase', () => ({ }, ], phone_matches: [], + address_matches: [], }, ], error: null, @@ -71,7 +76,7 @@ describe('ActivityScreen', () => { ) - const searchInput = screen.getByPlaceholderText('Sendtag, Phone, Send ID') + const searchInput = screen.getByPlaceholderText('Sendtag, Phone, Send ID, Address') fireEvent.changeText(searchInput, 'test') await act(async () => { jest.advanceTimersByTime(2000) diff --git a/packages/app/features/auth/sign-up/sign-up-form.tsx b/packages/app/features/auth/sign-up/sign-up-form.tsx index 80a946840..957406a63 100644 --- a/packages/app/features/auth/sign-up/sign-up-form.tsx +++ b/packages/app/features/auth/sign-up/sign-up-form.tsx @@ -103,6 +103,7 @@ export const SignUpForm = () => { ai={'center'} > submit()} br="$3" bc={'$green9Light'} diff --git a/packages/app/features/home/TokenDetailsHistory.tsx b/packages/app/features/home/TokenDetailsHistory.tsx index baa792884..547d054e2 100644 --- a/packages/app/features/home/TokenDetailsHistory.tsx +++ b/packages/app/features/home/TokenDetailsHistory.tsx @@ -5,6 +5,7 @@ import { Fragment } from 'react' import { useTokenActivityFeed } from './utils/useTokenActivityFeed' import { RowLabel, AnimateEnter } from './TokenDetails' import { TokenActivityRow } from './TokenActivityRow' +import { baseMainnet, tokenPaymasterAddress } from '@my/wagmi' export const TokenDetailsHistory = ({ coin }: { coin: coins[number] }) => { const result = useTokenActivityFeed({ @@ -42,7 +43,7 @@ export const TokenDetailsHistory = ({ coin }: { coin: coins[number] }) => { default: { let lastDate: string | undefined return pages?.map((activities) => { - return activities?.map((activity) => { + return activities.map((activity) => { const date = activity.created_at.toLocaleDateString() const isNewDate = !lastDate || date !== lastDate if (isNewDate) { diff --git a/packages/app/features/home/utils/useTokenActivityFeed.ts b/packages/app/features/home/utils/useTokenActivityFeed.ts index 94924bc15..2174023df 100644 --- a/packages/app/features/home/utils/useTokenActivityFeed.ts +++ b/packages/app/features/home/utils/useTokenActivityFeed.ts @@ -1,10 +1,12 @@ import type { PgBytea } from '@my/supabase/database.types' +import { tokenPaymasterAddress } from '@my/wagmi' import type { PostgrestError } from '@supabase/postgrest-js' import { useInfiniteQuery, type InfiniteData, type UseInfiniteQueryResult, } from '@tanstack/react-query' +import { hexToBytea } from 'app/utils/hexToBytea' import { useSupabase } from 'app/utils/supabase/useSupabase' import { throwIf } from 'app/utils/throwIf' import { EventArraySchema, Events, type Activity } from 'app/utils/zod/activity' @@ -37,8 +39,16 @@ export function useTokenActivityFeed(params: { query = query.eq('event_name', Events.SendAccountReceive) } + const pgPaymasterCondValues = Object.values(tokenPaymasterAddress) + .map((a) => `${hexToBytea(a)}`) + .join(',') + const { data, error } = await query .or('from_user.not.is.null, to_user.not.is.null') // only show activities with a send app user + .or( + // Filter out paymaster fees for gas + `data->t.is.null, data->f.is.null, and(data->>t.not.in.(${pgPaymasterCondValues}), data->>f.not.in.(${pgPaymasterCondValues}))` + ) .order('created_at', { ascending: false }) .range(from, to) throwIf(error) diff --git a/packages/app/features/send/SendAmountForm.tsx b/packages/app/features/send/SendAmountForm.tsx index 27305dae6..9e8ef5bbc 100644 --- a/packages/app/features/send/SendAmountForm.tsx +++ b/packages/app/features/send/SendAmountForm.tsx @@ -24,7 +24,7 @@ const SendAmountSchema = z.object({ token: formFields.coin, }) -export function SendAmountForm({ profile }: { profile: Functions<'profile_lookup'>[number] }) { +export function SendAmountForm() { const form = useForm>() const { data: sendAccount } = useSendAccount() const router = useRouter() @@ -158,7 +158,7 @@ export function SendAmountForm({ profile }: { profile: Functions<'profile_lookup > {({ amount, token }) => ( - + {amount} - - - - - - - @@ -935,7 +829,7 @@ exports[`SendScreen should render with search when on /send and no recipient in control={ { "_defaultValues": { - "query": "", + "query": "test", }, "_disableForm": [Function], "_executeSchema": [Function], @@ -947,7 +841,7 @@ exports[`SendScreen should render with search when on /send and no recipient in "ref": { "name": "query", }, - "value": "test", + "value": "testtest", }, }, }, @@ -971,7 +865,7 @@ exports[`SendScreen should render with search when on /send and no recipient in }, }, "_formValues": { - "query": "test", + "query": "testtest", }, "_getDirty": [Function], "_getFieldArray": [Function], @@ -1037,6 +931,9 @@ exports[`SendScreen should render with search when on /send and no recipient in { "next": [Function], }, + { + "next": [Function], + }, ], "subscribe": [Function], "unsubscribe": [Function], @@ -1060,7 +957,7 @@ exports[`SendScreen should render with search when on /send and no recipient in onBlur={[Function]} onChangeText={[Function]} onFocus={[Function]} - placeholder="Sendtag, Phone, Send ID" + placeholder="Sendtag, Phone, Send ID, Address" placeholderTextColor="#081619" readOnly={false} style={ @@ -1091,7 +988,7 @@ exports[`SendScreen should render with search when on /send and no recipient in "position": "relative", } } - value="test" + value="testtest" /> - - - - - - - ['data']> - export function SendConfirmScreen() { const [queryParams] = useSendScreenParams() const { recipient, idType, sendToken, amount } = queryParams const { data: profile, isLoading, error } = useProfileLookup(idType ?? 'tag', recipient ?? '') + const router = useRouter() useEffect(() => { - if (!profile || !recipient) + if (!profile && !recipient) router.replace({ pathname: '/send', query: { @@ -62,20 +62,28 @@ export function SendConfirmScreen() { }) }, [profile, recipient, idType, router, sendToken, amount]) if (error) throw new Error(error.message) - if (isLoading || !profile) return - return + if (isLoading && !profile) return + return } -export function SendConfirm({ profile }: { profile: ProfileProp }) { +export function SendConfirm() { const queryClient = useQueryClient() const { data: sendAccount } = useSendAccount() + + const [queryParams] = useSendScreenParams() + const { sendToken, recipient, idType } = queryParams + + const { + data: profile, + isLoading: isProfileLoading, + error: profileError, + } = useProfileLookup(idType ?? 'tag', recipient ?? '') + const webauthnCreds = sendAccount?.send_account_credentials .filter((c) => !!c.webauthn_credentials) .map((c) => c.webauthn_credentials as NonNullable) ?? [] const [sentTxHash, setSentTxHash] = useState() - const [queryParams] = useSendScreenParams() - const { sendToken, recipient, idType } = queryParams const router = useRouter() const { data: balance, isLoading: balanceIsLoading } = useBalance({ @@ -96,7 +104,7 @@ export function SendConfirm({ profile }: { profile: ProfileProp }) { const { data: userOp } = useGenerateTransferUserOp({ sender: sendAccount?.address, // @ts-expect-error some work to do here - to: profile?.address, + to: profile?.address ?? recipient, token: sendToken === 'eth' ? undefined : sendToken, amount: BigInt(amount), nonce: nonce ?? 0n, @@ -198,7 +206,8 @@ export function SendConfirm({ profile }: { profile: ProfileProp }) { } }, [sentTxHash, transfers, router, sendToken, tokenActivityError, dataFirstFetch, dataUpdatedAt]) - if (balanceIsLoading || nonceIsLoading) return + if (balanceIsLoading || nonceIsLoading || isProfileLoading) + return return ( - + @@ -369,10 +378,19 @@ export function SendConfirm({ profile }: { profile: ProfileProp }) { ) } -export function SendRecipient({ profile, ...props }: YStackProps & { profile: ProfileProp }) { +export function SendRecipient({ ...props }: YStackProps) { const [queryParams] = useSendScreenParams() - + const { recipient, idType } = queryParams const router = useRouter() + const { data: profile, isLoading, error } = useProfileLookup(idType ?? 'tag', recipient ?? '') + + if (isLoading) return + if (error) throw new Error(error.message) + + const href = + idType === 'address' + ? `${baseMainnet.blockExplorers.default.url}/address/${recipient}` + : `/profile/${recipient}` return ( @@ -410,7 +428,7 @@ export function SendRecipient({ profile, ...props }: YStackProps & { profile: Pr $theme-light={{ bc: '$gray3Light' }} f={1} > - + @@ -427,7 +445,16 @@ export function SendRecipient({ profile, ...props }: YStackProps & { profile: Pr lineHeight="$1" color="$color11" > - {profile?.tag ? `/${profile?.tag}` : `#${profile?.sendid}`} + {(() => { + switch (true) { + case idType === 'address': + return shorten(recipient, 6, 6) + case !!profile?.tag: + return `/${profile?.tag}` + default: + return `#${profile?.sendid}` + } + })()} diff --git a/packages/app/features/send/screen.test.tsx b/packages/app/features/send/screen.test.tsx index a542baf13..b7e078e1f 100644 --- a/packages/app/features/send/screen.test.tsx +++ b/packages/app/features/send/screen.test.tsx @@ -1,4 +1,5 @@ import * as solito from 'solito' +import * as params from 'app/routers/params' import { describe, expect, it } from '@jest/globals' import { TamaguiProvider, config } from '@my/ui' import { act, render, screen, userEvent, waitFor } from '@testing-library/react-native' @@ -7,8 +8,6 @@ jest.mock('expo-router', () => ({ usePathname: jest.fn().mockReturnValue('/send'), })) -// const params = {} - jest.mock('solito', () => { // console.log('mock solito') const mockCreateParam = jest.fn(() => { @@ -56,10 +55,22 @@ jest.mock('app/utils/supabase/useSupabase', () => ({ }), })) +jest.mock('app/routers/params', () => ({ + useSendScreenParams: jest + .fn() + .mockReturnValue([ + { idType: 'tag', recipient: 'test', amount: 'test', sendToken: 'test', note: 'test' }, + jest.fn(), + ]), + useRootScreenParams: jest.fn().mockReturnValue([{ search: 'test' }, jest.fn()]), +})) + import { SendScreen } from './screen' import { usePathname } from 'expo-router' import { useProfileLookup } from 'app/utils/useProfileLookup' +// @ts-expect-error mock +usePathname.mockReturnValue('/send') describe('SendScreen', () => { it('should render with search when on /send and no recipient in params', async () => { jest.useFakeTimers() @@ -77,7 +88,7 @@ describe('SendScreen', () => { expect(tree).toMatchSnapshot('render') expect(await screen.findByText('SEARCH BY')).toBeOnTheScreen() - expect(solito.createParam).toHaveBeenCalled() + expect(params.useRootScreenParams).toHaveBeenCalled() const searchBy = await screen.findByRole('search', { name: 'query' }) const user = userEvent.setup() diff --git a/packages/app/features/send/screen.tsx b/packages/app/features/send/screen.tsx index eb6614050..386b52601 100644 --- a/packages/app/features/send/screen.tsx +++ b/packages/app/features/send/screen.tsx @@ -18,12 +18,17 @@ import { useProfileLookup } from 'app/utils/useProfileLookup' import { useState } from 'react' import { SendAmountForm } from './SendAmountForm' import { SendRecipient } from './confirm/screen' +import { type Address, isAddress } from 'viem' export const SendScreen = () => { const [{ recipient, idType }] = useSendScreenParams() const { data: profile, isLoading, error } = useProfileLookup(idType ?? 'tag', recipient ?? '') if (isLoading) return if (error) throw new Error(error.message) + + if (idType === 'address' && isAddress(recipient as Address)) { + return + } if (!profile) return ( @@ -41,7 +46,7 @@ export const SendScreen = () => { return return ( - + // // // @@ -74,7 +79,7 @@ function NoSendAccount({ profile }: { profile: Functions<'profile_lookup'>[numbe const [clicked, setClicked] = useState(false) return ( - +

No send account

diff --git a/packages/app/package.json b/packages/app/package.json index 24d412e3c..30c4098f2 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -62,7 +62,7 @@ "superjson": "^1.13.1", "viem": "^2.13.7", "wagmi": "^2.9.10", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@babel/plugin-syntax-import-attributes": "^7.24.7", @@ -72,7 +72,7 @@ "@types/jsonwebtoken": "^9.0.2", "@types/react": "^18.2.15", "babel-preset-expo": "~11.0.0", - "debug": "^4.3.4", + "debug": "^4.3.5", "eslint": "^8.46.0", "eslint-config-custom": "workspace:*", "eslint-plugin-testing-library": "^6.2.2", diff --git a/packages/app/routers/params.tsx b/packages/app/routers/params.tsx index 018a3f337..9694c8874 100644 --- a/packages/app/routers/params.tsx +++ b/packages/app/routers/params.tsx @@ -1,8 +1,9 @@ import type { Enums } from '@my/supabase/database.types' import { baseMainnet, usdcAddress } from '@my/wagmi' import { createParam } from 'solito' +import type { Address } from 'viem' -export type RootParams = { nav?: 'home' | 'settings'; token?: string } +export type RootParams = { nav?: 'home' | 'settings'; token?: string; search?: string } const { useParam: useRootParam, useParams: useRootParams } = createParam() @@ -18,15 +19,23 @@ const useToken = () => { return [token, setTokenParam] as const } +const useSearch = () => { + const [search, setSearchParam] = useRootParam('search') + + return [search, setSearchParam] as const +} + export const useRootScreenParams = () => { const { setParams } = useRootParams() const [nav] = useNav() const [token] = useToken() + const [search] = useSearch() return [ { nav, token, + search, }, setParams, ] as const @@ -58,7 +67,7 @@ export const useRewardsScreenParams = () => { } export type SendScreenParams = { - idType?: Enums<'lookup_type_enum'> + idType?: Enums<'lookup_type_enum'> | Address recipient?: string amount?: string sendToken?: `0x${string}` | 'eth' diff --git a/packages/app/utils/useSearchResultHref.test.tsx b/packages/app/utils/useSearchResultHref.test.tsx index 74696b3d4..43a813060 100644 --- a/packages/app/utils/useSearchResultHref.test.tsx +++ b/packages/app/utils/useSearchResultHref.test.tsx @@ -4,6 +4,9 @@ import { useSearchResultHref } from './useSearchResultHref' import { usePathname } from 'app/utils/usePathname.native' import type { SearchResultCommonType } from 'app/components/SearchBar' import type { SendScreenParams } from 'app/routers/params' +import { baseMainnet } from '@my/wagmi' +import { useRootScreenParams } from 'app/routers/params' +import { zeroAddress } from 'viem' const sendParams: SendScreenParams = { idType: 'tag', @@ -14,6 +17,7 @@ const sendParams: SendScreenParams = { jest.mock('app/routers/params', () => ({ useSendScreenParams: jest.fn().mockReturnValue([sendParams]), + useRootScreenParams: jest.fn().mockReturnValue([{ search: 'alice' }]), })) jest.mock('expo-router', () => ({ @@ -42,6 +46,14 @@ describe('useSearchResultHref', () => { const href = useSearchResultHref(item) expect(href).toBe('/profile/12530') }) + it('should return basescan link for EOA for activity screen', () => { + //@ts-expect-error mock + useRootScreenParams.mockReturnValueOnce([{ search: zeroAddress }]) + // @ts-expect-error mock + usePathname.mockReturnValue('/activity') + const href = useSearchResultHref() + expect(href).toBe(`${baseMainnet.blockExplorers.default.url}/address/${zeroAddress}`) + }) it('should return the correct href for send screen', () => { // @ts-expect-error mock usePathname.mockReturnValue('/send') diff --git a/packages/app/utils/useSearchResultHref.tsx b/packages/app/utils/useSearchResultHref.tsx index c222d8487..91524cf57 100644 --- a/packages/app/utils/useSearchResultHref.tsx +++ b/packages/app/utils/useSearchResultHref.tsx @@ -1,10 +1,27 @@ -import { useSendScreenParams } from 'app/routers/params' +import { useRootScreenParams, useSendScreenParams } from 'app/routers/params' import { usePathname } from 'app/utils/usePathname' import type { SearchResultCommonType } from 'app/components/SearchBar' +import { isAddress } from 'viem' +import { baseMainnet } from '@my/wagmi' -export const useSearchResultHref = (profile: SearchResultCommonType) => { +export const useSearchResultHref = (profile?: SearchResultCommonType) => { + const [queryParams] = useRootScreenParams() + const { search: query } = queryParams const path = usePathname() const [sendParams] = useSendScreenParams() + if (!profile) { + switch (true) { + case !query || !isAddress(query): + return '' + case path === '/activity': + return `${baseMainnet.blockExplorers.default.url}/address/${query}` + case path === '/send': + return `/send?recipient=${query}&idType=address` + default: { + throw new Error(`Unhandled path: ${path}`) + } + } + } switch (path) { case '/activity': diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 13e503499..db30751d3 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -32,7 +32,7 @@ "@supabase/supabase-js": "2.44.2", "@types/bun": "latest", "@uniswap/v3-periphery": "^1.4.4", - "debug": "^4.3.4", + "debug": "^4.3.5", "solhint": "^3.6.2", "zx": "^8.1.2" } diff --git a/packages/playwright/package.json b/packages/playwright/package.json index b6004d6e6..3411fb26c 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -7,14 +7,14 @@ "@faker-js/faker": "^8.1.0", "@my/snaplet": "workspace:*", "@my/supabase": "workspace:*", - "@playwright/test": "^1.41.1", + "@playwright/test": "^1.45.1", "@supabase/supabase-js": "2.44.2", "@types/jsonwebtoken": "^9.0.2", "@types/pg": "^8", "@wagmi/core": "^2.10.5", "app": "workspace:*", "cbor": "^9.0.1", - "debug": "^4.3.4", + "debug": "^4.3.5", "dotenv-cli": "^7.3.0", "extract-zip": "^2.0.1", "jsonwebtoken": "^9.0.2", diff --git a/packages/playwright/tests/activity.onboarded.spec.ts b/packages/playwright/tests/activity.onboarded.spec.ts index 89ed5360b..8b03b4156 100644 --- a/packages/playwright/tests/activity.onboarded.spec.ts +++ b/packages/playwright/tests/activity.onboarded.spec.ts @@ -111,7 +111,7 @@ test('can search on activity page', async ({ page, context }) => { }) await expect(activityHeading(page)).toBeVisible() const isLoading = page.getByRole('progressbar', { name: 'Loading' }) - const searchInput = page.getByPlaceholder('Sendtag, Phone, Send ID') + const searchInput = page.getByPlaceholder('Sendtag, Phone, Send ID, Address') await searchInput.fill('test') await expect(searchInput).toHaveValue('test') await page.waitForResponse(`${SUPABASE_URL}/rest/v1/rpc/tag_search*`) diff --git a/packages/playwright/tests/send.onboarded.spec.ts b/packages/playwright/tests/send.onboarded.spec.ts index fff96f440..7a577a56a 100644 --- a/packages/playwright/tests/send.onboarded.spec.ts +++ b/packages/playwright/tests/send.onboarded.spec.ts @@ -12,6 +12,7 @@ import { erc20Abi, formatUnits, parseUnits, zeroAddress } from 'viem' import { ProfilePage } from './fixtures/profiles' import { SendPage } from './fixtures/send' import { sendTokenAddresses, testBaseClient, usdcAddress } from './fixtures/viem' +import { shorten } from 'app/utils/strings' const test = mergeTests(sendAccountTest, snapletTest) @@ -40,7 +41,9 @@ const tokens = [ }, ] as { symbol: string; address: `0x${string}`; decimals: number }[] -const idTypes = ['tag', 'sendid'] as const +const idTypes = ['tag', 'sendid', 'address'] as const + +const testEOA = zeroAddress.replace('0x0', '0x1') as `0x${string}` for (const token of tokens) { test(`can send ${token.symbol} starting from profile page`, async ({ page, seed, supabase }) => { @@ -84,14 +87,33 @@ for (const token of tokens) { : [userOnboarded] ) const profile = plan.profiles[0] + const tag = plan.tags[0] + assert(!!profile, 'profile not found') assert(!!profile.name, 'profile name not found') assert(!!profile.sendId, 'profile send id not found') - const recvAccount = plan.sendAccounts[0] - assert(!!recvAccount, 'send account not found') - const tag = plan.tags[0] - const query = isSendId ? profile?.sendId.toString() : tag?.name + const recvAccount: { address: `0x${string}` } = (() => { + switch (idType) { + case 'address': + return { address: testEOA } + default: + assert(!!plan.sendAccounts[0], 'send account not found') + return { address: plan.sendAccounts[0].address as `0x${string}` } + } + })() + + const query = (() => { + switch (idType) { + case 'sendid': + return profile?.sendId.toString() + case 'address': + return testEOA + default: + return tag?.name + } + })() + assert(!!query, 'query not found') // goto send page @@ -105,17 +127,38 @@ for (const token of tokens) { await expect(page).toHaveURL(/\/send/) // fill search input - const searchInput = page.getByPlaceholder('Sendtag, Phone, Send ID') + const searchInput = page.getByPlaceholder('Sendtag, Phone, Send ID, Address') await expect(searchInput).toBeVisible() await searchInput.fill(query) await expect(searchInput).toHaveValue(query) + let blockExplorerPagePromise: Promise | null = null + + if (idType === 'address') { + blockExplorerPagePromise = page.context().waitForEvent('page') + } + // click user await page .getByTestId('searchResults') .getByRole('link', { name: query, exact: false }) .click() + if (idType === 'address' && !!blockExplorerPagePromise) { + // confirm sending to external address + const dialog = page.getByRole('dialog', { name: 'Confirm External Send' }) + await expect(dialog).toBeVisible() + const blockExplorerButton = dialog.getByRole('button', { name: query }) + await expect(blockExplorerButton).toBeVisible() + await blockExplorerButton.click() + const blockExplorerPage = await blockExplorerPagePromise + await expect(blockExplorerPage).toHaveURL(new RegExp(`/address/${query}`)) + await blockExplorerPage.close() + const confirmButton = dialog.getByRole('button', { name: 'I Agree & Continue' }) + await expect(confirmButton).toBeVisible() + await confirmButton.click() + } + await expect(page).toHaveURL(/\/send/) await expect(() => { @@ -128,113 +171,36 @@ for (const token of tokens) { timeout: 5000, }) - const counterparty = isSendId ? profile.name : `/${tag?.name}` await expect(page.getByTestId('SendForm')).toHaveText( - new RegExp(isSendId ? `#${profile.sendId}` : `/${tag?.name}`) + new RegExp( + (() => { + switch (idType) { + case 'address': + return shorten(testEOA, 6, 6) + case 'sendid': + return `#${profile.sendId}` + default: + return `/${tag?.name}` + } + })() + ) ) + + const counterparty = (() => { + switch (idType) { + case 'address': + return testEOA + case 'sendid': + return profile.name + default: + return `/${tag?.name}` + } + })() await handleTokenTransfer({ token, supabase, page, counterparty, recvAccount, profile }) }) } } -test.skip('can send USDC to user on profile', async ({ page, seed }) => { - const plan = await seed.users([userOnboarded]) - const tag = plan.tags[0] - const profile = plan.profiles[0] - assert(!!tag?.name, 'tag not found') - assert(!!profile?.name, 'profile name not found') - assert(!!profile?.about, 'profile about not found') - const profilePage = new ProfilePage(page, { - name: profile.name, - about: profile.about, - }) - await profilePage.visit(tag.name, expect) - await expect(profilePage.sendButton).toBeVisible() - await profilePage.sendButton.click() - - // @todo create send form fixture - const sendDialog = page.getByTestId('sendDialogContainer') - await expect(sendDialog).toBeVisible() - const amountInput = sendDialog.getByLabel('Amount') - await expect(amountInput).toBeVisible() - await amountInput.fill('5') - const tokenSelect = sendDialog.getByRole('combobox') // @todo when tamagui supports this , { name: 'Token' }) - await expect(tokenSelect).toBeVisible() - await tokenSelect.selectOption('USDC') - const sendDialogButton = sendDialog.getByRole('button', { name: 'Send' }) - expect(sendDialogButton).toBeVisible() - await sendDialogButton.click() - await expect(sendDialog.getByText(/Sent user op [0-9a-f]+/).first()).toBeVisible({ - timeout: 20000, - }) - await expect(sendDialog.getByRole('link', { name: 'View on Otterscan' })).toBeVisible() -}) - -test.skip('can send USDC to user on profile using paymaster', async ({ - page, - seed, - supabase, - setEthBalance, -}) => { - const { data: sendAccount, error } = await supabase.from('send_accounts').select('*').single() - if (error) { - log('error fetching send account', error) - throw error - } - assert(!!sendAccount, 'no send account found') - assert(sendAccount.address !== zeroAddress, 'send account address is zero') - - await setEthBalance({ address: sendAccount.address, value: 0n }) // set balance to 0 ETH - - const plan = await seed.users([userOnboarded]) - const tag = plan.tags[0] - const profile = plan.profiles[0] - assert(!!tag?.name, 'tag not found') - assert(!!profile?.name, 'profile name not found') - assert(!!profile?.about, 'profile about not found') - const profilePage = new ProfilePage(page, { - name: profile.name, - about: profile.about, - }) - await profilePage.visit(tag.name, expect) - await expect(profilePage.sendButton).toBeVisible() - await profilePage.sendButton.click() - - // @todo create send form fixture - const sendDialog = page.getByTestId('sendDialogContainer') - await expect(sendDialog).toBeVisible() - const amountInput = sendDialog.getByLabel('Amount') - await expect(amountInput).toBeVisible() - await amountInput.fill('5') - const tokenSelect = sendDialog.getByRole('combobox') // @todo when tamagui supports this , { name: 'Token' }) - await expect(tokenSelect).toBeVisible() - await tokenSelect.selectOption('USDC') - const sendDialogButton = sendDialog.getByRole('button', { name: 'Send' }) - expect(sendDialogButton).toBeVisible() - const usdcBalBefore = await testBaseClient.readContract({ - address: usdcAddress[testBaseClient.chain.id], - abi: erc20Abi, - functionName: 'balanceOf', - args: [sendAccount.address], - }) - await sendDialogButton.click() - await expect(sendDialog.getByText(/Sent user op [0-9a-f]+/).first()).toBeVisible({ - timeout: 20000, - }) - await expect(sendDialog.getByRole('link', { name: 'View on Otterscan' })).toBeVisible() - - const usdcBalAfter = await testBaseClient.readContract({ - address: usdcAddress[testBaseClient.chain.id], - abi: erc20Abi, - functionName: 'balanceOf', - args: [sendAccount.address], - }) - expect(Number(formatUnits(usdcBalBefore, 6)) - Number(formatUnits(usdcBalAfter, 6))).toBeCloseTo( - 5, - 0 - ) // allow for ยข10 for gas -}) - /** * Handles the transfer process for a specified token. * diff --git a/packages/playwright/tests/sign-up.anon.spec.ts b/packages/playwright/tests/sign-up.anon.spec.ts index c0e40f3de..d9a3430aa 100644 --- a/packages/playwright/tests/sign-up.anon.spec.ts +++ b/packages/playwright/tests/sign-up.anon.spec.ts @@ -1,4 +1,4 @@ -import { expect, test as baseTest, mergeTests } from '@playwright/test' +import { expect, test as baseTest, mergeTests, type Expect, type Page } from '@playwright/test' import { test as snapletTest } from '@my/playwright/fixtures/snaplet' import { test as webauthnTest } from '@my/playwright/fixtures/webauthn' import { countries } from 'app/utils/country' @@ -17,14 +17,11 @@ test.beforeEach(async ({ page }) => { const randomCountry = () => countries[Math.floor(Math.random() * countries.length)] as (typeof countries)[number] -const signUp = async (page, phone, expect) => { +const signUp = async (page: Page, phone: string, expect: Expect) => { await page.getByLabel('Phone number').fill(phone) - const captcha = page - .frameLocator('iframe[title="Widget containing a Cloudflare security challenge"]') - .locator('#success') - await expect(captcha).toBeVisible() const signUpButton = page.getByRole('button', { name: 'Sign Up' }) await expect(signUpButton).toBeVisible() + await expect(signUpButton).toBeEnabled() await signUpButton.click() const otpInput = page.getByLabel('One-time Password') await expect(otpInput).toBeVisible() diff --git a/packages/ui/package.json b/packages/ui/package.json index e983f63ae..54a3f9833 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -38,7 +38,7 @@ "react-hook-form": "^7.48.2", "solito": "^4.0.1", "tamagui": "^1.101.7", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@tamagui/build": "^1.101.7", diff --git a/packages/wagmi/package.json b/packages/wagmi/package.json index 1ab86d70b..2189b22bb 100644 --- a/packages/wagmi/package.json +++ b/packages/wagmi/package.json @@ -12,7 +12,7 @@ }, "devDependencies": { "@wagmi/cli": "^2.1.8", - "debug": "^4.3.4", + "debug": "^4.3.5", "globby": "^14.0.0", "typescript": "^5.5.3" }, diff --git a/packages/webauthn-authenticator/package.json b/packages/webauthn-authenticator/package.json index 0ace61e84..07d2cb95e 100644 --- a/packages/webauthn-authenticator/package.json +++ b/packages/webauthn-authenticator/package.json @@ -17,7 +17,7 @@ "dependencies": { "@scure/base": "^1.1.5", "cbor": "^9.0.1", - "debug": "^4.3.4" + "debug": "^4.3.5" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", diff --git a/yarn.lock b/yarn.lock index 9fa7a6f23..d3b814ca0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,7 +40,7 @@ __metadata: "@types/debug": "npm:^4" "@vitest/coverage-v8": "npm:^0.34.6" cbor: "npm:^9.0.1" - debug: "npm:^4.3.4" + debug: "npm:^4.3.5" rollup: "npm:^4.6.1" rollup-plugin-polyfill-node: "npm:^0.13.0" typescript: "npm:^5.5.3" @@ -125,7 +125,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.21.4": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6": version: 7.21.4 resolution: "@babel/code-frame@npm:7.21.4" dependencies: @@ -134,16 +134,6 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/code-frame@npm:7.23.4" - dependencies: - "@babel/highlight": "npm:^7.23.4" - chalk: "npm:^2.4.2" - checksum: 5a210e42b0c3138f3870e452c7b6d06ddcfc43cba824231ef3023fffd1cb0613d00ea07c7d87d0718e14e830f891b86de56aac5cd034d41128383919c84ff4f6 - languageName: node - linkType: hard - "@babel/code-frame@npm:^7.24.7": version: 7.24.7 resolution: "@babel/code-frame@npm:7.24.7" @@ -210,7 +200,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.20.0, @babel/generator@npm:^7.21.4": +"@babel/generator@npm:^7.20.0": version: 7.21.4 resolution: "@babel/generator@npm:7.21.4" dependencies: @@ -246,18 +236,6 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/generator@npm:7.23.4" - dependencies: - "@babel/types": "npm:^7.23.4" - "@jridgewell/gen-mapping": "npm:^0.3.2" - "@jridgewell/trace-mapping": "npm:^0.3.17" - jsesc: "npm:^2.5.1" - checksum: 7b45b64505bfb3ddbdeaae01288d2814e0e8d1299b3485983f4abc6563d6c10837979f00021308c78c33564d33e6d715e63aed64ac407ed8440b76f6eeb79019 - languageName: node - linkType: hard - "@babel/generator@npm:^7.24.7": version: 7.24.7 resolution: "@babel/generator@npm:7.24.7" @@ -1020,17 +998,6 @@ __metadata: languageName: node linkType: hard -"@babel/highlight@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/highlight@npm:7.23.4" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.22.20" - chalk: "npm:^2.4.2" - js-tokens: "npm:^4.0.0" - checksum: 62fef9b5bcea7131df4626d009029b1ae85332042f4648a4ce6e740c3fd23112603c740c45575caec62f260c96b11054d3be5987f4981a5479793579c3aac71f - languageName: node - linkType: hard - "@babel/highlight@npm:^7.24.7": version: 7.24.7 resolution: "@babel/highlight@npm:7.24.7" @@ -1052,7 +1019,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.23.4": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7": version: 7.23.4 resolution: "@babel/parser@npm:7.23.4" bin: @@ -1061,7 +1028,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.6, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.4": +"@babel/parser@npm:^7.1.6, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7": version: 7.21.4 resolution: "@babel/parser@npm:7.21.4" bin: @@ -2712,43 +2679,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.21.2": - version: 7.21.4 - resolution: "@babel/traverse@npm:7.21.4" - dependencies: - "@babel/code-frame": "npm:^7.21.4" - "@babel/generator": "npm:^7.21.4" - "@babel/helper-environment-visitor": "npm:^7.18.9" - "@babel/helper-function-name": "npm:^7.21.0" - "@babel/helper-hoist-variables": "npm:^7.18.6" - "@babel/helper-split-export-declaration": "npm:^7.18.6" - "@babel/parser": "npm:^7.21.4" - "@babel/types": "npm:^7.21.4" - debug: "npm:^4.1.0" - globals: "npm:^11.1.0" - checksum: 22f3bf1d2acad9f7e85842361afff219f406408f680304be8f78348351a27f90fb66aef2afb03263d3f2b79d12462728e19de571ed19b646bdfb458c6ca5e25b - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.23.3": - version: 7.23.4 - resolution: "@babel/traverse@npm:7.23.4" - dependencies: - "@babel/code-frame": "npm:^7.23.4" - "@babel/generator": "npm:^7.23.4" - "@babel/helper-environment-visitor": "npm:^7.22.20" - "@babel/helper-function-name": "npm:^7.23.0" - "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.23.4" - "@babel/types": "npm:^7.23.4" - debug: "npm:^4.1.0" - globals: "npm:^11.1.0" - checksum: 0ff190a793d94c8ee3ff24bbe7d086c6401a84fa16f97d3c695c31aa42270916d937ae5994e315ba797e8f3728840e4d68866ad4d82a01132312d07ac45ca9d0 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.24.7": +"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.23.3, @babel/traverse@npm:^7.24.7": version: 7.24.7 resolution: "@babel/traverse@npm:7.24.7" dependencies: @@ -6001,11 +5932,11 @@ __metadata: "@trpc/server": "npm:11.0.0-next-beta.264" "@wagmi/core": "npm:^2.10.5" app: "workspace:*" - debug: "npm:^4.3.4" + debug: "npm:^4.3.5" p-queue: "npm:^8.0.1" superjson: "npm:^1.13.1" viem: "npm:^2.13.7" - zod: "npm:^3.22.4" + zod: "npm:^3.23.8" languageName: unknown linkType: soft @@ -6018,7 +5949,7 @@ __metadata: "@supabase/supabase-js": "npm:2.44.2" "@types/bun": "npm:latest" "@uniswap/v3-periphery": "npm:^1.4.4" - debug: "npm:^4.3.4" + debug: "npm:^4.3.5" solhint: "npm:^3.6.2" zx: "npm:^8.1.2" languageName: unknown @@ -6034,14 +5965,14 @@ __metadata: "@faker-js/faker": "npm:^8.1.0" "@my/snaplet": "workspace:*" "@my/supabase": "workspace:*" - "@playwright/test": "npm:^1.41.1" + "@playwright/test": "npm:^1.45.1" "@supabase/supabase-js": "npm:2.44.2" "@types/jsonwebtoken": "npm:^9.0.2" "@types/pg": "npm:^8" "@wagmi/core": "npm:^2.10.5" app: "workspace:*" cbor: "npm:^9.0.1" - debug: "npm:^4.3.4" + debug: "npm:^4.3.5" dotenv-cli: "npm:^7.3.0" extract-zip: "npm:^2.0.1" jsonwebtoken: "npm:^9.0.2" @@ -6115,7 +6046,7 @@ __metadata: solito: "npm:^4.0.1" tamagui: "npm:^1.101.7" typescript: "npm:^5.5.3" - zod: "npm:^3.22.4" + zod: "npm:^3.23.8" languageName: unknown linkType: soft @@ -6127,7 +6058,7 @@ __metadata: "@wagmi/cli": "npm:^2.1.8" "@wagmi/core": "npm:^2.10.5" change-case: "npm:^5.4.2" - debug: "npm:^4.3.4" + debug: "npm:^4.3.5" globby: "npm:^14.0.0" permissionless: "npm:^0.1.14" typescript: "npm:^5.5.3" @@ -6714,14 +6645,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:^1.41.1": - version: 1.41.1 - resolution: "@playwright/test@npm:1.41.1" +"@playwright/test@npm:^1.45.1": + version: 1.45.1 + resolution: "@playwright/test@npm:1.45.1" dependencies: - playwright: "npm:1.41.1" + playwright: "npm:1.45.1" bin: playwright: cli.js - checksum: 68d652462cf1cfcaa91886937a5d860c4ec4caefadf30ba6eb41f498e01ea097b084b956439b112eaa4e5a52a7421d100d3f125f5c1d864468bbe40389c2c9fd + checksum: 718316ae739438f686914350beb3aeded6c96d7adfe1b65509fc50c4e322172fe58b7c9f215c3d5bef52a263839b83162f843027ae8d8e96970b3dd8f87211d2 languageName: node linkType: hard @@ -13725,7 +13656,7 @@ __metadata: base64-arraybuffer: "npm:^1.0.2" burnt: "npm:^0.12.1" cbor: "npm:^9.0.1" - debug: "npm:^4.3.4" + debug: "npm:^4.3.5" dnum: "npm:^2.9.0" eslint: "npm:^8.46.0" eslint-config-custom: "workspace:*" @@ -13753,7 +13684,7 @@ __metadata: typescript: "npm:^5.5.3" viem: "npm:^2.13.7" wagmi: "npm:^2.9.10" - zod: "npm:^3.22.4" + zod: "npm:^3.23.8" languageName: unknown linkType: soft @@ -14698,6 +14629,26 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:1.20.2": + version: 1.20.2 + resolution: "body-parser@npm:1.20.2" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.11.0" + raw-body: "npm:2.5.2" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 3cf171b82190cf91495c262b073e425fc0d9e25cc2bf4540d43f7e7bbca27d6a9eae65ca367b6ef3993eea261159d9d2ab37ce444e8979323952e12eb3df319a + languageName: node + linkType: hard + "boolbase@npm:^1.0.0": version: 1.0.0 resolution: "boolbase@npm:1.0.0" @@ -14774,16 +14725,7 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.2, braces@npm:~3.0.2": - version: 3.0.2 - resolution: "braces@npm:3.0.2" - dependencies: - fill-range: "npm:^7.0.1" - checksum: 966b1fb48d193b9d155f810e5efd1790962f2c4e0829f8440b8ad236ba009222c501f70185ef732fef17a4c490bb33a03b90dab0631feafbdf447da91e8165b1 - languageName: node - linkType: hard - -"braces@npm:^3.0.3": +"braces@npm:^3.0.2, braces@npm:^3.0.3, braces@npm:~3.0.2": version: 3.0.3 resolution: "braces@npm:3.0.3" dependencies: @@ -16132,7 +16074,7 @@ __metadata: languageName: node linkType: hard -"content-type@npm:~1.0.4": +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 @@ -16195,6 +16137,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:0.6.0": + version: 0.6.0 + resolution: "cookie@npm:0.6.0" + checksum: c1f8f2ea7d443b9331680598b0ae4e6af18a618c37606d1bbdc75bec8361cce09fe93e727059a673f2ba24467131a9fb5a4eec76bb1b149c1b3e1ccb268dc583 + languageName: node + linkType: hard + "cookie@npm:^0.4.1": version: 0.4.2 resolution: "cookie@npm:0.4.2" @@ -16636,7 +16585,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4": +"debug@npm:^4, debug@npm:^4.3.5": version: 4.3.5 resolution: "debug@npm:4.3.5" dependencies: @@ -17099,9 +17048,9 @@ __metadata: "@types/supertest": "npm:^2.0.16" "@wagmi/core": "npm:^2.10.5" app: "workspace:*" - debug: "npm:^4.3.4" + debug: "npm:^4.3.5" dotenv-cli: "npm:^7.3.0" - express: "npm:^4.18.2" + express: "npm:^4.19.2" pino: "npm:^8.16.1" supertest: "npm:^6.3.3" typescript: "npm:^5.5.3" @@ -19795,6 +19744,45 @@ __metadata: languageName: node linkType: hard +"express@npm:^4.19.2": + version: 4.19.2 + resolution: "express@npm:4.19.2" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.2" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.6.0" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.2.0" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.1" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.7" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.11.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.18.0" + serve-static: "npm:1.15.0" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 3fcd792536f802c059789ef48db3851b87e78fba103423e524144d79af37da7952a2b8d4e1a007f423329c7377d686d9476ac42e7d9ea413b80345d495e30a3a + languageName: node + linkType: hard + "extension-port-stream@npm:^3.0.0": version: 3.0.0 resolution: "extension-port-stream@npm:3.0.0" @@ -19936,25 +19924,14 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.0.12": - version: 4.2.2 - resolution: "fast-xml-parser@npm:4.2.2" - dependencies: - strnum: "npm:^1.0.5" - bin: - fxparser: src/cli/cli.js - checksum: a46345ac4b901ff38f3f852bf71137835b7abbac790b2f3db21b0ff2ccae61ccb7191f588fd0a56fd160e1cc44d77c239816a09dff2748a24ec3eeaa4ba3166b - languageName: node - linkType: hard - -"fast-xml-parser@npm:^4.2.4": - version: 4.3.6 - resolution: "fast-xml-parser@npm:4.3.6" +"fast-xml-parser@npm:^4.0.12, fast-xml-parser@npm:^4.2.4": + version: 4.4.0 + resolution: "fast-xml-parser@npm:4.4.0" dependencies: strnum: "npm:^1.0.5" bin: fxparser: src/cli/cli.js - checksum: 3e431e594960f04996e60a01fb51d8f4346138a7ba60d97244bf7866a3072eaf2f6dc73008d7b07871b98b606a8d7db955efdeae787992f685dd0e5bcc67c36a + checksum: f1592fa810d3923c46c7037d5adf1c309580d1d14780312019f33e4967f6ffcb5632168a5e889fe9d30794f6a087a0a095bb21cdf62320a96c6b304395212658 languageName: node linkType: hard @@ -20087,15 +20064,6 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^7.0.1": - version: 7.0.1 - resolution: "fill-range@npm:7.0.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: e260f7592fd196b4421504d3597cc76f4a1ca7a9488260d533b611fc3cefd61e9a9be1417cb82d3b01ad9f9c0ff2dbf258e1026d2445e26b0cf5148ff4250429 - languageName: node - linkType: hard - "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -20323,12 +20291,12 @@ __metadata: linkType: hard "follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.9": - version: 1.15.3 - resolution: "follow-redirects@npm:1.15.3" + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" peerDependenciesMeta: debug: optional: true - checksum: 60d98693f4976892f8c654b16ef6d1803887a951898857ab0cdc009570b1c06314ad499505b7a040ac5b98144939f8597766e5e6a6859c0945d157b473aa6f5f + checksum: 70c7612c4cab18e546e36b991bbf8009a1a41cf85354afe04b113d1117569abf760269409cb3eb842d9f7b03d62826687086b081c566ea7b1e6613cf29030bf7 languageName: node linkType: hard @@ -23402,9 +23370,9 @@ __metadata: linkType: hard "jose@npm:^4.14.4": - version: 4.15.4 - resolution: "jose@npm:4.15.4" - checksum: 20fa941597150dffc7af3f41d994500cc3e71cd650b755243dbd80d91cf26c1053f95b78af588f05cfc4371e492a67c5c7a48f689b8605145a8fe28b484d725b + version: 4.15.9 + resolution: "jose@npm:4.15.9" + checksum: 256234b6f85cdc080b1331f2d475bd58c8ccf459cb20f70ac5e4200b271bce10002b1c2f8e5b96dd975d83065ae5a586d52cdf89d28471d56de5d297992f9905 languageName: node linkType: hard @@ -25718,7 +25686,7 @@ __metadata: react-native-web: "npm:~0.19.10" sharp: "npm:0.32.6" vercel: "npm:^33.5.2" - zod: "npm:^3.22.4" + zod: "npm:^3.23.8" languageName: unknown linkType: soft @@ -27518,27 +27486,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.41.1": - version: 1.41.1 - resolution: "playwright-core@npm:1.41.1" +"playwright-core@npm:1.45.1": + version: 1.45.1 + resolution: "playwright-core@npm:1.45.1" bin: playwright-core: cli.js - checksum: 12019f53bda0f0fcef9a9b68f5bcf3b7169330d657e091617b103e841963142c4101e8ced110fe92b0b8f0e3df698eb2f37af5189fe45e00e7549b2f8a22a6ed + checksum: 206a5ecd2de7b8cefa5136331fa22012416b37eb2c471e3105c09a8a17a10621efa900acb6a780314f06aa2a3d6651aad3a323fa360d046ccce8f3844b3ca615 languageName: node linkType: hard -"playwright@npm:1.41.1": - version: 1.41.1 - resolution: "playwright@npm:1.41.1" +"playwright@npm:1.45.1": + version: 1.45.1 + resolution: "playwright@npm:1.45.1" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.41.1" + playwright-core: "npm:1.45.1" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 7667d73e406549156200d0d1b6361eadc4037816e9020e68e86d4249e302963e6e645cb95de2da8cadb5e52080f9f25020e65dc020655ae0e52d2a12d7a03642 + checksum: 092d510a79ca8fb1d0c1a83460735b9eaf02261a48df2ae1b025f95ee31e2be9d962ddc62c7e5c0d2c44e5b982b66aaf3fe24243f736ab14dbfd2d6e88897824 languageName: node linkType: hard @@ -27664,7 +27632,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.31, postcss@npm:^8.4.31": +"postcss@npm:8.4.31": version: 8.4.31 resolution: "postcss@npm:8.4.31" dependencies: @@ -27675,18 +27643,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.21": - version: 8.4.23 - resolution: "postcss@npm:8.4.23" - dependencies: - nanoid: "npm:^3.3.6" - picocolors: "npm:^1.0.0" - source-map-js: "npm:^1.0.2" - checksum: 4171086e54a90b5d9e7e043b3ea4acf5dce808f3501ebf7bf10caaff73f69a5c8d0dff7036752a648beb6317777d144af5b5b8b3ef9a84428630308735df07c2 - languageName: node - linkType: hard - -"postcss@npm:^8.4.38": +"postcss@npm:^8.4.21, postcss@npm:^8.4.31, postcss@npm:^8.4.38, postcss@npm:~8.4.32": version: 8.4.39 resolution: "postcss@npm:8.4.39" dependencies: @@ -27697,17 +27654,6 @@ __metadata: languageName: node linkType: hard -"postcss@npm:~8.4.32": - version: 8.4.38 - resolution: "postcss@npm:8.4.38" - dependencies: - nanoid: "npm:^3.3.7" - picocolors: "npm:^1.0.0" - source-map-js: "npm:^1.2.0" - checksum: 6e44a7ed835ffa9a2b096e8d3e5dfc6bcf331a25c48aeb862dd54e3aaecadf814fa22be224fd308f87d08adf2299164f88c5fd5ab1c4ef6cbd693ceb295377f4 - languageName: node - linkType: hard - "postgres-array@npm:3.0.2, postgres-array@npm:~3.0.1": version: 3.0.2 resolution: "postgres-array@npm:3.0.2" @@ -28481,6 +28427,18 @@ __metadata: languageName: node linkType: hard +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: 863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95 + languageName: node + linkType: hard + "rc@npm:1.2.8, rc@npm:^1.2.7, rc@npm:^1.2.8, rc@npm:~1.2.7": version: 1.2.8 resolution: "rc@npm:1.2.8" @@ -29981,7 +29939,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0": +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.5.0, semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" bin: @@ -29990,7 +29948,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:6.3.1, semver@npm:^6.1.0, semver@npm:^6.3.1": +"semver@npm:6.3.1, semver@npm:^6.0.0, semver@npm:^6.1.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.2.0, semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" bin: @@ -30041,36 +29999,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.x, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4": - version: 7.5.4 - resolution: "semver@npm:7.5.4" - dependencies: - lru-cache: "npm:^6.0.0" - bin: - semver: bin/semver.js - checksum: 985dec0d372370229a262c737063860fabd4a1c730662c1ea3200a2f649117761a42184c96df62a0e885e76fbd5dace41087d6c1ac0351b13c0df5d6bcb1b5ac - languageName: node - linkType: hard - -"semver@npm:^5.5.0, semver@npm:^5.6.0": - version: 5.7.1 - resolution: "semver@npm:5.7.1" - bin: - semver: ./bin/semver - checksum: fbc71cf00736480ca0dd67f2527cda6e0fde5447af00bd2ce06cb522d510216603a63ed0c6c87d8904507c1a4e8113e628a71424ebd9e0fd7d345ee8ed249690 - languageName: node - linkType: hard - -"semver@npm:^6.0.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.2.0, semver@npm:^6.3.0": - version: 6.3.0 - resolution: "semver@npm:6.3.0" - bin: - semver: ./bin/semver.js - checksum: 8dd72e7c7cdbd8cff66b5530eeff9eec2342b127eef2c956259cdf66b85addf4829e6e4a045ca30d974d075595b0b03faa6318a597307eb3984649516b98b501 - languageName: node - linkType: hard - -"semver@npm:^7.3.4": +"semver@npm:7.x, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.1, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4": version: 7.6.2 resolution: "semver@npm:7.6.2" bin: @@ -30079,39 +30008,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.3.7": - version: 7.5.0 - resolution: "semver@npm:7.5.0" - dependencies: - lru-cache: "npm:^6.0.0" - bin: - semver: bin/semver.js - checksum: 5aaa7675f8439b845db0a383f1420217a206fa084f2bc4ebc4bb31c0a50b02e9c922be3da274214ba7d9870d77f63085ac163f84f6ac910346675e9ac8681bf8 - languageName: node - linkType: hard - -"semver@npm:^7.3.8": - version: 7.5.1 - resolution: "semver@npm:7.5.1" - dependencies: - lru-cache: "npm:^6.0.0" - bin: - semver: bin/semver.js - checksum: 01fcb5ff66fb8cb9ff54e898ac9786fbafec65f93d0df910ea9300451719b204b1c5e8007c99c1abb410eb60f84497a1f8c02b1a0e97880842b7f6075e1d82b6 - languageName: node - linkType: hard - -"semver@npm:^7.5.1": - version: 7.6.0 - resolution: "semver@npm:7.6.0" - dependencies: - lru-cache: "npm:^6.0.0" - bin: - semver: bin/semver.js - checksum: 1b41018df2d8aca5a1db4729985e8e20428c650daea60fcd16e926e9383217d00f574fab92d79612771884a98d2ee2a1973f49d630829a8d54d6570defe62535 - languageName: node - linkType: hard - "send@npm:0.18.0, send@npm:^0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -34468,7 +34364,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.13.0, ws@npm:^8.12.1": +"ws@npm:8.13.0": version: 8.13.0 resolution: "ws@npm:8.13.0" peerDependencies: @@ -34484,11 +34380,11 @@ __metadata: linkType: hard "ws@npm:^6.2.2": - version: 6.2.2 - resolution: "ws@npm:6.2.2" + version: 6.2.3 + resolution: "ws@npm:6.2.3" dependencies: async-limiter: "npm:~1.0.0" - checksum: bb791ac02ad7e59fd4208cc6dd3a5bf7a67dff4611a128ed33365996f9fc24fa0d699043559f1798b4bc8045639fd21a1fd3ceca81de560124444abd8e321afc + checksum: 19f8d1608317f4c98f63da6eebaa85260a6fe1ba459cbfedd83ebe436368177fb1e2944761e2392c6b7321cbb7a375c8a81f9e1be35d555b6b4647eb61eadd46 languageName: node linkType: hard @@ -34507,22 +34403,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.11.0": - version: 8.14.2 - resolution: "ws@npm:8.14.2" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 815ff01d9bc20a249b2228825d9739268a03a4408c2e0b14d49b0e2ae89d7f10847e813b587ba26992bdc33e9d03bed131e4cae73ff996baf789d53e99c31186 - languageName: node - linkType: hard - -"ws@npm:^8.14.2": +"ws@npm:^8.11.0, ws@npm:^8.12.1, ws@npm:^8.14.2": version: 8.16.0 resolution: "ws@npm:8.16.0" peerDependencies: @@ -34813,13 +34694,20 @@ __metadata: languageName: node linkType: hard -"zod@npm:3.22.4, zod@npm:^3.22.2, zod@npm:^3.22.4": +"zod@npm:3.22.4, zod@npm:^3.22.2": version: 3.22.4 resolution: "zod@npm:3.22.4" checksum: 73622ca36a916f785cf528fe612a884b3e0f183dbe6b33365a7d0fc92abdbedf7804c5e2bd8df0a278e1472106d46674281397a3dd800fa9031dc3429758c6ac languageName: node linkType: hard +"zod@npm:^3.23.8": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 846fd73e1af0def79c19d510ea9e4a795544a67d5b34b7e1c4d0425bf6bfd1c719446d94cdfa1721c1987d891321d61f779e8236fde517dc0e524aa851a6eff1 + languageName: node + linkType: hard + "zustand@npm:4.4.1, zustand@npm:^4.3.8": version: 4.4.1 resolution: "zustand@npm:4.4.1"